dtlpy 1.114.13__py3-none-any.whl → 1.114.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dtlpy/entities/model.py CHANGED
@@ -449,42 +449,26 @@ class Model(entities.BaseEntity):
449
449
  # methods #
450
450
  ###########
451
451
 
452
- def add_subset(self, subset_name: str, subset_filter: entities.Filters):
452
+ def add_subset(
453
+ self, subset_name: str, subset_filter=None, subset_annotation_filter=None
454
+ ):
453
455
  """
454
456
  Adds a subset for the model, specifying a subset of the model's dataset that could be used for training or
455
- validation.
457
+ validation. Optionally also adds an annotations subset.
456
458
 
457
459
  :param str subset_name: the name of the subset
458
- :param dtlpy.entities.Filters subset_filter: the filtering operation that this subset performs in the dataset.
459
-
460
- **Example**
461
-
462
- .. code-block:: python
463
-
464
- model.add_subset(subset_name='train', subset_filter=dtlpy.Filters(field='dir', values='/train'))
465
- model.metadata['system']['subsets']
466
- {'train': <dtlpy.entities.filters.Filters object at 0x1501dfe20>}
460
+ :param subset_filter: filtering for items subset. Can be `entities.Filters`, `dict`, or `None`
461
+ :param subset_annotation_filter: optional filtering for annotations subset. Can be `entities.Filters`, `dict`, or `None`
467
462
 
468
463
  """
469
- self.models.add_subset(self, subset_name, subset_filter)
464
+ self.models.add_subset(self, subset_name, subset_filter, subset_annotation_filter)
470
465
 
471
466
  def delete_subset(self, subset_name: str):
472
467
  """
473
- Removes a subset from the model's metadata.
468
+ Removes a subset from the model's metadata (both subsets and annotationsSubsets).
474
469
 
475
470
  :param str subset_name: the name of the subset
476
471
 
477
- **Example**
478
-
479
- .. code-block:: python
480
-
481
- model.add_subset(subset_name='train', subset_filter=dtlpy.Filters(field='dir', values='/train'))
482
- model.metadata['system']['subsets']
483
- {'train': <dtlpy.entities.filters.Filters object at 0x1501dfe20>}
484
- models.delete_subset(subset_name='train')
485
- metadata['system']['subsets']
486
- {}
487
-
488
472
  """
489
473
  self.models.delete_subset(self, subset_name)
490
474
 
@@ -529,6 +513,8 @@ class Model(entities.BaseEntity):
529
513
  tags: list = None,
530
514
  train_filter: entities.Filters = None,
531
515
  validation_filter: entities.Filters = None,
516
+ annotations_train_filter: entities.Filters = None,
517
+ annotations_validation_filter: entities.Filters = None,
532
518
  wait=True
533
519
  ):
534
520
  """
@@ -545,6 +531,8 @@ class Model(entities.BaseEntity):
545
531
  :param list tags: `list` of `str` - label of the model
546
532
  :param dtlpy.entities.filters.Filters train_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model train
547
533
  :param dtlpy.entities.filters.Filters validation_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model validation
534
+ :param dtlpy.entities.filters.Filters annotations_train_filter: Filters entity or a dictionary to define the annotations' scope in the specified dataset_id for the model train
535
+ :param dtlpy.entities.filters.Filters annotations_validation_filter: Filters entity or a dictionary to define the annotations' scope in the specified dataset_id for the model validation
548
536
  :param bool wait: `bool` wait for the model to be ready before returning
549
537
 
550
538
  :return: dl.Model which is a clone version of the existing model
@@ -561,6 +549,8 @@ class Model(entities.BaseEntity):
561
549
  tags=tags,
562
550
  train_filter=train_filter,
563
551
  validation_filter=validation_filter,
552
+ annotations_train_filter=annotations_train_filter,
553
+ annotations_validation_filter=annotations_validation_filter,
564
554
  wait=wait
565
555
  )
566
556
 
@@ -148,7 +148,7 @@ class PagedEntities:
148
148
  self.get_page()
149
149
  else:
150
150
  # For offset pagination, increment the offset
151
- self.page_offset += 1
151
+ self._move_page_offset(1)
152
152
  self.get_page()
153
153
 
154
154
  if not self.items:
@@ -166,7 +166,17 @@ class PagedEntities:
166
166
  yield self.items
167
167
  if self.page_offset == 0:
168
168
  break
169
- self.page_offset -= 1
169
+ self._move_page_offset(-1)
170
+
171
+ def _move_page_offset(self, offset: int) -> None:
172
+ """
173
+ Move the page offset by a given step.
174
+ :param offset: offset to move
175
+ """
176
+ self.page_offset += offset
177
+ if self.filters.custom_filter is not None:
178
+ if 'page' in self.filters.custom_filter and self.filters.custom_filter['page'] != self.page_offset:
179
+ self.filters.custom_filter['page'] = self.page_offset
170
180
 
171
181
  def return_page(self, page_offset: Optional[int] = None, page_size: Optional[int] = None) -> List[Any]:
172
182
  """
@@ -212,7 +222,6 @@ class PagedEntities:
212
222
  operator=operator_value,
213
223
  method=FiltersOperations.AND,
214
224
  )
215
-
216
225
  # Fetch data
217
226
  if self._list_function is None:
218
227
  result = self.items_repository._list(filters=req)
@@ -246,7 +255,7 @@ class PagedEntities:
246
255
  self.get_page()
247
256
  else:
248
257
  # For offset pagination, increment the offset
249
- self.page_offset += 1
258
+ self._move_page_offset(1)
250
259
  self.get_page()
251
260
 
252
261
  def prev_page(self) -> None:
@@ -256,7 +265,7 @@ class PagedEntities:
256
265
  """
257
266
  if self.use_id_based_paging:
258
267
  raise NotImplementedError("prev_page is not supported for keyset pagination.")
259
- self.page_offset -= 1
268
+ self._move_page_offset(-1)
260
269
  self.get_page()
261
270
 
262
271
  def go_to_page(self, page: int = 0) -> None:
@@ -504,6 +504,20 @@ class Pipeline(entities.BaseEntity):
504
504
  )
505
505
  return execution
506
506
 
507
+ def test(self, execution_input=None):
508
+ """
509
+ Execute a pipeline in test mode and return the pipeline execution as an object.
510
+
511
+ :param execution_input: list of the dl.FunctionIO or dict of pipeline input - example {'item': 'item_id'}
512
+ :return: entities.PipelineExecution object
513
+ """
514
+ execution = self.pipelines.test(
515
+ pipeline=self,
516
+ pipeline_id=self.id,
517
+ execution_input=execution_input,
518
+ )
519
+ return execution
520
+
507
521
  def execute_batch(
508
522
  self,
509
523
  filters,
@@ -591,3 +605,20 @@ class Pipeline(entities.BaseEntity):
591
605
  for variable in self.variables:
592
606
  if variable.name in keys:
593
607
  variable.value = kwargs[variable.name]
608
+
609
+ def validate(self):
610
+ """
611
+ Validate the pipeline configuration.
612
+
613
+ **prerequisites**: You must be an *owner* or *developer* to use this method.
614
+
615
+ :return: Validation result
616
+ :rtype: dict
617
+
618
+ **Example**:
619
+
620
+ .. code-block:: python
621
+
622
+ validation_result = pipeline.validate()
623
+ """
624
+ return self.pipelines.validate(pipeline_json=self.to_json())
dtlpy/entities/task.py CHANGED
@@ -9,6 +9,10 @@ from .. import repositories, entities, exceptions
9
9
  logger = logging.getLogger(name='dtlpy')
10
10
 
11
11
 
12
+ class AllocationMethod(str, Enum):
13
+ DISTRIBUTION = 'distribution'
14
+ PULLING = 'pulling'
15
+
12
16
  class ConsensusTaskType(str, Enum):
13
17
  CONSENSUS = 'consensus'
14
18
  QUALIFICATION = 'qualification'
@@ -15,6 +15,7 @@ from functools import partial
15
15
  import numpy as np
16
16
  from concurrent.futures import ThreadPoolExecutor
17
17
  import attr
18
+ from collections.abc import MutableMapping
18
19
  from .. import entities, utilities, repositories, exceptions
19
20
  from ..services import service_defaults
20
21
  from ..services.api_client import ApiClient
@@ -22,8 +23,89 @@ from ..services.api_client import ApiClient
22
23
  logger = logging.getLogger('ModelAdapter')
23
24
 
24
25
 
26
+ class ModelConfigurations(MutableMapping):
27
+ """
28
+ Manages model configuration using composition with a backing dict.
29
+
30
+ Uses MutableMapping to implement dict-like behavior without inheritance.
31
+ This avoids duplication: if we inherited from dict, we'd have two dicts
32
+ (one from inheritance, one from model_entity.configuration), leading to
33
+ data inconsistency and maintenance issues.
34
+ """
35
+
36
+ def __init__(self, base_model_adapter):
37
+ # Store reference to base_model_adapter dictionary
38
+ self._backing_dict = {}
39
+
40
+ if (
41
+ base_model_adapter is not None
42
+ and base_model_adapter.model_entity is not None
43
+ and base_model_adapter.model_entity.configuration is not None
44
+ ):
45
+ self._backing_dict = base_model_adapter.model_entity.configuration
46
+ if 'include_background' not in self._backing_dict:
47
+ self._backing_dict['include_background'] = False
48
+ self._base_model_adapter = base_model_adapter
49
+ # Don't call _update_model_entity during initialization to avoid premature updates
50
+
51
+ def _update_model_entity(self):
52
+ if self._base_model_adapter is not None and self._base_model_adapter.model_entity is not None:
53
+ self._base_model_adapter.model_entity.update()
54
+
55
+ def __ior__(self, other):
56
+ self.update(other)
57
+ return self
58
+
59
+ # Required MutableMapping abstract methods
60
+ def __getitem__(self, key):
61
+ return self._backing_dict[key]
62
+
63
+ def __setitem__(self, key, value):
64
+ # Note: This method only updates the backing dict, not object attributes.
65
+ # If you need to also update object attributes, be careful to avoid
66
+ # infinite recursion by not calling __setattr__ from here.
67
+ update = False
68
+ if key not in self._backing_dict or self._backing_dict.get(key) != value:
69
+ update = True
70
+ self._backing_dict[key] = value
71
+ if update:
72
+ self._update_model_entity()
73
+
74
+ def __delitem__(self, key):
75
+ del self._backing_dict[key]
76
+
77
+ def __iter__(self):
78
+ return iter(self._backing_dict)
79
+
80
+ def __len__(self):
81
+ return len(self._backing_dict)
82
+
83
+ def get(self, key, default=None):
84
+ if key not in self._backing_dict:
85
+ self.__setitem__(key, default)
86
+ return self._backing_dict.get(key)
87
+
88
+ def update(self, *args, **kwargs):
89
+ # Check if there will be any modifications
90
+ update_dict = dict(*args, **kwargs)
91
+ has_changes = False
92
+ for key, value in update_dict.items():
93
+ if key not in self._backing_dict or self._backing_dict[key] != value:
94
+ has_changes = True
95
+ break
96
+ self._backing_dict.update(*args, **kwargs)
97
+
98
+ if has_changes:
99
+ self._update_model_entity()
100
+
101
+ def setdefault(self, key, default=None):
102
+ if key not in self._backing_dict:
103
+ self._backing_dict[key] = default
104
+ return self._backing_dict[key]
105
+
106
+
25
107
  @dataclasses.dataclass
26
- class AdapterDefaults(dict):
108
+ class AdapterDefaults(ModelConfigurations):
27
109
  # for predict items, dataset, evaluate
28
110
  upload_annotations: bool = dataclasses.field(default=True)
29
111
  clean_annotations: bool = dataclasses.field(default=True)
@@ -34,20 +116,32 @@ class AdapterDefaults(dict):
34
116
  data_path: str = dataclasses.field(default=None)
35
117
  output_path: str = dataclasses.field(default=None)
36
118
 
37
- def __post_init__(self):
38
- # Initialize the internal dictionary with the dataclass fields
39
- self.update(**dataclasses.asdict(self))
119
+ def __init__(self, base_model_adapter=None):
120
+ super().__init__(base_model_adapter)
121
+ for f in dataclasses.fields(AdapterDefaults):
122
+ # if the field exists in model_entity.configuration, use it
123
+ # else set it from the attribute default value
124
+ if super().get(f.name) is not None:
125
+ super().__setattr__(f.name, super().get(f.name))
126
+ else:
127
+ super().__setitem__(f.name, f.default)
128
+
129
+ def __setattr__(self, key, value):
130
+ # Dataclass-like fields behave as attributes, so map to dict
131
+ super().__setattr__(key, value)
132
+ if not key.startswith("_"):
133
+ super().__setitem__(key, value)
40
134
 
41
- def update(self, **kwargs):
135
+ def update(self, *args, **kwargs):
42
136
  for f in dataclasses.fields(AdapterDefaults):
43
137
  if f.name in kwargs:
44
138
  setattr(self, f.name, kwargs[f.name])
45
- super().update(**kwargs)
139
+ super().update(*args, **kwargs)
46
140
 
47
141
  def resolve(self, key, *args):
48
-
49
142
  for arg in args:
50
143
  if arg is not None:
144
+ super().__setitem__(key, arg)
51
145
  return arg
52
146
  return self.get(key, None)
53
147
 
@@ -56,12 +150,13 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
56
150
  _client_api = attr.ib(type=ApiClient, repr=False)
57
151
 
58
152
  def __init__(self, model_entity: entities.Model = None):
59
- self.adapter_defaults = AdapterDefaults()
60
153
  self.logger = logger
61
154
  # entities
62
155
  self._model_entity = None
63
156
  self._package = None
64
157
  self._base_configuration = dict()
158
+ self._configuration = None
159
+ self.adapter_defaults = None
65
160
  self.package_name = None
66
161
  self.model = None
67
162
  self.bucket_path = None
@@ -81,7 +176,7 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
81
176
  def configuration(self) -> dict:
82
177
  # load from model
83
178
  if self._model_entity is not None:
84
- configuration = self.model_entity.configuration
179
+ configuration = self._configuration
85
180
  # else - load the default from the package
86
181
  elif self._package is not None:
87
182
  configuration = self.package.metadata.get('system', {}).get('ml', {}).get('defaultConfiguration', {})
@@ -90,10 +185,13 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
90
185
  return configuration
91
186
 
92
187
  @configuration.setter
93
- def configuration(self, d):
94
- assert isinstance(d, dict)
188
+ def configuration(self, configuration: dict):
189
+ assert isinstance(configuration, dict)
95
190
  if self._model_entity is not None:
96
- self._model_entity.configuration = d
191
+ # Update configuration with received dict
192
+ self._model_entity.configuration = configuration
193
+ self.adapter_defaults = AdapterDefaults(self)
194
+ self._configuration = self.adapter_defaults
97
195
 
98
196
  ############
99
197
  # Entities #
@@ -115,6 +213,8 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
115
213
  'Replacing Model from {!r} to {!r}'.format(self._model_entity.name, model_entity.name))
116
214
  self._model_entity = model_entity
117
215
  self.package = model_entity.package
216
+ self.adapter_defaults = AdapterDefaults(self)
217
+ self._configuration = self.adapter_defaults
118
218
 
119
219
  @property
120
220
  def package(self):
@@ -252,15 +352,39 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
252
352
 
253
353
  return processed
254
354
 
255
- def prepare_data(self,
256
- dataset: entities.Dataset,
257
- # paths
258
- root_path=None,
259
- data_path=None,
260
- output_path=None,
261
- #
262
- overwrite=False,
263
- **kwargs):
355
+ def __include_model_annotations(self, annotation_filters):
356
+ include_model_annotations = self.model_entity.configuration.get("include_model_annotations", False)
357
+ if include_model_annotations is False:
358
+ if annotation_filters.custom_filter is None:
359
+ annotation_filters.add(
360
+ field="metadata.system.model.name", values=False, operator=entities.FiltersOperations.EXISTS
361
+ )
362
+ else:
363
+ annotation_filters.custom_filter['filter']['$and'].append({'metadata.system.model.name': {'$exists': False}})
364
+ return annotation_filters
365
+
366
+ def __download_background_images(self, filters, data_subset_base_path, annotation_options):
367
+ background_list = list()
368
+ if self.configuration.get('include_background', False) is True:
369
+ filters.custom_filter["filter"]["$and"].append({"annotated": False})
370
+ background_list = self.model_entity.dataset.items.download(
371
+ filters=filters,
372
+ local_path=data_subset_base_path,
373
+ annotation_options=annotation_options,
374
+ )
375
+ return background_list
376
+
377
+ def prepare_data(
378
+ self,
379
+ dataset: entities.Dataset,
380
+ # paths
381
+ root_path=None,
382
+ data_path=None,
383
+ output_path=None,
384
+ #
385
+ overwrite=False,
386
+ **kwargs,
387
+ ):
264
388
  """
265
389
  Prepares dataset locally before training or evaluation.
266
390
  download the specific subset selected to data_path and preforms `self.convert` to the data_path dir
@@ -277,7 +401,6 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
277
401
  root_path = self.adapter_defaults.resolve("root_path", root_path)
278
402
  data_path = self.adapter_defaults.resolve("data_path", data_path)
279
403
  output_path = self.adapter_defaults.resolve("output_path", output_path)
280
-
281
404
  if root_path is None:
282
405
  now = datetime.datetime.now()
283
406
  root_path = os.path.join(dataloop_path,
@@ -300,54 +423,72 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
300
423
  annotation_options = entities.ViewAnnotationOptions.INSTANCE
301
424
 
302
425
  # Download the subset items
303
- subsets = self.model_entity.metadata.get("system", dict()).get("subsets", None)
426
+ subsets = self.model_entity.metadata.get("system", {}).get("subsets", None)
427
+ annotations_subsets = self.model_entity.metadata.get("system", {}).get("annotationsSubsets", {})
304
428
  if subsets is None:
305
429
  raise ValueError("Model (id: {}) must have subsets in metadata.system.subsets".format(self.model_entity.id))
306
430
  for subset, filters_dict in subsets.items():
307
- filters = entities.Filters(custom_filter=filters_dict)
308
431
  data_subset_base_path = os.path.join(data_path, subset)
309
432
  if os.path.isdir(data_subset_base_path) and not overwrite:
310
433
  # existing and dont overwrite
311
434
  self.logger.debug("Subset {!r} already exists (and overwrite=False). Skipping.".format(subset))
312
- else:
313
- self.logger.debug("Downloading subset {!r} of {}".format(subset,
314
- self.model_entity.dataset.name))
315
-
316
- annotation_filters = None
317
- if self.model_entity.output_type is not None and self.model_entity.output_type != "embedding":
318
- annotation_filters = entities.Filters(resource=entities.FiltersResource.ANNOTATION, use_defaults=False)
319
- if self.model_entity.output_type in [entities.AnnotationType.SEGMENTATION,
320
- entities.AnnotationType.POLYGON]:
321
- model_output_types = [entities.AnnotationType.SEGMENTATION, entities.AnnotationType.POLYGON]
322
- else:
323
- model_output_types = [self.model_entity.output_type]
324
-
325
- annotation_filters.add(
326
- field=entities.FiltersKnownFields.TYPE,
327
- values=model_output_types,
328
- operator=entities.FiltersOperations.IN
329
- )
330
-
331
- if not self.configuration.get("include_model_annotations", False):
332
- annotation_filters.add(
333
- field="metadata.system.model.name",
334
- values=False,
335
- operator=entities.FiltersOperations.EXISTS
336
- )
337
-
338
- ret_list = dataset.items.download(filters=filters,
339
- local_path=data_subset_base_path,
340
- annotation_options=annotation_options,
341
- annotation_filters=annotation_filters
342
- )
343
- if isinstance(ret_list, list) and len(ret_list) == 0:
344
- if annotation_filters is not None:
345
- annotation_filters_str = annotation_filters.prepare()
346
- raise ValueError(f"No items downloaded for subset {subset}! Cannot train model with empty subset.\n"
347
- f"Subset {subset} filters: {filters.prepare()}\nAnnotation filters: {annotation_filters_str}")
348
- else:
349
- raise ValueError(f"No items downloaded for subset {subset}! Cannot train model with empty subset.\n"
350
- f"Subset {subset} filters: {filters.prepare()}")
435
+ continue
436
+
437
+ filters = entities.Filters(custom_filter=filters_dict)
438
+ self.logger.debug("Downloading subset {!r} of {}".format(subset, self.model_entity.dataset.name))
439
+
440
+ annotation_filters = None
441
+ if subset in annotations_subsets:
442
+ annotation_filters = entities.Filters(
443
+ use_defaults=False,
444
+ resource=entities.FiltersResource.ANNOTATION,
445
+ custom_filter=annotations_subsets[subset]
446
+ )
447
+ # if user provided annotation_filters, skip the default filters
448
+ elif self.model_entity.output_type is not None and self.model_entity.output_type != "embedding":
449
+ annotation_filters = entities.Filters(resource=entities.FiltersResource.ANNOTATION, use_defaults=False)
450
+ if self.model_entity.output_type in [
451
+ entities.AnnotationType.SEGMENTATION,
452
+ entities.AnnotationType.POLYGON,
453
+ ]:
454
+ model_output_types = [entities.AnnotationType.SEGMENTATION, entities.AnnotationType.POLYGON]
455
+ else:
456
+ model_output_types = [self.model_entity.output_type]
457
+
458
+ annotation_filters.add(
459
+ field=entities.FiltersKnownFields.TYPE,
460
+ values=model_output_types,
461
+ operator=entities.FiltersOperations.IN,
462
+ )
463
+
464
+ annotation_filters = self.__include_model_annotations(annotation_filters)
465
+ annotations_subsets[subset] = annotation_filters.prepare()
466
+
467
+ ret_list = dataset.items.download(
468
+ filters=filters,
469
+ local_path=data_subset_base_path,
470
+ annotation_options=annotation_options,
471
+ annotation_filters=annotation_filters,
472
+ )
473
+ filters = entities.Filters(custom_filter=subsets[subset])
474
+ background_ret_list = self.__download_background_images(
475
+ filters=filters, data_subset_base_path=data_subset_base_path, annotation_options=annotation_options
476
+ )
477
+ ret_list = list(ret_list)
478
+ background_ret_list = list(background_ret_list)
479
+ self.logger.debug(f"Subset '{subset}': ret_list length: {len(ret_list)}, background_ret_list length: {len(background_ret_list)}")
480
+ # Combine ret_list and background_ret_list generators into a single generator
481
+ ret_list = ret_list + background_ret_list
482
+ if isinstance(ret_list, list) and len(ret_list) == 0:
483
+ if annotation_filters is not None:
484
+ annotation_filters_str = annotation_filters.prepare()
485
+ else:
486
+ annotation_filters_str = None
487
+ raise ValueError(
488
+ f"No items downloaded for subset {subset}! Cannot train model with empty subset.\n"
489
+ f"Subset {subset} filters: {filters.prepare()}\n"
490
+ f"Annotation filters: {annotation_filters_str}"
491
+ )
351
492
 
352
493
  self.convert_from_dtlpy(data_path=data_path, **kwargs)
353
494
  return root_path, data_path, output_path
@@ -365,10 +506,10 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
365
506
  self.model_entity = model_entity
366
507
  if local_path is None:
367
508
  local_path = os.path.join(service_defaults.DATALOOP_PATH, "models", self.model_entity.name)
368
- # Load configuration
369
- self.configuration = self.model_entity.configuration
370
- # Update the adapter config with the model config to run over defaults if needed
371
- self.adapter_defaults.update(**self.configuration)
509
+ # Load configuration and adapter defaults
510
+ self.adapter_defaults = AdapterDefaults(self)
511
+ # Point _configuration to the same object since AdapterDefaults inherits from ModelConfigurations
512
+ self._configuration = self.adapter_defaults
372
513
  # Download
373
514
  self.model_entity.artifacts.download(
374
515
  local_path=local_path,
dtlpy/new_instance.py CHANGED
@@ -10,7 +10,7 @@ class Dtlpy:
10
10
  # main entities
11
11
  Project, Dataset, ExpirationOptions, ExportVersion, Trigger, Item, Execution, AnnotationCollection, Annotation,
12
12
  Recipe, IndexDriver, AttributesTypes, AttributesRange, Dpk, App, AppModule, AppScope,
13
- Ontology, Label, Task, TaskPriority, ConsensusTaskType, Assignment, Service, Package, Codebase, Model,
13
+ Ontology, Label, Task, TaskPriority, ConsensusTaskType, AllocationMethod, Assignment, Service, Package, Codebase, Model,
14
14
  PackageModule, PackageFunction,
15
15
  # annotations
16
16
  Box, Cube, Cube3d, Point, Note, Message, Segmentation, Ellipse, Classification, Subtitle, Polyline, Pose, Gis, GisType,
@@ -136,16 +136,24 @@ class Downloader:
136
136
  if file_types is not None:
137
137
  filters.add(field='metadata.system.mimetype', values=file_types, operator=entities.FiltersOperations.IN)
138
138
  if annotation_filters is not None:
139
- for annotation_filter_and in annotation_filters.and_filter_list:
140
- filters.add_join(field=annotation_filter_and.field,
141
- values=annotation_filter_and.values,
142
- operator=annotation_filter_and.operator,
143
- method=entities.FiltersMethod.AND)
144
- for annotation_filter_or in annotation_filters.or_filter_list:
145
- filters.add_join(field=annotation_filter_or.field,
146
- values=annotation_filter_or.values,
147
- operator=annotation_filter_or.operator,
148
- method=entities.FiltersMethod.OR)
139
+ if len(annotation_filters.and_filter_list) > 0 or len(annotation_filters.or_filter_list) > 0:
140
+ for annotation_filter_and in annotation_filters.and_filter_list:
141
+ filters.add_join(field=annotation_filter_and.field,
142
+ values=annotation_filter_and.values,
143
+ operator=annotation_filter_and.operator,
144
+ method=entities.FiltersMethod.AND)
145
+ for annotation_filter_or in annotation_filters.or_filter_list:
146
+ filters.add_join(field=annotation_filter_or.field,
147
+ values=annotation_filter_or.values,
148
+ operator=annotation_filter_or.operator,
149
+ method=entities.FiltersMethod.OR)
150
+ elif annotation_filters.custom_filter is not None:
151
+ annotation_query_dict = annotation_filters.prepare()
152
+ items_query_dict = filters.prepare()
153
+ items_query_dict["join"] = annotation_query_dict
154
+ filters.reset()
155
+ filters.custom_filter = items_query_dict
156
+
149
157
  else:
150
158
  annotation_filters = entities.Filters(resource=entities.FiltersResource.ANNOTATION)
151
159
  filters._user_query = 'false'
@@ -254,7 +254,7 @@ class Dpks:
254
254
  local_path = os.path.dirname(manifest_filepath)
255
255
  if dpk.codebase is None:
256
256
  dpk.codebase = self.project.codebases.pack(directory=local_path,
257
- name=dpk.display_name,
257
+ name=dpk.name,
258
258
  extension='dpk',
259
259
  ignore_directories=['artifacts'],
260
260
  ignore_max_file_size=ignore_max_file_size)