endoreg-db 0.5.3__py3-none-any.whl → 0.6.1__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.

Potentially problematic release.


This version of endoreg-db might be problematic. Click here for more details.

Files changed (268) hide show
  1. endoreg_db/admin.py +90 -1
  2. endoreg_db/case_generator/case_generator.py +159 -0
  3. endoreg_db/case_generator/lab_sample_factory.py +33 -0
  4. endoreg_db/case_generator/utils.py +30 -0
  5. endoreg_db/data/__init__.py +50 -4
  6. endoreg_db/data/ai_model/data.yaml +7 -0
  7. endoreg_db/data/{label → ai_model_label}/label/data.yaml +27 -1
  8. endoreg_db/data/ai_model_label/label-set/data.yaml +21 -0
  9. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +5 -0
  10. endoreg_db/data/ai_model_video_segmentation_label/base_segmentation.yaml +176 -0
  11. endoreg_db/data/ai_model_video_segmentation_labelset/data.yaml +20 -0
  12. endoreg_db/data/center/data.yaml +35 -5
  13. endoreg_db/data/contraindication/bleeding.yaml +11 -0
  14. endoreg_db/data/distribution/numeric/data.yaml +14 -0
  15. endoreg_db/data/endoscope/data.yaml +93 -0
  16. endoreg_db/data/examination_indication/endoscopy.yaml +8 -0
  17. endoreg_db/data/examination_indication_classification/endoscopy.yaml +8 -0
  18. endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +101 -0
  19. endoreg_db/data/finding/data.yaml +141 -0
  20. endoreg_db/data/finding_intervention/endoscopy.yaml +138 -0
  21. endoreg_db/data/finding_intervention_type/endoscopy.yaml +15 -0
  22. endoreg_db/data/finding_location_classification/colonoscopy.yaml +46 -0
  23. endoreg_db/data/finding_location_classification_choice/colonoscopy.yaml +240 -0
  24. endoreg_db/data/finding_morphology_classification/colonoscopy.yaml +48 -0
  25. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_circularity_default.yaml +34 -0
  26. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_nice.yaml +20 -0
  27. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_paris.yaml +65 -0
  28. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_planarity_default.yaml +56 -0
  29. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_surface_intact_default.yaml +39 -0
  30. endoreg_db/data/finding_morphology_classification_choice/colonoscopy_size.yaml +57 -0
  31. endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +79 -0
  32. endoreg_db/data/finding_type/data.yaml +30 -0
  33. endoreg_db/data/gender/data.yaml +17 -0
  34. endoreg_db/data/lab_value/cardiac_enzymes.yaml +7 -1
  35. endoreg_db/data/lab_value/coagulation.yaml +6 -1
  36. endoreg_db/data/lab_value/electrolytes.yaml +39 -1
  37. endoreg_db/data/lab_value/gastrointestinal_function.yaml +12 -0
  38. endoreg_db/data/lab_value/hematology.yaml +17 -2
  39. endoreg_db/data/lab_value/hormones.yaml +6 -0
  40. endoreg_db/data/lab_value/lipids.yaml +12 -3
  41. endoreg_db/data/lab_value/misc.yaml +5 -2
  42. endoreg_db/data/lab_value/renal_function.yaml +2 -1
  43. endoreg_db/data/lx_client_tag/base.yaml +54 -0
  44. endoreg_db/data/lx_client_type/base.yaml +30 -0
  45. endoreg_db/data/lx_permission/base.yaml +24 -0
  46. endoreg_db/data/lx_permission/endoreg.yaml +52 -0
  47. endoreg_db/data/medication_indication/anticoagulation.yaml +44 -49
  48. endoreg_db/data/names_first/first_names.yaml +51 -0
  49. endoreg_db/data/names_last/last_names.yaml +51 -0
  50. endoreg_db/data/network_device/data.yaml +30 -0
  51. endoreg_db/data/organ/data.yaml +29 -0
  52. endoreg_db/data/pdf_type/data.yaml +2 -1
  53. endoreg_db/data/report_reader_flag/ukw-examination-generic.yaml +4 -0
  54. endoreg_db/forms/__init__.py +3 -1
  55. endoreg_db/forms/examination_form.py +11 -0
  56. endoreg_db/forms/patient_finding_intervention_form.py +19 -0
  57. endoreg_db/forms/patient_form.py +26 -0
  58. endoreg_db/management/commands/__init__.py +0 -0
  59. endoreg_db/management/commands/load_ai_model_data.py +57 -23
  60. endoreg_db/management/commands/load_ai_model_label_data.py +59 -0
  61. endoreg_db/management/commands/load_base_db_data.py +160 -118
  62. endoreg_db/management/commands/{load_endoscope_type_data.py → load_contraindication_data.py} +3 -7
  63. endoreg_db/management/commands/load_disease_data.py +29 -7
  64. endoreg_db/management/commands/load_endoscope_data.py +68 -0
  65. endoreg_db/management/commands/load_examination_indication_data.py +65 -0
  66. endoreg_db/management/commands/load_finding_data.py +171 -0
  67. endoreg_db/management/commands/load_lab_value_data.py +3 -3
  68. endoreg_db/management/commands/load_lx_data.py +64 -0
  69. endoreg_db/management/commands/load_medication_data.py +83 -21
  70. endoreg_db/management/commands/load_name_data.py +37 -0
  71. endoreg_db/management/commands/{load_endoscopy_processor_data.py → load_organ_data.py} +7 -9
  72. endoreg_db/migrations/0001_initial.py +1206 -728
  73. endoreg_db/migrations/0002_alter_frame_image_alter_rawframe_image.py +23 -0
  74. endoreg_db/migrations/0003_alter_frame_image_alter_rawframe_image.py +23 -0
  75. endoreg_db/migrations/0004_alter_rawvideofile_file_alter_video_file.py +25 -0
  76. endoreg_db/migrations/0005_rawvideofile_frame_count_and_more.py +33 -0
  77. endoreg_db/migrations/0006_frame_extracted_rawframe_extracted.py +23 -0
  78. endoreg_db/migrations/0007_rename_pseudo_patient_video_patient_and_more.py +24 -0
  79. endoreg_db/migrations/0008_remove_reportfile_patient_examination_and_more.py +48 -0
  80. endoreg_db/models/__init__.py +331 -28
  81. endoreg_db/models/ai_model/__init__.py +1 -0
  82. endoreg_db/models/ai_model/ai_model.py +103 -0
  83. endoreg_db/models/ai_model/lightning/__init__.py +3 -0
  84. endoreg_db/models/ai_model/lightning/inference_dataset.py +53 -0
  85. endoreg_db/models/ai_model/lightning/multilabel_classification_net.py +155 -0
  86. endoreg_db/models/ai_model/lightning/postprocess.py +53 -0
  87. endoreg_db/models/ai_model/lightning/predict.py +172 -0
  88. endoreg_db/models/ai_model/lightning/prediction_visualizer.py +55 -0
  89. endoreg_db/models/ai_model/lightning/preprocess.py +68 -0
  90. endoreg_db/models/ai_model/lightning/run_visualizer.py +21 -0
  91. endoreg_db/models/ai_model/model_meta.py +232 -6
  92. endoreg_db/models/ai_model/model_type.py +13 -3
  93. endoreg_db/models/annotation/__init__.py +31 -2
  94. endoreg_db/models/annotation/anonymized_image_annotation.py +73 -18
  95. endoreg_db/models/annotation/binary_classification_annotation_task.py +94 -57
  96. endoreg_db/models/annotation/image_classification.py +73 -14
  97. endoreg_db/models/annotation/video_segmentation_annotation.py +52 -0
  98. endoreg_db/models/annotation/video_segmentation_labelset.py +20 -0
  99. endoreg_db/models/case/__init__.py +1 -0
  100. endoreg_db/models/{persons/patient/case → case}/case.py +4 -0
  101. endoreg_db/models/case_template/__init__.py +10 -1
  102. endoreg_db/models/case_template/case_template.py +57 -13
  103. endoreg_db/models/case_template/case_template_rule.py +5 -5
  104. endoreg_db/models/case_template/case_template_rule_value.py +19 -4
  105. endoreg_db/models/center/__init__.py +7 -0
  106. endoreg_db/models/center/center.py +31 -5
  107. endoreg_db/models/center/center_product.py +0 -1
  108. endoreg_db/models/center/center_resource.py +16 -2
  109. endoreg_db/models/center/center_waste.py +6 -1
  110. endoreg_db/models/contraindication/__init__.py +21 -0
  111. endoreg_db/models/data_file/__init__.py +38 -5
  112. endoreg_db/models/data_file/base_classes/__init__.py +6 -1
  113. endoreg_db/models/data_file/base_classes/abstract_frame.py +64 -15
  114. endoreg_db/models/data_file/base_classes/abstract_pdf.py +136 -0
  115. endoreg_db/models/data_file/base_classes/abstract_video.py +744 -138
  116. endoreg_db/models/data_file/base_classes/frame_helpers.py +17 -0
  117. endoreg_db/models/data_file/base_classes/prepare_bulk_frames.py +19 -0
  118. endoreg_db/models/data_file/base_classes/utils.py +80 -0
  119. endoreg_db/models/data_file/frame.py +22 -38
  120. endoreg_db/models/data_file/import_classes/__init__.py +4 -18
  121. endoreg_db/models/data_file/import_classes/raw_pdf.py +162 -90
  122. endoreg_db/models/data_file/import_classes/raw_video.py +239 -294
  123. endoreg_db/models/data_file/metadata/__init__.py +10 -0
  124. endoreg_db/models/data_file/metadata/pdf_meta.py +4 -0
  125. endoreg_db/models/data_file/metadata/sensitive_meta.py +265 -6
  126. endoreg_db/models/data_file/metadata/video_meta.py +116 -50
  127. endoreg_db/models/data_file/report_file.py +30 -63
  128. endoreg_db/models/data_file/video/__init__.py +6 -2
  129. endoreg_db/models/data_file/video/video.py +187 -16
  130. endoreg_db/models/data_file/video_segment.py +162 -55
  131. endoreg_db/models/disease.py +25 -2
  132. endoreg_db/models/emission/__init__.py +5 -1
  133. endoreg_db/models/emission/emission_factor.py +71 -6
  134. endoreg_db/models/event.py +51 -0
  135. endoreg_db/models/examination/__init__.py +6 -1
  136. endoreg_db/models/examination/examination.py +53 -12
  137. endoreg_db/models/examination/examination_indication.py +170 -0
  138. endoreg_db/models/examination/examination_time.py +31 -5
  139. endoreg_db/models/examination/examination_time_type.py +28 -4
  140. endoreg_db/models/examination/examination_type.py +28 -6
  141. endoreg_db/models/finding/__init__.py +11 -0
  142. endoreg_db/models/finding/finding.py +75 -0
  143. endoreg_db/models/finding/finding_intervention.py +60 -0
  144. endoreg_db/models/finding/finding_location_classification.py +94 -0
  145. endoreg_db/models/finding/finding_morphology_classification.py +89 -0
  146. endoreg_db/models/finding/finding_type.py +22 -0
  147. endoreg_db/models/hardware/endoscope.py +16 -0
  148. endoreg_db/models/hardware/endoscopy_processor.py +31 -19
  149. endoreg_db/models/label/label.py +35 -7
  150. endoreg_db/models/laboratory/lab_value.py +12 -3
  151. endoreg_db/models/logging/__init__.py +8 -1
  152. endoreg_db/models/lx/__init__.py +4 -0
  153. endoreg_db/models/lx/client.py +57 -0
  154. endoreg_db/models/lx/identity.py +34 -0
  155. endoreg_db/models/lx/permission.py +18 -0
  156. endoreg_db/models/lx/user.py +16 -0
  157. endoreg_db/models/medication/__init__.py +19 -1
  158. endoreg_db/models/medication/medication.py +7 -122
  159. endoreg_db/models/medication/medication_indication.py +50 -0
  160. endoreg_db/models/medication/medication_indication_type.py +34 -0
  161. endoreg_db/models/medication/medication_intake_time.py +26 -0
  162. endoreg_db/models/medication/medication_schedule.py +37 -0
  163. endoreg_db/models/network/__init__.py +7 -1
  164. endoreg_db/models/network/network_device.py +13 -8
  165. endoreg_db/models/organ/__init__.py +38 -0
  166. endoreg_db/models/other/__init__.py +19 -1
  167. endoreg_db/models/other/distribution/__init__.py +44 -0
  168. endoreg_db/models/other/distribution/base_value_distribution.py +20 -0
  169. endoreg_db/models/other/distribution/date_value_distribution.py +91 -0
  170. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +32 -0
  171. endoreg_db/models/other/distribution/numeric_value_distribution.py +97 -0
  172. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +22 -0
  173. endoreg_db/models/other/distribution.py +1 -211
  174. endoreg_db/models/other/material.py +4 -0
  175. endoreg_db/models/other/transport_route.py +2 -1
  176. endoreg_db/models/patient/__init__.py +24 -0
  177. endoreg_db/models/patient/patient_examination.py +182 -0
  178. endoreg_db/models/patient/patient_finding.py +143 -0
  179. endoreg_db/models/patient/patient_finding_intervention.py +26 -0
  180. endoreg_db/models/patient/patient_finding_location.py +120 -0
  181. endoreg_db/models/patient/patient_finding_morphology.py +166 -0
  182. endoreg_db/models/persons/__init__.py +29 -2
  183. endoreg_db/models/persons/examiner/examiner.py +48 -4
  184. endoreg_db/models/persons/patient/__init__.py +1 -1
  185. endoreg_db/models/persons/patient/patient.py +227 -54
  186. endoreg_db/models/persons/patient/patient_disease.py +6 -0
  187. endoreg_db/models/persons/patient/patient_event.py +31 -1
  188. endoreg_db/models/persons/patient/patient_examination_indication.py +32 -0
  189. endoreg_db/models/persons/patient/patient_lab_sample.py +4 -2
  190. endoreg_db/models/persons/patient/patient_lab_value.py +37 -16
  191. endoreg_db/models/persons/patient/patient_medication.py +27 -12
  192. endoreg_db/models/persons/patient/patient_medication_schedule.py +62 -2
  193. endoreg_db/models/prediction/__init__.py +7 -1
  194. endoreg_db/models/prediction/image_classification.py +20 -6
  195. endoreg_db/models/prediction/video_prediction_meta.py +151 -89
  196. endoreg_db/models/product/__init__.py +10 -1
  197. endoreg_db/models/product/product.py +15 -2
  198. endoreg_db/models/product/product_group.py +8 -0
  199. endoreg_db/models/product/product_material.py +4 -0
  200. endoreg_db/models/product/product_weight.py +12 -0
  201. endoreg_db/models/product/reference_product.py +19 -3
  202. endoreg_db/models/quiz/__init__.py +8 -1
  203. endoreg_db/models/report_reader/__init__.py +6 -1
  204. endoreg_db/serializers/__init__.py +1 -1
  205. endoreg_db/serializers/annotation.py +2 -5
  206. endoreg_db/serializers/frame.py +1 -5
  207. endoreg_db/serializers/patient.py +26 -3
  208. endoreg_db/serializers/prediction.py +2 -7
  209. endoreg_db/serializers/raw_video_meta_validation.py +13 -0
  210. endoreg_db/serializers/video.py +6 -13
  211. endoreg_db/serializers/video_segmentation.py +492 -0
  212. endoreg_db/templates/admin/patient_finding_intervention.html +253 -0
  213. endoreg_db/templates/admin/start_examination.html +12 -0
  214. endoreg_db/templates/timeline.html +176 -0
  215. endoreg_db/urls.py +173 -0
  216. endoreg_db/utils/__init__.py +36 -1
  217. endoreg_db/utils/dataloader.py +45 -19
  218. endoreg_db/utils/dates.py +39 -0
  219. endoreg_db/utils/hashs.py +122 -4
  220. endoreg_db/utils/names.py +74 -0
  221. endoreg_db/utils/parse_and_generate_yaml.py +46 -0
  222. endoreg_db/utils/pydantic_models/__init__.py +6 -0
  223. endoreg_db/utils/pydantic_models/db_config.py +57 -0
  224. endoreg_db/utils/validate_endo_roi.py +19 -0
  225. endoreg_db/utils/validate_subcategory_dict.py +91 -0
  226. endoreg_db/utils/video/__init__.py +13 -0
  227. endoreg_db/utils/video/extract_frames.py +121 -0
  228. endoreg_db/utils/video/transcode_videofile.py +111 -0
  229. endoreg_db/views/__init__.py +2 -0
  230. endoreg_db/views/csrf.py +7 -0
  231. endoreg_db/views/patient_views.py +90 -0
  232. endoreg_db/views/raw_video_meta_validation_views.py +38 -0
  233. endoreg_db/views/report_views.py +96 -0
  234. endoreg_db/views/video_segmentation_views.py +149 -0
  235. endoreg_db/views/views_for_timeline.py +46 -0
  236. endoreg_db/views.py +0 -3
  237. endoreg_db-0.6.1.dist-info/METADATA +151 -0
  238. endoreg_db-0.6.1.dist-info/RECORD +420 -0
  239. {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/WHEEL +1 -1
  240. endoreg_db/data/active_model/data.yaml +0 -3
  241. endoreg_db/data/label/label-set/data.yaml +0 -18
  242. endoreg_db/management/commands/delete_legacy_images.py +0 -19
  243. endoreg_db/management/commands/delete_legacy_videos.py +0 -17
  244. endoreg_db/management/commands/extract_legacy_video_frames.py +0 -18
  245. endoreg_db/management/commands/import_legacy_images.py +0 -94
  246. endoreg_db/management/commands/import_legacy_videos.py +0 -76
  247. endoreg_db/management/commands/load_label_data.py +0 -67
  248. endoreg_db/migrations/0002_anonymizedimagelabel_anonymousimageannotation_and_more.py +0 -55
  249. endoreg_db/migrations/0003_anonymousimageannotation_original_image_url_and_more.py +0 -39
  250. endoreg_db/migrations/0004_alter_rawpdffile_file.py +0 -20
  251. endoreg_db/migrations/0005_uploadedfile_alter_rawpdffile_file_anonymizedfile.py +0 -40
  252. endoreg_db/migrations/0006_alter_rawpdffile_file.py +0 -20
  253. endoreg_db/migrations/0007_networkdevicelogentry_datetime_and_more.py +0 -43
  254. endoreg_db/migrations/0008_networkdevicelogentry_aglnet_ip_and_more.py +0 -28
  255. endoreg_db/migrations/0009_alter_networkdevicelogentry_vpn_service_status.py +0 -18
  256. endoreg_db/migrations/0010_remove_networkdevicelogentry_hostname.py +0 -17
  257. endoreg_db/models/legacy_data/__init__.py +0 -3
  258. endoreg_db/models/legacy_data/image.py +0 -34
  259. endoreg_db/models/patient_examination/__init__.py +0 -35
  260. endoreg_db/utils/video_metadata.py +0 -87
  261. endoreg_db-0.5.3.dist-info/METADATA +0 -28
  262. endoreg_db-0.5.3.dist-info/RECORD +0 -319
  263. /endoreg_db/{models/persons/patient/case → case_generator}/__init__.py +0 -0
  264. /endoreg_db/data/{label → ai_model_label}/label-type/data.yaml +0 -0
  265. /endoreg_db/data/{model_type → ai_model_type}/data.yaml +0 -0
  266. /endoreg_db/{data/distribution/numeric/.init → management/__init__.py} +0 -0
  267. /endoreg_db/management/commands/{load_report_reader_flag.py → load_report_reader_flag_data.py} +0 -0
  268. {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,24 +1,250 @@
1
+ """
2
+ This module defines the ModelMeta and ModelMetaManager classes for managing AI model metadata.
3
+ """
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Optional, TYPE_CHECKING
8
+ import shutil
9
+
1
10
  from django.db import models
11
+ from django.core.validators import FileExtensionValidator
12
+ from icecream import ic
13
+
14
+ if TYPE_CHECKING:
15
+ from endoreg_db.models import LabelSet, AiModel # pylint: disable=import-outside-toplevel
16
+
17
+ PSEUDO_DIR = Path(os.environ.get("DJANGO_PSEUDO_DIR", Path("./erc_data")))
18
+
19
+ STORAGE_LOCATION = PSEUDO_DIR
20
+ WEIGHTS_DIR_NAME = "db_model_weights"
21
+ WEIGHTS_DIR = STORAGE_LOCATION / WEIGHTS_DIR_NAME
22
+
23
+ if not WEIGHTS_DIR.exists():
24
+ WEIGHTS_DIR.mkdir(parents=True)
25
+
2
26
 
3
27
  class ModelMetaManager(models.Manager):
4
- # natural key is name and version
5
- def get_by_natural_key(self, name, version):
28
+ """
29
+ Custom manager for ModelMeta with additional query methods.
30
+ """
31
+
32
+ def get_by_natural_key(self, name: str, version: str) -> "ModelMeta":
33
+ """Get the model by its natural key"""
6
34
  return self.get(name=name, version=version)
7
35
 
36
+
8
37
  class ModelMeta(models.Model):
38
+ """
39
+ Attributes:
40
+ name (str): The name of the model.
41
+ version (str): The version of the model.
42
+ type (ModelType): The type of the model.
43
+ labelset (LabelSet): Associated labels.
44
+ weights (FileField): Path to the model weights.
45
+ """
46
+
9
47
  name = models.CharField(max_length=255)
10
48
  version = models.CharField(max_length=255)
11
- type = models.ForeignKey("ModelType", on_delete=models.CASCADE, related_name="models")
12
- labelset = models.ForeignKey("LabelSet", on_delete=models.CASCADE, related_name="models")
13
- weights = models.FileField(upload_to='weights/')
14
-
49
+
50
+ model = models.ForeignKey(
51
+ "AiModel", on_delete=models.CASCADE, related_name="models"
52
+ )
53
+
54
+ labelset = models.ForeignKey(
55
+ "LabelSet", on_delete=models.CASCADE, related_name="models"
56
+ )
57
+ activation = models.CharField(max_length=255, default="sigmoid")
58
+
59
+ weights = models.FileField(
60
+ upload_to=WEIGHTS_DIR_NAME,
61
+ validators=[FileExtensionValidator(allowed_extensions=["ckpt"])],
62
+ # storage=FileSystemStorage(location=STORAGE_LOCATION.resolve().as_posix()),
63
+ null=True,
64
+ blank=True,
65
+ )
66
+
67
+ # Means and stds for normalization
68
+ mean = models.CharField(max_length=255, default="0.45211223,0.27139644,0.19264949")
69
+ std = models.CharField(max_length=255, default="0.31418097,0.21088019,0.16059452")
70
+
71
+ size_x = models.IntegerField(default=716)
72
+ size_y = models.IntegerField(default=716)
73
+ axes = models.CharField(max_length=255, default="2,0,1")
74
+ batchsize = models.IntegerField(default=16)
75
+ num_workers = models.IntegerField(default=0)
76
+
15
77
  description = models.TextField(blank=True, null=True)
16
78
  date_created = models.DateTimeField(auto_now_add=True)
79
+
17
80
  objects = ModelMetaManager()
18
81
 
82
+ if TYPE_CHECKING:
83
+ labelset: "LabelSet"
84
+ ai_model: "AiModel"
85
+
86
+ @classmethod
87
+ def create_from_file(
88
+ cls,
89
+ meta_name,
90
+ model_name,
91
+ model_meta_version,
92
+ labelset_name,
93
+ weights_file,
94
+ bump_version,
95
+ **kwargs,
96
+ ):
97
+ """Create a new model meta from a file"""
98
+
99
+ from endoreg_db.models import LabelSet, AiModel # pylint: disable=import-outside-toplevel
100
+
101
+ ic("Start")
102
+ model_meta_version = kwargs.get("model_meta_version", None)
103
+
104
+ ai_model = AiModel.objects.get(name=model_name)
105
+
106
+ # check If ModelMeta with same name and model already exists
107
+ if not model_meta_version:
108
+ if cls.objects.filter(name=meta_name, model=ai_model).exists():
109
+ # get highest version number
110
+ highest_version = (
111
+ cls.objects.filter(name=meta_name, model=ai_model)
112
+ .latest("version")
113
+ .version
114
+ )
115
+ version = int(highest_version) + 1
116
+ else:
117
+ version = 1
118
+ else:
119
+ version = model_meta_version
120
+
121
+ meta_exists = cls.objects.filter(
122
+ name=meta_name, model=ai_model, version=version
123
+ ).exists()
124
+ if meta_exists and not bump_version:
125
+ raise ValueError(
126
+ f"ModelMeta with name {meta_name} and version {version} already exists"
127
+ )
128
+
129
+ assert labelset_name is not None, "Labelset name must be provided"
130
+ labelset = LabelSet.objects.get(name=labelset_name)
131
+ assert labelset is not None, "Labelset not found"
132
+
133
+ weights_path = Path(weights_file)
134
+ # If not under our WEIGHTS_DIR, copy it there
135
+ if not str(weights_path).startswith(str(WEIGHTS_DIR)):
136
+ target_path = WEIGHTS_DIR / weights_path.name
137
+ if not target_path.exists():
138
+ shutil.copy(weights_path, target_path)
139
+ weights_file = target_path
140
+
141
+ return cls.objects.create(
142
+ name=meta_name,
143
+ model=ai_model,
144
+ version=version,
145
+ labelset=labelset,
146
+ weights=str(weights_file),
147
+ **kwargs,
148
+ )
149
+
150
+ @classmethod
151
+ def get_latest_version(cls, name) -> int:
152
+ """
153
+ get the model meta with the highest version. Assumes Model Name and Meta Name are the same
154
+ """
155
+ from endoreg_db.models import AiModel # pylint: disable=import-outside-toplevel
156
+
157
+ ai_model = AiModel.objects.get(name=name)
158
+ ic(f"Model: {ai_model}")
159
+ assert ai_model is not None, "Model not found"
160
+
161
+ model_meta = cls.objects.filter(name=name, model=ai_model).latest("version")
162
+ ic(f"Latest ModelMeta: {model_meta}")
163
+
164
+ latest_version = model_meta.version
165
+ ic(f"Latest Version: {latest_version}")
166
+
167
+ return int(latest_version)
168
+
169
+ @classmethod
170
+ def get_activation_function(cls, activation: str):
171
+ """get the activation function"""
172
+ from torch import nn # pylint: disable=import-outside-toplevel
173
+
174
+ lookup = {
175
+ "sigmoid": nn.Sigmoid(),
176
+ "softmax": nn.Softmax(),
177
+ "none": nn.Identity(),
178
+ }
179
+
180
+ return lookup[activation]
181
+
182
+ def get_inference_dataset_config(self):
183
+ """
184
+ Creates a dictionary with the configuration for the inference dataset.
185
+
186
+ Example:
187
+ sample_config = {
188
+ # mean and std for normalization
189
+ "mean": (0.45211223, 0.27139644, 0.19264949),
190
+ "std": (0.31418097, 0.21088019, 0.16059452),
191
+ # Image Size
192
+ "size_x": 716,
193
+ "size_y": 716,
194
+ # how to wrangle axes of the image before putting them in the network
195
+ "axes": [2,0,1], # 2,1,0 for opencv
196
+ "batchsize": 16,
197
+ "num_workers": 0, # always 1 for Windows systems # FIXME: fix celery crash if multiprocessing
198
+ # maybe add sigmoid after prediction?
199
+ "activation": nn.Sigmoid(),
200
+ "labels": ['appendix', 'blood', 'diverticule', 'grasper', 'ileocaecalvalve', 'ileum', 'low_quality', 'nbi', 'needle', 'outside', 'polyp', 'snare', 'water_jet', 'wound']
201
+ }
202
+ """
203
+ mean = list(map(float, str(self.mean).split(",")))
204
+ std = list(map(float, str(self.std).split(",")))
205
+ axes = list(map(int, str(self.axes).split(",")))
206
+
207
+ config = {
208
+ "mean": mean,
209
+ "std": std,
210
+ "size_x": self.size_x,
211
+ "size_y": self.size_y,
212
+ "axes": axes,
213
+ "batchsize": self.batchsize,
214
+ "num_workers": self.num_workers,
215
+ "activation": self.get_activation_function(self.activation),
216
+ "labels": self.labelset.get_labels_in_order(), # pylint: disable=no-member
217
+ }
218
+
219
+ ic(f"ModelMeta - get_inference_dataset_config: {config}")
220
+
221
+ return config
222
+
19
223
  def natural_key(self):
224
+ """Return the natural key for this model"""
20
225
  return (self.name, self.version)
21
226
 
22
227
  def __str__(self):
23
228
  return f"{self.name} (v: {self.version})"
24
229
 
230
+ def get_config_dict(self) -> dict:
231
+ """Return the configuration as a dictionary"""
232
+ conf_dict = {
233
+ "mean": list(map(float, str(self.mean).split(","))),
234
+ "std": list(map(float, str(self.std).split(","))),
235
+ "size_x": self.size_x,
236
+ "size_y": self.size_y,
237
+ "axes": list(map(int, str(self.axes).split(","))),
238
+ "batchsize": self.batchsize,
239
+ "num_workers": self.num_workers,
240
+ "activation": self.activation,
241
+ "labels": self.labelset.get_labels_in_order(), # pylint: disable=no-member
242
+ }
243
+ return conf_dict
244
+
245
+ @classmethod
246
+ def get_by_name(cls, name: str, version: Optional[int]) -> "ModelMeta":
247
+ """Get the model by its name and version"""
248
+ if version is None:
249
+ version = cls.get_latest_version(name)
250
+ return cls.objects.get(name=name, version=version)
@@ -1,10 +1,17 @@
1
1
  from django.db import models
2
2
  from django.core import serializers
3
3
 
4
+
4
5
  class ModelTypeManager(models.Manager):
5
- def get_by_natural_key(self, name):
6
+ """
7
+ Custom manager for ModelType with additional query methods.
8
+ """
9
+
10
+ def get_by_natural_key(self, name: str) -> "ModelType":
11
+ """Get the model type by its natural key"""
6
12
  return self.get(name=name)
7
-
13
+
14
+
8
15
  class ModelType(models.Model):
9
16
  """
10
17
  A class representing a model type.
@@ -14,13 +21,16 @@ class ModelType(models.Model):
14
21
  description (str): A description of the model type.
15
22
 
16
23
  """
24
+
17
25
  name = models.CharField(max_length=255)
18
26
  description = models.TextField(blank=True, null=True)
19
27
 
20
28
  objects = ModelTypeManager()
21
29
 
22
30
  def natural_key(self):
31
+ """Return the natural key for this model type"""
23
32
  return (self.name,)
24
33
 
25
34
  def __str__(self):
26
- return self.name
35
+ """Return the name of the model type"""
36
+ return str(self.name)
@@ -1,3 +1,32 @@
1
+ """Module for annotation models"""
2
+
1
3
  from .image_classification import ImageClassificationAnnotation
2
- from .binary_classification_annotation_task import LegacyBinaryClassificationAnnotationTask, BinaryClassificationAnnotationTask
3
- from .anonymized_image_annotation import AnonymousImageAnnotation, DroppedName, AnonymizedImageLabel, AnonymizedFile, UploadedFile
4
+ from .binary_classification_annotation_task import (
5
+ BinaryClassificationAnnotationTask,
6
+ )
7
+ from .anonymized_image_annotation import (
8
+ AnonymousImageAnnotation,
9
+ DroppedName,
10
+ AnonymizedImageLabel,
11
+ AnonymizedFile,
12
+ UploadedFile,
13
+ )
14
+ from .video_segmentation_annotation import (
15
+ VideoSegmentationLabel,
16
+ VideoSegmentationAnnotation,
17
+ )
18
+
19
+ from .video_segmentation_labelset import VideoSegmentationLabelSet
20
+
21
+ __all__ = [
22
+ "ImageClassificationAnnotation",
23
+ "BinaryClassificationAnnotationTask",
24
+ "AnonymousImageAnnotation",
25
+ "DroppedName",
26
+ "AnonymizedImageLabel",
27
+ "AnonymizedFile",
28
+ "UploadedFile",
29
+ "VideoSegmentationLabel",
30
+ "VideoSegmentationLabelSet",
31
+ "VideoSegmentationAnnotation",
32
+ ]
@@ -1,60 +1,115 @@
1
1
  from django.db import models
2
2
  from django.utils.text import slugify
3
+ from django.utils import timezone
3
4
 
4
5
  class AnonymizedImageLabel(models.Model):
5
- name = models.CharField(max_length=255, unique=True) # Ensuring label names are unique
6
- description = models.TextField(blank=True, null=True) # Optional description field
6
+ """
7
+ Represents a label for anonymized image annotations.
8
+
9
+ Attributes:
10
+ name (str): Unique name of the label.
11
+ description (str): Optional description for the label.
12
+ """
13
+ name = models.CharField(max_length=255, unique=True)
14
+ description = models.TextField(blank=True, null=True)
7
15
 
8
- def __str__(self):
16
+ def __str__(self) -> str:
9
17
  return self.name
10
18
 
19
+
11
20
  class AnonymousImageAnnotation(models.Model):
21
+ """
22
+ Represents an annotation for anonymized images.
23
+
24
+ Attributes:
25
+ label (ForeignKey): Associated label for the image.
26
+ image_name (str): Name of the image file.
27
+ original_image_url (str): URL of the original image.
28
+ slug (str): Unique identifier for the annotation.
29
+ polyp_count (int): Number of polyps identified in the image.
30
+ comments (str): Additional comments about the annotation.
31
+ gender (str): Gender associated with the annotation (if applicable).
32
+ name_image_url (str): URL of the name image.
33
+ date_created (datetime): Date when the annotation was created.
34
+ processed (bool): Indicates if the annotation has been processed.
35
+ """
12
36
  label = models.ForeignKey(AnonymizedImageLabel, on_delete=models.CASCADE)
13
37
  image_name = models.CharField(max_length=255)
14
- original_image_url = models.CharField(max_length=255, default='https://example.com/placeholder.jpg') # Field for the original image
38
+ original_image_url = models.CharField(max_length=255, default='https://example.com/placeholder.jpg')
15
39
  slug = models.SlugField(unique=True, blank=True, null=True)
16
40
  polyp_count = models.IntegerField()
17
- comments = models.TextField(blank=True, null=True) # Comments can be longer
18
- gender = models.CharField(max_length=255) # Overall gender (if applicable)
41
+ comments = models.TextField(blank=True, null=True)
42
+ gender = models.CharField(max_length=255)
19
43
  name_image_url = models.CharField(max_length=255)
20
44
  date_created = models.DateTimeField(auto_now_add=True)
21
- processed = models.BooleanField(default=False) # Track if this annotation has been processed
45
+ processed = models.BooleanField(default=False)
22
46
 
23
47
  def save(self, *args, **kwargs):
48
+ """
49
+ Overrides the save method to generate a slug if not already set.
50
+ """
24
51
  if not self.slug:
25
52
  self.slug = slugify(f"{self.label}-{self.image_name}")
26
53
  super().save(*args, **kwargs)
27
54
 
28
- def __str__(self):
55
+ def __str__(self) -> str:
29
56
  return f"{self.image_name} - {self.label}"
30
57
 
58
+
31
59
  class DroppedName(models.Model):
60
+ """
61
+ Represents a dropped name in the annotation process.
62
+
63
+ Attributes:
64
+ annotation (ForeignKey): Associated annotation.
65
+ name (str): Dropped name.
66
+ gender (str): Gender associated with the name.
67
+ x (float): X-coordinate of the name.
68
+ y (float): Y-coordinate of the name.
69
+ name_image_url (str): URL of the name image.
70
+ box_coordinates (str): Optional coordinates of the bounding box.
71
+ """
32
72
  annotation = models.ForeignKey(AnonymousImageAnnotation, related_name='dropped_names', on_delete=models.CASCADE)
33
73
  name = models.CharField(max_length=255)
34
- gender = models.CharField(max_length=255) # Gender of the individual name
74
+ gender = models.CharField(max_length=255)
35
75
  x = models.FloatField()
36
76
  y = models.FloatField()
37
77
  name_image_url = models.CharField(max_length=255)
38
- box_coordinates = models.CharField(max_length=255, blank=True, null=True) # Store box coordinates if needed
78
+ box_coordinates = models.CharField(max_length=255, blank=True, null=True)
39
79
 
40
- def __str__(self):
80
+ def __str__(self) -> str:
41
81
  return f"{self.name} ({self.gender}) at ({self.x}, {self.y})"
42
82
 
43
- from django.db import models
44
- from django.utils import timezone
45
83
 
46
84
  class UploadedFile(models.Model):
85
+ """
86
+ Represents a file uploaded to the system.
87
+
88
+ Attributes:
89
+ original_file (FileField): The uploaded original file.
90
+ upload_date (datetime): Date when the file was uploaded.
91
+ description (str): Optional description of the file.
92
+ """
47
93
  original_file = models.FileField(upload_to='uploads/original/')
48
94
  upload_date = models.DateTimeField(default=timezone.now)
49
95
  description = models.TextField(blank=True, null=True)
50
-
51
- def __str__(self):
96
+
97
+ def __str__(self) -> str:
52
98
  return self.original_file.name
53
-
99
+
100
+
54
101
  class AnonymizedFile(models.Model):
102
+ """
103
+ Represents an anonymized version of an uploaded file.
104
+
105
+ Attributes:
106
+ original_file (OneToOneField): The original uploaded file.
107
+ anonymized_file (FileField): The anonymized file.
108
+ anonymization_date (datetime): Date when the file was anonymized.
109
+ """
55
110
  original_file = models.OneToOneField(UploadedFile, on_delete=models.CASCADE, related_name='anonymized_file')
56
111
  anonymized_file = models.FileField(upload_to='uploads/anonymized/')
57
112
  anonymization_date = models.DateTimeField(default=timezone.now)
58
-
59
- def __str__(self):
113
+
114
+ def __str__(self) -> str:
60
115
  return f"Anonymized version of {self.original_file.original_file.name}"
@@ -1,54 +1,76 @@
1
1
  from django.db import models
2
- from rest_framework import serializers
3
- from ..label import Label
4
- from ..data_file.video_segment import LegacyLabelVideoSegment
5
- from .image_classification import ImageClassificationAnnotation
2
+ # from rest_framework import serializers
3
+ # from ..label import Label
4
+ # from .image_classification import ImageClassificationAnnotation
6
5
 
7
6
  ANNOTATION_PER_S_THRESHOLD = 2
8
7
 
9
- def clear_finished_legacy_tasks():
10
- # fetch all BinaryClassificationAnnotationTasks that are finished and delete them
11
- tasks = LegacyBinaryClassificationAnnotationTask.objects.filter(finished=True)
8
+ # TODO Migrate
9
+ # def clear_finished_legacy_tasks():
10
+ # """
11
+ # Deletes all finished LegacyBinaryClassificationAnnotationTask entries.
12
+ # """
13
+ # tasks = LegacyBinaryClassificationAnnotationTask.objects.filter(is_finished=True)
14
+ # tasks.delete()
15
+
16
+ # def get_legacy_binary_classification_annotation_tasks_by_label(label:Label, n:int=100, legacy=False):
17
+ # clear_finished_legacy_tasks()
18
+ # """
19
+ # Retrieves legacy binary classification annotation tasks for a specific label.
20
+
21
+ # Args:
22
+ # label (Label): The label to filter tasks by.
23
+ # n (int): Maximum number of tasks to retrieve. Defaults to 100.
24
+ # legacy (bool): If True, includes legacy tasks. Defaults to False.
25
+ # """
26
+ # if legacy:
27
+ # # fetch all LegacyLabelVideoSegments with the given label
28
+ # _segments = LegacyLabelVideoSegment.objects.filter(label=label)
29
+ # frames_for_tasks = []
30
+
31
+ # for segment in _segments:
32
+ # # check if the segment has already been annotated
33
+ # annotations = list(ImageClassificationAnnotation.objects.filter(legacy_frame__in=segment.get_frames(), label=label))
34
+ # segment_len_in_s = segment.get_segment_len_in_s()
35
+
36
+ # target_annotation_number = segment_len_in_s * ANNOTATION_PER_S_THRESHOLD
37
+
38
+ # if len(annotations) < target_annotation_number:
39
+ # get_frame_number = int(target_annotation_number - len(annotations))
40
+ # frames = segment.get_frames_without_annotation(get_frame_number)
41
+ # frames_for_tasks.extend(frames)
42
+
43
+ # if len(frames_for_tasks) >= n:
44
+ # break
45
+
46
+ # # create tasks
47
+ # tasks = []
48
+ # for frame in frames_for_tasks:
49
+
50
+ # # get_or_create task
51
+ # task, created = LegacyBinaryClassificationAnnotationTask.objects.get_or_create(
52
+ # label=label,
53
+ # image_path=frame.image.path,
54
+ # frame_id=frame.pk,
55
+ # )
12
56
 
13
- # delete tasks with a bulk operation
14
- tasks.delete()
15
-
16
- def get_legacy_binary_classification_annotation_tasks_by_label(label:Label, n:int=100, legacy=False):
17
- clear_finished_legacy_tasks()
18
-
19
- if legacy:
20
- # fetch all LegacyLabelVideoSegments with the given label
21
- _segments = LegacyLabelVideoSegment.objects.filter(label=label)
22
- frames_for_tasks = []
23
-
24
- for segment in _segments:
25
- # check if the segment has already been annotated
26
- annotations = list(ImageClassificationAnnotation.objects.filter(legacy_frame__in=segment.get_frames(), label=label))
27
- segment_len_in_s = segment.get_segment_len_in_s()
28
-
29
- target_annotation_number = segment_len_in_s * ANNOTATION_PER_S_THRESHOLD
30
-
31
- if len(annotations) < target_annotation_number:
32
- get_frame_number = int(target_annotation_number - len(annotations))
33
- frames = segment.get_frames_without_annotation(get_frame_number)
34
- frames_for_tasks.extend(frames)
35
-
36
- if len(frames_for_tasks) >= n:
37
- break
38
-
39
- # create tasks
40
- tasks = []
41
- for frame in frames_for_tasks:
42
-
43
- # get_or_create task
44
- task, created = LegacyBinaryClassificationAnnotationTask.objects.get_or_create(
45
- label=label,
46
- image_path=frame.image.path,
47
- frame_id=frame.pk,
48
- )
49
-
50
57
 
51
58
  class AbstractBinaryClassificationAnnotationTask(models.Model):
59
+ """
60
+ Abstract base class for binary classification annotation tasks.
61
+
62
+ Attributes:
63
+ label (ForeignKey): The associated label.
64
+ is_finished (bool): Indicates if the task is completed.
65
+ date_created (datetime): The creation date of the task.
66
+ date_finished (datetime): The completion date of the task.
67
+ image_path (str): Path to the associated image.
68
+ image_type (str): The type of the image (e.g., "frame" or "legacy").
69
+ frame_id (int): Identifier of the associated frame.
70
+ labelstudio_project_id (int): The Label Studio project ID.
71
+ labelstudio_task_id (int): The Label Studio task ID.
72
+ """
73
+
52
74
  label = models.ForeignKey("Label", on_delete=models.CASCADE)
53
75
  is_finished = models.BooleanField(default=False)
54
76
  date_created = models.DateTimeField(auto_now_add=True)
@@ -62,19 +84,34 @@ class AbstractBinaryClassificationAnnotationTask(models.Model):
62
84
  class Meta:
63
85
  abstract = True
64
86
 
65
- class BinaryClassificationAnnotationTask(AbstractBinaryClassificationAnnotationTask):
66
- frame = models.ForeignKey("Frame", on_delete=models.CASCADE, related_name="binary_classification_annotation_tasks")
67
- image_type = models.CharField(max_length=255, default="frame")
68
-
69
- def get_frame(self):
70
- return self.video_segment.get_frame_by_id(self.frame_id)
71
87
 
72
- class LegacyBinaryClassificationAnnotationTask(AbstractBinaryClassificationAnnotationTask):
73
- frame = models.ForeignKey("LegacyFrame", on_delete=models.CASCADE, related_name="binary_classification_annotation_tasks")
74
- image_type = models.CharField(max_length=255, default="legacy")
88
+ class BinaryClassificationAnnotationTask(AbstractBinaryClassificationAnnotationTask):
89
+ """
90
+ Represents a binary classification task for a frame.
91
+
92
+ Attributes:
93
+ frame (ForeignKey): The associated frame for this task.
94
+ """
95
+
96
+ frame = models.ForeignKey(
97
+ "Frame",
98
+ on_delete=models.CASCADE,
99
+ related_name="binary_classification_annotation_tasks",
100
+ )
101
+ image_type = models.CharField(
102
+ max_length=255, default="frame"
103
+ ) # Default image type for non-legacy tasks
75
104
 
76
105
  def get_frame(self):
77
- return self.frame
78
-
79
-
80
-
106
+ """
107
+ Retrieves the frame associated with this task.
108
+ """
109
+ from endoreg_db.models.data_file.frame import Frame
110
+
111
+ frame = self.frame
112
+ if not frame:
113
+ return None
114
+ else:
115
+ assert isinstance(frame, Frame)
116
+
117
+ return frame