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,46 +1,70 @@
1
- from django.db import models
1
+ import shutil
2
+ import subprocess
2
3
  from pathlib import Path
3
- from collections import defaultdict, Counter
4
+ from django.db import models
5
+ from typing import TYPE_CHECKING, List, Tuple
6
+
7
+ from icecream import ic
8
+ from tqdm import tqdm
9
+ import cv2
10
+ from django.core.validators import FileExtensionValidator
11
+ from django.core.files.storage import FileSystemStorage
12
+
13
+ from endoreg_db.utils.validate_endo_roi import validate_endo_roi
14
+ from ..base_classes.utils import (
15
+ anonymize_frame,
16
+ RAW_VIDEO_DIR_NAME,
17
+ VIDEO_DIR,
18
+ STORAGE_LOCATION,
19
+ )
20
+ from ..base_classes.abstract_video import AbstractVideoFile
21
+
22
+ if TYPE_CHECKING:
23
+ # import Queryset
24
+ from django.db.models import QuerySet
25
+ from endoreg_db.models import (
26
+ SensitiveMeta,
27
+ LabelVideoSegment,
28
+ )
4
29
 
5
- from endoreg_db.utils.hashs import get_video_hash
6
- from endoreg_db.utils.file_operations import get_uuid_filename
7
- from endoreg_db.utils.ocr import extract_text_from_rois
8
30
 
9
- import shutil
10
- import os
11
- import subprocess
31
+ # pylint: disable=attribute-defined-outside-init,no-member
32
+ class RawVideoFile(AbstractVideoFile):
33
+ """ """
12
34
 
13
- from ..metadata import VideoMeta, SensitiveMeta
35
+ file = models.FileField(
36
+ upload_to=RAW_VIDEO_DIR_NAME,
37
+ validators=[FileExtensionValidator(allowed_extensions=["mp4"])], # FIXME
38
+ storage=FileSystemStorage(location=STORAGE_LOCATION.resolve().as_posix()),
39
+ )
14
40
 
15
- class RawVideoFile(models.Model):
16
- uuid = models.UUIDField()
17
- file = models.FileField(upload_to="raw_data/")
18
-
19
- sensitive_meta = models.OneToOneField(
20
- "SensitiveMeta", on_delete=models.CASCADE, blank=True, null=True
21
- )
41
+ patient = models.ForeignKey(
42
+ "Patient", on_delete=models.SET_NULL, blank=True, null=True
43
+ )
22
44
 
23
- center = models.ForeignKey("Center", on_delete=models.CASCADE)
24
- processor = models.ForeignKey(
25
- "EndoscopyProcessor", on_delete=models.CASCADE, blank=True, null=True
45
+ sensitive_meta = models.ForeignKey(
46
+ "SensitiveMeta",
47
+ on_delete=models.SET_NULL,
48
+ related_name="raw_videos",
49
+ null=True,
50
+ blank=True,
26
51
  )
27
- video_meta = models.OneToOneField(
28
- "VideoMeta", on_delete=models.CASCADE, blank=True, null=True
52
+
53
+ video = models.ForeignKey(
54
+ "Video",
55
+ on_delete=models.SET_NULL,
56
+ related_name="raw_videos",
57
+ null=True,
58
+ blank=True,
29
59
  )
30
- original_file_name = models.CharField(max_length=255)
31
- video_hash = models.CharField(max_length=255, unique=True)
32
- uploaded_at = models.DateTimeField(auto_now_add=True)
33
-
34
- # Frame Extraction States
35
- state_frames_required = models.BooleanField(default=True)
36
- state_frames_extracted = models.BooleanField(default=False)
37
-
38
- # Video
39
- ## Prediction
40
- state_initial_prediction_required = models.BooleanField(default=True)
41
- state_initial_prediction_completed = models.BooleanField(default=False)
42
- state_initial_prediction_import_required = models.BooleanField(default=True)
43
- state_initial_prediction_import_completed = models.BooleanField(default=False)
60
+
61
+ if TYPE_CHECKING:
62
+ sensitive_meta: "SensitiveMeta"
63
+ label_video_segments: "QuerySet[LabelVideoSegment]"
64
+
65
+ # Crop Frames
66
+ state_anonymized_frames_generated = models.BooleanField(default=False)
67
+
44
68
  ## OCR
45
69
  state_ocr_required = models.BooleanField(default=True)
46
70
  state_ocr_completed = models.BooleanField(default=False)
@@ -50,294 +74,215 @@ class RawVideoFile(models.Model):
50
74
 
51
75
  state_sensitive_data_retrieved = models.BooleanField(default=False)
52
76
 
53
- # Dataset complete?
54
- state_histology_required = models.BooleanField(blank=True, null=True)
55
- state_histology_available = models.BooleanField(default=False)
56
- state_follow_up_intervention_required = models.BooleanField(blank=True, null=True)
57
- state_follow_up_intervention_available = models.BooleanField(default=False)
58
- state_dataset_complete = models.BooleanField(default=False)
59
-
60
- # Finalizing for Upload
61
- state_anonym_video_required = models.BooleanField(default=True)
62
- state_anonym_video_performed = models.BooleanField(default=False)
63
- state_original_reports_deleted = models.BooleanField(default=False)
64
- state_original_video_deleted = models.BooleanField(default=False)
65
- state_finalized = models.BooleanField(default=False)
66
-
67
- frame_dir = models.CharField(max_length=255)
68
- prediction_dir = models.CharField(max_length=255)
69
-
70
- @classmethod
71
- def create_from_file(
72
- cls,
73
- file_path: Path,
74
- video_dir: Path,
75
- center_name: str,
76
- processor_name: str,
77
- frame_dir_parent: Path,
78
- save: bool = True,
79
- ):
80
- from endoreg_db.models import Center, EndoscopyProcessor
81
-
82
- print(f"Creating RawVideoFile from {file_path}")
83
- original_file_name = file_path.name
84
- # Rename and and move
85
-
86
- new_file_name, uuid = get_uuid_filename(file_path)
87
- framedir: Path = frame_dir_parent / str(uuid)
88
-
89
- if not framedir.exists():
90
- framedir.mkdir(parents=True, exist_ok=True)
91
-
92
- if not video_dir.exists():
93
- video_dir.mkdir(parents=True, exist_ok=True)
94
-
95
- video_hash = get_video_hash(file_path)
96
-
97
- center = Center.objects.get(name=center_name)
98
- assert center is not None, "Center must exist"
99
-
100
- processor = EndoscopyProcessor.objects.get(name=processor_name)
101
- assert processor is not None, "Processor must exist"
102
-
103
- new_filepath = video_dir / new_file_name
104
-
105
- print(f"Moving {file_path} to {new_filepath}")
106
- shutil.move(file_path.resolve().as_posix(), new_filepath.resolve().as_posix())
107
- print(f"Moved to {new_filepath}")
108
-
109
- # Make sure file was transferred correctly and hash is correct
110
- if not new_filepath.exists():
111
- print(f"File {file_path} was not transferred correctly to {new_filepath}")
112
- return None
113
-
114
- new_hash = get_video_hash(new_filepath)
115
- if new_hash != video_hash:
116
- print(f"Hash of file {file_path} is not correct")
117
- return None
118
-
119
- # make sure that no other file with the same hash exists
120
- if cls.objects.filter(video_hash=video_hash).exists():
121
- # log and print warnint
122
- print(f"File with hash {video_hash} already exists")
123
- return None
124
-
125
- else:
126
- print(center)
127
- # Create a new instance of RawVideoFile
128
- raw_video_file = cls(
129
- uuid=uuid,
130
- file=new_filepath.resolve().as_posix(),
131
- center=center,
132
- processor=processor,
133
- original_file_name=original_file_name,
134
- video_hash=video_hash,
135
- frame_dir=framedir.as_posix(),
136
- )
137
-
138
- # Save the instance to the database
139
- raw_video_file.save()
77
+ # Censor Outside
78
+ state_censor_outside_required = models.BooleanField(default=True)
79
+ state_censor_outside_completed = models.BooleanField(default=False)
80
+ state_make_anonymized_video_required = models.BooleanField(default=True)
81
+ state_make_anonymized_video_completed = models.BooleanField(default=False)
82
+
83
+ def get_anonymized_video_path(self):
84
+ video_dir = VIDEO_DIR
85
+ video_suffix = Path(self.file.path).suffix
86
+ video_name = f"{self.uuid}{video_suffix}"
87
+ anonymized_video_name = f"TMP_anonymized_{video_name}"
88
+ anonymized_video_path = video_dir / anonymized_video_name
89
+
90
+ return anonymized_video_path
91
+
92
+ def censor_outside_frames(self):
93
+ assert self.state_frames_extracted, "Frames not extracted"
94
+ assert self.state_initial_prediction_completed, (
95
+ "Initial prediction not completed"
96
+ )
97
+ assert self.state_sensitive_data_retrieved, "Sensitive data not retrieved"
98
+
99
+ ic(
100
+ "WARNING: Outside validation is not yet implemented and automatically set to true in this function"
101
+ )
102
+
103
+ self.state_outside_validated = True
104
+ self.save()
140
105
 
141
- return raw_video_file
106
+ assert self.state_outside_validated, "Outside validation not completed"
142
107
 
143
- def __str__(self):
144
- return self.file.name
108
+ outside_frame_paths = self.get_outside_frame_paths()
145
109
 
146
- def get_endo_roi(self):
147
- endo_roi = self.video_meta.get_endo_roi()
148
- return endo_roi
110
+ if not outside_frame_paths:
111
+ ic("No outside frames found")
149
112
 
150
- # video meta should be created when video file is created
151
- def save(self, *args, **kwargs):
152
- if self.video_meta is None:
153
- center = self.center
154
- processor = self.processor
155
- self.video_meta = VideoMeta.objects.create(
156
- center=center, processor=processor
157
- )
158
- self.video_meta.initialize_ffmpeg_meta(self.file.path)
159
- super(RawVideoFile, self).save(*args, **kwargs)
160
-
161
- def extract_frames(
162
- self,
163
- quality: int = 2,
164
- frame_dir: Path = None,
165
- overwrite: bool = False,
166
- ext="jpg",
167
- ):
168
- """
169
- Extract frames from the video file and save them to the frame_dir.
170
- For this, ffmpeg must be available in in the current environment.
171
- """
172
- if frame_dir is None:
173
- frame_dir = Path(self.frame_dir)
174
113
  else:
175
- frame_dir = Path(frame_dir)
114
+ ic(f"Found {len(outside_frame_paths)} outside frames")
115
+ # use cv2 to replace all outside frames with completely black frames
176
116
 
177
- if not frame_dir.exists():
178
- frame_dir.mkdir(parents=True, exist_ok=True)
117
+ for frame_path in tqdm(outside_frame_paths):
118
+ frame = cv2.imread(frame_path.as_posix())
119
+ frame.fill(0)
120
+ cv2.imwrite(frame_path.as_posix(), frame)
179
121
 
180
- if not overwrite and len(list(frame_dir.glob("*.jpg"))) > 0:
181
- print(f"Frames already extracted for {self.file.name}")
182
- return
122
+ self.state_censor_outside_required = False
123
+ self.state_censor_outside_completed = True
124
+ self.save()
183
125
 
184
- video_path = Path(self.file.path).resolve().as_posix()
126
+ def get_anonymized_frame_dir(self):
127
+ anonymized_frame_dir = Path(self.frame_dir).parent / f"tmp_{self.uuid}"
128
+ return anonymized_frame_dir
185
129
 
186
- frame_path_string = frame_dir.resolve().as_posix()
187
- command = [
188
- "ffmpeg",
189
- "-i",
190
- video_path, #
191
- "-q:v",
192
- str(quality),
193
- os.path.join(frame_path_string, f"frame_%07d.{ext}"),
194
- ]
130
+ def make_temporary_anonymized_frames(self) -> Tuple[Path, List[Path]]:
131
+ anonymized_frame_dir = self.get_anonymized_frame_dir()
195
132
 
196
- # Ensure FFmpeg is available
197
- if not shutil.which("ffmpeg"):
198
- raise EnvironmentError(
199
- "FFmpeg could not be found. Ensure it is installed and in your PATH."
200
- )
133
+ assert self.state_frames_extracted, "Frames not extracted"
134
+ assert self.processor, "Processor not set"
201
135
 
202
- # Extract frames from the video file
203
- # Execute the command
204
- result = subprocess.run(command, capture_output=True, text=True)
205
- if result.returncode != 0:
206
- raise Exception(f"Error extracting frames: {result.stderr}")
136
+ anonymized_frame_dir.mkdir(parents=True, exist_ok=True)
137
+ endo_roi = self.get_endo_roi()
138
+ assert validate_endo_roi(endo_roi), "Endoscope ROI is not valid"
139
+ generated_frame_paths = []
207
140
 
208
- self.state_frames_extracted = True
141
+ all_frames = self.frames.all()
142
+ outside_frames = self.get_outside_frames() #
143
+ outside_frame_numbers = [frame.frame_number for frame in outside_frames]
209
144
 
210
- return f"Frames extracted to {frame_dir} ({frame_path_string}) with quality {quality}"
145
+ # anonymize frames: copy endo-roi content while making other pixels black. (frames are Path objects to jpgs or pngs)
146
+ for frame in tqdm(all_frames):
147
+ frame_path = Path(frame.image.path)
148
+ frame_name = frame_path.name
149
+ frame_number = frame.frame_number
211
150
 
212
- def delete_frames(self):
213
- """
214
- Delete frames extracted from the video file.
215
- """
216
- frame_dir = Path(self.frame_dir)
217
- if frame_dir.exists():
218
- shutil.rmtree(frame_dir)
219
- self.state_frames_extracted = False
220
- self.save()
221
- return f"Frames deleted from {frame_dir}"
222
- else:
223
- return f"No frames to delete for {self.file.name}"
151
+ if frame_number in outside_frame_numbers:
152
+ all_black = True
153
+ else:
154
+ all_black = False
224
155
 
225
- def get_frame_path(self, n: int = 0):
226
- """
227
- Get the path to the n-th frame extracted from the video file.
228
- Note that the frame numbering starts at 1 in our naming convention.
229
- """
230
- # Adjust index
231
- n = n + 1
156
+ target_frame_path = anonymized_frame_dir / frame_name
157
+ anonymize_frame(
158
+ frame_path, target_frame_path, endo_roi, all_black=all_black
159
+ )
160
+ generated_frame_paths.append(target_frame_path)
232
161
 
233
- frame_dir = Path(self.frame_dir)
234
- return frame_dir / f"frame_{n:07d}.jpg"
235
-
236
- def get_frame_paths(self):
237
- if not self.state_frames_extracted:
238
- return None
239
- frame_dir = Path(self.frame_dir)
240
- paths = [p for p in frame_dir.glob('*')]
241
- indices = [int(p.stem.split("_")[1]) for p in paths]
242
- path_index_tuples = list(zip(paths, indices))
243
- # sort ascending by index
244
- path_index_tuples.sort(key=lambda x: x[1])
245
- paths, indices = zip(*path_index_tuples)
246
-
247
- return paths
248
-
249
- def get_prediction_dir(self):
250
- return Path(self.prediction_dir)
251
-
252
- def get_predictions_path(self, suffix = ".json"):
253
- pred_dir = self.get_prediction_dir()
254
- return pred_dir.joinpath("predictions").with_suffix(suffix)
255
-
256
- def get_smooth_predictions_path(self, suffix = ".json"):
257
- pred_dir = self.get_prediction_dir()
258
- return pred_dir.joinpath("smooth_predictions").with_suffix(suffix)
259
-
260
- def get_binary_predictions_path(self, suffix = ".json"):
261
- pred_dir = self.get_prediction_dir()
262
- return pred_dir.joinpath("binary_predictions").with_suffix(suffix)
263
-
264
- def get_raw_sequences_path(self, suffix = ".json"):
265
- pred_dir = self.get_prediction_dir()
266
- return pred_dir.joinpath("raw_sequences").with_suffix(suffix)
267
-
268
- def get_filtered_sequences_path(self, suffix=".json"):
269
- pred_dir = self.get_prediction_dir()
270
- return pred_dir.joinpath("filtered_sequences").with_suffix(suffix)
271
-
272
- def extract_text_information(self, frame_fraction: float = 0.001):
162
+ return anonymized_frame_dir, generated_frame_paths
163
+
164
+ def make_anonymized_video(self):
273
165
  """
274
- Extract text information from the video file.
275
- Makes sure that frames are extracted and then processes the frames.
276
- gets all frames from frame_dir and selects a fraction of them to process (at least 1)
166
+ Make an anonymized video from the anonymized frames.
277
167
  """
278
- if not self.state_frames_extracted:
279
- print(f"Frames not extracted for {self.file.name}")
280
- return None
281
168
 
282
- processor = self.processor
169
+ assert self.state_initial_prediction_completed, (
170
+ "Initial prediction not completed"
171
+ )
172
+ assert self.state_sensitive_data_retrieved, "Sensitive data not retrieved"
283
173
 
284
- frame_dir = Path(self.frame_dir)
285
- frames = list(frame_dir.glob("*"))
286
- n_frames = len(frames)
287
- n_frames_to_process = max(1, int(frame_fraction * n_frames))
288
-
289
- # Select evenly spaced frames
290
- frames = frames[:: n_frames // n_frames_to_process]
174
+ ic(
175
+ "WARNING: Outside validation is not yet implemented and automatically set to true in this function"
176
+ )
177
+ self.state_outside_validated = True
178
+ self.save()
291
179
 
292
- # extract text from each frame and store the value to
293
- # defaultdict of lists.
294
- # Then, extract the most frequent value from each list
295
- # Finally, return the dictionary of most frequent values
180
+ assert self.state_outside_validated, "Outside validation not completed"
296
181
 
297
- # Create a defaultdict to store the extracted text from each ROI
298
- rois_texts = defaultdict(list)
182
+ _anonymized_frame_dir, generated_frame_paths = (
183
+ self.make_temporary_anonymized_frames()
184
+ )
299
185
 
300
- print(f"Processing {n_frames_to_process} frames from {self.file.name}")
301
- # Process frames
302
- for frame_path in frames[:n_frames_to_process]:
303
- extracted_texts = extract_text_from_rois(frame_path, processor)
304
- for roi, text in extracted_texts.items():
305
- rois_texts[roi].append(text)
186
+ anonymized_video_path = self.get_anonymized_video_path()
187
+ # if anonymized video already exists, delete it
188
+ if anonymized_video_path.exists():
189
+ anonymized_video_path.unlink()
306
190
 
307
- # Get the most frequent text values for each ROI using Counter
308
- for key in rois_texts.keys():
309
- counter = Counter([text for text in rois_texts[key] if text])
310
- rois_texts[key] = counter.most_common(1)[0][0] if counter else None
191
+ # Use ffmpeg and the frame paths to create a video
192
+ fps = self.get_fps()
193
+ height, width = cv2.imread(generated_frame_paths[0].as_posix()).shape[:2]
194
+ ic("Assembling anonymized video")
195
+ ic(f"Frame width: {width}, height: {height}")
196
+ ic(f"FPS: {fps}")
311
197
 
312
- return rois_texts
198
+ command = [
199
+ "ffmpeg",
200
+ "-y",
201
+ "-pattern_type",
202
+ "glob",
203
+ "-f",
204
+ "image2",
205
+ "-framerate",
206
+ str(fps),
207
+ "-i",
208
+ f"{generated_frame_paths[0].parent.as_posix()}/frame_[0-9]*.jpg",
209
+ "-c:v",
210
+ "libx264",
211
+ "-pix_fmt",
212
+ "yuv420p",
213
+ "-vf",
214
+ f"scale={width}:{height}",
215
+ anonymized_video_path.as_posix(),
216
+ ]
313
217
 
314
- def update_text_metadata(self, ocr_frame_fraction=0.001):
315
- print(f"Updating metadata for {self.file.name}")
316
- texts = self.extract_text_information(ocr_frame_fraction)
218
+ subprocess.run(command, check=True)
219
+ ic(f"Anonymized video saved at {anonymized_video_path}")
317
220
 
318
- self.sensitive_meta = SensitiveMeta.create_from_dict(texts)
319
- self.state_sensitive_data_retrieved = True
221
+ self.state_make_anonymized_video_required = False
222
+ self.state_make_anonymized_video_completed = True
320
223
  self.save()
321
224
 
322
- # Resulting dict depends on defined ROIs for this processor type!
323
-
324
- def update_video_meta(self):
325
- video_meta = self.video_meta
326
- video_path = Path(self.file.path)
327
-
328
- if video_meta is None:
329
- video_meta = VideoMeta.create_from_video(video_path)
330
- self.video_meta = video_meta
331
- self.save()
225
+ return anonymized_video_path, generated_frame_paths
332
226
 
227
+ def delete_frames_anonymized(self):
228
+ """
229
+ Delete anonymized frames extracted from the video file.
230
+ """
231
+ frame_dir = Path(self.frame_dir)
232
+ anonymized_frame_dir = frame_dir.parent / f"anonymized_{self.uuid}"
233
+ if anonymized_frame_dir.exists():
234
+ shutil.rmtree(anonymized_frame_dir)
235
+ return f"Anonymized frames deleted from {anonymized_frame_dir}"
333
236
  else:
334
- video_meta.update_meta(video_path)
335
-
336
- def get_fps(self):
337
- if self.video_meta is None:
338
- self.update_video_meta()
339
-
340
- if self.video_meta.ffmpeg_meta is None:
341
- self.video_meta.initialize_ffmpeg_meta(self.file.path)
237
+ return f"No anonymized frames to delete for {self.file.name}"
238
+
239
+ def get_or_create_video(self):
240
+ from endoreg_db.models import Video, Patient, PatientExamination
241
+
242
+ video = self.video
243
+ expected_path = self.get_anonymized_video_path()
244
+ if not video:
245
+ video_hash = self.video_hash
246
+ if Video.objects.filter(video_hash=video_hash).exists():
247
+ video = Video.objects.filter(video_hash=video_hash).first()
248
+
249
+ else:
250
+ if not expected_path.exists():
251
+ ic(
252
+ f"No anonymized video found at {expected_path}, Creating new one"
253
+ )
254
+ video_path, frame_paths = self.make_anonymized_video()
255
+
256
+ else:
257
+ ic(f"Anonymized video found at {expected_path}")
258
+ video_path = expected_path
259
+ frame_dir = self.get_anonymized_frame_dir()
260
+ ic(f"Frame dir: {frame_dir}")
261
+ frame_paths = list(frame_dir.glob("*.jpg"))
262
+ ic(f"Found {len(frame_paths)} frames")
263
+
264
+ video_object = Video.create_from_file(
265
+ video_path,
266
+ self.center,
267
+ self.processor,
268
+ video_dir=VIDEO_DIR,
269
+ frame_paths=frame_paths,
270
+ )
271
+
272
+ ex: PatientExamination = self.sensitive_meta.pseudo_examination
273
+ pat: Patient = self.sensitive_meta.pseudo_patient
274
+ video_object.examination = ex
275
+ video_object.patient = pat
276
+
277
+ self.video = video_object
278
+ self.save()
279
+ video_object.sync_from_raw_video()
280
+
281
+ ic(f"Video object created: {video_object}")
282
+ return video_object
283
+
284
+ self.video = video
285
+ self.save()
342
286
 
343
- return self.video_meta.get_fps()
287
+ # self.vi
288
+ return video
@@ -1,3 +1,13 @@
1
1
  from .sensitive_meta import SensitiveMeta
2
2
  from .pdf_meta import PdfMeta, PdfType
3
3
  from .video_meta import VideoMeta, FFMpegMeta, VideoImportMeta
4
+
5
+
6
+ __all__ = [
7
+ "SensitiveMeta",
8
+ "PdfMeta",
9
+ "PdfType",
10
+ "VideoMeta",
11
+ "FFMpegMeta",
12
+ "VideoImportMeta",
13
+ ]
@@ -52,6 +52,10 @@ class PdfType(models.Model):
52
52
 
53
53
  return summary
54
54
 
55
+ @classmethod
56
+ def default_pdf_type(cls):
57
+ return PdfType.objects.get(name="ukw-endoscopy-examination-report-generic")
58
+
55
59
  class PdfMeta(models.Model):
56
60
  pdf_type = models.ForeignKey(PdfType, on_delete=models.CASCADE)
57
61
  date = models.DateField()