Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/database/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DatasetModel(DynamicDocument):
annotate_url = StringField(default="")

default_annotation_metadata = DictField(default={})
tags = DictField(default={})

deleted = BooleanField(default=False)
deleted_date = DateTimeField()
Expand Down
54 changes: 49 additions & 5 deletions backend/webserver/api/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
page_data.add_argument('limit', default=20, type=int)
page_data.add_argument('folder', default='', help='Folder for data')
page_data.add_argument('order', default='file_name', help='Order to display images')
page_data.add_argument('filters', default='[]', type=str, required=False)

delete_data = reqparse.RequestParser()
delete_data.add_argument('fully', default=False, type=bool,
Expand All @@ -52,6 +53,7 @@
update_dataset.add_argument('categories', location='json', type=list, help="New list of categories")
update_dataset.add_argument('default_annotation_metadata', location='json', type=dict,
help="Default annotation metadata")
update_dataset.add_argument('tags', location='json', type=dict, help="Dataset tags")

dataset_generate = reqparse.RequestParser()
dataset_generate.add_argument('keywords', location='json', type=list, default=[],
Expand Down Expand Up @@ -87,7 +89,6 @@ def post(self):

return query_util.fix_ids(dataset)


def download_images(output_dir, args):
for keyword in args['keywords']:
response = gid.googleimagesdownload()
Expand Down Expand Up @@ -262,6 +263,7 @@ def post(self, dataset_id):
categories = args.get('categories')
default_annotation_metadata = args.get('default_annotation_metadata')
set_default_annotation_metadata = args.get('set_default_annotation_metadata')
tags = args.get('tags')

if categories is not None:
dataset.categories = CategoryModel.bulk_create(categories)
Expand All @@ -279,7 +281,11 @@ def post(self, dataset_id):
AnnotationModel.objects(dataset_id=dataset.id, deleted=False)\
.update(**update)

if tags is not None:
dataset.tags = tags

dataset.update(
tags = dataset.tags,
categories=dataset.categories,
default_annotation_metadata=dataset.default_annotation_metadata
)
Expand Down Expand Up @@ -312,14 +318,28 @@ class DatasetData(Resource):
@login_required
def get(self):
""" Endpoint called by dataset viewer client """

args = page_data.parse_args()
limit = args['limit']
page = args['page']
folder = args['folder']

datasets = current_user.datasets.filter(deleted=False)
pagination = Pagination(datasets.count(), limit, page)
filters = args['filters']

# Convert filters string to dict mapping unique keys to lists of values
filters_dict = {}
for filter in json.loads(filters):
key, value = filter.split(":")
if key not in filters_dict:
filters_dict[key] = []
filters_dict[key].append(value)

# Get filtered list of datasets whose tags include all keys in filters_dict and any of each key's values
datasets = []
for dataset in current_user.datasets.filter(deleted=False):
tags = dataset.tags
if not filters_dict or all(key in tags and tags[key] in values for key, values in filters_dict.items()):
datasets.append(dataset)

pagination = Pagination(len(datasets), limit, page)
datasets = datasets[pagination.start:pagination.end]

datasets_json = []
Expand All @@ -343,6 +363,30 @@ def get(self):
"categories": query_util.fix_ids(current_user.categories.filter(deleted=False).all())
}


@api.route('/filters')
class DatasetFilters(Resource):

@login_required
def get(self):
""" Endpoint called by dataset viewer client """

# Get all unique tag items across all datasets per user
# Each tag becomes a string "key:value" that can be used to filter datasets in the client
datasets = current_user.datasets.filter(deleted=False)
filters = []

for dataset in datasets:
for key, value in dataset.tags.items():
if len(value) > 0:
filter_string = key + ":" + value
filters.append(filter_string)

return {
"filters": list(set(filters))
}


@api.route('/<int:dataset_id>/data')
class DatasetDataId(Resource):

Expand Down
66 changes: 54 additions & 12 deletions client/src/components/Metadata.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
<div>
<i
class="fa fa-plus"
style="float: right; margin: 0 4px; color: green"
style="float: right; margin: 5px 5px 0 -5px; color: green; cursor: pointer;"
@click="createMetadata"
/>

<p class="title" style="margin: 0">{{ title }}</p>
<p class="title mb-2">{{ title }}</p>

<!--
<div class="row">
<div class="col-sm">
<p class="subtitle">{{ keyTitle }}</p>
Expand All @@ -16,36 +17,52 @@
<p class="subtitle">{{ valueTitle }}</p>
</div>
</div>
-->

<ul class="list-group" style="height: 50%;">
<li v-if="metadataList.length == 0" class="list-group-item meta-item">
<i class="subtitle">No items in metadata.</i>
<i class="subtitle">{{ emptyMessage }}</i>
</li>
<li
v-for="(object, index) in metadataList"
:key="index"
class="list-group-item meta-item"
>
<div class="row" style="cell">
<div class="col-sm">
<div class="row d-flex justify-content-between" style="cell">
<div class="col-xs">
<input
v-model="object.key"
type="text"
class="meta-input"
:placeholder="keyTitle"
@input="validateKeys()"
/>
</div>

<div class="col-sm">
<div class="col-xs">
<input
v-model="object.value"
type="text"
class="meta-input"
:placeholder="valueTitle"
/>
</div>

<div class="col-xs d-flex align-items-center">
<i
class="fa fa-minus"
style="color:red; cursor: pointer"
@click="deleteMetadata(index)"
/>
</div>
</div>


</li>

<div :v-show="errorMessage" class="text-danger small">
{{ errorMessage }}
</div>
</ul>
</div>
</template>
Expand All @@ -64,20 +81,25 @@ export default {
},
keyTitle: {
type: String,
default: "Keys"
default: "Key"
},
valueTitle: {
type: String,
default: "Values"
default: "Value"
},
exclude: {
type: String,
default: ""
},
emptyMessage: {
type: String,
default: "No items in metadata"
}
},
data() {
return {
metadataList: []
metadataList: [],
errorMessage: null,
};
},
methods: {
Expand All @@ -102,6 +124,16 @@ export default {
createMetadata() {
this.metadataList.push({ key: "", value: "" });
},
deleteMetadata(index) {
delete this.metadataList[index];
this.metadataList = this.metadataList.filter(metadata => metadata);
this.validateKeys();
},
clearEmptyItems() {
this.metadataList = this.metadataList.filter((metadata) => {
return metadata.key || metadata.value;
})
},
loadMetadata() {
if (this.metadata != null) {
for (var key in this.metadata) {
Expand All @@ -116,7 +148,17 @@ export default {
this.metadataList.push({ key: key, value: value });
}
}
}
},
validateKeys() {
const keys = this.metadataList.map(metadata => metadata.key).filter(key => key.length);
const uniqueKeys = [...new Set(keys)];

this.errorMessage = keys.length !== uniqueKeys.length
? "Keys must be unique"
: null;

this.$emit("error", !!this.errorMessage);
},
},
watch: {
metadata() {
Expand All @@ -139,9 +181,9 @@ export default {
}

.meta-item {
padding: 3px;
background-color: inherit;
height: 40px;
padding-top: 2px !important;
padding-bottom: 2px !important;
border: none;
}

Expand Down
9 changes: 4 additions & 5 deletions client/src/components/TagsInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,16 @@ export default {
type: Function,
default: () => true
},

addTagsOnComma: {
type: Boolean,
default: false
},

wrapperClass: {
type: String,
default: "tags-input-wrapper-default"
}
},

data() {
return {
badgeId: 0,
Expand Down Expand Up @@ -268,7 +266,7 @@ export default {

// Emit events
this.$emit("tag-added", slug);
this.$emit("tags-updated");
this.$emit("tags-updated", this.tags);
},

removeLastTag() {
Expand All @@ -285,7 +283,7 @@ export default {

// Emit events
this.$emit("tag-removed", slug);
this.$emit("tags-updated");
this.$emit("tags-updated", this.tags);
},

searchTag() {
Expand Down Expand Up @@ -448,5 +446,6 @@ export default {
<style>
.tags-input-root {
position: relative;
width: 100%;
}
</style>
28 changes: 24 additions & 4 deletions client/src/components/cards/DatasetCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,22 @@
:typeahead-activation-threshold="0"
/>
</div>
<hr />

<Metadata
:metadata="defaultMetadata"
title="Default Annotation Metadata"
key-name="Default Key"
value-name="Default Value"
ref="defaultAnnotation"
v-on:error="onValidationError($event)"
/>
<hr />

<Metadata
:metadata="datasetTags"
title="Dataset Tags"
ref="datasetTags"
empty-message="No tags"
v-on:error="onValidationError($event)"
/>
</form>
</div>
Expand All @@ -143,6 +152,7 @@
class="btn btn-success"
@click="onSave"
data-dismiss="modal"
:disabled="!allowSave"
>
Save
</button>
Expand Down Expand Up @@ -237,9 +247,11 @@ export default {
imageError: false,
selectedCategories: [],
defaultMetadata: this.dataset.default_annotation_metadata,
datasetTags: this.dataset.tags,
noImageUrl: require("@/assets/no-image.png"),
notFoundImageUrl: require("@/assets/404-image.png"),
sharedUsers: []
sharedUsers: [],
allowSave: true,
};
},
methods: {
Expand Down Expand Up @@ -278,13 +290,19 @@ export default {
this.$parent.updatePage();
});
},
onValidationError(error) {
this.allowSave = !error;
},
onSave() {
this.dataset.categories = this.selectedCategories;
this.$refs.defaultAnnotation.clearEmptyItems();
this.$refs.datasetTags.clearEmptyItems();

axios
.post("/api/dataset/" + this.dataset.id, {
categories: this.selectedCategories,
default_annotation_metadata: this.$refs.defaultAnnotation.export()
default_annotation_metadata: this.$refs.defaultAnnotation.export(),
tags: this.$refs.datasetTags.export()
})
.then(() => {
this.$parent.updatePage();
Expand Down Expand Up @@ -389,6 +407,7 @@ p {
padding: 2px;
background-color: #4b5162;
}

.icon-more {
width: 10%;
margin: 3px 0;
Expand All @@ -401,6 +420,7 @@ p {
margin: 0 5px 7px 5px;
height: 5px;
}

.card-footer {
padding: 2px;
font-size: 11px;
Expand Down
Loading