dtlpy 1.114.17__py3-none-any.whl → 1.116.6__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/__init__.py +491 -491
- dtlpy/__version__.py +1 -1
- dtlpy/assets/__init__.py +26 -26
- dtlpy/assets/code_server/config.yaml +2 -2
- dtlpy/assets/code_server/installation.sh +24 -24
- dtlpy/assets/code_server/launch.json +13 -13
- dtlpy/assets/code_server/settings.json +2 -2
- dtlpy/assets/main.py +53 -53
- dtlpy/assets/main_partial.py +18 -18
- dtlpy/assets/mock.json +11 -11
- dtlpy/assets/model_adapter.py +83 -83
- dtlpy/assets/package.json +61 -61
- dtlpy/assets/package_catalog.json +29 -29
- dtlpy/assets/package_gitignore +307 -307
- dtlpy/assets/service_runners/__init__.py +33 -33
- dtlpy/assets/service_runners/converter.py +96 -96
- dtlpy/assets/service_runners/multi_method.py +49 -49
- dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
- dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
- dtlpy/assets/service_runners/multi_method_item.py +52 -52
- dtlpy/assets/service_runners/multi_method_json.py +52 -52
- dtlpy/assets/service_runners/single_method.py +37 -37
- dtlpy/assets/service_runners/single_method_annotation.py +43 -43
- dtlpy/assets/service_runners/single_method_dataset.py +43 -43
- dtlpy/assets/service_runners/single_method_item.py +41 -41
- dtlpy/assets/service_runners/single_method_json.py +42 -42
- dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
- dtlpy/assets/voc_annotation_template.xml +23 -23
- dtlpy/caches/base_cache.py +32 -32
- dtlpy/caches/cache.py +473 -473
- dtlpy/caches/dl_cache.py +201 -201
- dtlpy/caches/filesystem_cache.py +89 -89
- dtlpy/caches/redis_cache.py +84 -84
- dtlpy/dlp/__init__.py +20 -20
- dtlpy/dlp/cli_utilities.py +367 -367
- dtlpy/dlp/command_executor.py +764 -764
- dtlpy/dlp/dlp +1 -1
- dtlpy/dlp/dlp.bat +1 -1
- dtlpy/dlp/dlp.py +128 -128
- dtlpy/dlp/parser.py +651 -651
- dtlpy/entities/__init__.py +83 -83
- dtlpy/entities/analytic.py +347 -311
- dtlpy/entities/annotation.py +1879 -1879
- dtlpy/entities/annotation_collection.py +699 -699
- dtlpy/entities/annotation_definitions/__init__.py +20 -20
- dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
- dtlpy/entities/annotation_definitions/box.py +195 -195
- dtlpy/entities/annotation_definitions/classification.py +67 -67
- dtlpy/entities/annotation_definitions/comparison.py +72 -72
- dtlpy/entities/annotation_definitions/cube.py +204 -204
- dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
- dtlpy/entities/annotation_definitions/description.py +32 -32
- dtlpy/entities/annotation_definitions/ellipse.py +124 -124
- dtlpy/entities/annotation_definitions/free_text.py +62 -62
- dtlpy/entities/annotation_definitions/gis.py +69 -69
- dtlpy/entities/annotation_definitions/note.py +139 -139
- dtlpy/entities/annotation_definitions/point.py +117 -117
- dtlpy/entities/annotation_definitions/polygon.py +182 -182
- dtlpy/entities/annotation_definitions/polyline.py +111 -111
- dtlpy/entities/annotation_definitions/pose.py +92 -92
- dtlpy/entities/annotation_definitions/ref_image.py +86 -86
- dtlpy/entities/annotation_definitions/segmentation.py +240 -240
- dtlpy/entities/annotation_definitions/subtitle.py +34 -34
- dtlpy/entities/annotation_definitions/text.py +85 -85
- dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
- dtlpy/entities/app.py +220 -220
- dtlpy/entities/app_module.py +107 -107
- dtlpy/entities/artifact.py +174 -174
- dtlpy/entities/assignment.py +399 -399
- dtlpy/entities/base_entity.py +214 -214
- dtlpy/entities/bot.py +113 -113
- dtlpy/entities/codebase.py +292 -296
- dtlpy/entities/collection.py +38 -38
- dtlpy/entities/command.py +169 -169
- dtlpy/entities/compute.py +449 -442
- dtlpy/entities/dataset.py +1299 -1285
- dtlpy/entities/directory_tree.py +44 -44
- dtlpy/entities/dpk.py +470 -470
- dtlpy/entities/driver.py +235 -223
- dtlpy/entities/execution.py +397 -397
- dtlpy/entities/feature.py +124 -124
- dtlpy/entities/feature_set.py +145 -145
- dtlpy/entities/filters.py +798 -645
- dtlpy/entities/gis_item.py +107 -107
- dtlpy/entities/integration.py +184 -184
- dtlpy/entities/item.py +959 -953
- dtlpy/entities/label.py +123 -123
- dtlpy/entities/links.py +85 -85
- dtlpy/entities/message.py +175 -175
- dtlpy/entities/model.py +684 -684
- dtlpy/entities/node.py +1005 -1005
- dtlpy/entities/ontology.py +810 -803
- dtlpy/entities/organization.py +287 -287
- dtlpy/entities/package.py +657 -657
- dtlpy/entities/package_defaults.py +5 -5
- dtlpy/entities/package_function.py +185 -185
- dtlpy/entities/package_module.py +113 -113
- dtlpy/entities/package_slot.py +118 -118
- dtlpy/entities/paged_entities.py +299 -299
- dtlpy/entities/pipeline.py +624 -624
- dtlpy/entities/pipeline_execution.py +279 -279
- dtlpy/entities/project.py +394 -394
- dtlpy/entities/prompt_item.py +505 -499
- dtlpy/entities/recipe.py +301 -301
- dtlpy/entities/reflect_dict.py +102 -102
- dtlpy/entities/resource_execution.py +138 -138
- dtlpy/entities/service.py +963 -958
- dtlpy/entities/service_driver.py +117 -117
- dtlpy/entities/setting.py +294 -294
- dtlpy/entities/task.py +495 -495
- dtlpy/entities/time_series.py +143 -143
- dtlpy/entities/trigger.py +426 -426
- dtlpy/entities/user.py +118 -118
- dtlpy/entities/webhook.py +124 -124
- dtlpy/examples/__init__.py +19 -19
- dtlpy/examples/add_labels.py +135 -135
- dtlpy/examples/add_metadata_to_item.py +21 -21
- dtlpy/examples/annotate_items_using_model.py +65 -65
- dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
- dtlpy/examples/annotations_convert_to_voc.py +9 -9
- dtlpy/examples/annotations_convert_to_yolo.py +9 -9
- dtlpy/examples/convert_annotation_types.py +51 -51
- dtlpy/examples/converter.py +143 -143
- dtlpy/examples/copy_annotations.py +22 -22
- dtlpy/examples/copy_folder.py +31 -31
- dtlpy/examples/create_annotations.py +51 -51
- dtlpy/examples/create_video_annotations.py +83 -83
- dtlpy/examples/delete_annotations.py +26 -26
- dtlpy/examples/filters.py +113 -113
- dtlpy/examples/move_item.py +23 -23
- dtlpy/examples/play_video_annotation.py +13 -13
- dtlpy/examples/show_item_and_mask.py +53 -53
- dtlpy/examples/triggers.py +49 -49
- dtlpy/examples/upload_batch_of_items.py +20 -20
- dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
- dtlpy/examples/upload_items_with_modalities.py +43 -43
- dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
- dtlpy/examples/upload_yolo_format_annotations.py +70 -70
- dtlpy/exceptions.py +125 -125
- dtlpy/miscellaneous/__init__.py +20 -20
- dtlpy/miscellaneous/dict_differ.py +95 -95
- dtlpy/miscellaneous/git_utils.py +217 -217
- dtlpy/miscellaneous/json_utils.py +14 -14
- dtlpy/miscellaneous/list_print.py +105 -105
- dtlpy/miscellaneous/zipping.py +130 -130
- dtlpy/ml/__init__.py +20 -20
- dtlpy/ml/base_feature_extractor_adapter.py +27 -27
- dtlpy/ml/base_model_adapter.py +1257 -1086
- dtlpy/ml/metrics.py +461 -461
- dtlpy/ml/predictions_utils.py +274 -274
- dtlpy/ml/summary_writer.py +57 -57
- dtlpy/ml/train_utils.py +60 -60
- dtlpy/new_instance.py +252 -252
- dtlpy/repositories/__init__.py +56 -56
- dtlpy/repositories/analytics.py +85 -85
- dtlpy/repositories/annotations.py +916 -916
- dtlpy/repositories/apps.py +383 -383
- dtlpy/repositories/artifacts.py +452 -452
- dtlpy/repositories/assignments.py +599 -599
- dtlpy/repositories/bots.py +213 -213
- dtlpy/repositories/codebases.py +559 -559
- dtlpy/repositories/collections.py +332 -332
- dtlpy/repositories/commands.py +152 -158
- dtlpy/repositories/compositions.py +61 -61
- dtlpy/repositories/computes.py +439 -435
- dtlpy/repositories/datasets.py +1504 -1291
- dtlpy/repositories/downloader.py +976 -903
- dtlpy/repositories/dpks.py +433 -433
- dtlpy/repositories/drivers.py +482 -470
- dtlpy/repositories/executions.py +815 -817
- dtlpy/repositories/feature_sets.py +226 -226
- dtlpy/repositories/features.py +255 -238
- dtlpy/repositories/integrations.py +484 -484
- dtlpy/repositories/items.py +912 -909
- dtlpy/repositories/messages.py +94 -94
- dtlpy/repositories/models.py +1000 -988
- dtlpy/repositories/nodes.py +80 -80
- dtlpy/repositories/ontologies.py +511 -511
- dtlpy/repositories/organizations.py +525 -525
- dtlpy/repositories/packages.py +1941 -1941
- dtlpy/repositories/pipeline_executions.py +451 -451
- dtlpy/repositories/pipelines.py +640 -640
- dtlpy/repositories/projects.py +539 -539
- dtlpy/repositories/recipes.py +419 -399
- dtlpy/repositories/resource_executions.py +137 -137
- dtlpy/repositories/schema.py +120 -120
- dtlpy/repositories/service_drivers.py +213 -213
- dtlpy/repositories/services.py +1704 -1704
- dtlpy/repositories/settings.py +339 -339
- dtlpy/repositories/tasks.py +1477 -1477
- dtlpy/repositories/times_series.py +278 -278
- dtlpy/repositories/triggers.py +536 -536
- dtlpy/repositories/upload_element.py +257 -257
- dtlpy/repositories/uploader.py +661 -651
- dtlpy/repositories/webhooks.py +249 -249
- dtlpy/services/__init__.py +22 -22
- dtlpy/services/aihttp_retry.py +131 -131
- dtlpy/services/api_client.py +1785 -1782
- dtlpy/services/api_reference.py +40 -40
- dtlpy/services/async_utils.py +133 -133
- dtlpy/services/calls_counter.py +44 -44
- dtlpy/services/check_sdk.py +68 -68
- dtlpy/services/cookie.py +115 -115
- dtlpy/services/create_logger.py +156 -156
- dtlpy/services/events.py +84 -84
- dtlpy/services/logins.py +235 -235
- dtlpy/services/reporter.py +256 -256
- dtlpy/services/service_defaults.py +91 -91
- dtlpy/utilities/__init__.py +20 -20
- dtlpy/utilities/annotations/__init__.py +16 -16
- dtlpy/utilities/annotations/annotation_converters.py +269 -269
- dtlpy/utilities/base_package_runner.py +285 -264
- dtlpy/utilities/converter.py +1650 -1650
- dtlpy/utilities/dataset_generators/__init__.py +1 -1
- dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
- dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
- dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
- dtlpy/utilities/local_development/__init__.py +1 -1
- dtlpy/utilities/local_development/local_session.py +179 -179
- dtlpy/utilities/reports/__init__.py +2 -2
- dtlpy/utilities/reports/figures.py +343 -343
- dtlpy/utilities/reports/report.py +71 -71
- dtlpy/utilities/videos/__init__.py +17 -17
- dtlpy/utilities/videos/video_player.py +598 -598
- dtlpy/utilities/videos/videos.py +470 -470
- {dtlpy-1.114.17.data → dtlpy-1.116.6.data}/scripts/dlp +1 -1
- dtlpy-1.116.6.data/scripts/dlp.bat +2 -0
- {dtlpy-1.114.17.data → dtlpy-1.116.6.data}/scripts/dlp.py +128 -128
- {dtlpy-1.114.17.dist-info → dtlpy-1.116.6.dist-info}/METADATA +186 -183
- dtlpy-1.116.6.dist-info/RECORD +239 -0
- {dtlpy-1.114.17.dist-info → dtlpy-1.116.6.dist-info}/WHEEL +1 -1
- {dtlpy-1.114.17.dist-info → dtlpy-1.116.6.dist-info}/licenses/LICENSE +200 -200
- tests/features/environment.py +551 -551
- dtlpy/assets/__pycache__/__init__.cpython-310.pyc +0 -0
- dtlpy-1.114.17.data/scripts/dlp.bat +0 -2
- dtlpy-1.114.17.dist-info/RECORD +0 -240
- {dtlpy-1.114.17.dist-info → dtlpy-1.116.6.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.114.17.dist-info → dtlpy-1.116.6.dist-info}/top_level.txt +0 -0
dtlpy/ml/predictions_utils.py
CHANGED
|
@@ -1,274 +1,274 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
|
-
import pandas as pd
|
|
4
|
-
import numpy as np
|
|
5
|
-
import traceback
|
|
6
|
-
import datetime
|
|
7
|
-
import logging
|
|
8
|
-
import tqdm
|
|
9
|
-
import uuid
|
|
10
|
-
import os
|
|
11
|
-
|
|
12
|
-
from .. import entities
|
|
13
|
-
from . import BaseModelAdapter, metrics
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(name='dtlpy')
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def mean_or_nan(arr):
|
|
19
|
-
if isinstance(arr, list) and len(arr) == 0:
|
|
20
|
-
return np.nan
|
|
21
|
-
else:
|
|
22
|
-
return np.mean(arr)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def model_info_name(model: entities.Model, package: entities.Package):
|
|
26
|
-
if model is None:
|
|
27
|
-
return "{}-no-model".format(package.name)
|
|
28
|
-
else:
|
|
29
|
-
return "{}-{}".format(package.name, model.name)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def measure_annotations(
|
|
33
|
-
annotations_set_one: entities.AnnotationCollection,
|
|
34
|
-
annotations_set_two: entities.AnnotationCollection,
|
|
35
|
-
match_threshold=0.5,
|
|
36
|
-
ignore_labels=False,
|
|
37
|
-
ignore_attributes=False,
|
|
38
|
-
compare_types=None):
|
|
39
|
-
"""
|
|
40
|
-
Compares list (or collections) of annotations
|
|
41
|
-
This will also return the precision and recall of the two sets, given that the first that is a GT and the second set
|
|
42
|
-
is the detection (this affects the denominator of the calculation).
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
:param annotations_set_one: dl.AnnotationCollection entity with a list of annotations to compare
|
|
46
|
-
:param annotations_set_two: dl.AnnotationCollection entity with a list of annotations to compare
|
|
47
|
-
:param match_threshold: IoU threshold to count as a match
|
|
48
|
-
:param ignore_labels: ignore label when comparing - measure only geometry
|
|
49
|
-
:param ignore_attributes: ignore attribute score for final annotation score
|
|
50
|
-
:param compare_types: list of type to compare. enum dl.AnnotationType
|
|
51
|
-
|
|
52
|
-
Returns a dictionary of all the compare data
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
if compare_types is None:
|
|
56
|
-
compare_types = [entities.AnnotationType.BOX,
|
|
57
|
-
entities.AnnotationType.CLASSIFICATION,
|
|
58
|
-
entities.AnnotationType.POLYGON,
|
|
59
|
-
entities.AnnotationType.POINT,
|
|
60
|
-
entities.AnnotationType.SEGMENTATION]
|
|
61
|
-
final_results = dict()
|
|
62
|
-
all_scores = list()
|
|
63
|
-
true_positives = 0
|
|
64
|
-
false_positives = 0
|
|
65
|
-
false_negatives = 0
|
|
66
|
-
|
|
67
|
-
# for local annotations - set random id if None
|
|
68
|
-
for annotation in annotations_set_one:
|
|
69
|
-
if annotation.id is None:
|
|
70
|
-
annotation.id = str(uuid.uuid1())
|
|
71
|
-
for annotation in annotations_set_two:
|
|
72
|
-
if annotation.id is None:
|
|
73
|
-
annotation.id = str(uuid.uuid1())
|
|
74
|
-
|
|
75
|
-
# start comparing
|
|
76
|
-
for compare_type in compare_types:
|
|
77
|
-
matches = metrics.Matches()
|
|
78
|
-
annotation_subset_one = entities.AnnotationCollection()
|
|
79
|
-
annotation_subset_two = entities.AnnotationCollection()
|
|
80
|
-
annotation_subset_one.annotations = [a for a in annotations_set_one if
|
|
81
|
-
a.type == compare_type and not a.metadata.get('system', dict()).get(
|
|
82
|
-
'system', False)]
|
|
83
|
-
annotation_subset_two.annotations = [a for a in annotations_set_two if
|
|
84
|
-
a.type == compare_type and not a.metadata.get('system', dict()).get(
|
|
85
|
-
'system', False)]
|
|
86
|
-
# create 2d dataframe with annotation id as names and set all to -1 -> not calculated
|
|
87
|
-
if ignore_labels:
|
|
88
|
-
matches = metrics.Matchers.general_match(matches=matches,
|
|
89
|
-
first_set=annotation_subset_one,
|
|
90
|
-
second_set=annotation_subset_two,
|
|
91
|
-
match_type=compare_type,
|
|
92
|
-
match_threshold=match_threshold,
|
|
93
|
-
ignore_labels=ignore_labels,
|
|
94
|
-
ignore_attributes=ignore_attributes)
|
|
95
|
-
else:
|
|
96
|
-
unique_labels = np.unique([a.label for a in annotation_subset_one] +
|
|
97
|
-
[a.label for a in annotation_subset_two])
|
|
98
|
-
for label in unique_labels:
|
|
99
|
-
first_set = [a for a in annotation_subset_one if a.label == label]
|
|
100
|
-
second_set = [a for a in annotation_subset_two if a.label == label]
|
|
101
|
-
matches = metrics.Matchers.general_match(matches=matches,
|
|
102
|
-
first_set=first_set,
|
|
103
|
-
second_set=second_set,
|
|
104
|
-
match_type=compare_type,
|
|
105
|
-
match_threshold=match_threshold,
|
|
106
|
-
ignore_labels=ignore_labels,
|
|
107
|
-
ignore_attributes=ignore_attributes
|
|
108
|
-
)
|
|
109
|
-
if len(matches) == 0:
|
|
110
|
-
continue
|
|
111
|
-
all_scores.extend(matches.to_df()['annotation_score'])
|
|
112
|
-
final_results[compare_type] = metrics.Results(matches=matches,
|
|
113
|
-
annotation_type=compare_type)
|
|
114
|
-
true_positives += final_results[compare_type].summary()['n_annotations_matched_total']
|
|
115
|
-
false_positives += final_results[compare_type].summary()['n_annotations_unmatched_set_two']
|
|
116
|
-
false_negatives += final_results[compare_type].summary()['n_annotations_unmatched_set_one']
|
|
117
|
-
|
|
118
|
-
final_results['total_mean_score'] = mean_or_nan(all_scores)
|
|
119
|
-
final_results['precision'] = true_positives / (true_positives + false_positives)
|
|
120
|
-
final_results['recall'] = true_positives / (true_positives + false_negatives)
|
|
121
|
-
return final_results
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def measure_item(ref_item: entities.Item, test_item: entities.Item,
|
|
125
|
-
ref_project: entities.Project = None, test_project: entities.Project = None,
|
|
126
|
-
ignore_labels=False,
|
|
127
|
-
ignore_attributes=False,
|
|
128
|
-
match_threshold=0.5,
|
|
129
|
-
pbar=None):
|
|
130
|
-
"""
|
|
131
|
-
Compare annotations sets between two items
|
|
132
|
-
|
|
133
|
-
:param ref_item:
|
|
134
|
-
:param test_item:
|
|
135
|
-
:param ref_project:
|
|
136
|
-
:param test_project:
|
|
137
|
-
:param ignore_labels:
|
|
138
|
-
:param ignore_attributes:
|
|
139
|
-
:param match_threshold:
|
|
140
|
-
:param pbar:
|
|
141
|
-
:return:
|
|
142
|
-
"""
|
|
143
|
-
try:
|
|
144
|
-
annotations_set_one = ref_item.annotations.list()
|
|
145
|
-
annotations_set_two = test_item.annotations.list()
|
|
146
|
-
final = measure_annotations(annotations_set_one=annotations_set_one,
|
|
147
|
-
annotations_set_two=annotations_set_two,
|
|
148
|
-
ignore_labels=ignore_labels,
|
|
149
|
-
ignore_attributes=ignore_attributes,
|
|
150
|
-
match_threshold=match_threshold)
|
|
151
|
-
|
|
152
|
-
# get times
|
|
153
|
-
try:
|
|
154
|
-
ref_item_duration_s = metrics.item_annotation_duration(item=ref_item, project=ref_project)
|
|
155
|
-
ref_item_duration = str(datetime.timedelta(seconds=int(np.round(ref_item_duration_s))))
|
|
156
|
-
except Exception:
|
|
157
|
-
ref_item_duration_s = -1
|
|
158
|
-
ref_item_duration = ''
|
|
159
|
-
|
|
160
|
-
try:
|
|
161
|
-
test_item_duration_s = metrics.item_annotation_duration(item=test_item, project=test_project)
|
|
162
|
-
test_item_duration = str(datetime.timedelta(seconds=int(np.round(test_item_duration_s))))
|
|
163
|
-
except Exception:
|
|
164
|
-
test_item_duration_s = -1
|
|
165
|
-
test_item_duration = ''
|
|
166
|
-
|
|
167
|
-
final.update({'ref_url': ref_item.platform_url,
|
|
168
|
-
'test_url': test_item.platform_url,
|
|
169
|
-
'filename': ref_item.filename,
|
|
170
|
-
'ref_item_duration[s]': ref_item_duration_s,
|
|
171
|
-
'test_item_duration[s]': test_item_duration_s,
|
|
172
|
-
'diff_duration[s]': test_item_duration_s - ref_item_duration_s,
|
|
173
|
-
# round to sec
|
|
174
|
-
'ref_item_duration': ref_item_duration,
|
|
175
|
-
'test_item_duration': test_item_duration,
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
return True, final
|
|
179
|
-
except Exception:
|
|
180
|
-
fail_msg = 'failed measuring. ref_item: {!r}, test_item: {!r}'.format(ref_item.id, test_item.id)
|
|
181
|
-
return False, '{}\n{}'.format(fail_msg, traceback.format_exc())
|
|
182
|
-
finally:
|
|
183
|
-
if pbar is not None:
|
|
184
|
-
pbar.update()
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def measure_items(ref_items, ref_project, ref_dataset, ref_name,
|
|
188
|
-
test_items, test_project, test_dataset, test_name,
|
|
189
|
-
dump_path=None):
|
|
190
|
-
from multiprocessing.pool import ThreadPool
|
|
191
|
-
ref_items_filepath_dict = {item.filename: item for page in ref_items for item in page}
|
|
192
|
-
test_items_filepath_dict = {item.filename: item for page in test_items for item in page}
|
|
193
|
-
pool = ThreadPool(processes=32)
|
|
194
|
-
pbar = tqdm.tqdm(total=len(ref_items_filepath_dict), file=sys.stdout)
|
|
195
|
-
jobs = dict()
|
|
196
|
-
for filepath, ref_item in ref_items_filepath_dict.items():
|
|
197
|
-
if filepath in test_items_filepath_dict:
|
|
198
|
-
test_item = test_items_filepath_dict[filepath]
|
|
199
|
-
jobs[ref_item.filename] = pool.apply_async(measure_item, kwds={'test_item': test_item,
|
|
200
|
-
'ref_item': ref_item,
|
|
201
|
-
'ref_project': ref_project,
|
|
202
|
-
'test_project': test_project,
|
|
203
|
-
'pbar': pbar})
|
|
204
|
-
pool.close()
|
|
205
|
-
pool.join()
|
|
206
|
-
_ = [job.wait() for job in jobs.values()]
|
|
207
|
-
raw_items_summary = dict()
|
|
208
|
-
failed_items_errors = dict()
|
|
209
|
-
for filename, job in jobs.items():
|
|
210
|
-
success, result = job.get()
|
|
211
|
-
if success:
|
|
212
|
-
raw_items_summary[filename] = result
|
|
213
|
-
else:
|
|
214
|
-
failed_items_errors[filename] = result
|
|
215
|
-
pool.terminate()
|
|
216
|
-
pbar.close()
|
|
217
|
-
|
|
218
|
-
df, raw_items_summary = create_summary(ref_name=ref_name, test_name=test_name, raw_items_summary=raw_items_summary)
|
|
219
|
-
|
|
220
|
-
if len(failed_items_errors) != 0:
|
|
221
|
-
logger.error(failed_items_errors)
|
|
222
|
-
if dump_path is not None:
|
|
223
|
-
save_to_file(dump_path=dump_path,
|
|
224
|
-
df=df,
|
|
225
|
-
ref_name=ref_name,
|
|
226
|
-
test_name=test_name)
|
|
227
|
-
return df, raw_items_summary, failed_items_errors
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def create_summary(ref_name, test_name, raw_items_summary):
|
|
231
|
-
summary = list()
|
|
232
|
-
ref_column_name = 'Ref-{!r}'.format(ref_name)
|
|
233
|
-
test_column_name = 'Test-{!r}'.format(test_name)
|
|
234
|
-
for filename, scores in raw_items_summary.items():
|
|
235
|
-
line = {'filename': scores['filename'],
|
|
236
|
-
ref_column_name: scores['ref_url'],
|
|
237
|
-
test_column_name: scores['test_url'],
|
|
238
|
-
'total_score': scores['total_mean_score'],
|
|
239
|
-
'ref_duration[s]': scores['ref_item_duration[s]'],
|
|
240
|
-
'test_duration[s]': scores['test_item_duration[s]'],
|
|
241
|
-
'diff_duration[s]': scores['diff_duration[s]']}
|
|
242
|
-
for tool_type in list(entities.AnnotationType):
|
|
243
|
-
if tool_type in scores:
|
|
244
|
-
res = scores[tool_type].summary()
|
|
245
|
-
line['{}_annotation_score'.format(tool_type)] = res['mean_annotations_scores']
|
|
246
|
-
line['{}_attributes_score'.format(tool_type)] = res['mean_attributes_scores']
|
|
247
|
-
line['{}_ref_number'.format(tool_type)] = res['n_annotations_set_one']
|
|
248
|
-
line['{}_test_number'.format(tool_type)] = res['n_annotations_set_two']
|
|
249
|
-
line['{}_match_number'.format(tool_type)] = res['n_annotations_matched_total']
|
|
250
|
-
summary.append(line)
|
|
251
|
-
df = pd.DataFrame(summary)
|
|
252
|
-
# Drop column only if all the values are None
|
|
253
|
-
df = df.dropna(how='all', axis=1)
|
|
254
|
-
####
|
|
255
|
-
|
|
256
|
-
return df, raw_items_summary
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def save_to_file(df, dump_path, ref_name, test_name):
|
|
260
|
-
# df = df.sort_values(by='box_score')
|
|
261
|
-
ref_column_name = 'Ref-{!r}'.format(ref_name)
|
|
262
|
-
test_column_name = 'Test-{!r}'.format(test_name)
|
|
263
|
-
|
|
264
|
-
def make_clickable(val):
|
|
265
|
-
return '<a href="{}">{}</a>'.format(val, 'item')
|
|
266
|
-
|
|
267
|
-
s = df.style.format({ref_column_name: make_clickable,
|
|
268
|
-
test_column_name: make_clickable}).render()
|
|
269
|
-
os.makedirs(dump_path, exist_ok=True)
|
|
270
|
-
html_filepath = os.path.join(dump_path, '{}-vs-{}.html'.format(ref_name, test_name))
|
|
271
|
-
csv_filepath = os.path.join(dump_path, '{}-vs-{}.csv'.format(ref_name, test_name))
|
|
272
|
-
with open(html_filepath, 'w') as f:
|
|
273
|
-
f.write(s)
|
|
274
|
-
df.to_csv(csv_filepath)
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
import traceback
|
|
6
|
+
import datetime
|
|
7
|
+
import logging
|
|
8
|
+
import tqdm
|
|
9
|
+
import uuid
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
from .. import entities
|
|
13
|
+
from . import BaseModelAdapter, metrics
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(name='dtlpy')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mean_or_nan(arr):
|
|
19
|
+
if isinstance(arr, list) and len(arr) == 0:
|
|
20
|
+
return np.nan
|
|
21
|
+
else:
|
|
22
|
+
return np.mean(arr)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def model_info_name(model: entities.Model, package: entities.Package):
|
|
26
|
+
if model is None:
|
|
27
|
+
return "{}-no-model".format(package.name)
|
|
28
|
+
else:
|
|
29
|
+
return "{}-{}".format(package.name, model.name)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def measure_annotations(
|
|
33
|
+
annotations_set_one: entities.AnnotationCollection,
|
|
34
|
+
annotations_set_two: entities.AnnotationCollection,
|
|
35
|
+
match_threshold=0.5,
|
|
36
|
+
ignore_labels=False,
|
|
37
|
+
ignore_attributes=False,
|
|
38
|
+
compare_types=None):
|
|
39
|
+
"""
|
|
40
|
+
Compares list (or collections) of annotations
|
|
41
|
+
This will also return the precision and recall of the two sets, given that the first that is a GT and the second set
|
|
42
|
+
is the detection (this affects the denominator of the calculation).
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
:param annotations_set_one: dl.AnnotationCollection entity with a list of annotations to compare
|
|
46
|
+
:param annotations_set_two: dl.AnnotationCollection entity with a list of annotations to compare
|
|
47
|
+
:param match_threshold: IoU threshold to count as a match
|
|
48
|
+
:param ignore_labels: ignore label when comparing - measure only geometry
|
|
49
|
+
:param ignore_attributes: ignore attribute score for final annotation score
|
|
50
|
+
:param compare_types: list of type to compare. enum dl.AnnotationType
|
|
51
|
+
|
|
52
|
+
Returns a dictionary of all the compare data
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
if compare_types is None:
|
|
56
|
+
compare_types = [entities.AnnotationType.BOX,
|
|
57
|
+
entities.AnnotationType.CLASSIFICATION,
|
|
58
|
+
entities.AnnotationType.POLYGON,
|
|
59
|
+
entities.AnnotationType.POINT,
|
|
60
|
+
entities.AnnotationType.SEGMENTATION]
|
|
61
|
+
final_results = dict()
|
|
62
|
+
all_scores = list()
|
|
63
|
+
true_positives = 0
|
|
64
|
+
false_positives = 0
|
|
65
|
+
false_negatives = 0
|
|
66
|
+
|
|
67
|
+
# for local annotations - set random id if None
|
|
68
|
+
for annotation in annotations_set_one:
|
|
69
|
+
if annotation.id is None:
|
|
70
|
+
annotation.id = str(uuid.uuid1())
|
|
71
|
+
for annotation in annotations_set_two:
|
|
72
|
+
if annotation.id is None:
|
|
73
|
+
annotation.id = str(uuid.uuid1())
|
|
74
|
+
|
|
75
|
+
# start comparing
|
|
76
|
+
for compare_type in compare_types:
|
|
77
|
+
matches = metrics.Matches()
|
|
78
|
+
annotation_subset_one = entities.AnnotationCollection()
|
|
79
|
+
annotation_subset_two = entities.AnnotationCollection()
|
|
80
|
+
annotation_subset_one.annotations = [a for a in annotations_set_one if
|
|
81
|
+
a.type == compare_type and not a.metadata.get('system', dict()).get(
|
|
82
|
+
'system', False)]
|
|
83
|
+
annotation_subset_two.annotations = [a for a in annotations_set_two if
|
|
84
|
+
a.type == compare_type and not a.metadata.get('system', dict()).get(
|
|
85
|
+
'system', False)]
|
|
86
|
+
# create 2d dataframe with annotation id as names and set all to -1 -> not calculated
|
|
87
|
+
if ignore_labels:
|
|
88
|
+
matches = metrics.Matchers.general_match(matches=matches,
|
|
89
|
+
first_set=annotation_subset_one,
|
|
90
|
+
second_set=annotation_subset_two,
|
|
91
|
+
match_type=compare_type,
|
|
92
|
+
match_threshold=match_threshold,
|
|
93
|
+
ignore_labels=ignore_labels,
|
|
94
|
+
ignore_attributes=ignore_attributes)
|
|
95
|
+
else:
|
|
96
|
+
unique_labels = np.unique([a.label for a in annotation_subset_one] +
|
|
97
|
+
[a.label for a in annotation_subset_two])
|
|
98
|
+
for label in unique_labels:
|
|
99
|
+
first_set = [a for a in annotation_subset_one if a.label == label]
|
|
100
|
+
second_set = [a for a in annotation_subset_two if a.label == label]
|
|
101
|
+
matches = metrics.Matchers.general_match(matches=matches,
|
|
102
|
+
first_set=first_set,
|
|
103
|
+
second_set=second_set,
|
|
104
|
+
match_type=compare_type,
|
|
105
|
+
match_threshold=match_threshold,
|
|
106
|
+
ignore_labels=ignore_labels,
|
|
107
|
+
ignore_attributes=ignore_attributes
|
|
108
|
+
)
|
|
109
|
+
if len(matches) == 0:
|
|
110
|
+
continue
|
|
111
|
+
all_scores.extend(matches.to_df()['annotation_score'])
|
|
112
|
+
final_results[compare_type] = metrics.Results(matches=matches,
|
|
113
|
+
annotation_type=compare_type)
|
|
114
|
+
true_positives += final_results[compare_type].summary()['n_annotations_matched_total']
|
|
115
|
+
false_positives += final_results[compare_type].summary()['n_annotations_unmatched_set_two']
|
|
116
|
+
false_negatives += final_results[compare_type].summary()['n_annotations_unmatched_set_one']
|
|
117
|
+
|
|
118
|
+
final_results['total_mean_score'] = mean_or_nan(all_scores)
|
|
119
|
+
final_results['precision'] = true_positives / (true_positives + false_positives)
|
|
120
|
+
final_results['recall'] = true_positives / (true_positives + false_negatives)
|
|
121
|
+
return final_results
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def measure_item(ref_item: entities.Item, test_item: entities.Item,
|
|
125
|
+
ref_project: entities.Project = None, test_project: entities.Project = None,
|
|
126
|
+
ignore_labels=False,
|
|
127
|
+
ignore_attributes=False,
|
|
128
|
+
match_threshold=0.5,
|
|
129
|
+
pbar=None):
|
|
130
|
+
"""
|
|
131
|
+
Compare annotations sets between two items
|
|
132
|
+
|
|
133
|
+
:param ref_item:
|
|
134
|
+
:param test_item:
|
|
135
|
+
:param ref_project:
|
|
136
|
+
:param test_project:
|
|
137
|
+
:param ignore_labels:
|
|
138
|
+
:param ignore_attributes:
|
|
139
|
+
:param match_threshold:
|
|
140
|
+
:param pbar:
|
|
141
|
+
:return:
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
annotations_set_one = ref_item.annotations.list()
|
|
145
|
+
annotations_set_two = test_item.annotations.list()
|
|
146
|
+
final = measure_annotations(annotations_set_one=annotations_set_one,
|
|
147
|
+
annotations_set_two=annotations_set_two,
|
|
148
|
+
ignore_labels=ignore_labels,
|
|
149
|
+
ignore_attributes=ignore_attributes,
|
|
150
|
+
match_threshold=match_threshold)
|
|
151
|
+
|
|
152
|
+
# get times
|
|
153
|
+
try:
|
|
154
|
+
ref_item_duration_s = metrics.item_annotation_duration(item=ref_item, project=ref_project)
|
|
155
|
+
ref_item_duration = str(datetime.timedelta(seconds=int(np.round(ref_item_duration_s))))
|
|
156
|
+
except Exception:
|
|
157
|
+
ref_item_duration_s = -1
|
|
158
|
+
ref_item_duration = ''
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
test_item_duration_s = metrics.item_annotation_duration(item=test_item, project=test_project)
|
|
162
|
+
test_item_duration = str(datetime.timedelta(seconds=int(np.round(test_item_duration_s))))
|
|
163
|
+
except Exception:
|
|
164
|
+
test_item_duration_s = -1
|
|
165
|
+
test_item_duration = ''
|
|
166
|
+
|
|
167
|
+
final.update({'ref_url': ref_item.platform_url,
|
|
168
|
+
'test_url': test_item.platform_url,
|
|
169
|
+
'filename': ref_item.filename,
|
|
170
|
+
'ref_item_duration[s]': ref_item_duration_s,
|
|
171
|
+
'test_item_duration[s]': test_item_duration_s,
|
|
172
|
+
'diff_duration[s]': test_item_duration_s - ref_item_duration_s,
|
|
173
|
+
# round to sec
|
|
174
|
+
'ref_item_duration': ref_item_duration,
|
|
175
|
+
'test_item_duration': test_item_duration,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
return True, final
|
|
179
|
+
except Exception:
|
|
180
|
+
fail_msg = 'failed measuring. ref_item: {!r}, test_item: {!r}'.format(ref_item.id, test_item.id)
|
|
181
|
+
return False, '{}\n{}'.format(fail_msg, traceback.format_exc())
|
|
182
|
+
finally:
|
|
183
|
+
if pbar is not None:
|
|
184
|
+
pbar.update()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def measure_items(ref_items, ref_project, ref_dataset, ref_name,
|
|
188
|
+
test_items, test_project, test_dataset, test_name,
|
|
189
|
+
dump_path=None):
|
|
190
|
+
from multiprocessing.pool import ThreadPool
|
|
191
|
+
ref_items_filepath_dict = {item.filename: item for page in ref_items for item in page}
|
|
192
|
+
test_items_filepath_dict = {item.filename: item for page in test_items for item in page}
|
|
193
|
+
pool = ThreadPool(processes=32)
|
|
194
|
+
pbar = tqdm.tqdm(total=len(ref_items_filepath_dict), file=sys.stdout)
|
|
195
|
+
jobs = dict()
|
|
196
|
+
for filepath, ref_item in ref_items_filepath_dict.items():
|
|
197
|
+
if filepath in test_items_filepath_dict:
|
|
198
|
+
test_item = test_items_filepath_dict[filepath]
|
|
199
|
+
jobs[ref_item.filename] = pool.apply_async(measure_item, kwds={'test_item': test_item,
|
|
200
|
+
'ref_item': ref_item,
|
|
201
|
+
'ref_project': ref_project,
|
|
202
|
+
'test_project': test_project,
|
|
203
|
+
'pbar': pbar})
|
|
204
|
+
pool.close()
|
|
205
|
+
pool.join()
|
|
206
|
+
_ = [job.wait() for job in jobs.values()]
|
|
207
|
+
raw_items_summary = dict()
|
|
208
|
+
failed_items_errors = dict()
|
|
209
|
+
for filename, job in jobs.items():
|
|
210
|
+
success, result = job.get()
|
|
211
|
+
if success:
|
|
212
|
+
raw_items_summary[filename] = result
|
|
213
|
+
else:
|
|
214
|
+
failed_items_errors[filename] = result
|
|
215
|
+
pool.terminate()
|
|
216
|
+
pbar.close()
|
|
217
|
+
|
|
218
|
+
df, raw_items_summary = create_summary(ref_name=ref_name, test_name=test_name, raw_items_summary=raw_items_summary)
|
|
219
|
+
|
|
220
|
+
if len(failed_items_errors) != 0:
|
|
221
|
+
logger.error(failed_items_errors)
|
|
222
|
+
if dump_path is not None:
|
|
223
|
+
save_to_file(dump_path=dump_path,
|
|
224
|
+
df=df,
|
|
225
|
+
ref_name=ref_name,
|
|
226
|
+
test_name=test_name)
|
|
227
|
+
return df, raw_items_summary, failed_items_errors
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def create_summary(ref_name, test_name, raw_items_summary):
|
|
231
|
+
summary = list()
|
|
232
|
+
ref_column_name = 'Ref-{!r}'.format(ref_name)
|
|
233
|
+
test_column_name = 'Test-{!r}'.format(test_name)
|
|
234
|
+
for filename, scores in raw_items_summary.items():
|
|
235
|
+
line = {'filename': scores['filename'],
|
|
236
|
+
ref_column_name: scores['ref_url'],
|
|
237
|
+
test_column_name: scores['test_url'],
|
|
238
|
+
'total_score': scores['total_mean_score'],
|
|
239
|
+
'ref_duration[s]': scores['ref_item_duration[s]'],
|
|
240
|
+
'test_duration[s]': scores['test_item_duration[s]'],
|
|
241
|
+
'diff_duration[s]': scores['diff_duration[s]']}
|
|
242
|
+
for tool_type in list(entities.AnnotationType):
|
|
243
|
+
if tool_type in scores:
|
|
244
|
+
res = scores[tool_type].summary()
|
|
245
|
+
line['{}_annotation_score'.format(tool_type)] = res['mean_annotations_scores']
|
|
246
|
+
line['{}_attributes_score'.format(tool_type)] = res['mean_attributes_scores']
|
|
247
|
+
line['{}_ref_number'.format(tool_type)] = res['n_annotations_set_one']
|
|
248
|
+
line['{}_test_number'.format(tool_type)] = res['n_annotations_set_two']
|
|
249
|
+
line['{}_match_number'.format(tool_type)] = res['n_annotations_matched_total']
|
|
250
|
+
summary.append(line)
|
|
251
|
+
df = pd.DataFrame(summary)
|
|
252
|
+
# Drop column only if all the values are None
|
|
253
|
+
df = df.dropna(how='all', axis=1)
|
|
254
|
+
####
|
|
255
|
+
|
|
256
|
+
return df, raw_items_summary
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def save_to_file(df, dump_path, ref_name, test_name):
|
|
260
|
+
# df = df.sort_values(by='box_score')
|
|
261
|
+
ref_column_name = 'Ref-{!r}'.format(ref_name)
|
|
262
|
+
test_column_name = 'Test-{!r}'.format(test_name)
|
|
263
|
+
|
|
264
|
+
def make_clickable(val):
|
|
265
|
+
return '<a href="{}">{}</a>'.format(val, 'item')
|
|
266
|
+
|
|
267
|
+
s = df.style.format({ref_column_name: make_clickable,
|
|
268
|
+
test_column_name: make_clickable}).render()
|
|
269
|
+
os.makedirs(dump_path, exist_ok=True)
|
|
270
|
+
html_filepath = os.path.join(dump_path, '{}-vs-{}.html'.format(ref_name, test_name))
|
|
271
|
+
csv_filepath = os.path.join(dump_path, '{}-vs-{}.csv'.format(ref_name, test_name))
|
|
272
|
+
with open(html_filepath, 'w') as f:
|
|
273
|
+
f.write(s)
|
|
274
|
+
df.to_csv(csv_filepath)
|