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,97 @@
1
+ '''Model for numeric value distribution'''
2
+
3
+ from django.db import models
4
+ import numpy as np
5
+ from .base_value_distribution import BaseValueDistribution
6
+ from scipy.stats import skewnorm
7
+
8
+
9
+ class NumericValueDistributionManager(models.Manager):
10
+ '''Object manager for NumericValueDistribution'''
11
+ def get_by_natural_key(self, name):
12
+ '''Retrieve a NumericValueDistribution by its natural key.'''
13
+ return self.get(name=name)
14
+
15
+ class NumericValueDistribution(BaseValueDistribution):
16
+ """
17
+ Numeric value distribution model.
18
+ Supports uniform, normal, and skewed normal distributions with hard limits.
19
+ """
20
+ objects = NumericValueDistributionManager()
21
+ DISTRIBUTION_CHOICES = [
22
+ ('uniform', 'Uniform'),
23
+ ('normal', 'Normal'),
24
+ ('skewed_normal', 'Skewed Normal'),
25
+ ]
26
+
27
+ distribution_type = models.CharField(max_length=20, choices=DISTRIBUTION_CHOICES)
28
+ min_descriptor = models.CharField(
29
+ max_length=20
30
+ )
31
+
32
+ max_descriptor = models.CharField(
33
+ max_length=20
34
+ )
35
+
36
+ def generate_value(self, lab_value, patient):
37
+ '''Generate a value based on the distribution rules.'''
38
+ #FIXME
39
+ from endoreg_db.models import LabValue, Patient
40
+ assert isinstance(patient, Patient)
41
+ assert isinstance(lab_value, LabValue)
42
+ default_normal_range_dict = lab_value.get_normal_range(patient.age(), patient.gender)
43
+ assert isinstance(default_normal_range_dict, dict)
44
+ if self.distribution_type == 'uniform':
45
+ assert self.min_descriptor and self.max_descriptor
46
+ assert "min" in default_normal_range_dict and "max" in default_normal_range_dict
47
+ value = self._generate_value_uniform(default_normal_range_dict)
48
+
49
+ return value
50
+
51
+ elif self.distribution_type == 'normal':
52
+ value = np.random.normal(self.mean, self.std_dev)
53
+ return np.clip(value, self.min_value, self.max_value)
54
+ elif self.distribution_type == 'skewed_normal':
55
+ value = skewnorm.rvs(a=self.skewness, loc=self.mean, scale=self.std_dev)
56
+ return np.clip(value, self.min_value, self.max_value)
57
+ else:
58
+ raise ValueError("Unsupported distribution type")
59
+
60
+ def parse_value_descriptor(self, value_descriptor:str):
61
+ '''Parse the value descriptor string into a dict with a lambda function.'''
62
+ # strings of shape f"{value_key}_{operator}_{value}"
63
+ # extract value_key, operator, value
64
+ value_key, operator, value = value_descriptor.split("_")
65
+ value = float(value)
66
+
67
+ operator_functions = {
68
+ "+": lambda x: x + value,
69
+ "-": lambda x: x - value,
70
+ "x": lambda x: x * value,
71
+ "/": lambda x: x / value,
72
+ }
73
+
74
+ return {value_key: operator_functions[operator]}
75
+
76
+ # create dict with {value_key: lambda x: x operator value}
77
+
78
+ def _generate_value_uniform(self, default_normal_range_dict:dict):
79
+ value_function_dict = self.parse_value_descriptor(
80
+ self.min_descriptor
81
+ )
82
+
83
+ _ = self.parse_value_descriptor(
84
+ self.max_descriptor
85
+ )
86
+
87
+ value_function_dict.update(_)
88
+
89
+ result_dict = {
90
+ key: value_function(default_normal_range_dict[key])
91
+ for key, value_function in value_function_dict.items()
92
+ }
93
+
94
+ # generate value
95
+ return np.random.uniform(result_dict["min"], result_dict["max"])
96
+
97
+
@@ -0,0 +1,22 @@
1
+ from django.db import models
2
+ import numpy as np
3
+ from .base_value_distribution import BaseValueDistribution
4
+
5
+
6
+ class SingleCategoricalValueDistributionManager(models.Manager):
7
+ def get_by_natural_key(self, name):
8
+ return self.get(name=name)
9
+
10
+ class SingleCategoricalValueDistribution(BaseValueDistribution):
11
+ """
12
+ Single categorical value distribution model.
13
+ Assigns a single value based on specified probabilities.
14
+ """
15
+ objects = SingleCategoricalValueDistributionManager()
16
+ categories = models.JSONField() # { "category": "probability", ... }
17
+
18
+ def generate_value(self):
19
+ categories, probabilities = zip(*self.categories.items())
20
+ return np.random.choice(categories, p=probabilities)
21
+
22
+
@@ -1,215 +1,5 @@
1
- from django.db import models
2
- import numpy as np
3
- from scipy.stats import skewnorm
4
1
 
5
- class BaseValueDistribution(models.Model):
6
- """
7
- Abstract base class for value distributions.
8
- """
9
- name = models.CharField(max_length=100)
10
2
 
11
- class Meta:
12
- abstract = True
13
3
 
14
- def generate_value(self):
15
- """
16
- Generate a value based on the distribution rules.
17
- Must be implemented by subclasses.
18
- """
19
- raise NotImplementedError("Subclasses must implement this method")
20
-
21
- def natural_key(self):
22
- return (self.name,)
23
4
 
24
- class NumericValueDistributionManager(models.Manager):
25
- def get_by_natural_key(self, name):
26
- return self.get(name=name)
27
-
28
- class NumericValueDistribution(BaseValueDistribution):
29
- """
30
- Numeric value distribution model.
31
- Supports uniform, normal, and skewed normal distributions with hard limits.
32
- """
33
- objects = NumericValueDistributionManager()
34
- DISTRIBUTION_CHOICES = [
35
- ('uniform', 'Uniform'),
36
- ('normal', 'Normal'),
37
- ('skewed_normal', 'Skewed Normal'),
38
- ]
39
-
40
- distribution_type = models.CharField(max_length=20, choices=DISTRIBUTION_CHOICES)
41
- min_value = models.FloatField()
42
- max_value = models.FloatField()
43
- mean = models.FloatField(null=True, blank=True)
44
- std_dev = models.FloatField(null=True, blank=True)
45
- skewness = models.FloatField(null=True, blank=True)
46
-
47
- def generate_value(self):
48
- if self.distribution_type == 'uniform':
49
- return np.random.uniform(self.min_value, self.max_value)
50
- elif self.distribution_type == 'normal':
51
- value = np.random.normal(self.mean, self.std_dev)
52
- return np.clip(value, self.min_value, self.max_value)
53
- elif self.distribution_type == 'skewed_normal':
54
- value = skewnorm.rvs(a=self.skewness, loc=self.mean, scale=self.std_dev)
55
- return np.clip(value, self.min_value, self.max_value)
56
- else:
57
- raise ValueError("Unsupported distribution type")
58
-
59
-
60
- class SingleCategoricalValueDistributionManager(models.Manager):
61
- def get_by_natural_key(self, name):
62
- return self.get(name=name)
63
-
64
- class SingleCategoricalValueDistribution(BaseValueDistribution):
65
- """
66
- Single categorical value distribution model.
67
- Assigns a single value based on specified probabilities.
68
- """
69
- objects = SingleCategoricalValueDistributionManager()
70
- categories = models.JSONField() # { "category": "probability", ... }
71
-
72
- def generate_value(self):
73
- categories, probabilities = zip(*self.categories.items())
74
- return np.random.choice(categories, p=probabilities)
75
-
76
-
77
- class MultipleCategoricalValueDistributionManager(models.Manager):
78
- def get_by_natural_key(self, name):
79
- return self.get(name=name)
80
-
81
- class MultipleCategoricalValueDistribution(BaseValueDistribution):
82
- """
83
- Multiple categorical value distribution model.
84
- Assigns a specific number or varying number of values based on probabilities.
85
- """
86
- objects = MultipleCategoricalValueDistributionManager()
87
- categories = models.JSONField() # { "category": "probability", ... }
88
- min_count = models.IntegerField()
89
- max_count = models.IntegerField()
90
- count_distribution_type = models.CharField(max_length=20, choices=[('uniform', 'Uniform'), ('normal', 'Normal')])
91
- count_mean = models.FloatField(null=True, blank=True)
92
- count_std_dev = models.FloatField(null=True, blank=True)
93
-
94
- def generate_value(self):
95
- if self.count_distribution_type == 'uniform':
96
- count = np.random.randint(self.min_count, self.max_count + 1)
97
- elif self.count_distribution_type == 'normal':
98
- count = int(np.random.normal(self.count_mean, self.count_std_dev))
99
- count = np.clip(count, self.min_count, self.max_count)
100
- else:
101
- raise ValueError("Unsupported count distribution type")
102
-
103
- categories, probabilities = zip(*self.categories.items())
104
- return list(np.random.choice(categories, size=count, p=probabilities))
105
-
106
-
107
- class DateValueDistributionManager(models.Manager):
108
- def get_by_natural_key(self, name):
109
- return self.get(name=name)
110
-
111
- from datetime import date, timedelta
112
- class DateValueDistribution(BaseValueDistribution):
113
- """
114
- Assign date values based on specified distribution.
115
- Expects distribution_type (uniform, normal) and mode (date, timedelta) and based on this either
116
- date_min, date_max, date_mean, date_std_dev or
117
- timedelta_days_min, timedelta_days_max, timedelta_days_mean, timedelta_days_std_dev
118
- """
119
- objects = DateValueDistributionManager()
120
- name = models.CharField(max_length=100)
121
- name_de = models.CharField(max_length=100, blank=True, null=True)
122
- name_en = models.CharField(max_length=100, blank=True, null=True)
123
- description = models.TextField(blank=True, null=True)
124
- DISTRIBUTION_CHOICES = [
125
- ('uniform', 'Uniform'),
126
- ('normal', 'Normal'),
127
- ]
128
- MODE_CHOICES = [
129
- ('date', 'Date'),
130
- ('timedelta', 'Timedelta'),
131
- ]
132
-
133
- distribution_type = models.CharField(max_length=20, choices=DISTRIBUTION_CHOICES)
134
- mode = models.CharField(max_length=20, choices=MODE_CHOICES)
135
-
136
- # Date-related fields
137
- date_min = models.DateField(blank=True, null=True)
138
- date_max = models.DateField(blank=True, null=True)
139
- date_mean = models.DateField(blank=True, null=True)
140
- date_std_dev = models.IntegerField(blank=True, null=True) # Standard deviation in days
141
-
142
- # Timedelta-related fields
143
- timedelta_days_min = models.IntegerField(blank=True, null=True)
144
- timedelta_days_max = models.IntegerField(blank=True, null=True)
145
- timedelta_days_mean = models.IntegerField(blank=True, null=True)
146
- timedelta_days_std_dev = models.IntegerField(blank=True, null=True)
147
-
148
- def generate_value(self):
149
- if self.mode == 'date':
150
- return self._generate_date_value()
151
- elif self.mode == 'timedelta':
152
- return self._generate_timedelta_value()
153
- else:
154
- raise ValueError("Unsupported mode")
155
-
156
- def _generate_date_value(self):
157
- #UNTESTED
158
- if self.distribution_type == 'uniform':
159
- start_date = self.date_min.toordinal()
160
- end_date = self.date_max.toordinal()
161
- random_ordinal = np.random.randint(start_date, end_date)
162
- return date.fromordinal(random_ordinal)
163
- elif self.distribution_type == 'normal':
164
- mean_ordinal = self.date_mean.toordinal()
165
- std_dev_days = self.date_std_dev
166
- random_ordinal = int(np.random.normal(mean_ordinal, std_dev_days))
167
- random_ordinal = np.clip(random_ordinal, self.date_min.toordinal(), self.date_max.toordinal())
168
- return date.fromordinal(random_ordinal)
169
- else:
170
- raise ValueError("Unsupported distribution type")
171
-
172
- def _generate_timedelta_value(self):
173
- if self.distribution_type == 'uniform':
174
- random_days = np.random.randint(self.timedelta_days_min, self.timedelta_days_max + 1)
175
-
176
-
177
- elif self.distribution_type == 'normal':
178
- random_days = int(np.random.normal(self.timedelta_days_mean, self.timedelta_days_std_dev))
179
- random_days = np.clip(random_days, self.timedelta_days_min, self.timedelta_days_max)
180
-
181
- else:
182
- raise ValueError("Unsupported distribution type")
183
-
184
- current_date = date.today()
185
- generated_date = current_date - timedelta(days=random_days)
186
- print(generated_date)
187
- return(generated_date)
188
-
189
- # Example Usage
190
- # Numeric distribution for age
191
- # age_distribution = NumericValueDistribution.objects.create(
192
- # name='Age Distribution',
193
- # distribution_type='normal',
194
- # min_value=0,
195
- # max_value=100,
196
- # mean=50,
197
- # std_dev=15
198
- # )
199
-
200
- # # Single categorical distribution for gender
201
- # gender_distribution = SingleCategoricalValueDistribution.objects.create(
202
- # name='Gender Distribution',
203
- # categories={'male': 0.5, 'female': 0.5}
204
- # )
205
-
206
- # # Multiple categorical distribution for symptoms
207
- # symptoms_distribution = MultipleCategoricalValueDistribution.objects.create(
208
- # name='Symptoms Distribution',
209
- # categories={'fever': 0.3, 'cough': 0.4, 'fatigue': 0.2, 'nausea': 0.1},
210
- # min_count=1,
211
- # max_count=3,
212
- # count_distribution_type='normal',
213
- # count_mean=2,
214
- # count_std_dev=0.5
215
- # )
5
+
@@ -14,3 +14,7 @@ class Material(models.Model):
14
14
 
15
15
  def natural_key(self):
16
16
  return (self.name,)
17
+
18
+ def __str__(self):
19
+ result = f"{self.name} - EmissionFactor: {self.emission_factor}"
20
+ return result
@@ -18,4 +18,5 @@ class TransportRoute(models.Model):
18
18
  return (self.name,)
19
19
 
20
20
  def __str__(self):
21
- return self.name
21
+ result = f"{self.name} ({self.distance} {self.unit}) - {self.emission_factor}"
22
+ return result
@@ -0,0 +1,24 @@
1
+ from .patient_examination import PatientExamination
2
+ from .patient_finding import (
3
+ PatientFinding
4
+ )
5
+ from .patient_finding_location import (
6
+ PatientFindingLocation
7
+ )
8
+ from .patient_finding_morphology import (
9
+ PatientFindingMorphology
10
+ )
11
+
12
+ from .patient_finding_intervention import (
13
+ PatientFindingIntervention
14
+ )
15
+
16
+ # TODO Migrate to persons/patient
17
+
18
+ __all__ = [
19
+ "PatientExamination",
20
+ "PatientFinding",
21
+ "PatientFindingLocation",
22
+ "PatientFindingMorphology",
23
+ "PatientFindingIntervention"
24
+ ]
@@ -0,0 +1,182 @@
1
+ from django.db import models
2
+ from typing import List
3
+
4
+ # Serializer located in serializers/examination.py
5
+ from typing import Optional
6
+ from datetime import datetime
7
+
8
+
9
+ class PatientExamination(models.Model):
10
+ patient = models.ForeignKey(
11
+ "Patient", on_delete=models.CASCADE, related_name="patient_examinations"
12
+ )
13
+ examination = models.ForeignKey(
14
+ "Examination", on_delete=models.CASCADE, null=True, blank=True
15
+ )
16
+ video = models.OneToOneField(
17
+ "Video",
18
+ on_delete=models.CASCADE,
19
+ null=True,
20
+ blank=True,
21
+ related_name="patient_examination",
22
+ )
23
+ date_start = models.DateField(null=True, blank=True)
24
+ date_end = models.DateField(null=True, blank=True)
25
+ hash = models.CharField(max_length=255, unique=True)
26
+
27
+ # report_files
28
+ class Meta:
29
+ verbose_name = "Patient Examination"
30
+ verbose_name_plural = "Patient Examinations"
31
+ ordering = ["patient", "examination", "date_start"]
32
+
33
+ @classmethod
34
+ def get_or_create_pseudo_patient_examination_by_hash(
35
+ cls,
36
+ patient_hash: str,
37
+ examination_hash: str,
38
+ examination_name: Optional[str] = None,
39
+ ):
40
+ from endoreg_db.models import Patient, Examination
41
+
42
+ created = False
43
+
44
+ if PatientExamination.objects.filter(
45
+ patient__patient_hash=patient_hash, hash=examination_hash
46
+ ).exists():
47
+ return PatientExamination.objects.get(
48
+ patient__patient_hash=patient_hash, hash=examination_hash
49
+ ), created
50
+
51
+ patient, created = Patient.get_or_create_pseudo_patient_by_hash(patient_hash)
52
+ if examination_name is not None:
53
+ examination = Examination.objects.get(name=examination_name)
54
+ else:
55
+ examination = None
56
+
57
+ patient_examination = cls.objects.create(
58
+ patient=patient, examination=examination, hash=examination_hash
59
+ )
60
+
61
+ patient_examination.save()
62
+
63
+ created = True
64
+ return patient_examination, created
65
+
66
+ def __str__(self):
67
+ return f"{self.patient} - {self.examination} - {self.date_start}"
68
+
69
+ # override save method to make sure that the hash is always set,
70
+ # if none is existing generate an unique string
71
+
72
+ def generate_default_hash(self):
73
+ # create random hash
74
+ import random
75
+ import string
76
+
77
+ _hash = "DEFAULT_HASH_" + "".join(
78
+ random.choices(string.ascii_uppercase + string.digits, k=10)
79
+ )
80
+
81
+ return _hash
82
+
83
+ def save(self, *args, **kwargs):
84
+ if not self.hash:
85
+ self.hash = self.generate_default_hash()
86
+ super().save(*args, **kwargs)
87
+
88
+ def get_patient_age_at_examination(self) -> int:
89
+ """
90
+ Returns the patient's age at the time of the examination.
91
+ """
92
+ from endoreg_db.models import Patient
93
+ from datetime import datetime
94
+
95
+ patient: Patient = self.patient
96
+ dob = patient.get_dob()
97
+ date_start = self.date_start
98
+ return (date_start - dob).days // 365
99
+
100
+ def get_available_findings(self):
101
+ """
102
+ Returns all findings that are associated with the examination of this patient examination.
103
+ """
104
+ from endoreg_db.models import Finding, Examination
105
+
106
+ examination: Examination = self.examination
107
+ findings: List[Finding] = [_ for _ in examination.get_available_findings()]
108
+ return findings
109
+
110
+ def get_findings(self):
111
+ """
112
+ Returns all findings that are associated with this patient examination.
113
+ """
114
+ from endoreg_db.models import PatientFinding
115
+
116
+ patient_findings: List[PatientFinding] = [
117
+ _ for _ in self.patient_findings.all()
118
+ ]
119
+ return patient_findings
120
+
121
+ def get_indications(self):
122
+ """
123
+ Returns all indications that are associated with this patient examination.
124
+ """
125
+ from endoreg_db.models import PatientExaminationIndication
126
+
127
+ indications: List[PatientExaminationIndication] = [
128
+ _ for _ in self.indications.all()
129
+ ]
130
+ return indications
131
+
132
+ def get_indication_choices(self):
133
+ """
134
+ Returns all indication choices that are associated with this patient examination.
135
+ """
136
+ from endoreg_db.models import ExaminationIndicationClassificationChoice
137
+
138
+ choices: List[ExaminationIndicationClassificationChoice] = [
139
+ _.indication_choice for _ in self.get_indications()
140
+ ]
141
+ return choices
142
+
143
+ def create_finding(self, finding):
144
+ """
145
+ Adds a finding to this patient examination.
146
+ """
147
+ from endoreg_db.models import Finding, Examination, PatientFinding
148
+
149
+ examination: Examination = self.examination
150
+ assert examination
151
+
152
+ finding: Finding
153
+
154
+ patient_finding = PatientFinding.objects.create(
155
+ patient_examination=self, finding=finding
156
+ )
157
+
158
+ patient_finding.save()
159
+
160
+ return patient_finding
161
+
162
+ def find_matching_video_from_patient(self):
163
+ """
164
+ Finds a video for this patient examination based on the patient's videos.
165
+ For this, the videos date must be the same as the report file's date.
166
+ #TODO add more criteria for matching: Examination type
167
+ """
168
+ videos = self.patient.video_set.filter(
169
+ date=self.report_file.date, patient_examination__isnull=True
170
+ )
171
+ if videos:
172
+ if len(videos) > 1:
173
+ print(
174
+ f"Warning: Found more than one video for patient {self.patient} on date {self.report_file.date}. Choosing the first one."
175
+ )
176
+ return videos[0]
177
+ else:
178
+ videos = self.patient.video_set.filter(patient_examination__isnull=True)
179
+ if len(videos) == 1:
180
+ return videos[0]
181
+
182
+ return None