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,21 +1,190 @@
1
1
  from django.db import models
2
+ from endoreg_db.utils.hashs import (
3
+ get_patient_hash,
4
+ get_patient_examination_hash,
5
+ # get_hash_string,
6
+ )
7
+ from hashlib import sha256
8
+
9
+ # from datetime import date
10
+ from icecream import ic
11
+ import os
12
+
13
+ # get DJANGO_SALT from settings
14
+ SECRET_SALT = os.getenv("DJANGO_SALT", "default_salt")
15
+
2
16
 
3
17
  class SensitiveMeta(models.Model):
4
18
  examination_date = models.DateField(blank=True, null=True)
19
+ pseudo_patient = models.ForeignKey(
20
+ "Patient",
21
+ on_delete=models.CASCADE,
22
+ blank=True,
23
+ null=True,
24
+ )
5
25
  patient_first_name = models.CharField(max_length=255, blank=True, null=True)
6
26
  patient_last_name = models.CharField(max_length=255, blank=True, null=True)
7
27
  patient_dob = models.DateField(blank=True, null=True)
28
+ pseudo_examination = models.ForeignKey(
29
+ "PatientExamination",
30
+ on_delete=models.CASCADE,
31
+ blank=True,
32
+ null=True,
33
+ )
34
+ patient_gender = models.ForeignKey(
35
+ "Gender",
36
+ on_delete=models.CASCADE,
37
+ blank=True,
38
+ null=True,
39
+ )
40
+ examiners = models.ManyToManyField(
41
+ "Examiner",
42
+ blank=True,
43
+ )
44
+ center = models.ForeignKey(
45
+ "Center",
46
+ on_delete=models.CASCADE,
47
+ blank=True,
48
+ null=True,
49
+ )
50
+
51
+ examiner_first_name = models.CharField(max_length=255, blank=True, null=True)
52
+ examiner_last_name = models.CharField(max_length=255, blank=True, null=True)
53
+
54
+ examination_hash = models.CharField(max_length=255, blank=True, null=True)
55
+ patient_hash = models.CharField(max_length=255, blank=True, null=True)
56
+
8
57
  endoscope_type = models.CharField(max_length=255, blank=True, null=True)
9
58
  endoscope_sn = models.CharField(max_length=255, blank=True, null=True)
59
+ state_verified = models.BooleanField(default=False)
60
+ state_hash_generated = models.BooleanField(default=False)
61
+ state_names_substituted = models.BooleanField(default=False)
62
+ state_dob_substituted = models.BooleanField(default=False)
63
+ state_examination_date_substituted = models.BooleanField(default=False)
64
+ state_endoscope_sn_substituted = models.BooleanField(default=False)
65
+ state_examiners_substituted = models.BooleanField(default=False)
10
66
 
11
67
  @classmethod
12
68
  def create_from_dict(cls, data: dict):
69
+ from endoreg_db.models import Center, Examiner
70
+ from endoreg_db.utils import guess_name_gender
71
+
13
72
  # data can contain more fields than the model has
14
73
  field_names = [_.name for _ in cls._meta.fields]
15
74
  selected_data = {k: v for k, v in data.items() if k in field_names}
16
75
 
17
- return cls.objects.create(**selected_data)
18
-
76
+ first_name = selected_data.get("patient_first_name")
77
+ last_name = selected_data.get("patient_last_name")
78
+ center_name = data.get("center_name")
79
+
80
+ try:
81
+ center = Center.objects.get_by_natural_key(center_name)
82
+ except Exception as exc:
83
+ raise ValueError(f"Center with name {center_name} does not exist") from exc
84
+
85
+ selected_data["center"] = center
86
+
87
+ try:
88
+ # TODO Add to documentation and replace with better method
89
+ gender = guess_name_gender(first_name)
90
+ except:
91
+ raise ValueError(f"Gender for name {first_name} could not be guessed")
92
+
93
+ selected_data["patient_gender"] = gender
94
+
95
+ if first_name and last_name:
96
+ # TODO Add to documentation
97
+ cls._update_name_db(first_name, last_name)
98
+
99
+ sensitive_meta = cls.objects.create(**selected_data)
100
+ sensitive_meta.get_or_create_pseudo_examiner()
101
+ sensitive_meta.get_or_create_pseudo_patient()
102
+ sensitive_meta.get_or_create_pseudo_patient_examination()
103
+
104
+ ic("EXAMINER_FIRST_NAME", sensitive_meta.examiner_first_name)
105
+ ic("EXAMINER_LAST_NAME", sensitive_meta.examiner_last_name)
106
+
107
+ return sensitive_meta
108
+
109
+ def get_or_create_pseudo_examiner(self):
110
+ ic("GETTING OR CREATING EXAMINER")
111
+
112
+ if self.examiners.exists():
113
+ examiner = self.examiners.first()
114
+ ic(f"Exisiting examiner: {examiner}")
115
+
116
+ else:
117
+ examiner = self.create_pseudo_examiner()
118
+ ic(f"Created examiner: {examiner}")
119
+
120
+ return examiner
121
+
122
+ def create_pseudo_examiner(self):
123
+ from endoreg_db.models import Examiner, Center
124
+
125
+ first_name = self.examiner_first_name
126
+ last_name = self.examiner_last_name
127
+ center = self.center
128
+ ic("CREATING EXAMINER", first_name, last_name, center)
129
+ if not first_name or not last_name or not center:
130
+ default_center = Center.objects.get_by_natural_key("endoreg_db_demo")
131
+ examiner, _created = Examiner.custom_get_or_create(
132
+ first_name="Unknown", last_name="Unknown", center=default_center
133
+ )
134
+ else:
135
+ examiner, _created = Examiner.custom_get_or_create(
136
+ first_name=first_name, last_name=last_name, center=center
137
+ )
138
+ self.examiners.add(examiner)
139
+ self.save()
140
+
141
+ return examiner
142
+
143
+ def get_or_create_pseudo_patient(self):
144
+ if not self.pseudo_patient:
145
+ self.pseudo_patient = self.create_pseudo_patient()
146
+ self.save()
147
+ return self.pseudo_patient
148
+
149
+ def create_pseudo_patient(self):
150
+ from endoreg_db.models import Patient
151
+ from datetime import date
152
+
153
+ dob = self.patient_dob
154
+
155
+ if isinstance(dob, str):
156
+ dob = date.fromisoformat(dob)
157
+
158
+ month = dob.month
159
+ year = dob.year
160
+
161
+ patient_hash = self.get_patient_hash()
162
+ patient, _created = Patient.get_or_create_pseudo_patient_by_hash(
163
+ patient_hash=patient_hash,
164
+ center=self.center,
165
+ gender=self.patient_gender,
166
+ birth_year=year,
167
+ birth_month=month,
168
+ )
169
+
170
+ return patient
171
+
172
+ def get_or_create_pseudo_patient_examination(self):
173
+ from endoreg_db.models import PatientExamination
174
+
175
+ patient_hash = self.get_patient_hash()
176
+ examination_hash = self.get_patient_examination_hash()
177
+
178
+ patient_examination, _created = (
179
+ PatientExamination.get_or_create_pseudo_patient_examination_by_hash(
180
+ patient_hash, examination_hash
181
+ )
182
+ )
183
+
184
+ self.pseudo_examination = patient_examination
185
+
186
+ return patient_examination
187
+
19
188
  def update_from_dict(self, data: dict):
20
189
  # data can contain more fields than the model has
21
190
  field_names = [_.name for _ in self._meta.fields]
@@ -23,9 +192,99 @@ class SensitiveMeta(models.Model):
23
192
 
24
193
  for k, v in selected_data.items():
25
194
  setattr(self, k, v)
26
-
195
+
27
196
  self.save()
28
-
197
+ first_name = self.patient_first_name
198
+ last_name = self.patient_last_name
199
+
200
+ if first_name and last_name:
201
+ SensitiveMeta._update_name_db(first_name=first_name, last_name=last_name)
202
+
203
+ if not self.examination_hash:
204
+ self.examination_hash = self.get_patient_examination_hash()
205
+ if not self.patient_hash:
206
+ self.patient_hash = self.get_patient_hash()
207
+
208
+ examiner_first_name = data.get("examiner_first_name", "")
209
+ examiner_last_name = data.get("examiner_last_name", "")
210
+
211
+ if examiner_first_name and examiner_last_name:
212
+ self.examiner_first_name = examiner_first_name
213
+ self.examiner_last_name = examiner_last_name
214
+ _examiner = self.get_or_create_pseudo_examiner()
215
+
216
+ return self
217
+
29
218
  def __str__(self):
30
- return f"SensitiveMeta: {self.examination_date} {self.patient_first_name} {self.patient_last_name} (*{self.patient_dob})"
31
-
219
+ result_str = "SensitiveMeta:"
220
+ result_str += f"\tExamination Date: {self.examination_date}"
221
+ result_str += f"\tFirst Name: {self.patient_first_name}"
222
+ result_str += f"\tLast Name: {self.patient_last_name}"
223
+ result_str += f"\tDate of Birth: (*{self.patient_dob})"
224
+ result_str += f"\tGender: {self.patient_gender}"
225
+ result_str += f"\tCenter: {self.center}"
226
+ result_str += f"\tExaminers: {self.examiners.all()}"
227
+ result_str += f"\tEndoscope Type: {self.endoscope_type}"
228
+ result_str += f"\tEndoscope SN: {self.endoscope_sn}"
229
+ result_str += f"\tState Verified: {self.state_verified}"
230
+ result_str += f"\tPatient Hash: {self.patient_hash}"
231
+ result_str += f"\tExamination Hash: {self.examination_hash}"
232
+
233
+ return result_str
234
+
235
+ def __repr__(self):
236
+ return self.__str__()
237
+
238
+ def get_patient_hash(self, salt=SECRET_SALT):
239
+ dob = self.patient_dob
240
+ first_name = self.patient_first_name
241
+ last_name = self.patient_last_name
242
+ center = self.center
243
+
244
+ assert dob, "Patient DOB is required"
245
+ assert center, "Center is required"
246
+
247
+ hash_str = get_patient_hash(
248
+ first_name=first_name,
249
+ last_name=last_name,
250
+ dob=dob,
251
+ center=self.center.name,
252
+ salt=salt,
253
+ )
254
+ return sha256(hash_str.encode()).hexdigest()
255
+
256
+ def get_patient_examination_hash(self, salt=SECRET_SALT):
257
+ dob = self.patient_dob
258
+ first_name = self.patient_first_name
259
+ last_name = self.patient_last_name
260
+ examination_date = self.examination_date
261
+ center = self.center
262
+
263
+ assert dob, "Patient DOB is required"
264
+ assert examination_date, "Examination date is required"
265
+
266
+ hash_str = get_patient_examination_hash(
267
+ first_name=first_name,
268
+ last_name=last_name,
269
+ dob=dob,
270
+ examination_date=examination_date,
271
+ center=center.name,
272
+ salt=salt,
273
+ )
274
+
275
+ return sha256(hash_str.encode()).hexdigest()
276
+
277
+ # override save method to update hashes
278
+ def save(self, *args, **kwargs):
279
+ self.examination_hash = self.get_patient_examination_hash()
280
+ self.patient_hash = self.get_patient_hash()
281
+ self.pseudo_patient = self.create_pseudo_patient()
282
+ self.pseudo_examination = self.get_or_create_pseudo_patient_examination()
283
+ super().save(*args, **kwargs)
284
+
285
+ @classmethod
286
+ def _update_name_db(cls, first_name, last_name):
287
+ from endoreg_db.models import FirstName, LastName
288
+
289
+ FirstName.objects.get_or_create(name=first_name)
290
+ LastName.objects.get_or_create(name=last_name)
@@ -1,38 +1,77 @@
1
1
  from django.db import models
2
- import ffmpeg
2
+ import subprocess
3
+ import json
3
4
  from pathlib import Path
5
+ from typing import Optional, TYPE_CHECKING
4
6
 
5
7
  # import endoreg_center_id from django settings
6
8
  from django.conf import settings
7
9
 
8
10
  # check if endoreg_center_id is set
9
- if not hasattr(settings, 'ENDOREG_CENTER_ID'):
11
+ if not hasattr(settings, "ENDOREG_CENTER_ID"):
10
12
  ENDOREG_CENTER_ID = 9999
11
13
  else:
12
14
  ENDOREG_CENTER_ID = settings.ENDOREG_CENTER_ID
13
15
 
16
+ if TYPE_CHECKING:
17
+ from endoreg_db.models import EndoscopyProcessor, Endoscope, Center
18
+
19
+
14
20
  # VideoMeta
15
21
  class VideoMeta(models.Model):
16
- processor = models.ForeignKey('EndoscopyProcessor', on_delete=models.CASCADE, blank=True, null=True)
17
- endoscope = models.ForeignKey('Endoscope', on_delete=models.CASCADE, blank=True, null=True)
18
- center = models.ForeignKey('Center', on_delete=models.CASCADE)
19
- import_meta = models.OneToOneField('VideoImportMeta', on_delete=models.CASCADE, blank=True, null=True)
20
- ffmpeg_meta = models.OneToOneField('FFMpegMeta', on_delete=models.CASCADE, blank=True, null=True)
22
+ processor = models.ForeignKey(
23
+ "EndoscopyProcessor", on_delete=models.CASCADE, blank=True, null=True
24
+ )
25
+ endoscope = models.ForeignKey(
26
+ "Endoscope", on_delete=models.CASCADE, blank=True, null=True
27
+ )
28
+ center = models.ForeignKey("Center", on_delete=models.CASCADE)
29
+ import_meta = models.OneToOneField(
30
+ "VideoImportMeta", on_delete=models.CASCADE, blank=True, null=True
31
+ )
32
+ ffmpeg_meta = models.OneToOneField(
33
+ "FFMpegMeta", on_delete=models.CASCADE, blank=True, null=True
34
+ ) ##
21
35
 
22
- def __str__(self):
36
+ @classmethod
37
+ def create_from_file(
38
+ cls,
39
+ file_path: Path,
40
+ center: Optional["Center"],
41
+ processor: Optional["EndoscopyProcessor"] = None,
42
+ endoscope: Optional["Endoscope"] = None,
43
+ ):
44
+ """Create a new VideoMeta from a file."""
45
+ meta = cls.objects.create(center=center)
46
+ meta.update_meta(file_path)
47
+
48
+ if processor:
49
+ meta.processor = processor
50
+ meta.get_endo_roi()
51
+ if endoscope:
52
+ meta.endoscope = endoscope
53
+
54
+ meta.save()
55
+
56
+ return meta
23
57
 
58
+ def __str__(self):
24
59
  processor_name = self.processor.name if self.processor is not None else "None"
25
60
  endoscope_name = self.endoscope.name if self.endoscope is not None else "None"
26
61
  center_name = self.center.name if self.center is not None else "None"
62
+ ffmpeg_meta_str = self.ffmpeg_meta.__str__()
63
+ import_meta_str = self.import_meta.__str__()
27
64
 
28
65
  result_html = ""
29
66
 
30
- result_html += f"Processor: {processor_name}<br>"
31
- result_html += f"Endoscope: {endoscope_name}<br>"
32
- result_html += f"Center: {center_name}<br>"
67
+ result_html += f"Processor: {processor_name}\n"
68
+ result_html += f"Endoscope: {endoscope_name}\n"
69
+ result_html += f"Center: {center_name}\n"
70
+ result_html += f"FFMpeg Meta: {ffmpeg_meta_str}\n"
71
+ result_html += f"Import Meta: {import_meta_str}\n"
33
72
 
34
73
  return result_html
35
-
74
+
36
75
  # import meta should be created when video meta is created
37
76
  def save(self, *args, **kwargs):
38
77
  if self.import_meta is None:
@@ -50,15 +89,18 @@ class VideoMeta(models.Model):
50
89
  self.save()
51
90
 
52
91
  def get_endo_roi(self):
53
- endo_roi = self.processor.get_roi_endoscope_image()
92
+ from endoreg_db.models import EndoscopyProcessor
93
+
94
+ processor: EndoscopyProcessor = self.processor
95
+ endo_roi = processor.get_roi_endoscope_image()
54
96
  return endo_roi
55
97
 
56
98
  def get_fps(self):
57
99
  if not self.ffmpeg_meta:
58
100
  return None
59
-
101
+
60
102
  return self.ffmpeg_meta.frame_rate
61
-
103
+
62
104
 
63
105
  class FFMpegMeta(models.Model):
64
106
  # Existing fields
@@ -66,7 +108,7 @@ class FFMpegMeta(models.Model):
66
108
  width = models.IntegerField(blank=True, null=True)
67
109
  height = models.IntegerField(blank=True, null=True)
68
110
  frame_rate = models.FloatField(blank=True, null=True)
69
-
111
+
70
112
  # New fields for comprehensive information
71
113
  video_codec = models.CharField(max_length=50, blank=True, null=True)
72
114
  audio_codec = models.CharField(max_length=50, blank=True, null=True)
@@ -77,45 +119,67 @@ class FFMpegMeta(models.Model):
77
119
 
78
120
  @classmethod
79
121
  def create_from_file(cls, file_path: Path):
80
- """Creates an FFMpegMeta instance from a video file using ffmpeg probe."""
81
- try:
82
- probe = ffmpeg.probe(file_path.resolve().as_posix())
83
- except: # ffmpeg.Error as e:
84
- # print(e.stderr)
85
- print(f"Error while probing {file_path}")
86
- return None
87
-
88
- video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
89
- audio_streams = [stream for stream in probe['streams'] if stream['codec_type'] == 'audio']
90
-
91
- # Check for the existence of a video stream
92
- if video_stream is None:
93
- print(f"No video stream found in {file_path}")
122
+ cmd = [
123
+ "ffprobe",
124
+ "-v",
125
+ "quiet",
126
+ "-print_format",
127
+ "json",
128
+ "-show_streams",
129
+ str(file_path),
130
+ ]
131
+ proc = subprocess.run(cmd, capture_output=True, text=True, check=True)
132
+ probe = json.loads(proc.stdout)
133
+
134
+ video_stream = next(
135
+ (stream for stream in probe["streams"] if stream["codec_type"] == "video"),
136
+ None,
137
+ )
138
+ audio_streams = [
139
+ stream for stream in probe["streams"] if stream["codec_type"] == "audio"
140
+ ]
141
+
142
+ if not video_stream:
94
143
  return None
95
144
 
96
- # Extract and store video metadata
97
145
  metadata = {
98
- 'duration': float(video_stream.get('duration', 0)),
99
- 'width': int(video_stream.get('width', 0)),
100
- 'height': int(video_stream.get('height', 0)),
101
- 'frame_rate': float(next(iter(video_stream.get('avg_frame_rate', '').split('/')), 0)),
102
- 'video_codec': video_stream.get('codec_name', ''),
146
+ "duration": float(video_stream.get("duration", 0)),
147
+ "width": int(video_stream.get("width", 0)),
148
+ "height": int(video_stream.get("height", 0)),
149
+ "frame_rate": float(
150
+ next(iter(video_stream.get("avg_frame_rate", "").split("/")), 0)
151
+ ),
152
+ "video_codec": video_stream.get("codec_name", ""),
103
153
  }
104
154
 
105
- # If there are audio streams, extract and store audio metadata from the first stream
106
155
  if audio_streams:
107
156
  first_audio_stream = audio_streams[0]
108
- metadata.update({
109
- 'audio_codec': first_audio_stream.get('codec_name', ''),
110
- 'audio_channels': int(first_audio_stream.get('channels', 0)),
111
- 'audio_sample_rate': int(first_audio_stream.get('sample_rate', 0)),
112
- })
157
+ metadata.update(
158
+ {
159
+ "audio_codec": first_audio_stream.get("codec_name", ""),
160
+ "audio_channels": int(first_audio_stream.get("channels", 0)),
161
+ "audio_sample_rate": int(first_audio_stream.get("sample_rate", 0)),
162
+ }
163
+ )
113
164
 
114
- # Create and return the FFMpegMeta instance
115
165
  return cls.objects.create(**metadata)
116
166
 
117
- class VideoImportMeta(models.Model):
167
+ def __str__(self):
168
+ result_html = ""
169
+
170
+ result_html += f"Duration: {self.duration}\n"
171
+ result_html += f"Width: {self.width}\n"
172
+ result_html += f"Height: {self.height}\n"
173
+ result_html += f"Frame Rate: {self.frame_rate}\n"
174
+ result_html += f"Video Codec: {self.video_codec}\n"
175
+ result_html += f"Audio Codec: {self.audio_codec}\n"
176
+ result_html += f"Audio Channels: {self.audio_channels}\n"
177
+ result_html += f"Audio Sample Rate: {self.audio_sample_rate}\n"
118
178
 
179
+ return result_html
180
+
181
+
182
+ class VideoImportMeta(models.Model):
119
183
  video_anonymized = models.BooleanField(default=False)
120
184
  video_patient_data_detected = models.BooleanField(default=False)
121
185
  outside_detected = models.BooleanField(default=False)
@@ -125,9 +189,11 @@ class VideoImportMeta(models.Model):
125
189
  def __str__(self):
126
190
  result_html = ""
127
191
 
128
- result_html += f"Video anonymized: {self.video_anonymized}<br>"
129
- result_html += f"Video patient data detected: {self.video_patient_data_detected}<br>"
130
- result_html += f"Outside detected: {self.outside_detected}<br>"
131
- result_html += f"Patient data removed: {self.patient_data_removed}<br>"
132
- result_html += f"Outside removed: {self.outside_removed}<br>"
133
- return result_html
192
+ result_html += f"Video anonymized: {self.video_anonymized}\n"
193
+ result_html += (
194
+ f"Video patient data detected: {self.video_patient_data_detected}\n"
195
+ )
196
+ result_html += f"Outside detected: {self.outside_detected}\n"
197
+ result_html += f"Patient data removed: {self.patient_data_removed}\n"
198
+ result_html += f"Outside removed: {self.outside_removed}\n"
199
+ return result_html
@@ -1,82 +1,52 @@
1
- from django.db import models
2
- from ..center import Center
3
- import hashlib
4
1
  from datetime import date, time
2
+ from django.db import models
3
+ from .base_classes.abstract_pdf import AbstractPdfFile
4
+
5
5
 
6
- class ReportFile(models.Model):
7
- pdf = models.FileField(upload_to="raw_report_pdfs", blank=True, null=True)
8
- pdf_hash = models.CharField(max_length=255, unique=True)
9
- center = models.ForeignKey(Center, on_delete=models.CASCADE)
6
+ class ReportFile(AbstractPdfFile):
10
7
  meta = models.JSONField(blank=True, null=True)
11
8
  text = models.TextField(blank=True, null=True)
12
- text_anonymized = models.TextField(blank=True, null=True)
13
- patient = models.ForeignKey("Patient", on_delete=models.CASCADE, blank=True, null=True)
14
- examiner = models.ForeignKey("Examiner", on_delete=models.CASCADE, blank=True, null=True)
9
+ # sensitive_meta = models.ForeignKey(
10
+ # "SensitiveMeta",
11
+ # on_delete=models.SET_NULL,
12
+ # related_name="report_files",
13
+ # null=True,
14
+ # blank=True,
15
+ # )
16
+ # examination = models.ForeignKey(
17
+ # "PatientExamination",
18
+ # on_delete=models.SET_NULL,
19
+ # blank=True,
20
+ # null=True,
21
+ # related_name="report_files",
22
+ # )
23
+ # patient = models.ForeignKey(
24
+ # "Patient", on_delete=models.DO_NOTHING, blank=True, null=True
25
+ # )
26
+ # examiner = models.ForeignKey(
27
+ # "Examiner", on_delete=models.DO_NOTHING, blank=True, null=True
28
+ # )
15
29
  date = models.DateField(blank=True, null=True)
16
30
  time = models.TimeField(blank=True, null=True)
17
31
 
18
- def get_pdf_hash(self):
19
- #FIXME should use endoreg_db.utils.get_pdf_hash in future
20
- pdf = self.pdf
21
- pdf_hash = None
22
-
23
- if pdf:
24
- # Open the file in binary mode and read its contents
25
- with pdf.open(mode='rb') as f:
26
- pdf_contents = f.read()
27
- # Create a hash object using SHA-256 algorithm
28
- hash_object = hashlib.sha256(pdf_contents, usedforsecurity=False)
29
- # Get the hexadecimal representation of the hash
30
- pdf_hash = hash_object.hexdigest()
31
- assert len(pdf_hash) <= 255, "Hash length exceeds 255 characters"
32
-
33
- return pdf_hash
34
-
35
- def initialize_metadata_in_db(self, report_meta=None):
36
- if not report_meta:
37
- report_meta = self.meta
38
- self.set_examination_date_and_time(report_meta)
39
- self.patient, created = self.get_or_create_patient(report_meta)
40
- self.examiner, created = self.get_or_create_examiner(report_meta)
41
- self.save()
42
-
43
- def get_or_create_patient(self, report_meta=None):
44
- from ..persons import Patient
45
- if not report_meta:
46
- report_meta = self.meta
47
- patient_first_name = report_meta['patient_first_name']
48
- patient_last_name = report_meta['patient_last_name']
49
- patient_dob = report_meta['patient_dob']
50
-
51
- patient, created = Patient.objects.get_or_create(
52
- first_name=patient_first_name,
53
- last_name=patient_last_name,
54
- dob=patient_dob
55
- )
56
-
57
- return patient, created
58
-
59
- def get_or_create_examiner(self, report_meta= None):
32
+ def get_or_create_examiner(self, examiner_first_name, examiner_last_name):
60
33
  from ..persons import Examiner
61
- if not report_meta:
62
- report_meta = self.meta
63
- examiner_first_name = report_meta['examiner_first_name']
64
- examiner_last_name = report_meta['examiner_last_name']
34
+
65
35
  examiner_center = self.center
66
36
 
67
37
  examiner, created = Examiner.objects.get_or_create(
68
38
  first_name=examiner_first_name,
69
39
  last_name=examiner_last_name,
70
- center=examiner_center
40
+ center=examiner_center,
71
41
  )
72
42
 
73
43
  return examiner, created
74
-
44
+
75
45
  def set_examination_date_and_time(self, report_meta=None):
76
46
  if not report_meta:
77
47
  report_meta = self.meta
78
- examination_date_str = report_meta['examination_date']
79
- examination_time_str = report_meta['examination_time']
48
+ examination_date_str = report_meta["examination_date"]
49
+ examination_time_str = report_meta["examination_time"]
80
50
 
81
51
  if examination_date_str:
82
52
  # TODO: get django DateField compatible date from string (e.g. "2021-01-01")
@@ -84,6 +54,3 @@ class ReportFile(models.Model):
84
54
  if examination_time_str:
85
55
  # TODO: get django TimeField compatible time from string (e.g. "12:00")
86
56
  self.time = time.fromisoformat(examination_time_str)
87
-
88
-
89
-
@@ -1,7 +1,11 @@
1
1
  from .video import (
2
2
  Video,
3
- LegacyVideo,
4
3
  )
5
4
  from ..metadata import (
6
5
  VideoImportMeta,
7
- )
6
+ )
7
+
8
+ __all__ = [
9
+ "Video",
10
+ "VideoImportMeta",
11
+ ]