From 68fbcdec43e0b5e63e26bd85f40d9c52bc60fc30 Mon Sep 17 00:00:00 2001 From: Maria Khrustaleva Date: Thu, 9 Dec 2021 16:45:01 +0300 Subject: [PATCH] Added sorting methods (#3937) --- CHANGELOG.md | 1 + cvat-core/package-lock.json | 4 +- cvat-core/package.json | 2 +- cvat-core/src/enums.js | 19 +++ cvat-core/src/session.js | 12 ++ cvat-ui/package-lock.json | 4 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/tasks-actions.ts | 1 + .../advanced-configuration-form.tsx | 43 +++++- .../create-task-page/create-task-content.tsx | 3 +- cvat/apps/engine/backup.py | 1 + cvat/apps/engine/media_extractors.py | 81 ++++++++-- .../migrations/0045_data_sorting_method.py | 19 +++ cvat/apps/engine/models.py | 14 ++ cvat/apps/engine/serializers.py | 8 +- cvat/apps/engine/task.py | 83 +++++++--- cvat/apps/engine/tests/test_rest_api.py | 142 +++++++++++++++--- cvat/apps/engine/utils.py | 2 +- cvat/apps/engine/views.py | 5 +- cvat/requirements/base.txt | 1 + .../basics/creating_an_annotation_task.md | 10 +- site/content/en/images/image128.jpg | Bin 0 -> 64413 bytes site/content/en/images/image128_use_cache.jpg | Bin 61950 -> 0 bytes utils/cli/core/core.py | 2 + utils/cli/core/definition.py | 7 + utils/dataset_manifest/core.py | 29 ++-- utils/dataset_manifest/create.py | 4 +- utils/dataset_manifest/requirements.txt | 3 +- utils/dataset_manifest/utils.py | 30 ++++ 29 files changed, 440 insertions(+), 92 deletions(-) create mode 100644 cvat/apps/engine/migrations/0045_data_sorting_method.py create mode 100644 site/content/en/images/image128.jpg delete mode 100644 site/content/en/images/image128_use_cache.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 61796bbe..90dd189b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Google Cloud Storage support in UI () - Add project tasks paginations () - Add remove issue button () +- Data sorting option () - Options to change font size & position of text labels on the canvas () - Add "tag" return type for automatic annotation in Nuclio () diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index 4389ca73..5de6b5ca 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-core", - "version": "3.20.1", + "version": "3.21.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-core", - "version": "3.20.1", + "version": "3.21.0", "license": "MIT", "dependencies": { "axios": "^0.21.4", diff --git a/cvat-core/package.json b/cvat-core/package.json index 3552b5be..511e7c72 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "name": "cvat-core", - "version": "3.20.1", + "version": "3.21.0", "description": "Part of Computer Vision Tool which presents an interface for client-side integration", "main": "babel.config.js", "scripts": { diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index 03fcb9e5..3b63224f 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -367,6 +367,24 @@ KEY_FILE_PATH: 'KEY_FILE_PATH', }); + /** + * Sorting methods + * @enum {string} + * @name SortingMethod + * @memberof module:API.cvat.enums + * @property {string} LEXICOGRAPHICAL 'lexicographical' + * @property {string} NATURAL 'natural' + * @property {string} PREDEFINED 'predefined' + * @property {string} RANDOM 'random' + * @readonly + */ + const SortingMethod = Object.freeze({ + LEXICOGRAPHICAL: 'lexicographical', + NATURAL: 'natural', + PREDEFINED: 'predefined', + RANDOM: 'random', + }); + module.exports = { ShareFileType, TaskStatus, @@ -384,5 +402,6 @@ DimensionType, CloudStorageProviderType, CloudStorageCredentialsType, + SortingMethod, }; })(); diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index 83c50b99..6f7b3b04 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -1017,6 +1017,7 @@ copy_data: undefined, dimension: undefined, cloud_storage_id: undefined, + sorting_method: undefined, }; const updatedFields = new FieldUpdateTrigger({ @@ -1549,6 +1550,16 @@ cloudStorageId: { get: () => data.cloud_storage_id, }, + sortingMethod: { + /** + * @name sortingMethod + * @type {module:API.cvat.enums.SortingMethod} + * @memberof module:API.cvat.classes.Task + * @instance + * @readonly + */ + get: () => data.sorting_method, + }, _internalData: { get: () => data, }, @@ -2061,6 +2072,7 @@ image_quality: this.imageQuality, use_zip_chunks: this.useZipChunks, use_cache: this.useCache, + sorting_method: this.sortingMethod, }; if (typeof this.startFrame !== 'undefined') { diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 027dd930..f9bad34c 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-ui", - "version": "1.28.1", + "version": "1.28.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-ui", - "version": "1.28.1", + "version": "1.28.2", "license": "MIT", "dependencies": { "@ant-design/icons": "^4.6.3", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 2ef52978..fa806bb7 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.28.1", + "version": "1.28.2", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/tasks-actions.ts b/cvat-ui/src/actions/tasks-actions.ts index c584c141..9ef4fc91 100644 --- a/cvat-ui/src/actions/tasks-actions.ts +++ b/cvat-ui/src/actions/tasks-actions.ts @@ -353,6 +353,7 @@ export function createTaskAsync(data: any): ThunkAction, {}, {}, A image_quality: 70, use_zip_chunks: data.advanced.useZipChunks, use_cache: data.advanced.useCache, + sorting_method: data.advanced.sortingMethod, }; if (data.projectId) { diff --git a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx index 6cbf3860..537426cc 100644 --- a/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx +++ b/cvat-ui/src/components/create-task-page/advanced-configuration-form.tsx @@ -7,6 +7,7 @@ import { Row, Col } from 'antd/lib/grid'; import { PercentageOutlined } from '@ant-design/icons'; import Input from 'antd/lib/input'; import Select from 'antd/lib/select'; +import Radio from 'antd/lib/radio'; import Checkbox from 'antd/lib/checkbox'; import Form, { FormInstance, RuleObject, RuleRender } from 'antd/lib/form'; import Text from 'antd/lib/typography/Text'; @@ -16,6 +17,13 @@ import patterns from 'utils/validation-patterns'; const { Option } = Select; +export enum SortingMethod { + LEXICOGRAPHICAL = 'lexicographical', + NATURAL = 'natural', + PREDEFINED = 'predefined', + RANDOM = 'random', +} + export interface AdvancedConfiguration { bugTracker?: string; imageQuality?: number; @@ -31,6 +39,7 @@ export interface AdvancedConfiguration { dataChunkSize?: number; useCache: boolean; copyData?: boolean; + sortingMethod: SortingMethod; } const initialValues: AdvancedConfiguration = { @@ -39,6 +48,7 @@ const initialValues: AdvancedConfiguration = { useZipChunks: true, useCache: true, copyData: false, + sortingMethod: SortingMethod.LEXICOGRAPHICAL, }; interface Props { @@ -178,6 +188,33 @@ class AdvancedConfigurationForm extends React.PureComponent { ); } + private renderSortingMethodRadio(): JSX.Element { + return ( + + + + Lexicographical + + Natural + + Predefined + + Random + + + ); + } + private renderImageQuality(): JSX.Element { return ( @@ -290,8 +327,7 @@ class AdvancedConfigurationForm extends React.PureComponent { > @@ -384,6 +420,9 @@ class AdvancedConfigurationForm extends React.PureComponent { const { installedGit, activeFileManagerTab } = this.props; return (
+ + {this.renderSortingMethodRadio()} + {activeFileManagerTab === 'share' ? ( {this.renderCopyDataChechbox()} diff --git a/cvat-ui/src/components/create-task-page/create-task-content.tsx b/cvat-ui/src/components/create-task-page/create-task-content.tsx index bb7cff45..77663e1a 100644 --- a/cvat-ui/src/components/create-task-page/create-task-content.tsx +++ b/cvat-ui/src/components/create-task-page/create-task-content.tsx @@ -20,7 +20,7 @@ import { Files } from 'components/file-manager/file-manager'; import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form'; import ProjectSearchField from './project-search-field'; import ProjectSubsetField from './project-subset-field'; -import AdvancedConfigurationForm, { AdvancedConfiguration } from './advanced-configuration-form'; +import AdvancedConfigurationForm, { AdvancedConfiguration, SortingMethod } from './advanced-configuration-form'; export interface CreateTaskData { projectId: number | null; @@ -54,6 +54,7 @@ const defaultState = { lfs: false, useZipChunks: true, useCache: true, + sortingMethod: SortingMethod.LEXICOGRAPHICAL, }, labels: [], files: { diff --git a/cvat/apps/engine/backup.py b/cvat/apps/engine/backup.py index 3d340b81..1803cf85 100644 --- a/cvat/apps/engine/backup.py +++ b/cvat/apps/engine/backup.py @@ -65,6 +65,7 @@ class _TaskBackupBase(): 'chunk_type', 'storage_method', 'storage', + 'sorting_method', } self._prepare_meta(allowed_fields, data) diff --git a/cvat/apps/engine/media_extractors.py b/cvat/apps/engine/media_extractors.py index 109fd7ba..740da1ca 100644 --- a/cvat/apps/engine/media_extractors.py +++ b/cvat/apps/engine/media_extractors.py @@ -14,11 +14,13 @@ from contextlib import closing import av import numpy as np +from natsort import os_sorted from pyunpack import Archive from PIL import Image, ImageFile +from random import shuffle import open3d as o3d from cvat.apps.engine.utils import rotate_image -from cvat.apps.engine.models import DimensionType +from cvat.apps.engine.models import DimensionType, SortingMethod # fixes: "OSError:broken data stream" when executing line 72 while loading images downloaded from the web # see: https://stackoverflow.com/questions/42462431/oserror-broken-data-stream-when-reading-image-file @@ -47,9 +49,22 @@ def files_to_ignore(directory): return True return False +def sort(images, sorting_method=SortingMethod.LEXICOGRAPHICAL, func=None): + if sorting_method == SortingMethod.LEXICOGRAPHICAL: + return sorted(images, key=func) + elif sorting_method == SortingMethod.NATURAL: + return os_sorted(images, key=func) + elif sorting_method == SortingMethod.PREDEFINED: + return images + elif sorting_method == SortingMethod.RANDOM: + shuffle(images) + return images + else: + raise NotImplementedError() + class IMediaReader(ABC): def __init__(self, source_path, step, start, stop, dimension): - self._source_path = sorted(source_path) + self._source_path = source_path self._step = step self._start = start self._stop = stop @@ -90,7 +105,13 @@ class IMediaReader(ABC): return range(self._start, self._stop, self._step) class ImageListReader(IMediaReader): - def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + def __init__(self, + source_path, + step=1, + start=0, + stop=None, + dimension=DimensionType.DIM_2D, + sorting_method=SortingMethod.LEXICOGRAPHICAL): if not source_path: raise Exception('No image found') @@ -102,13 +123,15 @@ class ImageListReader(IMediaReader): assert stop > start super().__init__( - source_path=source_path, + source_path=sort(source_path, sorting_method), step=step, start=start, stop=stop, dimension=dimension ) + self._sorting_method = sorting_method + def __iter__(self): for i in range(self._start, self._stop, self._step): yield (self.get_image(i), self.get_path(i), i) @@ -121,7 +144,8 @@ class ImageListReader(IMediaReader): step=self._step, start=self._start, stop=self._stop, - dimension=self._dimension + dimension=self._dimension, + sorting_method=self._sorting_method ) def get_path(self, i): @@ -154,7 +178,8 @@ class ImageListReader(IMediaReader): source_path=source_files, step=step, start=start, - stop=stop + stop=stop, + sorting_method=self._sorting_method, ) self._dimension = dimension @@ -163,7 +188,13 @@ class ImageListReader(IMediaReader): return [self.get_path(idx) for idx, _ in enumerate(self._source_path)] class DirectoryReader(ImageListReader): - def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + def __init__(self, + source_path, + step=1, + start=0, + stop=None, + dimension=DimensionType.DIM_2D, + sorting_method=SortingMethod.LEXICOGRAPHICAL): image_paths = [] for source in source_path: for root, _, files in os.walk(source): @@ -176,10 +207,17 @@ class DirectoryReader(ImageListReader): start=start, stop=stop, dimension=dimension, + sorting_method=sorting_method, ) class ArchiveReader(DirectoryReader): - def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + def __init__(self, + source_path, + step=1, + start=0, + stop=None, + dimension=DimensionType.DIM_2D, + sorting_method=SortingMethod.LEXICOGRAPHICAL): self._archive_source = source_path[0] extract_dir = source_path[1] if len(source_path) > 1 else os.path.dirname(source_path[0]) Archive(self._archive_source).extractall(extract_dir) @@ -190,11 +228,18 @@ class ArchiveReader(DirectoryReader): step=step, start=start, stop=stop, - dimension=dimension + dimension=dimension, + sorting_method=sorting_method, ) class PdfReader(ImageListReader): - def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + def __init__(self, + source_path, + step=1, + start=0, + stop=None, + dimension=DimensionType.DIM_2D, + sorting_method=SortingMethod.LEXICOGRAPHICAL): if not source_path: raise Exception('No PDF found') @@ -223,14 +268,26 @@ class PdfReader(ImageListReader): start=start, stop=stop, dimension=dimension, + sorting_method=sorting_method, ) class ZipReader(ImageListReader): - def __init__(self, source_path, step=1, start=0, stop=None, dimension=DimensionType.DIM_2D): + def __init__(self, + source_path, + step=1, + start=0, + stop=None, + dimension=DimensionType.DIM_2D, + sorting_method=SortingMethod.LEXICOGRAPHICAL): self._zip_source = zipfile.ZipFile(source_path[0], mode='r') self.extract_dir = source_path[1] if len(source_path) > 1 else None file_list = [f for f in self._zip_source.namelist() if files_to_ignore(f) and get_mime(f) == 'image'] - super().__init__(file_list, step=step, start=start, stop=stop, dimension=dimension) + super().__init__(file_list, + step=step, + start=start, + stop=stop, + dimension=dimension, + sorting_method=sorting_method) def __del__(self): self._zip_source.close() diff --git a/cvat/apps/engine/migrations/0045_data_sorting_method.py b/cvat/apps/engine/migrations/0045_data_sorting_method.py new file mode 100644 index 00000000..2ec382b3 --- /dev/null +++ b/cvat/apps/engine/migrations/0045_data_sorting_method.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.13 on 2021-12-03 08:06 + +import cvat.apps.engine.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('engine', '0044_auto_20211123_0824'), + ] + + operations = [ + migrations.AddField( + model_name='data', + name='sorting_method', + field=models.CharField(choices=[('lexicographical', 'LEXICOGRAPHICAL'), ('natural', 'NATURAL'), ('predefined', 'PREDEFINED'), ('random', 'RANDOM')], default=cvat.apps.engine.models.SortingMethod['LEXICOGRAPHICAL'], max_length=15), + ), + ] diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index 31107e48..806f48dc 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -81,6 +81,19 @@ class StorageChoice(str, Enum): def __str__(self): return self.value +class SortingMethod(str, Enum): + LEXICOGRAPHICAL = 'lexicographical' + NATURAL = 'natural' + PREDEFINED = 'predefined' + RANDOM = 'random' + + @classmethod + def choices(cls): + return tuple((x.value, x.name) for x in cls) + + def __str__(self): + return self.value + class Data(models.Model): chunk_size = models.PositiveIntegerField(null=True) size = models.PositiveIntegerField(default=0) @@ -95,6 +108,7 @@ class Data(models.Model): storage_method = models.CharField(max_length=15, choices=StorageMethodChoice.choices(), default=StorageMethodChoice.FILE_SYSTEM) storage = models.CharField(max_length=15, choices=StorageChoice.choices(), default=StorageChoice.LOCAL) cloud_storage = models.ForeignKey('CloudStorage', on_delete=models.SET_NULL, null=True, related_name='data') + sorting_method = models.CharField(max_length=15, choices=SortingMethod.choices(), default=SortingMethod.LEXICOGRAPHICAL) class Meta: default_permissions = () diff --git a/cvat/apps/engine/serializers.py b/cvat/apps/engine/serializers.py index b118f422..2cf801d3 100644 --- a/cvat/apps/engine/serializers.py +++ b/cvat/apps/engine/serializers.py @@ -281,7 +281,7 @@ class DataSerializer(serializers.ModelSerializer): model = models.Data fields = ('chunk_size', 'size', 'image_quality', 'start_frame', 'stop_frame', 'frame_filter', 'compressed_chunk_type', 'original_chunk_type', 'client_files', 'server_files', 'remote_files', 'use_zip_chunks', - 'cloud_storage_id', 'use_cache', 'copy_data', 'storage_method', 'storage') + 'cloud_storage_id', 'use_cache', 'copy_data', 'storage_method', 'storage', 'sorting_method') # pylint: disable=no-self-use def validate_frame_filter(self, value): @@ -308,9 +308,9 @@ class DataSerializer(serializers.ModelSerializer): client_files = validated_data.pop('client_files') server_files = validated_data.pop('server_files') remote_files = validated_data.pop('remote_files') - validated_data.pop('use_zip_chunks') - validated_data.pop('use_cache') - validated_data.pop('copy_data') + for extra_key in { 'use_zip_chunks', 'use_cache', 'copy_data' }: + validated_data.pop(extra_key) + db_data = models.Data.objects.create(**validated_data) data_path = db_data.get_data_dirname() diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index 2003d541..57de30ba 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -22,7 +22,7 @@ from django.db import transaction from cvat.apps.engine import models from cvat.apps.engine.log import slogger from cvat.apps.engine.media_extractors import (MEDIA_TYPES, Mpeg4ChunkWriter, Mpeg4CompressedChunkWriter, - ValidateDimension, ZipChunkWriter, ZipCompressedChunkWriter, get_mime) + ValidateDimension, ZipChunkWriter, ZipCompressedChunkWriter, get_mime, sort) from cvat.apps.engine.utils import av_scan_paths from utils.dataset_manifest import ImageManifestManager, VideoManifestManager from utils.dataset_manifest.core import VideoManifestValidator @@ -123,15 +123,18 @@ def _count_files(data, manifest_file=None): raise ValueError("Bad file path: " + path) server_files.append(path) - server_files.sort(reverse=True) + sorted_server_files = sorted(server_files, reverse=True) # The idea of the code is trivial. After sort we will have files in the # following order: 'a/b/c/d/2.txt', 'a/b/c/d/1.txt', 'a/b/c/d', 'a/b/c' # Let's keep all items which aren't substrings of the previous item. In # the example above only 2.txt and 1.txt files will be in the final list. # Also need to correctly handle 'a/b/c0', 'a/b/c' case. - data['server_files'] = [v[1] for v in zip([""] + server_files, server_files) + without_extra_dirs = [v[1] for v in zip([""] + sorted_server_files, sorted_server_files) if not os.path.dirname(v[0]).startswith(v[1])] + # we need to keep the original sequence of files + data['server_files'] = [f for f in server_files if f in without_extra_dirs] + def count_files(file_mapping, counter): for rel_path, full_path in file_mapping.items(): mime = get_mime(full_path) @@ -141,7 +144,7 @@ def _count_files(data, manifest_file=None): manifest_file.append(rel_path) else: slogger.glob.warn("Skip '{}' file (its mime type doesn't " - "correspond to a video or an image file)".format(full_path)) + "correspond to supported MIME file type)".format(full_path)) counter = { media_type: [] for media_type in MEDIA_TYPES.keys() } @@ -213,6 +216,7 @@ def _download_data(urls, upload_dir): def _get_manifest_frame_indexer(start_frame=0, frame_step=1): return lambda frame_id: start_frame + frame_id * frame_step + @transaction.atomic def _create_thread(tid, data, isImport=False): slogger.glob.info("create task #{}".format(tid)) @@ -222,15 +226,13 @@ def _create_thread(tid, data, isImport=False): upload_dir = db_data.get_upload_dirname() if data['remote_files']: - if db_data.storage != models.StorageChoice.CLOUD_STORAGE: - data['remote_files'] = _download_data(data['remote_files'], upload_dir) + data['remote_files'] = _download_data(data['remote_files'], upload_dir) manifest_file = [] media = _count_files(data, manifest_file) media, task_mode = _validate_data(media, manifest_file) - if manifest_file: - assert settings.USE_CACHE and db_data.storage_method == models.StorageMethodChoice.CACHE, \ - "File with meta information can be uploaded if 'Use cache' option is also selected" + if manifest_file and (not settings.USE_CACHE or db_data.storage_method != models.StorageMethodChoice.CACHE): + raise Exception("File with meta information can be uploaded if 'Use cache' option is also selected") if data['server_files']: if db_data.storage == models.StorageChoice.LOCAL: @@ -252,19 +254,22 @@ def _create_thread(tid, data, isImport=False): 'specific_attributes': db_cloud_storage.get_specific_attributes() } cloud_storage_instance = get_cloud_storage_instance(cloud_provider=db_cloud_storage.provider_type, **details) - first_sorted_media_image = sorted(media['image'])[0] + sorted_media = sort(media['image'], data['sorting_method']) + first_sorted_media_image = sorted_media[0] cloud_storage_instance.download_file(first_sorted_media_image, os.path.join(upload_dir, first_sorted_media_image)) # prepare task manifest file from cloud storage manifest file + # NOTE we should create manifest before defining chunk_size + # FIXME in the future when will be implemented archive support manifest = ImageManifestManager(db_data.get_manifest_path()) cloud_storage_manifest = ImageManifestManager( os.path.join(db_data.cloud_storage.get_storage_dirname(), manifest_file[0]), db_data.cloud_storage.get_storage_dirname() ) cloud_storage_manifest.set_index() - media_files = sorted(media['image']) - content = cloud_storage_manifest.get_subset(media_files) - manifest.create(content) + sequence, content = cloud_storage_manifest.get_subset(sorted_media) + sorted_content = (i[1] for i in sorted(zip(sequence, content))) + manifest.create(sorted_content) av_scan_paths(upload_dir) @@ -292,24 +297,48 @@ def _create_thread(tid, data, isImport=False): if media_files: if extractor is not None: raise Exception('Combined data types are not supported') - source_paths=[os.path.join(upload_dir, f) for f in media_files] - if media_type in {'archive', 'zip'} and db_data.storage == models.StorageChoice.SHARE: - source_paths.append(db_data.get_upload_dirname()) - upload_dir = db_data.get_upload_dirname() - db_data.storage = models.StorageChoice.LOCAL if isImport and media_type == 'image' and db_data.storage == models.StorageChoice.SHARE: manifest_index = _get_manifest_frame_indexer(db_data.start_frame, db_data.get_frame_step()) db_data.start_frame = 0 data['stop_frame'] = None db_data.frame_filter = '' + if isImport and media_type != 'video' and db_data.storage_method == models.StorageMethodChoice.CACHE: + # we should sort media_files according to the manifest content sequence + manifest = ImageManifestManager(db_data.get_manifest_path()) + manifest.set_index() + sorted_media_files = [] + for idx in range(len(media_files)): + properties = manifest[manifest_index(idx)] + image_name = properties.get('name', None) + image_extension = properties.get('extension', None) + + full_image_path = f"{image_name}{image_extension}" if image_name and image_extension else None + if full_image_path and full_image_path in media_files: + sorted_media_files.append(full_image_path) + media_files = sorted_media_files.copy() + del sorted_media_files + data['sorting_method'] = models.SortingMethod.PREDEFINED + source_paths=[os.path.join(upload_dir, f) for f in media_files] + if manifest_file and not isImport and data['sorting_method'] in {models.SortingMethod.RANDOM, models.SortingMethod.PREDEFINED}: + raise Exception("It isn't supported to upload manifest file and use random sorting") + if isImport and db_data.storage_method == models.StorageMethodChoice.FILE_SYSTEM and \ + data['sorting_method'] in {models.SortingMethod.RANDOM, models.SortingMethod.PREDEFINED}: + raise Exception("It isn't supported to import the task that was created without cache but with random/predefined sorting") - extractor = MEDIA_TYPES[media_type]['extractor']( - source_path=source_paths, - step=db_data.get_frame_step(), - start=db_data.start_frame, - stop=data['stop_frame'], - ) + if media_type in {'archive', 'zip'} and db_data.storage == models.StorageChoice.SHARE: + source_paths.append(db_data.get_upload_dirname()) + upload_dir = db_data.get_upload_dirname() + db_data.storage = models.StorageChoice.LOCAL + details = { + 'source_path': source_paths, + 'step': db_data.get_frame_step(), + 'start': db_data.start_frame, + 'stop': data['stop_frame'], + } + if media_type != 'video': + details['sorting_method'] = data['sorting_method'] + extractor = MEDIA_TYPES[media_type]['extractor'](**details) validate_dimension = ValidateDimension() if isinstance(extractor, MEDIA_TYPES['zip']['extractor']): @@ -474,8 +503,12 @@ def _create_thread(tid, data, isImport=False): chunk_paths = [(extractor.get_path(i), i) for i in chunk_frames] img_sizes = [] - for _, frame_id in chunk_paths: + for chunk_path, frame_id in chunk_paths: properties = manifest[manifest_index(frame_id)] + + # check mapping + if not chunk_path.endswith(f"{properties['name']}{properties['extension']}"): + raise Exception('Incorrect file mapping to manifest content') if db_task.dimension == models.DimensionType.DIM_2D: resolution = (properties['width'], properties['height']) else: diff --git a/cvat/apps/engine/tests/test_rest_api.py b/cvat/apps/engine/tests/test_rest_api.py index 6262ff99..f5499c62 100644 --- a/cvat/apps/engine/tests/test_rest_api.py +++ b/cvat/apps/engine/tests/test_rest_api.py @@ -30,9 +30,9 @@ from rest_framework.test import APIClient, APITestCase from datumaro.util.test_utils import TestDir from cvat.apps.engine.models import (AttributeSpec, AttributeType, Data, Job, Project, - Segment, StatusChoice, Task, Label, StorageMethodChoice, StorageChoice) -from cvat.apps.engine.media_extractors import ValidateDimension -from cvat.apps.engine.models import DimensionType + Segment, StatusChoice, Task, Label, StorageMethodChoice, StorageChoice, DimensionType, + SortingMethod) +from cvat.apps.engine.media_extractors import ValidateDimension, sort from utils.dataset_manifest import ImageManifestManager, VideoManifestManager def create_db_users(cls): @@ -2169,17 +2169,29 @@ class TaskImportExportAPITestCase(APITestCase): with open(path, "wb") as image: image.write(data.read()) - cls.media_data.append( - { - **{"image_quality": 75, - "copy_data": True, - "start_frame": 2, - "stop_frame": 9, - "frame_filter": "step=2", - }, - **{"server_files[{}]".format(i): imagename_pattern.format(i) for i in range(image_count)}, - } - ) + data = { + "image_quality": 75, + "copy_data": True, + "start_frame": 2, + "stop_frame": 9, + "frame_filter": "step=2", + **{"server_files[{}]".format(i): imagename_pattern.format(i) for i in range(image_count)}, + } + use_cache_data = { + **data, + 'use_cache': True, + } + cls.media_data.append(data) + + data['sorting_method'] = SortingMethod.NATURAL + cls.media_data.append(data) + cls.media_data.append(use_cache_data) + + use_cache_data['sorting_method'] = SortingMethod.NATURAL + cls.media_data.append(use_cache_data) + + use_cache_data['sorting_method'] = SortingMethod.RANDOM + cls.media_data.append(use_cache_data) filename = "test_video_1.mp4" path = os.path.join(settings.SHARE_ROOT, filename) @@ -2267,13 +2279,47 @@ class TaskImportExportAPITestCase(APITestCase): } ) + data = { + "client_files[0]": generate_image_file("test_1.jpg")[1], + "client_files[1]": generate_image_file("test_2.jpg")[1], + "client_files[2]": generate_image_file("test_10.jpg")[1], + "client_files[3]": generate_image_file("test_3.jpg")[1], + "image_quality": 75, + } + use_cache_data = { + **data, + 'use_cache': True, + } cls.media_data.extend([ # image list local + # sorted data + # natural: test_1.jpg, test_2.jpg, test_3.jpg, test_10.jpg { - "client_files[0]": generate_image_file("test_1.jpg")[1], - "client_files[1]": generate_image_file("test_2.jpg")[1], - "client_files[2]": generate_image_file("test_3.jpg")[1], - "image_quality": 75, + **use_cache_data, + 'sorting_method': SortingMethod.NATURAL, + }, + { + **data, + 'sorting_method': SortingMethod.NATURAL, + }, + # random + { + **use_cache_data, + 'sorting_method': SortingMethod.RANDOM, + }, + # predefined: test_1.jpg, test_2.jpg, test_10.jpg, test_2.jpg + { + **use_cache_data, + 'sorting_method': SortingMethod.PREDEFINED, + }, + # lexicographical: test_1.jpg, test_10.jpg, test_2.jpg, test_3.jpg + { + **use_cache_data, + 'sorting_method': SortingMethod.LEXICOGRAPHICAL, + }, + { + **data, + 'sorting_method': SortingMethod.LEXICOGRAPHICAL, }, # video local { @@ -2576,7 +2622,7 @@ def generate_manifest_file(data_type, manifest_path, sources): kwargs = { 'images': { 'sources': sources, - 'is_sorted': False, + 'sorting_method': SortingMethod.LEXICOGRAPHICAL, }, 'video': { 'media_file': sources[0], @@ -2633,6 +2679,13 @@ class TaskDataAPITestCase(APITestCase): image.write(data.read()) cls._image_sizes[filename] = img_size + filename = "test_10.jpg" + path = os.path.join(settings.SHARE_ROOT, filename) + img_size, data = generate_image_file(filename) + with open(path, "wb") as image: + image.write(data.read()) + cls._image_sizes[filename] = img_size + filename = os.path.join("data", "test_3.jpg") path = os.path.join(settings.SHARE_ROOT, filename) os.makedirs(os.path.dirname(path)) @@ -2732,6 +2785,9 @@ class TaskDataAPITestCase(APITestCase): path = os.path.join(settings.SHARE_ROOT, "test_3.jpg") os.remove(path) + path = os.path.join(settings.SHARE_ROOT, "test_10.jpg") + os.remove(path) + path = os.path.join(settings.SHARE_ROOT, "data", "test_3.jpg") os.remove(path) @@ -2892,9 +2948,9 @@ class TaskDataAPITestCase(APITestCase): client_files = [img for key, img in data.items() if key.startswith("client_files")] if server_files: - source_files = [os.path.join(settings.SHARE_ROOT, f) for f in sorted(server_files)] + source_files = [os.path.join(settings.SHARE_ROOT, f) for f in sort(server_files, data.get('sorting_method', SortingMethod.LEXICOGRAPHICAL))] else: - source_files = [f for f in sorted(client_files, key=lambda e: e.name)] + source_files = [f for f in sort(client_files, data.get('sorting_method', SortingMethod.LEXICOGRAPHICAL), func=lambda e: e.name)] source_images = [] for f in source_files: @@ -3128,7 +3184,7 @@ class TaskDataAPITestCase(APITestCase): image_sizes, StorageMethodChoice.CACHE, StorageChoice.LOCAL) task_spec = { - "name": "cached images task without copying #16", + "name": "cached images task with default sorting data and without copying #16", "overlap": 0, "segment_size": 0, "labels": [ @@ -3140,14 +3196,14 @@ class TaskDataAPITestCase(APITestCase): task_data = { "server_files[0]": "test_1.jpg", "server_files[1]": "test_2.jpg", - "server_files[2]": "test_3.jpg", + "server_files[2]": "test_10.jpg", "image_quality": 70, "use_cache": True, } image_sizes = [ self._image_sizes[task_data["server_files[0]"]], - self._image_sizes[task_data["server_files[1]"]], self._image_sizes[task_data["server_files[2]"]], + self._image_sizes[task_data["server_files[1]"]], ] self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.IMAGESET, @@ -3381,6 +3437,44 @@ class TaskDataAPITestCase(APITestCase): self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.IMAGESET, self.ChunkType.IMAGESET, image_sizes, StorageMethodChoice.CACHE, StorageChoice.LOCAL) + # test predefined sorting + task_spec.update([('name', 'task custom data sequence #28')]) + task_data = { + "server_files[0]": "test_1.jpg", + "server_files[1]": "test_3.jpg", + "server_files[2]": "test_2.jpg", + "image_quality": 70, + "use_cache": True, + "sorting_method": SortingMethod.PREDEFINED + } + image_sizes = [ + self._image_sizes[task_data["server_files[0]"]], + self._image_sizes[task_data["server_files[1]"]], + self._image_sizes[task_data["server_files[2]"]], + ] + + self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.IMAGESET, self.ChunkType.IMAGESET, + image_sizes, StorageMethodChoice.CACHE, StorageChoice.SHARE) + + # test a natural data sequence + task_spec.update([('name', 'task native data sequence #29')]) + task_data = { + "server_files[0]": "test_10.jpg", + "server_files[1]": "test_2.jpg", + "server_files[2]": "test_1.jpg", + "image_quality": 70, + "use_cache": True, + "sorting_method": SortingMethod.NATURAL + } + image_sizes = [ + self._image_sizes[task_data["server_files[2]"]], + self._image_sizes[task_data["server_files[1]"]], + self._image_sizes[task_data["server_files[0]"]], + ] + + self._test_api_v1_tasks_id_data_spec(user, task_spec, task_data, self.ChunkType.IMAGESET, self.ChunkType.IMAGESET, + image_sizes, StorageMethodChoice.CACHE, StorageChoice.SHARE) + def test_api_v1_tasks_id_data_admin(self): self._test_api_v1_tasks_id_data(self.admin) diff --git a/cvat/apps/engine/utils.py b/cvat/apps/engine/utils.py index c7d8ed49..d1df1c71 100644 --- a/cvat/apps/engine/utils.py +++ b/cvat/apps/engine/utils.py @@ -105,4 +105,4 @@ def parse_specific_attributes(specific_attributes): return { item.split('=')[0].strip(): item.split('=')[1].strip() for item in specific_attributes.split('&') - } if specific_attributes else dict() \ No newline at end of file + } if specific_attributes else dict() diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index e2a75461..2228bb7f 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -637,9 +637,8 @@ class TaskViewSet(auth.TaskGetQuerySetMixin, viewsets.ModelViewSet): db_task.data = db_data db_task.save() data = {k:v for k, v in serializer.data.items()} - data['use_zip_chunks'] = serializer.validated_data['use_zip_chunks'] - data['use_cache'] = serializer.validated_data['use_cache'] - data['copy_data'] = serializer.validated_data['copy_data'] + for extra_key in { 'use_zip_chunks', 'use_cache', 'copy_data' }: + data[extra_key] = serializer.validated_data[extra_key] if data['use_cache']: db_task.data.storage_method = StorageMethodChoice.CACHE db_task.data.save(update_fields=['storage_method']) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 2e96902e..41580bac 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -55,3 +55,4 @@ google-cloud-storage==1.42.0 # when pycocotools is installed by wheel in python 3.8+ datumaro==0.2.0 --no-binary=datumaro --no-binary=pycocotools urllib3>=1.26.5 # not directly required, pinned by Snyk to avoid a vulnerability +natsort==8.0.0 diff --git a/site/content/en/docs/manual/basics/creating_an_annotation_task.md b/site/content/en/docs/manual/basics/creating_an_annotation_task.md index 82a6cab9..ee4e9e0e 100644 --- a/site/content/en/docs/manual/basics/creating_an_annotation_task.md +++ b/site/content/en/docs/manual/basics/creating_an_annotation_task.md @@ -125,7 +125,15 @@ To create a 3D task, you need to use the following directory structures: ## Advanced configuration -![](/images/image128_use_cache.jpg) +![](/images/image128.jpg) + +### Sorting method + +Option to sort the data. It is not relevant for videos. +For example, the sequence `2.jpeg, 10.jpeg, 1.jpeg` after sorting will be: +- `lexicographical`: 1.jpeg, 10.jpeg, 2.jpeg +- `natural`: 1.jpeg, 2.jpeg, 10.jpeg +- `predefined`: 2.jpeg, 10.jpeg, 1.jpeg ### Use zip chunks diff --git a/site/content/en/images/image128.jpg b/site/content/en/images/image128.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1538dc61ef723f317cc453d583cba3b54973979 GIT binary patch literal 64413 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%0D}fS!gzWt+Mo>~`@L+p{CW z)MBD9XMVEH@#JHy0xP|GsNP3CQhsB@7mUk3cbNlVXW~Vc z^BA7@O#wQ(zQr35)_e!_#i0z%)ZEy>P~aggmA4 zH=!2XO*mFkiZwpXQnBc-9|WQ<%1|h!-b0IQBce-S=bBu#99JN=7ar()uh(1ZeUGKpuT!1Ye9>eo(oN)m zg0|%Z{&2KNThNWg&gDA#N%bAAQ%Da3{1O@Yqb@@i!E!CHzo+Zo$f8S(Y1S7wb@@1E z!~91faqeKl`Tjo6nRt4@I}Lwc1rWmrD7~9gc@ztx5+7rI1Q#{|YElAjKu2J;sjSQ! zP)A|v@xCZL#0nUG_=?DkkmV;}!0R}K`~Vm+ve>WuZ6KR-cGqY?(aJwuD)amCoga6$ zfq5Dqu*H`)<$wnP;MeJyKyl0s2rg1Fe{m*y!Sv!^*wN1?{!@iG%F&S4#nF=k`Ww(- zMnNS(TOqF%p__5M6t#5PhfTE`&|eG3Ke0&WS<(^$DxkLtP^&;GQFId zrnOzKfZ8#@iUKTM(mDx{GJFbHz~K|{8xZ^1&~+tXMO_zP!wvQeDio2~x1oY?$Tnmb z07isR{dMiHvKqDwE$ey9Vok~a2$@3u88+)(@eAK`wT=H(F7u~c6^tfaw;YK*Kkvo~ zXAexg&gx*@4PHh&65}CPe#A#8VOEJRCb(u@`lk-%Mp>QFWpjH=6u_K4(e_{xb%z)0f3k#Ptrc5_`nIDC_i+B{o6Tf#XSCX5H?PTLMF?t@8f zn&(&qpz&Zw{D*E&vx zRiOct@Lq?F&Y>GnJ{_L4D95W88o?$LvT(v1kf5gzVRRbhwr3PDJuO41r=}{?^e%N+BK=*JB zoY9jbw{MDPW}MS`bts~)WEYMfjX0Ddu>&y+hoDqZBjum->T@(C0A*B9dpHO$;0GokXVhTt<}3_ntH60er~? zm94uNQ!&veQH7GvN--R}=)NJQ#RSE)TP0a9*9pFzA5Y$DNjMqt@L~dJDz1w{=K>jW zS^ge+(wV|osbaX|%zjmGUcRI?o{z+Z8Y2?rVaAYCGb43`Z@7Tr#H0N<`KrdjiW=Md`T<+V#5>SqY^+J>O56MVB0^&X z8H|8Kd}_(Hwf?)j?4kOTZZM7dp<}m-zNfN1dPX;(OF{LjVYUibL(I`|iLU-ssK$K+ z+(kf!G{7{382sX7i&6ynKo1z>RGxkgH&<$pGb zR1iaXbFQO3>OA}8MS1M-S_e-8OGuh{K2I9`g-eEJL>rR5>kkD$9aURx_q9rdo> z=tGM=anF(s_cli+XF?=}u_(l5F`vph+VU_SVy8IrCg6+qs^%VEy#DvG;eJ)hgG z9aXHzA#Px|Yv0|gOf^s2O3*Jbg|45RUN|q4`h_&#dr(^$V-lz)Orj-&O@ZaX>*!Q%}vY9Kj)5WzLn@$k8NptLRFyN`tVLU_8T7K!5IK@alK{b4Kz22Eo{w zdPPnG<<}W}A|3yry1DB{{n$Q-q$F>(W;qkhAHyrRom`HTB*gz7(ZyZ6kDjubZn7`m z%Ic0;SLpGRDD51hzxN?9<5Se@1VxIGAILphC?@_v$yE`%+j6m*STxlNSB+Yav!9_6 zL739I2m0CzMw})fyrydxX8+65&@-Ld7xSsk|0AXsjMVM-)Q=sQc8PZ ztU`fAVJY=)QE`4xp0*hKjxF{F!2t@IM;sikX}TLkWAZdh1uv-DZ83S z7bUSF^kZI7uMdSv2DIM>_V0SQ$39s?)leVa&P?F2$t#$g3)*yKk!3j$L2Ewn*=JD_ z@U5Vz)^9#+nd2TjCYy9Z55rS%B?_?JW+75)sI9KLB(s#?kHaff58HljB-KxbMhqhS zuv5_3cile^op8>zbg2^^Njxe)9K4{3XURjdp$jc-&tTbk4LW{UZ+zlWwvVAQr)xFU z4|@~x%DQ}V<3jB^H*k|hw_r~9ghITx(0!>26mC#Fp`HhRr6w*4a zD>>&Jg#|%(XK)yf@JI(+dOJ-By23$~#sJH!b5zC@dlhVt9=kT);#s?W3p~%Ji39`a zHMj5y5Q+GEYV)&h=b!hRkqKUGXGR{NJH>y;ChOFHoOzW6qIyhw7D8p13ir`4IPEpK zk^t$Y=e_lTT-?)x%yU5Y@qa!48C^QL2W*c(?|cLDY`X!qD&1}>B2O{JeS35R`W!QH zeMocz!j-z!`djqsm(KoQ>{wwW+kHF0u_jiAcE)4d;WIoM!Rl{8rYx6X8F%E`mwPB7 z%k0F;UdrCspKz}v!%7rxK;q2Bm#ge>8gKfB5hyp`Qs}`rSw*O7GND_GUj#8HA4&!@ zNPGhoN$sx>@F!JHpjpbSHc*CC1tSfy1Qb!KlLQhC8un%#lE4Qksj)9dXpb{2bC6^) z=!wx`2G5w_UeQ+aZrulb=og#t^B@3(__t8tuOIF97X(7$jbBBtk&2pjKQ)~Niu`g8 zg3*CltV7d=-l?mTAh{x`s;jGC*d!6gy6kID>9m(VLqf&l8 z4-jp<1Xz`h@M>>Bjey~>$ncxy&$#`qht9aw`V+74U)#0w`_MjT{79~BlBXz!2((5p zJ`jW`3#9XJki4zfl|kc|T&PXiQyMnL9Y+0&m+KmAtp`l}<`SdlEnNV#Yq!YujtI+t<3d8rZo zf46r0t5(ZAOVRz!N7tUf%95ddYkDfw(ScSx<)#acr8}mJl)38qeD6P~sM?N=$ly9n z_udQdT(^@b-{4SjiVrJFe^~;zjh+AWLiB3|gwrq5BoMC5tg&V+vIPQsnR9ZVU+{%e@z8~js75(G7nn3`ZYyWvFX1#>)Tm$!YN&#;W zm&=I$)GSRXx`o|UWMS!tIgx%}`cCSk;KLURu>cb^3PJDi&hX_3BvPa>y}oC`AgB@` zf`GJgf`Kit%-wvt&1IalXw>ys@0gL}pJ!{xmDzR8X!-a}sk*!RH!@hgsSt2Z$yk(y z;OTuWw7k0rr+wvNl@4~U`TKRAU(fh8;_QGT$|%QrcL#Vav1nBM1+y60I$E;L$JlF8JF3+V6}DOJ{Sj` zqH$%H=WJ?bOzmP!>%p>yXd3e^k8*WMzQuF0@297a`ZA zV=lL>5dbpuKj+Y&^XPxY@u%kM-{h1FoWXQaQe2#L4(VlxG(^PoGT(8phvwo}tV9ow zfoz^PfJo?I`00Fgk3!ZqCT>6ofB_z=^w;nIIr%6LLzc!BF3wT`hO`DE}|Nkkc^G@JlSb%H7SVyQ3_UCo4 z2aS8%RoN3zXOUFQipc6foX(~ue>KEMWovUapS&J}Q;5UVhuDl^`|7Zq%}7f{dAK#U zD{4jHTCc@6bk*p`L(O>;aZXMb6EqF&`H6kmWAXKC=WA@6^mvV2mxuRoDC-8&x zp0pnqetr#Y%LNXnL}lG6DokQqUaODaO)nFx)cXND6;#-qo*<_Hf=12PW}mXNVc&5k zs|x}%^Rv!sU~*KjB(@N$aJ|l*`VwxRP%c+gZ~v3*WtI4#Y10?CVhPFQdIQf!&)rK9ZC&sUoGhh4!W}cN!{+H>`#_r1 z0(#FUR%avtIcy!dW8&jT`lJL>_VFb{KY~jP87Mg!p{MjZlRfN11%-n0UXr>U5E-YC zl7_*?gqCQ#>}_mIU$ztAdY*<;v_N{QzvKGEHGrM3)h6crk8DIz*X(*vnV37cQ$wd3 z{q3M^p3K$>vB{7V;iq=n_vYm*Rcxa=n04t31pKw`tSqV_>#&F@Px8)FRtmD?6^MiF z9Mh!UGB!pON9x6{XS8^F^%{S`;@t8w7yP*3Q;k|w`4({0*$Jb~fh z7Ui0X#;VY*&e-w&r)5D|#-wrTc+(7I8u6v6(W~eBjT%?1klKr=-X``oy|+J(w3z{# zS*?88#eslW2ffv4$-uEK#dhDWc`Ln8``8|gtI4Z^R;b7maJgliM(spMy+W1(*1L?E z>5hyu7qS8^+aepa#Qvx)2PUHUXd6yR5nGS^z5Tjzb85fC`qU z%A&AN`_yXO;(h!lT0atmdt()u&RtGbSUaZfl&tvw7{!RZJ13BIA)y<`Z1J(t*QhXfVG&f zh7sE+h$KScf?@sXm!3e(5A$ctFCB>a{Xh%|`;PtYGYsZK!L%_a2zImijAI3}|CHgc zo`B6C!)>b)-OZLRR%?7(yCJBb(+_WV`tcl<1q<1O zL>6%~x)X?9D*PSH;B?gBN{{N7bHti~Z=Oth?QumAtI72P?SZD=OCjDUXiogfxxV?N z@44U!sIJQPG;a$|?&j6eECBJRDUYd;y+<88HKkLN$-%{0IOx5#vKRXCX;d(SuFYbV z*SzR8qHWV|Zqr$<$S>!v@CKfTovn8MjZ^@YRji&nt9g)8tdc!-=!r8d($I&4dHMq` z*E=z?Zo+dZA+(h%l1rg8jb=D@=@2`0oK`Fa8!MYmK~Vgj5?blwv>3lC6A8#H!Gjoi zhtFZpej~!$&2?5DH~^#L*a48vdpVwuSuyTm<)ByW+SAVnjBU9(3eseduo1pu@wK8^ z_~eZ%M#a!9v1jnLYH{XSdf`30*Kr(~8uw}Zb<`?Bxt{osY1)nXB@tujqVyD|*MOXe z&BXOFkP{)2y44c1c9^pJnu=;EU4WG+lt?+Y1cp9cT}US!b}ymfisX2B{y0xRdYJm*>S~1GTs=k3EgWDe=UBHRichE`v>XR|j>gJW` z-ciJ+ePaxI3sffydc_(=K1656Ef+ua`2j5&veny0$i>}R$h;C{-|3fgXP)6%w8r{b zd2IkMPnxI^rfRUA2tKZYRaio~FE1fH46g1^mB=^fXgxgp?+J#=6iwy*a`~-WX;?=` ze)}1HzC8$TH`g)#-ah~oAA#&P0KOXJ*@RyJ@%*5STdlu^#$f|DAhGG<%MEroTiCCS zpMdwqUv%|Xz5R&+zWp>me$)J`>%Rp4Z%X(7ubOGHzbr@1^wDugi&c7{QjLLls(BIC z5B(E)?LYKdvF}^mw|qm<*H_)!jnDXnuLmJTn%rDyzYS!ir1^$mcD<%9@Ti+coFt?u zfr^(k$%lro1Cbmn2WqCXaXKuMJ;M!09=nR&wJd#{o7IlczIBzsFMO#q{&g6jDmHWT z8@uAqD+_PgDm%b}yr1C6pE1y0k0Z=cjESi@TT@33G!|?QuhxH0c`kCq>I4VY?r>qz zVueG&eCEJ})st@cv4-j=yV1{8tW52%p`?*w)86hE9?k`rxmJ-f!iBsF{IEKU9*JdW zYu-wMXPSSrY85tOu+p`c!7A0n8eB?8!rozH8-lJU!7N8#nHob@YyHsOz_6424$lev zn0{q@F5WyBT6sEYJfe@T`T5&>&KP-lNe5SQ!==z8gsCB2fZ$048dkxjgb4YR@m$TO z=(G9C^5H}=m2PWoY+8nex|iN2U(|~y`4yHQ<2_4w_LE+P%?-RDY?>%ON0=MZwF`8< zXHrvNMT8ddbet{I`?>W9dDI#5_woKPch30@byuQ?>o&{cHGHg9xKM4p5Dh^$*j&YamY-f7J$@`0!ZWp}*Exvp602T`6HkQo%|(hl z8)fhNo|Wo^vtV6Uw)cy{Hka^xuZ0IDf(|;4?uj3bB!&Gk(KQU;P=pY42J8Sv|HH+V z>`SxHC!2(02sELRtb-;iioQ|nXRX|C6}2W|Q;*_z+37!=GNM|HVSGZbLyxL(Xg&IF z<_&KuuHO1lBss4%y2H*j&A&6(+daY5i^NL%ej3I%lUBozIJm!#84Am_3hySp0d1w4 zRam~yBlSA_5aCMk<*HiDtz&Leaw^2iC0gUbH7u9N=fFMFzc2Pqk8NpfYcG*QH}o(# z_IutBDT0i`Ox8u=2dmFb1DOL5oco9GI0(04%XMidj%uK=2XGTp$BeJK-nSgldw$r4 z=B;6=E}<+tlpR9WhKglX648!yETmAM0ghG7Wb(&FzK5YPypC-R0 z=<3Ovf0iqfd@}L1K^l3Q#!kY*b!lh2UlM_~;hEiASDpszD1&aJ%Gg!T1;u-6YFL`c z8@xF7p-Dzzn-`ZI*AN5yNhMw6)fxzWOfy+sWSoHYDI;A(bE*@g@;ezr)JMCHy9qTX zn*}=SHW@WjW(nhWYdNy_NI7NtXYgKwI;qXG1R)0A6v+ggEqPY=H}=Zbzu966peyK+K%#lngC*dq=y=xmzucT zQDl>$1YOBeibR`+?5QDATdR(wb{wCCdf(2N3-6x~Yi_unp04LKTu#3SC(RVv??&~6 zGli-a1G{%kS0a>-4{0@0G{&h6v9g(ejeUL|7<~6DE$wD7oV(B9>8pM2?XIXkNTpUQ zQbhY#wU2a&Fxtb@%{~fi3O1J`4aA%cjg5%Oskdpk#wl;d7s+D}XFTUD5iL6S)>6{z z^f)bToi?@1)nw-A%~cJ%i9=tDQwI{}e?M|z8Pd(Xh`Dl@n?Y|s_&#b>r72V~r4(%F zW1n=jhJ>p$4W6*epCqVG0PED4NgUDy&@$z>;)CM8=aSfB!|;oNe0I@NRh7U6b8t!H z7+d;C&qgGoa}M`NOrU7Ygc;9O1J;gXN|F)avb+z{Xb8#legP$jeiFvssX3YKdjCPh zJM{Aek!LO^)D1MTL_;Cv)c&THej#|wSm})fRij(r50N+!#!4hbzuQx zA(tF0wnI7LhL)ffQ;6mAjC4?gYZ)HrXY5WVF1yp|eWK$tiWtDDZ2l)0(SMuK{tcMm z&v2pr3LmKYQI4dW0#nF0~&x9 z!;t`QhU_!SvrNrtiIr!jcKAB2iFowr)lSw3qi@EI&Wr_W=VVwg@;H*LD^+7cQ4x}; zK4GLxu1A8U`hWoDI1s?RqXPimhgLU44>y4T=R4LLQ2UQVY#Qf^cdUK2Eu>$>?^63( zqU3hSJ6O_{RDHswub0R0Qo`_F5v@Bq$@TmtnLud^gqK9Bn$Ga%uWY@U=Fg1U_olaC zun#>EBvs8#0u0m-P5?Ivs-7q@k}_tK&^!r^-qC!?q)4U65%_E3;r5N_LEYCQ$Hc@(Z6+IY#PG4kCqgBtiH0`g`DO_6uggquuswa}iJ?@y08wwMj=|ME zJ2G2NMFP(u9~RHl)R8py@1A3r^o^E|5jS+e0&Spf${jjWogV}7 z`Y*9Sjgr%?#0@CE5IXO&>6YjiK*qptAIxz|On!-GQfr#r%JGaf6HkIrsCtf@Dap{# zIMDo$-V1>(AVa4fhb9-Z5ZSN{W3GWe5c0j@ z{<(+h(zH*r;$M6wX6?Z}-ral|C0~*7ZCVD5J{_sqI^^?yjlV;_e$?w%tqJ?}o?-+= zp1kIcj%F`$-o{SSiCeuw#@cSE#jrWrRbL%uu~o)T=$=!1Z^Z2u{r(2zN*mUk_!~98 zB`(mpQvu^nCkDY_U`1pxM*&9S} zeed6l5QKuy>w$HW$reOW@9r!`_)b?Z7RnggJlTz$d*sP5(|NBssd(9)(m(BwK3S}d z$oPV(mQ7lW8FeS+<7W{gBGq3u{byu zkum;9D}GW+KOZKkR>DW89^KTogb!^9H4cn0h#O3g5yuGE50>q1tpsA)k1*wrL_$&1Gf`&=zJDZ@JEMag_V+uOSR$%hXoG!(QS(cG z)OylmxDa-#AiQYKu*H((TT1b&`2FWlIAMnH_8!n2@}4K2VC=NdP9ArV9OdQ=Tia$F ztd_wNgjeOto)_{`LVW~MwQtXZzZsy2=CDzuPn9(lJ|K|h+}Nj3;pg67F)Zo~-K2{Q z53A|5mNl#VO)qnhqB?Bl8B=~=4bgeRUOAFVafeitrm^Ji<)O)RQRBADaawS*bJPdb zNvFZ_J=Bd*Mx{l=bAJELWGI3qDas`qWM&E9$5dx={PCM;AJG_sB?TLz*c^%!tD2QQJfqIn9kVn0xRZ*P? zMzeUSELiW{mi97lUwi8bL3OG{TzVpVMjq&v?Ta^{dOw|2VFlW}FKbh`hT?DLP_&B= zA8c&5X?+;ds?^-Vx^UV@Utd1v__}tOSwBb{IX5@gD{gEXmAMn~^)#@=_OTe!QW2)s zA3leK@?Ssy$4MQCIQ@4O3;ivaqgC|d8Ckce{s@Vc_D({6^fh~x6jkFLc%_T?pD~S= z8tFD7C2}U$)lYe2;{=lCbaXBEW$5;rGn_)xAM2DAiG8mwvb7`Kbe*E(RX>%nm5xd3JLZ%vo?G8t4PGoiNHU*Lrmy zV}{f(8>a|9Sdhb&rj;r|V#=i%<3sn!$Ps@R_7W|Y9ote-mZj7(*1m@&h$S@k2YR(& zOh5-6rMU1$ZT96nu(k0WWlNpT-jRAKt~ZqBz&`Q_M>Xa$X8t^ylS;0()3LO^on7mS zZlbLTvCup#9?~S-HsL}=`7B!8$TGX{xv?+b_GkJIh|g#0FgwXa7zQXCX{(`fJfKB_ z6-hxgN(L)t8GW@ZPpYi%_w?1HeXw9v=pc*1ck+a=L68b@s$lv+O8yf|Ry#zHis;zM=d=T1C#%NagHD;#wb$1!_HZlCZ zf1iu1z}Jr9D;hBb%3L>ld#qXVpx81oJSr$^3&%Lx_>Fj6? zAH4K5eRFYw&&qq(F?BD&YL$VFHn82afj44WJKWeU1?SSUgWU(Kmy!ti19xJP+kje_ ze6;}&pR=r+Z9K4r+x>ew#xyHJ@q4jb?ObFa-t`9I_roi`8YRjB>PV zQazKa+FOk>NA<^K>qhloqz#=V@Oo8QjaUtA52Z&+>SB4G)O=R1?l&hx+Ar&&v#BRy z@gAeBclcvDP4Y6sUB##24)})y1mE|^p_b|E7qMxWnE|=a7+`^OJWttBIG)QmCV%J6 zp+?(W0J^brL~hmAi+23t!)Ge%gh}cxhGrlBSOIZ7-f>}CC*BEOsy!*1NRad*2y6dT zGoM5zrj6Tg%P1&kBDW+83?t3c1YcO3L zl@?06QEswJK6bgx0*@TFG%{`4N746`O3V7{c7q?cyTAyB^)z)1+zl*YflJ-tvQ!E0 zs`!4i2%{bo5cr3@$`a}`HMTJup%~`E$tMyz_(py#{MKUA6>#KpJ_$pw?XKU#r7#!m zh&Eja6Q+n@rLQn2zLD#2y}K%re#O)&Zq1BWm@ht{SpP#|u}^t?apBu!LuJ-=RWb_= zp_ZxTbFB#Z1AIibr{ObXHs0L6*IaJ(@FPJv@WhDbGhMyx8FNRs80v9RrgL} z2u6t*v$e8XQE3BBVPgBa#ArDrpgWSt3KGBr?Roki*R)K#t+l$oaS47^QJHdb15&V& zssAJu8*l(htxp!Lk~LYPUD?$WffdhA{+O~3e;%=+F+i>~9dkcq1-&d-fU&v2u^Q`1 zN{8fH%aqy*w4t&+-dwn{*QIqNqtZ5UN3xA5Zj zVkP)~F83!`D9_=?1B`dDop#4|o(Ua7-)u2@T+8u@0JTW5aE3XuUBnLR)8jmq7iGZ= zvVzX&kCLhbB&mq!_%6RK3EQh&dx@@m7KG@huH~&=#05K#yfVqNH;OV4w{*uZ5o>)~ zY>&tJnA39-?{$l<3kvW-A*kL~G1=@esq0zWA?s~uh8OD{7{7vmM?NE!Zi#$mTp zZ4cHLi*zMQ+1~~n*?uIy-^L$qckx`ZDhZ-aS`ywYrz9SV?StNpGGf)S{efI&E$FUW z#L+G<{6qzlOaX_Brv2N~!Rt>Q%S9tZqIK;eyN_1Z`*d8ROxm>Qu9`$gzm)B>eTF|I zj0-|;h(4%SuY1M5X7hCBMUmL6%Cg`Tj<*Vc!CivK!JFrG^OPrhbO{t!hgFE+BeD6?ud8( zKKFtAmbteyWjr@CP#-y2w1zq-GLd6frmgwa-UG>1LFgTUQB-&G!grzd5{mdo>>({- zI=o&y<7qk*vN|rbN+0B5@9ujP2wAKo2j@di1HI9fmr};YXMi0o8Z^h3WXqC`BeKL< zYaeSE*L*U0Ewh}mnw=_BTH~{LuMoQ8zXIzcYt1sXMu_8K^zonRvp9fC`y(p2e+4Y(PeN{5_6GgcFv5-0J`3HNlSdmQcUAKsh9 z=U|q3@Wtq9-WQ}4gnZIRZB!U%fnZc?XB2SabO4mLDq-BhOeQ_7HlRVlTy zIB~xOy576>nz8?hWixXFsMlQIbIB&_s3k+USWkAH0#>Zck=0biv_6%C2*l~aL7y=N z7$aUh44_l|s`m`Eq5A#8w^O7&Rm>J&NzHf^YC4)TGVBEYMic5zjj}2|Q!Pn5_-O|F zR1T#5C*ex}3i<#0J^s{pz!p9GkiLoQ*3|69Jj_)!(NsFVGqv{8UvnvWsao>}l!tpZ?T({>RHAjHo$M2__b=#V%&8iQZ9Rb0J#TMdo;)uAU%=09fHfr3o_0ku9eXy&{`WXKyU@<#rn~=c;GP zR&iFUwuOZFePh1Y!yW_A8kaioWmnEsm*GAGWmj~GQegnl1ry^+1U)O z)H7mN5eEdCs#y~0k`RQazT>HcuAQZFo~3@D|5)r_41Tp>8m8lMt-VnHGD?S`zBFNO zg>~!e{OTS(EX+7J#54KJUQOkq-Q)$hfI^;`3PXD*tff{sJE?P@R>GBFwiU(_QjPB^ zU1;*X)Y$(K_mLmI#6fZ0o}5=k+%#-OQogLQReA1JDtR`ktmp|yVkK3g;; z!%hv8NdfKG`z@gGzf(f$urCQ|Iid=#<5!wkax-+7d+LZF#CjaA0L1MfUyRA#+AT4FC z2)R};nH)p-xrj0IjxJ{|!`JGprGen7lz5LK@r6%Z*HT;Kc?MTVN5Nj zmLysx9fBsxs9(FTA^ODuAnf=rNA(Vw;EOfAkjk)fqvyL7Q z_#^96u@qy9162JL=>=)TK0bin0v`xu$F1coj6k|!_^lLO%8eC?ZgRI#Nh@T!@b94V*apxf)#DbM+ zNv$Suu1Bd+>XM6)@U?bpxxOs@*bG8OL=LJ_W9JSpQZw6jU!B!L%caV?0R8*EMX+Sq zXu=IhIgnM@ys|1%&_nAA+HYR1YQBsi*{ivnAI|tN^>}LHYUh&m>yNFgxVc1wD41*M z_OGSWzn;G?@pK+6W=jXlQpoR0f(ry6P0)sNSmK|`-WMmk8!j8Q)_hO1+m{+Cn1}sJ zeBZ2YAq>ZjhgF1v|0r2SO-|%QHkBiXEF&VWTgi{gk^WPvT>Y|I^6<{~q00@3`!sMf zrl+i_3(`ViaI5U-cJYjEYP%gFxbf~D=1I+G@fs-?*i0R{$@=xds*``d$O++A7S^CmpLnF+ zd|`8&Nd5hcYr*|<=_CtG^At4zz7DC`x9k4s02PpZAa!@t(~|9TFY z*gW6d%xx-eo{#-NWV$EVA|RJEE@0xYE}*liK6UiQ`C}lnhsVd4CM*{B(L#srn(k+Z&0Ref%Q(`ia_1bKZ(Z5zg-1TmU+?#j;fOgot0VbvqTH?c^(B8jmvdz1fKm*7 z)w@F4yJ~vnA=oGTqIzVT>r$3vo9<&}Zn(HQo*eL z#6>$CO{=zD(znWtHVGbnHJ{{|_$<&6RzclrYH{JHNP$P}zx&$xJEzB2u9p|559f0h zV6!8o9eSQ*!#kBV?DBc}S<$ECQ(66y^a0Q!+t0WkvmJ)w9WWk85{hNE-$P+d)E7Oo zh6~pZ?T9Wgj91`h%|MrNEiKK|vH`EJ{?c8BS`gz7itv*@ryJ1kSW~RfmEz-5_8RQk z9q0kS0fi?Agv!lPaft0oqex3@{spCqS(V2LlG?#L57%Z}z4P|%UN4(!rEyCh70W5- zhRU|<_B<}x%3`*0?pT%URowK4kXc3Hyr$pj?q`MHFSFmT#^vA0gm15_|LKxvfUNkh zj{joT`HM>kU{d`8>n5-}tc&ob_iXqw>uuz0Q+p@dJv(3~F(5=K_Ny-59*5C#+n zf5y{vyvqysqtMGQ(IU>q>k@+DAv0x*Wmxb%+Ijawl&SL1@y+6^$>yzIzEt1il-vn> zc#czo@FLg{_j*@<40$weTzy1W=;VM-dKh1mLYBO&n{UZh^$RIzIK5_``0P@0kB-zc zp+3%VgNVwc(!WEYx*@^R6BcFFfdp)S8oGtqx4E}9q2I=-c?tQy z5`&h>aeWiaKhzs%p5^O&tC$X;Pw43;!tJr6W5S<~0!3CLF`d%bEpVEJ%je}chC0}!Qjn$;Ez zGiGvfNOMXfsG`AKy?TT->ys7X$1GH)xyHeShB%*}xfOMa6#={|Ojh0zAEE{MO(qMQz;H+Qc=u=Y6QSxuHWhq8B6V zm~28zzrFsIG^skJnr`x!a3KhxynrReciT}n%vbP~e!Xn-6|>fdT1mRLqUcymxU+w= zWWDk45ZL{hYyK3j?C-hw<~u2?4{s4$*wigjr;WU?_@?1dX-`1=giyTru%d*%kryhP<#tHfF$bi1ZSO9?w#1z!Vf?;s-0?2 zsZT4O5oM=S?vl%f1+{8NXBrmYzWJIJ9YX^t1}n9Ycy!>Mm*=YS{EH)mSk=@e&q|v@ zn`M09l3H!=%_$DgXuoQInB3ayChris#>I?d)Z?m(L;SqfYtWqmD+jzY^_8zqUK|Vf z`uBKJFqB;rr;Z-_qGLKvvwmeNK0L;+%19mw6Y}DeJ1eipxJ%=2RkkvJcT1@rp8VCc zYTQ`Svo#8f1beiHlS~(xiDNVCOLkmBx>pQ=q7+ju4o8(8*daTx_?9xkRtXT2VG>ES zf(?ZiDZk*>r@|>IJHC@ME?t@>DEDsg#sa^1=MK-^r9kCQd)?$sPn!M3PHhRHgNPEz zV5e~M_EX5EQ*)AeH8I$*oqK2W6NN9raEPh^ZDQG;KQ%-9!Lnhxn`A;#^m!irMC3%xHpX2P zdE@pBUA*%_+U0+LkQNx9a~3n+@f~XOzUcb5~(1*T-kS6;j)JkSF=q>_m)qlU0nD76g{ za*1w!UwA3)f_;8-5po8bMA2yVs>w6YB4e&7Rse$q2EYGFFGsNvlFie4+k&hv^MPR0 z=Vp;s9Zoy}zc+GJK5O1^9fbq@oyd+WLCpzVLqP1`-YK{wcSz(TESs_NEnn5vJw4<| z0p3zulkVEsXLEY4TpQjHSL7(ZMl4o%N=rSy>zLxjzx>L~7636TeEYoJ-Cm0Q5@2Nx zF?)GKN;+yrP-w$?$R`aG-XmnyQGq*C+r6=KjS7KS(xNQfJudJ0<1vh*7ZM1AV&*dI zB9+wg-c`!R@7mE{%0*gW-}bNS6+9wHb=O8jz7VQu6s8A$YmNeL!MSLRhU zzA$z;A}+DsL*5>2U!Zy%LV|c`zy~T{@{_hObltQvbf*!WmgNxNvK;Rc3L`eAy~;iZ z^QTLob{%Tq=4HNB(h)0?m0}U*={Lgd?rDYxaaH(DhY@4V^+zy<)D2eF<15d=6K7OQu7f^nC+FpRE{SpT7|6nmUgLA_R$aWo zaeMH) z$FCG+wZ7hEzf@|7Hhyj1F1(#PwiBNHlG?Gghlp21GLCU*&Ni~84D!nc;K{hf2|&jt)bQsU}E-?Ql@vrH}2IipX-Hjerx$z zv=jMPrVy(R2mFt-;PSa=5j=MWC_i#}>o(l`K5CM@MNV~89fnmip&QiEt7O<$HM_qj<(8dsI3%MY;vBB&Eo!O{Mc0s4Qz=LNb4=P z)!Ig0(G)WzzDvGZ^F?5V@gSE1CF2 z*KM|%_+Yv*h9p2REd+b0p`<%^OUE*-QmnJ=pbPkCu`#M6scRRLX*wOr-f$uH`SvmS zyFxFs%V^aq>ZmNJ;&$$6ME58pRtIn&Pj4`v#SGk>@3#m$iN@0kZtNCNWNv*DTA>z! ziIVGE^>GMk01$8Y5qod%7Xg%pq1+ex(!wLcB4{B!b%vguPh=lW*kiAkbu7&@EUF}T zaf7$Vu{(;C8AWzmv7eI9;qDmRmZHQgkCwB~Y)@2fL&B(>8@ zDtx`BmwtN%aU|9&?!{o4K(1=e8fd{-?NK}5r>D=2^rdb49)4MO3CFMLe0bT!F?INe zocqXPuaHvtJJl<+H)x0F_VTN~z5@;+vp}DulL7i9Sg!OFb9+Et97k^^X!+?&VGxFI zZ?9Ni@fB?O$AkEQ@U*uT-}h%`A0_YV8Ju>KD%S(XlO`qR-uB+Vg)Pz0lzwL%~XO5Fw~#<4KpRH}_QBPJubxm|B%ymhFR98XEQ>h&nnQE3=4I zDIK2{tZ4ONL-^{`{D)NhLHS-T91t~HhJyW8Kii^;F$szUoHC>W<`z^A`QG-vhv|z9 zQ;cXeXZby<4E}y!RGSzzu4!baD4a$Z{EaK6k9fM_%zJu@>mgdbqK*K62mWXyeX3Nj?q;$XWMW^OZ-4Wy>8AVKS)c zvU9@U$bAESmsxy=q-(tYs?h4x`J#5b>Jp>5RuKkQV5&WQ6Hx7w!G5bVa4DYJ-VIo@ z1j#Jwdq|cC+nf&Fav|znyTcfsT13bD@-j@+nLa|+z;+XLXB^(A4(>IazuohG-SzYX zeVk3PNI}oMb);nxt8bN_t@t`T)-`G4IgcrB#Ik?<^J&A`6nYSWii7$d^bu@{EC&hf zvH71xpFz6>w{#S54u$A7COlu1MU1Q?n7H^BIhF$-nm9}q)H3y*i@%G+_RA82D| z50Id}0iC;Xf6B8yE!lcR+~4lH?nlS-tVU7VX4{T9M$7F{rQ4P!mY?IRH{)K(${R!U zULOq$);9)c8p<1jSrO>zmN^I@IRy0ndrKmsY~@u=XC4u_JAp_QCSK}?=f)C6uX(Gh zz12kf+-JskFLi3DC&VOIR}MAj&a%wSXoMx&7kB%*I{4|lFr zSch%I>d)i_7T-^?615HJz>_6@%A{l8#UVVFbVTG&0)J1P?l43{F@VB$0r()TVj09+ zE_`Rb(=k_nuYR~uu-Mp=F{Em*u_?MSOkoF)e{ zuPi>Qm^3Z)nR&3_Rwqc{pa{bog!M)Y^g1A?r8M){<{AX%oftW&Zt(axZys*DMH4&1 zlo&<{<(I|l)sm7KcD|$qVVE(b<#j3sZ?n`U%EnA@2WJWdPl+joSFxC%`*$1OSAhwh%XA(3 z(E7@eMmdTlHM1;Yca2#@7xU?Fk{Glx4Tm?+<=)s0qo!Hx%sg~mO)CL+$LelosaB6C ztUJfd+~>7Jl$sK!X2dNKaxRLUtdH)~2BH&I)M*Rs;@hzkuIKBa-YMDr}WxV$(}`Z-Ac_ZQ5I+y{HCgl(dVVe}IBQP#XE-Erux|I=rnc zKDBaS{JyZt)b}+Dy|4TeGy)a5C6m>X;s#ZGi<1Zb3vHyqf(^YoBT?Nn%}3Y+))+@I zAn^K398+%6n^yp6M?{l5t!|0+QGuRi`c0hHibQLd#(Gw;6gP?$)UjY_sjeTDy^mHm`J zp@<}#QRhwkHf!8Nk7ox|uFhcLh68u-%z}Qulb}@@--LYN%LDP7sIRtYL>1|oeKZ#qDZ1h{5|pjs5^?k}GCcSpH;Xg|$4s~)~o8~p*>o;BCrJ;+QldvCCA zDphepIdDh#;Vlzletm$KVBKiO+9l6Ab)5EE%yZG4KqGnhP?IGtDBk}!isqWKv_5Jr6#y7&dB}l_U*RjqS%Vv(zs#t;qNAnGU z-dm?&w^1@4`ZmC1j#ADxK4J_vw#$oKTI*^TD%7i1r3oEdXwF05w^E$2ZWMtz6)OYG zg!#>y?XAK&B?JSwF9GeY+#5jQ*FB0xDZX#j{4qrOpKV6;XYTi>jEpqv)1g&eP;@5S zZ->xmJP>JqHyYi-@s9E#obLSMXL2E+UtWz<{3BAYbo3@K=g*YNA_x)_iV~BJ|CxU} zS%yLvxdu`H4WIPa*MC*ZU)|xaQSzVvc3^v58*)IOwvsp)o+@M)*FBa+T`{t7#j&IQ z$BNMnqvWFykhMrrPA^+%JVXPor4 zPj-lGL3I3>ym4%OZCzTWV@EZJEBBc6#KXF88sJBbs$BKO2c02$X`ND<1leh+xb}Jj z44gc9_Wd3A{5qxRPP-hY_BVx_$qu;dsiu-y>%lD1Yi=Ri{Whz-xJ@2171Stvl)1#f3cn-v1iwz4-NfIyMK_vDx%keF9=L2#Ehgk&W8@&6- zq|kvf^l?`Y3b%~h4SsZ(a>Qe4)ZL5#aZ*U0A$Wf&*wP_NnJ26bsN*FQ%1_+ zr^m~97&{HrTB+TZZJroKK6cs%hAHh#@};(yd}+zRenNL|56d$PW25-d3Lk z7_P82iHt9a=P0u*gU3?|hpG}K;(X6?7Uad_HJ=N}GxLFHHl>XDLE#E({-~eWBDeMW z#QKP&6}dHkaH2*!9^8*D23pyOkM8!=f=aNL5`A#-OW1g6Pf&O^kzvys3EtGDXZ2?T zMWV27&kSZG6R!+MK8>i3_1?8KbQIx|JK-;n^_#l2#ozHt-YLm+g69}7f2E@4HoP^4 z@@uGK?fZe-y1WSlE4nX9{H!<)GCJLD%e=U=-NPrFQ#e&g&1n+0RY2I1jSMy$bnU`F zKJ2~JFG?HaO9_lOnp}_m=oN!;C@<40D#?n4LG2L|`-{rC6kT!uq#$dE>$771fOCBn zf1;Zy{}9F371`)7qA%sd>o-~YdjW}+xMbrq5k&#B&>FUAm>hgTHV4sJF1J#T?XJ?G;!@|LU z;Aci(aS0wX9c(?OQT?SM)%tJ(b+cl1l9=Adhg0?4?8S(}99BUScgAcyo3nzJ2=zO8 z=;nF!GC!-SP3$u)TdQ4I&>Nm)Gzv4j^;LW#d9l+4&nQ`w!rlOED9DW1n9OJ_vgW7+j?s68%}0Etu>%u6|(T*m1}rOZy&MtUGB#p^!78)ENpcwv!# z(YmtcJVw<#kiW;TOsZNm7&DN;P1Sen?2agvSZl!_Rdh#G177xeD+yyXTJUeN>Wj?n z{V(Nl%ij%fUzTHc&!@T{=`dDJv{dHW6>fH;i!Xs~| zkx=!wj`=W1e+(4(#>?a z!ZdJ9(!s$u;x*{is;Eg$AOE0>2T}|{zwo3Ce}heda*AL*#jxW8EL6CDgC-n$b{vUn zrUwvLMgIn_)PGSTkgIOXUF7PVXJu&7QJyp^?8VVE;0{{oSVeFT3A= zu_6D<glIK(4W>bv`2PL3coS*m8cZ zu@`v_qIbO@3M@nB z8D4`93MH3|QKWzgIH^{NtDyH-?=k6zqYmmDnGMM-PPvmW?sY0?9%o&1Ax)A*Tm*O&`Je3-9)cuh) zdR5*y+9cx1Tg;hFg>sAtDf*N9Dna`M8uwi%^S>0s&Tl6M={3@E6=?+n!{K!RT!IbIT0?>4+L3ygW6QbVv2mOCgVE-D-?Fs#PcoAe z(oM|6^1}iQj5)M?=$HI^cRAgg9j*dkYcW8#IiXIu2FdSied|YpMcO)ed#2jQZMq5i zFOeg20@l_?)Xfn*g$f!v&a-QPYR?U;@*jmG{YRhY$$9An>oXXW+{PkA#Tn=fI~|+t zWbI#eHZz??W?Mdx@z1xMf_-JTHL0j}JE?k6SW+7#dJ$};WhgR{2+40sz{C(2kY}s_ z0<{3;)yNAHaVbDJTz3sR3%P)lF2gpq0iYPlY|u-V0`LF@EMt9I4uw7OOLW2CoKN&4 zDn2&()Vr~c!RG}moH4O)B^ARZS&lR>I52oV+9!+E?zR zJ0B&lwTf_%w&_I?gX#UFBdkLH&%z~_HC$IwZ%1y!mP%nLhDN}LjLeL^QMz<5`(r{u z>3sSgL~u3>!uJ8Y?bV(Y^iEBdx}oDFH(6rzR`>z zN5$j@oOsBk6isOCmxJ4VBGd_%}Pnqahhf5*`z*k9D zZtO0*CEsQqK*MN7Di6F;<@2_9U7!Rd`AXHcERgvDepkfve$rdS{86p3U2aZJu;1HK z7RdpnhK(kz5F*I{xIw2*(c1#QwlPhM$?*8oIQNDh6^K>i$R2H{&Cl`k)nO&MX}0!p zI>1oEP3fWg!Q$$F4Y~_UA*=1;fG)5QZqjS5)3B!@8XsVo8@omoS6Bm2WcuRfov bNrR{;F0&4^{;yZv@Bu9uC4{h{Uw{2yZ~=VB literal 0 HcmV?d00001 diff --git a/site/content/en/images/image128_use_cache.jpg b/site/content/en/images/image128_use_cache.jpg deleted file mode 100644 index 3080718fca436d3f2935606f9797287f00452b79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61950 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 b+0wNm5KyB{)sGj(6S2RRy6E}KkI8=lEDYvy diff --git a/utils/cli/core/core.py b/utils/cli/core/core.py index 2c05d4bc..70dc0589 100644 --- a/utils/cli/core/core.py +++ b/utils/cli/core/core.py @@ -45,6 +45,8 @@ class CLI(): data['copy_data'] = kwargs.get('copy_data') if 'use_cache' in kwargs: data['use_cache'] = kwargs.get('use_cache') + if 'sorting_method' in kwargs: + data['sorting_method'] = kwargs.get('sorting_method') response = self.session.post(url, data=data, files=files) response.raise_for_status() diff --git a/utils/cli/core/definition.py b/utils/cli/core/definition.py index fbd10808..e2fc937a 100644 --- a/utils/cli/core/definition.py +++ b/utils/cli/core/definition.py @@ -208,6 +208,13 @@ task_create_parser.add_argument( action='store_false', help='''set the option to use the cache (default: %(default)s)''' ) +task_create_parser.add_argument( + '--sorting-method', + default='lexicographical', + choices=['lexicographical', 'natural', 'predefined', 'random'], + help='''data soring method (default: %(default)s)''' +) + ####################################################################### # Delete ####################################################################### diff --git a/utils/dataset_manifest/core.py b/utils/dataset_manifest/core.py index 7d2ca852..dcf71633 100644 --- a/utils/dataset_manifest/core.py +++ b/utils/dataset_manifest/core.py @@ -10,7 +10,7 @@ from contextlib import closing from tempfile import NamedTemporaryFile from PIL import Image -from .utils import md5_hash, rotate_image +from .utils import md5_hash, rotate_image, sort, SortingMethod class VideoStreamReader: def __init__(self, source_path, chunk_size, force): @@ -146,14 +146,14 @@ class DatasetImagesReader: def __init__(self, sources, meta=None, - is_sorted=True, + sorting_method=SortingMethod.PREDEFINED, use_image_hash=False, start = 0, step = 1, stop = None, *args, **kwargs): - self._sources = sources if is_sorted else sorted(sources) + self._sources = sort(sources, sorting_method) self._meta = meta self._data_dir = kwargs.get('data_dir', None) self._use_image_hash = use_image_hash @@ -601,11 +601,18 @@ class ImageManifestManager(_ManifestManager): return (f"{image['name']}{image['extension']}" for _, image in self) def get_subset(self, subset_names): - return ({ - 'name': f"{image['name']}", - 'extension': f"{image['extension']}", - 'width': image['width'], - 'height': image['height'], - 'meta': image['meta'], - 'checksum': f"{image['checksum']}" - } for _, image in self if f"{image['name']}{image['extension']}" in subset_names) + index_list = [] + subset = [] + for _, image in self: + image_name = f"{image['name']}{image['extension']}" + if image_name in subset_names: + index_list.append(subset_names.index(image_name)) + subset.append({ + 'name': f"{image['name']}", + 'extension': f"{image['extension']}", + 'width': image['width'], + 'height': image['height'], + 'meta': image['meta'], + 'checksum': f"{image['checksum']}" + }) + return index_list, subset diff --git a/utils/dataset_manifest/create.py b/utils/dataset_manifest/create.py index 8884fa7c..3cad4ab1 100644 --- a/utils/dataset_manifest/create.py +++ b/utils/dataset_manifest/create.py @@ -17,6 +17,8 @@ def get_args(): 'if by default the video does not meet the requirements and a manifest file is not prepared') parser.add_argument('--output-dir',type=str, help='Directory where the manifest file will be saved', default=os.getcwd()) + parser.add_argument('--sorting', choices=['lexicographical', 'natural', 'predefined', 'random'], + type=str, default='lexicographical') parser.add_argument('source', type=str, help='Source paths') return parser.parse_args() @@ -63,7 +65,7 @@ def main(): try: assert len(sources), 'A images was not found' manifest = ImageManifestManager(manifest_path=manifest_directory) - manifest.link(sources=sources, meta=meta, is_sorted=False, + manifest.link(sources=sources, meta=meta, sorting_method=args.sorting, use_image_hash=True, data_dir=data_dir) manifest.create(_tqdm=tqdm) except Exception as ex: diff --git a/utils/dataset_manifest/requirements.txt b/utils/dataset_manifest/requirements.txt index 6eb5832b..bb866fb2 100644 --- a/utils/dataset_manifest/requirements.txt +++ b/utils/dataset_manifest/requirements.txt @@ -1,4 +1,5 @@ av==8.0.2 --no-binary=av opencv-python-headless==4.4.0.42 Pillow==7.2.0 -tqdm==4.58.0 \ No newline at end of file +tqdm==4.58.0 +natsort==8.0.0 diff --git a/utils/dataset_manifest/utils.py b/utils/dataset_manifest/utils.py index e5987782..bb198bac 100644 --- a/utils/dataset_manifest/utils.py +++ b/utils/dataset_manifest/utils.py @@ -1,12 +1,16 @@ # Copyright (C) 2021 Intel Corporation # # SPDX-License-Identifier: MIT + import os import re import hashlib import mimetypes import cv2 as cv from av import VideoFrame +from enum import Enum +from natsort import os_sorted +from random import shuffle def rotate_image(image, angle): height, width = image.shape[:2] @@ -187,3 +191,29 @@ def detect_related_images(image_paths, root_path): elif data_are_3d: return _detect_related_images_3D(image_paths, root_path) return {} + +class SortingMethod(str, Enum): + LEXICOGRAPHICAL = 'lexicographical' + NATURAL = 'natural' + PREDEFINED = 'predefined' + RANDOM = 'random' + + @classmethod + def choices(cls): + return tuple((x.value, x.name) for x in cls) + + def __str__(self): + return self.value + +def sort(images, sorting_method=SortingMethod.LEXICOGRAPHICAL, func=None): + if sorting_method == SortingMethod.LEXICOGRAPHICAL: + return sorted(images, key=func) + elif sorting_method == SortingMethod.NATURAL: + return os_sorted(images, key=func) + elif sorting_method == SortingMethod.PREDEFINED: + return images + elif sorting_method == SortingMethod.RANDOM: + shuffle(images) + return images + else: + raise NotImplementedError()