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
@@ -0,0 +1,143 @@
1
+ from django.db import models
2
+ from typing import List
3
+
4
+ class PatientFinding(models.Model):
5
+ patient_examination = models.ForeignKey('PatientExamination', on_delete=models.CASCADE, related_name='patient_findings')
6
+ finding = models.ForeignKey('Finding', on_delete=models.CASCADE, related_name='patient_findings')
7
+ locations = models.ManyToManyField(
8
+ 'PatientFindingLocation',
9
+ blank=True,
10
+ related_name='patient_findings'
11
+ )
12
+ morphologies = models.ManyToManyField(
13
+ 'PatientFindingMorphology',
14
+ blank=True,
15
+ related_name='patient_findings'
16
+ )
17
+
18
+
19
+ class Meta:
20
+ verbose_name = 'Patient Finding'
21
+ verbose_name_plural = 'Patient Findings'
22
+ ordering = ['patient_examination', 'finding']
23
+
24
+ def __str__(self):
25
+ return f"{self.patient_examination} - {self.finding}"
26
+
27
+ # functions to get all associated location and morphology choices
28
+ def get_locations(self):
29
+ """
30
+ Returns all location choices that are associated with this patient finding.
31
+ """
32
+ from endoreg_db.models import PatientFindingLocation
33
+ locations:List[PatientFindingLocation] = [_ for _ in self.locations.all()]
34
+ return locations
35
+
36
+ def get_morphologies(self):
37
+ """
38
+ Returns all morphology choices that are associated with this patient finding.
39
+ """
40
+ from endoreg_db.models import PatientFindingMorphology
41
+ morphologies:List[PatientFindingMorphology] = [_ for _ in self.morphologies.all()]
42
+ return morphologies
43
+
44
+ def add_morphology_choice(self, morphology_choice, morphology_classification):
45
+ """
46
+ Adds a morphology choice to this patient finding morphology.
47
+ """
48
+ from endoreg_db.models import (
49
+ FindingMorphologyClassificationChoice,
50
+ FindingMorphologyClassification,
51
+ PatientFindingMorphology
52
+ )
53
+ morphology_choice: FindingMorphologyClassificationChoice
54
+ morphology_classification: FindingMorphologyClassification
55
+
56
+ patient_finding_morphology = PatientFindingMorphology.objects.create(
57
+ morphology_classification=morphology_classification,
58
+ morphology_choice=morphology_choice
59
+ )
60
+ patient_finding_morphology.save()
61
+
62
+ self.morphologies.add(patient_finding_morphology)
63
+ self.save()
64
+
65
+ return patient_finding_morphology
66
+
67
+ def add_intervention(self, intervention, state="pending", date=None):
68
+ """
69
+ Adds an intervention to this patient finding.
70
+ """
71
+ from endoreg_db.models import PatientFindingIntervention
72
+ patient_finding_intervention = PatientFindingIntervention.objects.create(
73
+ patient_finding=self,
74
+ intervention=intervention,
75
+ state = state,
76
+ date = date
77
+ )
78
+ patient_finding_intervention.save()
79
+
80
+ return patient_finding_intervention
81
+
82
+ def add_location_choice(self, location_choice, location_classification):
83
+ """
84
+ Adds a location choice to this patient finding location.
85
+ """
86
+ from endoreg_db.models import (
87
+ FindingLocationClassificationChoice,
88
+ FindingLocationClassification,
89
+ PatientFindingLocation
90
+ )
91
+ location_choice: FindingLocationClassificationChoice
92
+ location_classification: FindingLocationClassification
93
+
94
+ patient_finding_location = PatientFindingLocation.objects.create(
95
+ location_classification=location_classification,
96
+ location_choice=location_choice
97
+ )
98
+ patient_finding_location.save()
99
+
100
+ self.locations.add(patient_finding_location)
101
+ self.save()
102
+
103
+ return patient_finding_location
104
+
105
+ def get_interventions(self):
106
+ """
107
+ Returns all interventions that are associated with this patient finding.
108
+ """
109
+ from endoreg_db.models import PatientFindingIntervention
110
+ interventions:List[PatientFindingIntervention] = [_ for _ in self.interventions.all()]
111
+ return interventions
112
+
113
+ def set_random_location(
114
+ self, location_classification
115
+ ):
116
+ """
117
+ Sets a random location for this finding based on the location classification.
118
+ """
119
+ from endoreg_db.models import (
120
+ FindingLocationClassificationChoice,
121
+ FindingLocationClassification,
122
+ PatientFindingLocation
123
+ )
124
+ import random
125
+ from typing import List
126
+ location_classification:FindingLocationClassification
127
+
128
+ # assert location_classification in self.finding.location_classifications.all()
129
+
130
+ location_choices:List[FindingLocationClassificationChoice] = location_classification.choices.all()
131
+ location_choice = random.choice(location_choices)
132
+
133
+ patient_finding_location = PatientFindingLocation.objects.create(
134
+ location_classification=location_classification,
135
+ location_choice=location_choice
136
+ )
137
+
138
+ self.locations.add(patient_finding_location)
139
+ self.save()
140
+
141
+ return patient_finding_location
142
+
143
+
@@ -0,0 +1,26 @@
1
+ from django.db import models
2
+
3
+ class PatientFindingIntervention(models.Model):
4
+ patient_finding = models.ForeignKey(
5
+ 'PatientFinding',
6
+ on_delete=models.CASCADE,
7
+ related_name='interventions'
8
+ )
9
+ intervention = models.ForeignKey(
10
+ 'FindingIntervention',
11
+ on_delete=models.CASCADE,
12
+ related_name='patient_finding_interventions'
13
+ )
14
+ state = models.CharField(max_length=100, blank=True, null=True)
15
+ time_start = models.DateTimeField(blank=True, null=True)
16
+ time_end = models.DateTimeField(blank=True, null=True)
17
+ date = models.DateField(blank=True, null=True)
18
+
19
+ def __str__(self):
20
+ return self.intervention.name
21
+
22
+ def natural_key(self):
23
+ return (self.intervention.name,)
24
+
25
+
26
+ objects = models.Manager()
@@ -0,0 +1,120 @@
1
+ from django.db import models
2
+
3
+ class PatientFindingLocation(models.Model):
4
+ location_classification = models.ForeignKey('FindingLocationClassification', on_delete=models.CASCADE, related_name='patient_finding_locations')
5
+ location_choice = models.ForeignKey('FindingLocationClassificationChoice', on_delete=models.CASCADE, related_name='patient_finding_locations')
6
+ subcategories = models.JSONField(blank=True, null=True)
7
+ numerical_descriptors = models.JSONField(blank=True, null=True)
8
+
9
+ class Meta:
10
+ verbose_name = 'Patient Finding Location'
11
+ verbose_name_plural = 'Patient Finding Locations'
12
+ ordering = ['location_classification', 'location_choice']
13
+
14
+ def __str__(self):
15
+ return f"{self.location_classification} - {self.location_choice}"
16
+
17
+ # override save method to do the following:
18
+ # - check if location_choice is in location_classification.choices
19
+ # - check if subcategories and numerical_descriptors exist
20
+ # - if not set, fetch them from location_choice and set them
21
+
22
+ def save(self, *args, **kwargs):
23
+ if self.location_choice not in self.location_classification.choices.all():
24
+ raise ValueError("location_choice must be in location_classification.choices")
25
+
26
+ if not self.subcategories:
27
+ self.subcategories = self.location_choice.subcategories
28
+
29
+ if not self.numerical_descriptors:
30
+ self.numerical_descriptors = self.location_choice.numerical_descriptors
31
+
32
+ super().save(*args, **kwargs)
33
+
34
+ def set_subcategory(self, subcategory_name, subcategory_value):
35
+ """
36
+ Sets a subcategory for this location.
37
+ """
38
+ assert subcategory_name in self.subcategories, "Subcategory must be in subcategories."
39
+ self.subcategories[subcategory_name]["value"] = subcategory_value
40
+ self.save()
41
+
42
+ return self.subcategories[subcategory_name]
43
+
44
+ def set_random_subcategories(self):
45
+ """
46
+ Sets random subcategories for this location if they are required.
47
+ """
48
+ import random
49
+ if not self.subcategories or not self.numerical_descriptors:
50
+ self.save()
51
+
52
+ self.refresh_from_db()
53
+ assert self.subcategories, "Subcategories must be set."
54
+
55
+ subcategories = self.subcategories
56
+ # print("SUBCATS")
57
+ # print(subcategories)
58
+
59
+ # subcategories is dict with keys as subcategory names and values as dict with keys as "choices" (List of str) and "required" (bool)
60
+ # for each subcategory, set a random choice if it is required
61
+ for subcategory_name, subcategory_dict in subcategories.items():
62
+ if subcategory_dict["required"]:
63
+ subcategory_choice = random.choice(subcategory_dict["choices"])
64
+ self.subcategories[subcategory_name]["value"] = subcategory_choice
65
+
66
+ self.save()
67
+
68
+ return self.subcategories
69
+
70
+ def set_random_numerical_descriptor(self, descriptor_name):
71
+ """
72
+ Sets a random numerical descriptor for this location.
73
+ """
74
+ import random
75
+ if descriptor_name not in self.numerical_descriptors:
76
+ raise ValueError("Descriptor name must be in numerical descriptors.")
77
+
78
+ numerical_descriptor = self.numerical_descriptors[descriptor_name]
79
+ min_value = numerical_descriptor["min"]
80
+ max_value = numerical_descriptor["max"]
81
+
82
+ assert min_value <= max_value, "Min value must be less than or equal to max value."
83
+
84
+ random_value = random.uniform(min_value, max_value)
85
+ self.numerical_descriptors[descriptor_name]["value"] = random_value
86
+ self.save()
87
+
88
+ return self.numerical_descriptors[descriptor_name]
89
+
90
+
91
+ def set_random_numerical_descriptors(self):
92
+ """
93
+ Sets random numerical descriptors for this location if they are required.
94
+ """
95
+ import random
96
+ from endoreg_db.models import FindingLocationClassificationChoice
97
+ if not self.subcategories or not self.numerical_descriptors:
98
+ self.save()
99
+
100
+ numerical_descriptors = self.numerical_descriptors
101
+ numerical_descriptor = {}
102
+
103
+ # numerical_descriptors is dict with keys as numerical descriptor names
104
+ # and values as dict with keys as "min" (float), "max" (float), required (bool), distribution_name (str)
105
+ # distribution name can be either "uniform" or "normal"
106
+ # for each numerical descriptor, set a random value between min and max
107
+ for numerical_descriptor_name, numerical_descriptor_dict in numerical_descriptors.items():
108
+ min_value = numerical_descriptor_dict["min"]
109
+ max_value = numerical_descriptor_dict["max"]
110
+
111
+ assert min_value <= max_value, "Min value must be less than or equal to max value."
112
+
113
+
114
+ random_value = random.uniform(min_value, max_value)
115
+ numerical_descriptor[numerical_descriptor_name] = random_value
116
+
117
+ self.numerical_descriptors = numerical_descriptor
118
+ self.save()
119
+
120
+ return numerical_descriptor
@@ -0,0 +1,166 @@
1
+ from django.db import models
2
+
3
+ class PatientFindingMorphology(models.Model):
4
+ morphology_classification = models.ForeignKey(
5
+ 'FindingMorphologyClassification', on_delete=models.CASCADE, related_name='patient_finding_morphologies'
6
+ )
7
+ morphology_choice = models.ForeignKey(
8
+ 'FindingMorphologyClassificationChoice', on_delete=models.CASCADE, related_name='patient_finding_morphologies'
9
+ )
10
+ subcategories = models.JSONField(default=dict)
11
+ numerical_descriptors = models.JSONField(default=dict)
12
+
13
+ class Meta:
14
+ verbose_name = 'Patient Finding Morphology'
15
+ verbose_name_plural = 'Patient Finding Morphologies'
16
+ ordering = ['morphology_classification', 'morphology_choice']
17
+
18
+ def __str__(self):
19
+
20
+ _str = f"{self.morphology_classification} - {self.morphology_choice}"
21
+
22
+ if self.subcategories:
23
+ for key, _dict in self.subcategories.items():
24
+ value = _dict.get("value", None)
25
+ if value:
26
+ _str += f" - {key}: {value}"
27
+
28
+ if self.numerical_descriptors:
29
+ for key, _dict in self.numerical_descriptors.items():
30
+ value = _dict.get("value", None)
31
+ if value:
32
+ _str += f" - {key}: {value}"
33
+
34
+ return _str
35
+
36
+ # override save method to do the following:
37
+ # - check if morphology_choice is in morphology_classification.choices
38
+ # - check if subcategories and numerical_descriptors exist
39
+ # - if not set, fetch them from morphology_choice and set them
40
+
41
+ def set_subcategory(self, subcategory_name, subcategory_value):
42
+ """
43
+ Sets a subcategory for this morphology.
44
+ """
45
+ assert subcategory_name in self.subcategories, "Subcategory must be in subcategories."
46
+ self.subcategories[subcategory_name]["value"] = subcategory_value
47
+ self.save()
48
+
49
+ return self.subcategories[subcategory_name]
50
+
51
+ def save(self, *args, **kwargs):
52
+ if self.morphology_choice not in self.morphology_classification.choices.all():
53
+ raise ValueError("morphology_choice must be in morphology_classification.choices")
54
+
55
+ if not self.subcategories:
56
+ self.subcategories = self.morphology_choice.subcategories
57
+ if not self.subcategories:
58
+ self.subcategories = {}
59
+
60
+ if not self.numerical_descriptors:
61
+ self.numerical_descriptors = self.morphology_choice.numerical_descriptors
62
+ if not self.numerical_descriptors:
63
+ self.numerical_descriptors = {}
64
+
65
+
66
+ super().save(*args, **kwargs)
67
+
68
+ def get_subcategories(self):
69
+ """
70
+ Returns all subcategories that are associated with this patient finding morphology.
71
+ """
72
+ if not self.subcategories:
73
+ self.save()
74
+ return self.subcategories
75
+
76
+ def get_numerical_descriptors(self):
77
+ """
78
+ Returns all numerical descriptors that are associated with this patient finding morphology.
79
+ """
80
+ if not self.numerical_descriptors:
81
+ self.save()
82
+ return self.numerical_descriptors
83
+
84
+ def get_random_value_for_numerical_descriptor(self, descriptor_name):
85
+ """
86
+ Returns a random value for a numerical descriptor.
87
+ """
88
+ import numpy as np
89
+ assert descriptor_name in self.numerical_descriptors, "Descriptor must be in numerical descriptors."
90
+ descriptor = self.numerical_descriptors[descriptor_name]
91
+ min_val = descriptor.get("min", 0)
92
+ max_val = descriptor.get("max", 1)
93
+ distribution = descriptor.get("distribution", "normal")
94
+ if distribution == "normal":
95
+ mean = descriptor.get("mean", 0.5)
96
+ std = descriptor.get("std", 0.1)
97
+ value = np.random.normal(mean, std)
98
+ # clip value to min and max
99
+ value = np.clip(value, min_val, max_val)
100
+ elif distribution == "uniform":
101
+ value = np.random.uniform(min_val, max_val)
102
+ else:
103
+ raise ValueError("Distribution not supported")
104
+
105
+ return value
106
+
107
+ def set_numerical_descriptor_random(self, descriptor_name):
108
+ """
109
+ Sets a numerical descriptor for this patient finding morphology.
110
+ """
111
+ assert descriptor_name in self.numerical_descriptors, "Descriptor must be in numerical descriptors."
112
+
113
+ value = self.get_random_value_for_numerical_descriptor(descriptor_name)
114
+ self.numerical_descriptors[descriptor_name]["value"] = value
115
+ self.save()
116
+
117
+ return self.numerical_descriptors[descriptor_name]
118
+
119
+ def set_random_numerical_descriptors(self): #TODO Update
120
+ """
121
+ Sets random numerical descriptors for this patient finding morphology.
122
+ """
123
+ import numpy as np
124
+ # get numerical descriptors from morphology_choice
125
+ try:
126
+ numerical_descriptors = self.morphology_choice.numerical_descriptors
127
+ assert numerical_descriptors
128
+ return numerical_descriptors
129
+ except:
130
+ # print(f"Numerical descriptors not found for {self.morphology_choice}")
131
+ return None
132
+ # If available, numerical descriptors is a dict like this:
133
+ # {
134
+ # "DESCRIPTOR_NAME": {
135
+ # "min": 0.5,
136
+ # "max": 1.5,
137
+ # "unit": "mm"
138
+ # "mean": 1.0
139
+ # "std": 0.1
140
+ # "required": True
141
+ # }
142
+ #}
143
+ # Iterate over all numerical descriptors
144
+ # if required is true, set random values following the constraints and distribution
145
+ # available distributions are: normal, uniform
146
+
147
+ for descriptor_name, descriptor in numerical_descriptors.items():
148
+ required = descriptor.get("required", False)
149
+ if required:
150
+ min_val = descriptor.get("min", 0)
151
+ max_val = descriptor.get("max", 1)
152
+ distribution = descriptor.get("distribution", "normal")
153
+ if distribution == "normal":
154
+ mean = descriptor.get("mean", 0.5)
155
+ std = descriptor.get("std", 0.1)
156
+ value = np.random.normal(mean, std)
157
+ # clip value to min and max
158
+ value = np.clip(value, min_val, max_val)
159
+ elif distribution == "uniform":
160
+ value = np.random.uniform(min_val, max_val)
161
+ else:
162
+ raise ValueError("Distribution not supported")
163
+
164
+ self.numerical_descriptors[descriptor_name]["value"] = value
165
+
166
+ self.save()
@@ -1,7 +1,34 @@
1
1
  from .gender import Gender
2
2
  from .person import Person
3
- from .patient import *
3
+ from .patient import (
4
+ Patient, PatientForm,
5
+ PatientEvent,
6
+ PatientDisease,
7
+ PatientLabSample, PatientLabSampleType,
8
+ PatientLabValue,
9
+ PatientMedication,
10
+ PatientMedicationSchedule,
11
+ PatientExaminationIndication
12
+ )
4
13
  from .examiner import Examiner, ExaminerSerializer
5
14
  from .portal_user_information import PortalUserInfo, Profession
6
15
  from .first_name import FirstName
7
- from .last_name import LastName
16
+ from .last_name import LastName
17
+
18
+
19
+ __all__ = [
20
+ "Gender",
21
+ "Person",
22
+ "Patient", "PatientForm",
23
+ "PatientEvent",
24
+ "PatientDisease",
25
+ "PatientLabSample", "PatientLabSampleType",
26
+ "PatientLabValue",
27
+ "PatientMedication",
28
+ "PatientMedicationSchedule",
29
+ "PatientExaminationIndication",
30
+ "Examiner", "ExaminerSerializer",
31
+ "PortalUserInfo", "Profession",
32
+ "FirstName",
33
+ "LastName"
34
+ ]
@@ -1,16 +1,60 @@
1
1
  from django.db import models
2
2
  from ..person import Person
3
3
  from rest_framework import serializers
4
+ from endoreg_db.utils import get_examiner_hash
5
+
6
+ import os
7
+ # from icecream import ic
8
+ # from datetime import date
9
+
10
+ # get DJANGO_SALT from environment
11
+ SALT = os.getenv("DJANGO_SALT", "default_salt")
12
+
4
13
 
5
14
  class Examiner(Person):
6
- center = models.ForeignKey('Center', on_delete=models.CASCADE, blank=True, null=True)
15
+ center = models.ForeignKey(
16
+ "Center", on_delete=models.CASCADE, blank=True, null=True
17
+ )
18
+ hash = models.CharField(max_length=255, unique=True)
7
19
 
8
20
  def __str__(self):
9
21
  return self.first_name + " " + self.last_name
10
-
22
+
23
+ @classmethod
24
+ def custom_get_or_create(cls, first_name: str, last_name: str, center: "Center"):
25
+ from endoreg_db.models import Center
26
+ from endoreg_db.utils import create_mock_examiner_name
27
+
28
+ assert isinstance(center, Center), (
29
+ f"center must be an instance of Center, not {type(center)}"
30
+ )
31
+ created = False
32
+
33
+ hash = get_examiner_hash(
34
+ first_name=first_name, #
35
+ last_name=last_name,
36
+ center_name=center.name,
37
+ salt=SALT,
38
+ )
39
+
40
+ examiner_exists = cls.objects.filter(hash=hash).exists()
41
+ if examiner_exists:
42
+ examiner = cls.objects.get(hash=hash)
43
+
44
+ else:
45
+ first_name, last_name = create_mock_examiner_name()
46
+ examiner = cls.objects.create(
47
+ first_name=first_name,
48
+ last_name=last_name,
49
+ center=center,
50
+ hash=hash,
51
+ )
52
+ examiner.save()
53
+ created = True
54
+ return examiner, created
55
+
11
56
 
12
57
  class ExaminerSerializer(serializers.ModelSerializer):
13
-
14
58
  class Meta:
15
59
  model = Examiner
16
- fields = '__all__'
60
+ fields = "__all__"
@@ -5,4 +5,4 @@ from .patient_lab_sample import PatientLabSample, PatientLabSampleType
5
5
  from .patient_lab_value import PatientLabValue
6
6
  from .patient_medication import PatientMedication
7
7
  from .patient_medication_schedule import PatientMedicationSchedule
8
- from .case import *
8
+ from .patient_examination_indication import PatientExaminationIndication