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,25 +1,196 @@
1
- from ..base_classes import AbstractVideo
2
1
  from django.db import models
3
-
4
2
  from endoreg_db.models.data_file.frame import Frame
5
- from endoreg_db.models.data_file.frame import LegacyFrame
3
+ from django.core.validators import FileExtensionValidator
4
+ from django.core.files.storage import FileSystemStorage
5
+ from typing import TYPE_CHECKING
6
+ import cv2
7
+ from ..base_classes import AbstractVideoFile
8
+ from ..base_classes.utils import (
9
+ VIDEO_DIR_NAME,
10
+ STORAGE_LOCATION,
11
+ FRAME_PROCESSING_BATCH_SIZE,
12
+ )
13
+
14
+ if TYPE_CHECKING:
15
+ from django.db.models import QuerySet
16
+ from endoreg_db.models import (
17
+ LabelVideoSegment,
18
+ VideoImportMeta,
19
+ SensitiveMeta,
20
+ Patient,
21
+ PatientExamination,
22
+ RawVideoFile,
23
+ )
24
+
25
+
26
+ class Video(AbstractVideoFile):
27
+ file = models.FileField(
28
+ upload_to=VIDEO_DIR_NAME,
29
+ validators=[FileExtensionValidator(allowed_extensions=["mp4"])], # FIXME
30
+ storage=FileSystemStorage(location=STORAGE_LOCATION.resolve().as_posix()),
31
+ )
32
+
33
+ patient = models.ForeignKey(
34
+ "Patient",
35
+ on_delete=models.CASCADE,
36
+ blank=True,
37
+ null=True,
38
+ related_name="videos",
39
+ )
40
+
41
+ examination = models.ForeignKey(
42
+ "PatientExamination",
43
+ on_delete=models.SET_NULL,
44
+ blank=True,
45
+ null=True,
46
+ related_name="videos",
47
+ )
48
+
49
+ # Deprecate and move to video meta?
50
+ date = models.DateField(blank=True, null=True)
51
+ suffix = models.CharField(max_length=255, blank=True, null=True)
52
+ fps = models.FloatField(blank=True, null=True)
53
+ duration = models.FloatField(blank=True, null=True)
54
+ width = models.IntegerField(blank=True, null=True)
55
+ height = models.IntegerField(blank=True, null=True)
56
+ endoscope_image_x = models.IntegerField(blank=True, null=True)
57
+ endoscope_image_y = models.IntegerField(blank=True, null=True)
58
+ endoscope_image_width = models.IntegerField(blank=True, null=True)
59
+ endoscope_image_height = models.IntegerField(blank=True, null=True)
60
+
61
+ state_frames_extracted = models.BooleanField(default=False)
62
+
63
+ meta = models.JSONField(blank=True, null=True)
64
+
65
+ sensitive_meta = models.ForeignKey(
66
+ "SensitiveMeta",
67
+ on_delete=models.CASCADE,
68
+ related_name="videos",
69
+ null=True,
70
+ blank=True,
71
+ )
72
+
73
+ import_meta = models.OneToOneField(
74
+ "VideoImportMeta", on_delete=models.CASCADE, blank=True, null=True
75
+ )
76
+
77
+ if TYPE_CHECKING:
78
+ import_meta: "VideoImportMeta"
79
+ patient: "Patient"
80
+ examination: "PatientExamination"
81
+ frames: "QuerySet[Frame]"
82
+ label_video_segments: (
83
+ "QuerySet[LabelVideoSegment]" # Foreign key in LabelVideoSegment.video
84
+ )
85
+ sensitive_meta: "SensitiveMeta"
86
+ raw_videos: "QuerySet[RawVideoFile]"
87
+
88
+ # override the save method to set fps if not set
89
+ def save(self, *args, **kwargs):
90
+ if not self.fps:
91
+ self.fps = self.get_fps()
92
+ super().save(*args, **kwargs)
93
+
94
+ def label_segments_to_frame_annotations(self):
95
+ """
96
+ Generate annotations for all label video segments.
97
+ """
98
+ for lvs in self.label_video_segments.all():
99
+ lvs.generate_annotations()
100
+
101
+ def sync_from_raw_video(self):
102
+ """
103
+ Sync metadata from the associated raw video file.
104
+ """
105
+ from endoreg_db.models import RawVideoFile, LabelVideoSegment
106
+
107
+ raw_video: RawVideoFile = self.raw_videos.first()
108
+
109
+ assert isinstance(raw_video, RawVideoFile), "Raw video is not a file"
110
+
111
+ self.predictions = raw_video.predictions
112
+ self.readable_predictions = raw_video.readable_predictions
113
+ self.sequences = raw_video.sequences
114
+
115
+ label_video_segments = raw_video.label_video_segments.all()
116
+
117
+ label_video_segments = [
118
+ LabelVideoSegment.from_raw(
119
+ video=self, raw_label_video_segment=raw_label_video_segment
120
+ )
121
+ # LabelVideoSegment.from_raw(self, raw_label_video_segment)
122
+ for raw_label_video_segment in label_video_segments
123
+ ]
124
+
125
+ for lvs in label_video_segments:
126
+ lvs.save()
127
+
128
+ self.state_histology_required = raw_video.state_histology_required
129
+ self.state_histology_available = raw_video.state_histology_available
130
+ self.state_follow_up_intervention_required = (
131
+ raw_video.state_follow_up_intervention_required
132
+ )
133
+ self.state_follow_up_intervention_available = (
134
+ raw_video.state_follow_up_intervention_available
135
+ )
136
+ self.state_dataset_complete = raw_video.state_dataset_complete
137
+
138
+ self.save()
139
+
140
+ ## Deprecated
141
+ # def get_roi_endoscope_image(self):
142
+ # return {
143
+ # "x": self.endoscope_image_content_x,
144
+ # "y": self.endoscope_image_content_y,
145
+ # "width": self.endoscope_image_content_width,
146
+ # "height": self.endoscope_image_content_height,
147
+ # }
148
+
149
+ # def initialize_metadata_in_db(self, video_meta=None):
150
+ # if not video_meta:
151
+ # video_meta = self.meta
152
+ # self.set_examination_date_from_video_meta(video_meta)
153
+ # self.patient, created = self.get_or_create_patient(video_meta)
154
+ # self.save()
155
+
156
+ # def get_or_create_patient(self, video_meta=None):
157
+ # from ...persons import Patient
6
158
 
7
- BATCH_SIZE = 1000
159
+ # if not video_meta:
160
+ # video_meta = self.meta
8
161
 
9
- class Video(AbstractVideo):
10
- import_meta = models.OneToOneField('VideoImportMeta', on_delete=models.CASCADE, blank=True, null=True)
11
- def get_video_model(self):
12
- return Video
162
+ # patient_first_name = video_meta["patient_first_name"]
163
+ # patient_last_name = video_meta["patient_last_name"]
164
+ # patient_dob = video_meta["patient_dob"]
13
165
 
14
- def get_frame_model(self):
15
- return Frame
166
+ # # assert that we got all the necessary information
167
+ # assert patient_first_name and patient_last_name and patient_dob, (
168
+ # "Missing patient information"
169
+ # )
16
170
 
171
+ # patient, created = Patient.objects.get_or_create(
172
+ # first_name=patient_first_name, last_name=patient_last_name, dob=patient_dob
173
+ # )
17
174
 
18
- class LegacyVideo(AbstractVideo):
19
- file = models.FileField(upload_to="legacy_videos", blank=True, null=True)
175
+ # return patient, created
20
176
 
21
- def get_video_model(self):
22
- return LegacyVideo
177
+ # DEPRECATED
178
+ # def set_examination_date_from_video_meta(self, video_meta=None):
179
+ # if not video_meta:
180
+ # video_meta = self.meta
181
+ # date_str = video_meta["examination_date"] # e.g. 2020-01-01
182
+ # if date_str:
183
+ # self.date = date.fromisoformat(date_str)
184
+ # self.save()
23
185
 
24
- def get_frame_model(self):
25
- return LegacyFrame
186
+ def initialize_video_specs(self):
187
+ """
188
+ Initialize and save video metadata like framerate, dimensions, and duration.
189
+ """
190
+ video = cv2.VideoCapture(self.file.path)
191
+ self.fps = video.get(cv2.CAP_PROP_FPS)
192
+ self.width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
193
+ self.height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
194
+ self.duration = video.get(cv2.CAP_PROP_FRAME_COUNT) / self.fps
195
+ self.save()
196
+ video.release()
@@ -1,6 +1,19 @@
1
1
  from django.db import models
2
2
  import numpy as np
3
3
  from ..annotation import ImageClassificationAnnotation
4
+ from typing import TYPE_CHECKING, Union
5
+ from tqdm import tqdm
6
+
7
+ if TYPE_CHECKING:
8
+ from endoreg_db.models import (
9
+ RawVideoFile,
10
+ Video,
11
+ Label,
12
+ InformationSource,
13
+ VideoPredictionMeta,
14
+ RawVideoPredictionMeta,
15
+ )
16
+
4
17
 
5
18
  def find_segments_in_prediction_array(prediction_array: np.array, min_frame_len: int):
6
19
  """
@@ -8,100 +21,194 @@ def find_segments_in_prediction_array(prediction_array: np.array, min_frame_len:
8
21
  Returns a list of tuples (start_frame_number, end_frame_number) that represent the segments.
9
22
  """
10
23
  # Add False to the beginning and end to detect changes at the array boundaries
11
- padded_prediction = np.pad(prediction_array, (1, 1), 'constant', constant_values=False)
12
-
24
+ padded_prediction = np.pad(
25
+ prediction_array, (1, 1), "constant", constant_values=False
26
+ )
27
+
13
28
  # Find the start points and end points of the segments
14
29
  diffs = np.diff(padded_prediction.astype(int))
15
30
  segment_starts = np.where(diffs == 1)[0]
16
31
  segment_ends = np.where(diffs == -1)[0]
17
-
32
+
18
33
  # Filter segments based on min_frame_len
19
- segments = [(start, end) for start, end in zip(segment_starts, segment_ends) if end - start >= min_frame_len]
20
-
34
+ segments = [
35
+ (start, end)
36
+ for start, end in zip(segment_starts, segment_ends)
37
+ if end - start >= min_frame_len
38
+ ]
39
+
21
40
  return segments
22
41
 
42
+
23
43
  class AbstractLabelVideoSegment(models.Model):
24
- video = None # Placeholder for the video field, to be defined in derived classes
25
- prediction_meta = None # Placeholder for the prediction_meta field, to be defined in derived classes
26
44
  start_frame_number = models.IntegerField()
27
45
  end_frame_number = models.IntegerField()
28
- source = models.ForeignKey("InformationSource", on_delete=models.CASCADE)
29
- label = models.ForeignKey("Label", on_delete=models.CASCADE)
46
+ source = models.ForeignKey(
47
+ "InformationSource", on_delete=models.SET_NULL, null=True
48
+ )
49
+ label = models.ForeignKey("Label", on_delete=models.SET_NULL, null=True, blank=True)
50
+
51
+ if TYPE_CHECKING:
52
+ label: "Label"
53
+ source: "InformationSource"
54
+ prediction_meta: Union["RawVideoPredictionMeta", "VideoPredictionMeta"]
55
+ video: Union[Video, RawVideoFile]
30
56
 
31
57
  class Meta:
32
58
  abstract = True
33
59
 
34
60
  def __str__(self):
35
- return self.video.file.path + " Label - " + self.label.name + " - " + str(self.start_frame_number) + " - " + str(self.end_frame_number)
36
-
61
+ video = self.video
62
+ label = self.label
63
+
64
+ str_repr = (
65
+ video.file.path
66
+ + " Label - "
67
+ + label.name
68
+ + " - "
69
+ + str(self.start_frame_number)
70
+ + " - "
71
+ + str(self.end_frame_number)
72
+ )
73
+ assert isinstance(str_repr, str), "String representation is not a string"
74
+ return str_repr
75
+
76
+ def get_model_meta(self):
77
+ return self.prediction_meta.model_meta
78
+
37
79
  def get_frames(self):
38
- return self.video.get_frame_range(self.start_frame_number, self.end_frame_number)
39
-
40
- def get_annotations(self) -> ImageClassificationAnnotation.objects:
80
+ video = self.video
81
+ return video.get_frame_range(self.start_frame_number, self.end_frame_number)
82
+
83
+ def get_annotations(self):
41
84
  frames = self.get_frames()
42
- annotations = ImageClassificationAnnotation.objects.filter(frame__in=frames, label=self.label)
85
+ annotations = ImageClassificationAnnotation.objects.filter(
86
+ frame__in=frames, label=self.label
87
+ )
43
88
 
44
89
  return annotations
45
-
46
- def get_frames_without_annotation(self):
47
- """
48
- Get a frame without an annotation.
49
- """
50
- assert 1 == 2, "This method should be overridden in derived classes"
51
90
 
52
91
  def get_segment_len_in_s(self):
53
- return (self.end_frame_number - self.start_frame_number) / self.video.fps
92
+ return (self.end_frame_number - self.start_frame_number) / self.video.get_fps()
54
93
 
55
- class LegacyLabelVideoSegment(AbstractLabelVideoSegment):
56
- video = models.ForeignKey("LegacyVideo", on_delete=models.CASCADE)
57
- prediction_meta = models.ForeignKey("LegacyVideoPredictionMeta", on_delete=models.CASCADE, related_name="video_segments")
58
-
59
- def get_video_model(self):
60
- from endoreg_db.models.data_file.video import LegacyVideo
61
- return LegacyVideo
62
-
63
- def get_annotations(self) -> ImageClassificationAnnotation.objects:
64
- frames = self.get_frames()
65
- annotations = ImageClassificationAnnotation.objects.filter(legacy_frame__in=frames, label=self.label)
66
-
67
- return annotations
68
-
69
- def get_frames_without_annotation(self, n_frames):
94
+ def get_frames_without_annotation(self, n_frames: int):
70
95
  """
71
96
  Get a frame without an annotation.
72
97
  """
73
98
  frames = self.get_frames()
74
- annotations = ImageClassificationAnnotation.objects.filter(legacy_frame__in=frames, label=self.label)
99
+ annotations = ImageClassificationAnnotation.objects.filter(
100
+ frame__in=frames, label=self.label
101
+ )
75
102
 
76
- annotated_frames = [annotation.legacy_frame for annotation in annotations]
77
- frames_without_annotation = [frame for frame in frames if frame not in annotated_frames]
103
+ annotated_frames = [annotation.frame for annotation in annotations]
104
+ frames_without_annotation = [
105
+ frame for frame in frames if frame not in annotated_frames
106
+ ]
78
107
 
79
108
  # draw n random frames
80
109
  if len(frames_without_annotation) > n_frames:
81
- frames_without_annotation = np.random.choice(frames_without_annotation, n_frames, replace=False)
110
+ frames_without_annotation = np.random.choice(
111
+ frames_without_annotation, n_frames, replace=False
112
+ )
82
113
 
83
114
  return frames_without_annotation
84
-
115
+
116
+
85
117
  class LabelVideoSegment(AbstractLabelVideoSegment):
86
- video = models.ForeignKey("Video", on_delete=models.CASCADE)
87
- prediction_meta = models.ForeignKey("VideoPredictionMeta", on_delete=models.CASCADE, related_name="video_segments")
118
+ video = models.ForeignKey(
119
+ "Video", on_delete=models.CASCADE, related_name="label_video_segments"
120
+ )
121
+ prediction_meta = models.ForeignKey(
122
+ "VideoPredictionMeta",
123
+ on_delete=models.SET_NULL,
124
+ null=True,
125
+ blank=True,
126
+ related_name="label_video_segments",
127
+ )
128
+
129
+ if TYPE_CHECKING:
130
+ video: "Video"
131
+ label: "Label"
132
+ source: "InformationSource"
133
+ prediction_meta: "VideoPredictionMeta"
134
+
135
+ @classmethod
136
+ def from_raw(cls, video: "Video", raw_label_video_segment: "LabelRawVideoSegment"):
137
+ from endoreg_db.models import VideoPredictionMeta
138
+
139
+ raw_video_prediction_meta = raw_label_video_segment.prediction_meta
140
+
141
+ prediction_meta = VideoPredictionMeta.from_raw(
142
+ video=video, raw_video_prediction_meta=raw_video_prediction_meta
143
+ )
144
+ existing_segment = cls.objects.filter(
145
+ video=video,
146
+ start_frame_number=raw_label_video_segment.start_frame_number,
147
+ end_frame_number=raw_label_video_segment.end_frame_number,
148
+ source=raw_label_video_segment.source,
149
+ label=raw_label_video_segment.label,
150
+ prediction_meta=prediction_meta,
151
+ ).first()
152
+ if existing_segment:
153
+ return existing_segment
154
+ segment = cls(
155
+ start_frame_number=raw_label_video_segment.start_frame_number,
156
+ end_frame_number=raw_label_video_segment.end_frame_number,
157
+ source=raw_label_video_segment.source,
158
+ label=raw_label_video_segment.label,
159
+ video=video,
160
+ prediction_meta=prediction_meta,
161
+ )
162
+
163
+ segment.save()
164
+ return segment
88
165
 
89
166
  def get_video_model(self):
90
- from endoreg_db.models.data_file.video import Video
167
+ from endoreg_db.models import Video
168
+
91
169
  return Video
92
170
 
93
- def get_frames_without_annotation(self, n_frames):
171
+ def generate_annotations(self):
94
172
  """
95
- Get a frame without an annotation.
173
+ Generate annotations for the segment.
96
174
  """
97
- frames = self.get_frames()
98
- annotations = ImageClassificationAnnotation.objects.filter(frame__in=frames, label=self.label)
175
+ from endoreg_db.models import InformationSource
99
176
 
100
- annotated_frames = [annotation.frame for annotation in annotations]
101
- frames_without_annotation = [frame for frame in frames if frame not in annotated_frames]
177
+ frames = self.get_frames()
178
+ model_meta = self.get_model_meta()
179
+ information_source, _created = InformationSource.objects.get_or_create(
180
+ name="prediction"
181
+ )
182
+
183
+ for frame in tqdm(frames):
184
+ ImageClassificationAnnotation.objects.get_or_create(
185
+ frame=frame,
186
+ label=self.label,
187
+ model_meta=model_meta,
188
+ value=1,
189
+ information_source=information_source,
190
+ )
191
+
192
+
193
+ class LabelRawVideoSegment(AbstractLabelVideoSegment):
194
+ video = models.ForeignKey(
195
+ "RawVideoFile", on_delete=models.CASCADE, related_name="label_video_segments"
196
+ )
197
+ prediction_meta = models.ForeignKey(
198
+ "RawVideoPredictionMeta",
199
+ on_delete=models.SET_NULL,
200
+ null=True,
201
+ blank=True,
202
+ related_name="label_video_segments",
203
+ )
204
+
205
+ if TYPE_CHECKING:
206
+ video: "RawVideoFile"
207
+ label: "Label"
208
+ source: "InformationSource"
209
+ prediction_meta: "RawVideoPredictionMeta"
102
210
 
103
- # draw n random frames
104
- if len(frames_without_annotation) > n_frames:
105
- frames_without_annotation = np.random.choice(frames_without_annotation, n_frames, replace=False)
211
+ def get_video_model(self):
212
+ from endoreg_db.models import RawVideoFile
106
213
 
107
- return frames_without_annotation
214
+ return RawVideoFile
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+ from typing import List
2
3
 
3
4
  class DiseaseManager(models.Manager):
4
5
  def get_by_natural_key(self, name):
@@ -8,6 +9,8 @@ class Disease(models.Model):
8
9
  name = models.CharField(max_length=255, unique=True)
9
10
  name_de = models.CharField(max_length=255, blank=True, null=True)
10
11
  name_en = models.CharField(max_length=255, blank=True, null=True)
12
+ subcategories = models.JSONField(default=dict)
13
+ numerical_descriptors = models.JSONField(default=dict)
11
14
 
12
15
  objects = DiseaseManager()
13
16
 
@@ -17,6 +20,16 @@ class Disease(models.Model):
17
20
  def __str__(self):
18
21
  return self.name
19
22
 
23
+
24
+
25
+
26
+
27
+
28
+
29
+ def get_classifications(self)->List['DiseaseClassification']:
30
+ classifications: List[DiseaseClassification] = [_ for _ in self.disease_classifications.all()]
31
+ return classifications
32
+
20
33
  class DiseaseClassificationManager(models.Manager):
21
34
  def get_by_natural_key(self, name):
22
35
  return self.get(name=name)
@@ -26,7 +39,10 @@ class DiseaseClassification(models.Model):
26
39
  name_de = models.CharField(max_length=255, blank=True, null=True)
27
40
  name_en = models.CharField(max_length=255, blank=True, null=True)
28
41
 
29
- disease = models.ForeignKey(Disease, on_delete=models.CASCADE)
42
+ disease = models.ForeignKey(
43
+ Disease, on_delete=models.CASCADE,
44
+ related_name='disease_classifications'
45
+ )
30
46
 
31
47
  objects = DiseaseClassificationManager()
32
48
 
@@ -36,6 +52,10 @@ class DiseaseClassification(models.Model):
36
52
  def __str__(self):
37
53
  return self.name
38
54
 
55
+ def get_choices(self)->List['DiseaseClassificationChoice']:
56
+ choices:List[DiseaseClassificationChoice] = [_ for _ in self.disease_classification_choices.all()]
57
+ return choices
58
+
39
59
  class DiseaseClassificationChoiceManager(models.Manager):
40
60
  def get_by_natural_key(self, name):
41
61
  return self.get(name=name)
@@ -45,7 +65,10 @@ class DiseaseClassificationChoice(models.Model):
45
65
  name_de = models.CharField(max_length=255, blank=True, null=True)
46
66
  name_en = models.CharField(max_length=255, blank=True, null=True)
47
67
 
48
- disease_classification = models.ForeignKey(DiseaseClassification, on_delete=models.CASCADE)
68
+ disease_classification = models.ForeignKey(
69
+ DiseaseClassification, on_delete=models.CASCADE,
70
+ related_name='disease_classification_choices'
71
+ )
49
72
 
50
73
  objects = DiseaseClassificationChoiceManager()
51
74
 
@@ -1 +1,5 @@
1
- from .emission_factor import EmissionFactor
1
+ from .emission_factor import EmissionFactor
2
+
3
+ __all__ = [
4
+ 'EmissionFactor',
5
+ ]
@@ -1,10 +1,27 @@
1
1
  from django.db import models
2
-
2
+ from typing import List
3
3
  class EmissionFactorManager(models.Manager):
4
- def get_by_natural_key(self, name):
4
+ """
5
+ Manager for EmissionFactor with custom query methods.
6
+ """
7
+ def get_by_natural_key(self, name: str) -> "EmissionFactor":
5
8
  return self.get(name=name)
6
-
9
+
10
+ # get debug from settings
11
+ # from django.conf import settings
12
+ # DEBUG = settings.DEBUG
13
+
7
14
  class EmissionFactor(models.Model):
15
+ """
16
+ Represents an emission factor with associated unit and value.
17
+
18
+ Attributes:
19
+ name (str): The name of the emission factor.
20
+ name_de (str): The German name of the emission factor.
21
+ name_en (str): The English name of the emission factor.
22
+ unit (ForeignKey): The unit associated with the emission factor.
23
+ value (float): The value of the emission factor.
24
+ """
8
25
  objects = EmissionFactorManager()
9
26
 
10
27
  name = models.CharField(max_length=255)
@@ -13,8 +30,56 @@ class EmissionFactor(models.Model):
13
30
  unit = models.ForeignKey("Unit", on_delete=models.SET_NULL, null=True)
14
31
  value = models.FloatField()
15
32
 
16
- def natural_key(self):
33
+ def natural_key(self) -> tuple:
34
+ """
35
+ Returns the natural key for the emission factor.
36
+
37
+ Returns:
38
+ tuple: The natural key consisting of the name.
39
+ """
17
40
  return (self.name,)
41
+
42
+ def __str__(self, verbose: bool = False) -> str:
43
+ """
44
+ String representation of the emission factor.
45
+
46
+ Args:
47
+ verbose (bool): If True, includes additional details.
48
+
49
+ Returns:
50
+ str: The string representation of the emission factor.
51
+ """
52
+ result = f"{self.name}: {self.value} per {self.unit}"
53
+ if verbose:
54
+ result += "\n\tSources:"
55
+ for source in self.sources():
56
+ result += f"\n\t\t{source}"
57
+
58
+ return result
18
59
 
19
- def __str__(self):
20
- return self.name
60
+ def get_reference_products(self) -> List["ReferenceProduct"]:
61
+ """
62
+ Retrieves all reference products associated with the emission factor.
63
+
64
+ Returns:
65
+ list: A list of ReferenceProduct instances associated with this emission factor.
66
+ """
67
+ from endoreg_db.models import ReferenceProduct
68
+
69
+ reference_products = []
70
+ reference_products += ReferenceProduct.objects.filter(emission_factor_total=self)
71
+ reference_products += ReferenceProduct.objects.filter(emission_factor_package=self)
72
+ reference_products += ReferenceProduct.objects.filter(emission_factor_product=self)
73
+
74
+ return reference_products
75
+
76
+ def sources(self) -> List:
77
+ """
78
+ Retrieves all sources related to the emission factor.
79
+
80
+ Returns:
81
+ list: A list of sources related to the emission factor.
82
+ """
83
+ sources = []
84
+ sources.extend(self.get_reference_products())
85
+ return sources