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.
- endoreg_db/admin.py +90 -1
- endoreg_db/case_generator/case_generator.py +159 -0
- endoreg_db/case_generator/lab_sample_factory.py +33 -0
- endoreg_db/case_generator/utils.py +30 -0
- endoreg_db/data/__init__.py +50 -4
- endoreg_db/data/ai_model/data.yaml +7 -0
- endoreg_db/data/{label → ai_model_label}/label/data.yaml +27 -1
- endoreg_db/data/ai_model_label/label-set/data.yaml +21 -0
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +5 -0
- endoreg_db/data/ai_model_video_segmentation_label/base_segmentation.yaml +176 -0
- endoreg_db/data/ai_model_video_segmentation_labelset/data.yaml +20 -0
- endoreg_db/data/center/data.yaml +35 -5
- endoreg_db/data/contraindication/bleeding.yaml +11 -0
- endoreg_db/data/distribution/numeric/data.yaml +14 -0
- endoreg_db/data/endoscope/data.yaml +93 -0
- endoreg_db/data/examination_indication/endoscopy.yaml +8 -0
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +8 -0
- endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +101 -0
- endoreg_db/data/finding/data.yaml +141 -0
- endoreg_db/data/finding_intervention/endoscopy.yaml +138 -0
- endoreg_db/data/finding_intervention_type/endoscopy.yaml +15 -0
- endoreg_db/data/finding_location_classification/colonoscopy.yaml +46 -0
- endoreg_db/data/finding_location_classification_choice/colonoscopy.yaml +240 -0
- endoreg_db/data/finding_morphology_classification/colonoscopy.yaml +48 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_circularity_default.yaml +34 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_nice.yaml +20 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_paris.yaml +65 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_planarity_default.yaml +56 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_surface_intact_default.yaml +39 -0
- endoreg_db/data/finding_morphology_classification_choice/colonoscopy_size.yaml +57 -0
- endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +79 -0
- endoreg_db/data/finding_type/data.yaml +30 -0
- endoreg_db/data/gender/data.yaml +17 -0
- endoreg_db/data/lab_value/cardiac_enzymes.yaml +7 -1
- endoreg_db/data/lab_value/coagulation.yaml +6 -1
- endoreg_db/data/lab_value/electrolytes.yaml +39 -1
- endoreg_db/data/lab_value/gastrointestinal_function.yaml +12 -0
- endoreg_db/data/lab_value/hematology.yaml +17 -2
- endoreg_db/data/lab_value/hormones.yaml +6 -0
- endoreg_db/data/lab_value/lipids.yaml +12 -3
- endoreg_db/data/lab_value/misc.yaml +5 -2
- endoreg_db/data/lab_value/renal_function.yaml +2 -1
- endoreg_db/data/lx_client_tag/base.yaml +54 -0
- endoreg_db/data/lx_client_type/base.yaml +30 -0
- endoreg_db/data/lx_permission/base.yaml +24 -0
- endoreg_db/data/lx_permission/endoreg.yaml +52 -0
- endoreg_db/data/medication_indication/anticoagulation.yaml +44 -49
- endoreg_db/data/names_first/first_names.yaml +51 -0
- endoreg_db/data/names_last/last_names.yaml +51 -0
- endoreg_db/data/network_device/data.yaml +30 -0
- endoreg_db/data/organ/data.yaml +29 -0
- endoreg_db/data/pdf_type/data.yaml +2 -1
- endoreg_db/data/report_reader_flag/ukw-examination-generic.yaml +4 -0
- endoreg_db/forms/__init__.py +3 -1
- endoreg_db/forms/examination_form.py +11 -0
- endoreg_db/forms/patient_finding_intervention_form.py +19 -0
- endoreg_db/forms/patient_form.py +26 -0
- endoreg_db/management/commands/__init__.py +0 -0
- endoreg_db/management/commands/load_ai_model_data.py +57 -23
- endoreg_db/management/commands/load_ai_model_label_data.py +59 -0
- endoreg_db/management/commands/load_base_db_data.py +160 -118
- endoreg_db/management/commands/{load_endoscope_type_data.py → load_contraindication_data.py} +3 -7
- endoreg_db/management/commands/load_disease_data.py +29 -7
- endoreg_db/management/commands/load_endoscope_data.py +68 -0
- endoreg_db/management/commands/load_examination_indication_data.py +65 -0
- endoreg_db/management/commands/load_finding_data.py +171 -0
- endoreg_db/management/commands/load_lab_value_data.py +3 -3
- endoreg_db/management/commands/load_lx_data.py +64 -0
- endoreg_db/management/commands/load_medication_data.py +83 -21
- endoreg_db/management/commands/load_name_data.py +37 -0
- endoreg_db/management/commands/{load_endoscopy_processor_data.py → load_organ_data.py} +7 -9
- endoreg_db/migrations/0001_initial.py +1206 -728
- endoreg_db/migrations/0002_alter_frame_image_alter_rawframe_image.py +23 -0
- endoreg_db/migrations/0003_alter_frame_image_alter_rawframe_image.py +23 -0
- endoreg_db/migrations/0004_alter_rawvideofile_file_alter_video_file.py +25 -0
- endoreg_db/migrations/0005_rawvideofile_frame_count_and_more.py +33 -0
- endoreg_db/migrations/0006_frame_extracted_rawframe_extracted.py +23 -0
- endoreg_db/migrations/0007_rename_pseudo_patient_video_patient_and_more.py +24 -0
- endoreg_db/migrations/0008_remove_reportfile_patient_examination_and_more.py +48 -0
- endoreg_db/models/__init__.py +331 -28
- endoreg_db/models/ai_model/__init__.py +1 -0
- endoreg_db/models/ai_model/ai_model.py +103 -0
- endoreg_db/models/ai_model/lightning/__init__.py +3 -0
- endoreg_db/models/ai_model/lightning/inference_dataset.py +53 -0
- endoreg_db/models/ai_model/lightning/multilabel_classification_net.py +155 -0
- endoreg_db/models/ai_model/lightning/postprocess.py +53 -0
- endoreg_db/models/ai_model/lightning/predict.py +172 -0
- endoreg_db/models/ai_model/lightning/prediction_visualizer.py +55 -0
- endoreg_db/models/ai_model/lightning/preprocess.py +68 -0
- endoreg_db/models/ai_model/lightning/run_visualizer.py +21 -0
- endoreg_db/models/ai_model/model_meta.py +232 -6
- endoreg_db/models/ai_model/model_type.py +13 -3
- endoreg_db/models/annotation/__init__.py +31 -2
- endoreg_db/models/annotation/anonymized_image_annotation.py +73 -18
- endoreg_db/models/annotation/binary_classification_annotation_task.py +94 -57
- endoreg_db/models/annotation/image_classification.py +73 -14
- endoreg_db/models/annotation/video_segmentation_annotation.py +52 -0
- endoreg_db/models/annotation/video_segmentation_labelset.py +20 -0
- endoreg_db/models/case/__init__.py +1 -0
- endoreg_db/models/{persons/patient/case → case}/case.py +4 -0
- endoreg_db/models/case_template/__init__.py +10 -1
- endoreg_db/models/case_template/case_template.py +57 -13
- endoreg_db/models/case_template/case_template_rule.py +5 -5
- endoreg_db/models/case_template/case_template_rule_value.py +19 -4
- endoreg_db/models/center/__init__.py +7 -0
- endoreg_db/models/center/center.py +31 -5
- endoreg_db/models/center/center_product.py +0 -1
- endoreg_db/models/center/center_resource.py +16 -2
- endoreg_db/models/center/center_waste.py +6 -1
- endoreg_db/models/contraindication/__init__.py +21 -0
- endoreg_db/models/data_file/__init__.py +38 -5
- endoreg_db/models/data_file/base_classes/__init__.py +6 -1
- endoreg_db/models/data_file/base_classes/abstract_frame.py +64 -15
- endoreg_db/models/data_file/base_classes/abstract_pdf.py +136 -0
- endoreg_db/models/data_file/base_classes/abstract_video.py +744 -138
- endoreg_db/models/data_file/base_classes/frame_helpers.py +17 -0
- endoreg_db/models/data_file/base_classes/prepare_bulk_frames.py +19 -0
- endoreg_db/models/data_file/base_classes/utils.py +80 -0
- endoreg_db/models/data_file/frame.py +22 -38
- endoreg_db/models/data_file/import_classes/__init__.py +4 -18
- endoreg_db/models/data_file/import_classes/raw_pdf.py +162 -90
- endoreg_db/models/data_file/import_classes/raw_video.py +239 -294
- endoreg_db/models/data_file/metadata/__init__.py +10 -0
- endoreg_db/models/data_file/metadata/pdf_meta.py +4 -0
- endoreg_db/models/data_file/metadata/sensitive_meta.py +265 -6
- endoreg_db/models/data_file/metadata/video_meta.py +116 -50
- endoreg_db/models/data_file/report_file.py +30 -63
- endoreg_db/models/data_file/video/__init__.py +6 -2
- endoreg_db/models/data_file/video/video.py +187 -16
- endoreg_db/models/data_file/video_segment.py +162 -55
- endoreg_db/models/disease.py +25 -2
- endoreg_db/models/emission/__init__.py +5 -1
- endoreg_db/models/emission/emission_factor.py +71 -6
- endoreg_db/models/event.py +51 -0
- endoreg_db/models/examination/__init__.py +6 -1
- endoreg_db/models/examination/examination.py +53 -12
- endoreg_db/models/examination/examination_indication.py +170 -0
- endoreg_db/models/examination/examination_time.py +31 -5
- endoreg_db/models/examination/examination_time_type.py +28 -4
- endoreg_db/models/examination/examination_type.py +28 -6
- endoreg_db/models/finding/__init__.py +11 -0
- endoreg_db/models/finding/finding.py +75 -0
- endoreg_db/models/finding/finding_intervention.py +60 -0
- endoreg_db/models/finding/finding_location_classification.py +94 -0
- endoreg_db/models/finding/finding_morphology_classification.py +89 -0
- endoreg_db/models/finding/finding_type.py +22 -0
- endoreg_db/models/hardware/endoscope.py +16 -0
- endoreg_db/models/hardware/endoscopy_processor.py +31 -19
- endoreg_db/models/label/label.py +35 -7
- endoreg_db/models/laboratory/lab_value.py +12 -3
- endoreg_db/models/logging/__init__.py +8 -1
- endoreg_db/models/lx/__init__.py +4 -0
- endoreg_db/models/lx/client.py +57 -0
- endoreg_db/models/lx/identity.py +34 -0
- endoreg_db/models/lx/permission.py +18 -0
- endoreg_db/models/lx/user.py +16 -0
- endoreg_db/models/medication/__init__.py +19 -1
- endoreg_db/models/medication/medication.py +7 -122
- endoreg_db/models/medication/medication_indication.py +50 -0
- endoreg_db/models/medication/medication_indication_type.py +34 -0
- endoreg_db/models/medication/medication_intake_time.py +26 -0
- endoreg_db/models/medication/medication_schedule.py +37 -0
- endoreg_db/models/network/__init__.py +7 -1
- endoreg_db/models/network/network_device.py +13 -8
- endoreg_db/models/organ/__init__.py +38 -0
- endoreg_db/models/other/__init__.py +19 -1
- endoreg_db/models/other/distribution/__init__.py +44 -0
- endoreg_db/models/other/distribution/base_value_distribution.py +20 -0
- endoreg_db/models/other/distribution/date_value_distribution.py +91 -0
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +32 -0
- endoreg_db/models/other/distribution/numeric_value_distribution.py +97 -0
- endoreg_db/models/other/distribution/single_categorical_value_distribution.py +22 -0
- endoreg_db/models/other/distribution.py +1 -211
- endoreg_db/models/other/material.py +4 -0
- endoreg_db/models/other/transport_route.py +2 -1
- endoreg_db/models/patient/__init__.py +24 -0
- endoreg_db/models/patient/patient_examination.py +182 -0
- endoreg_db/models/patient/patient_finding.py +143 -0
- endoreg_db/models/patient/patient_finding_intervention.py +26 -0
- endoreg_db/models/patient/patient_finding_location.py +120 -0
- endoreg_db/models/patient/patient_finding_morphology.py +166 -0
- endoreg_db/models/persons/__init__.py +29 -2
- endoreg_db/models/persons/examiner/examiner.py +48 -4
- endoreg_db/models/persons/patient/__init__.py +1 -1
- endoreg_db/models/persons/patient/patient.py +227 -54
- endoreg_db/models/persons/patient/patient_disease.py +6 -0
- endoreg_db/models/persons/patient/patient_event.py +31 -1
- endoreg_db/models/persons/patient/patient_examination_indication.py +32 -0
- endoreg_db/models/persons/patient/patient_lab_sample.py +4 -2
- endoreg_db/models/persons/patient/patient_lab_value.py +37 -16
- endoreg_db/models/persons/patient/patient_medication.py +27 -12
- endoreg_db/models/persons/patient/patient_medication_schedule.py +62 -2
- endoreg_db/models/prediction/__init__.py +7 -1
- endoreg_db/models/prediction/image_classification.py +20 -6
- endoreg_db/models/prediction/video_prediction_meta.py +151 -89
- endoreg_db/models/product/__init__.py +10 -1
- endoreg_db/models/product/product.py +15 -2
- endoreg_db/models/product/product_group.py +8 -0
- endoreg_db/models/product/product_material.py +4 -0
- endoreg_db/models/product/product_weight.py +12 -0
- endoreg_db/models/product/reference_product.py +19 -3
- endoreg_db/models/quiz/__init__.py +8 -1
- endoreg_db/models/report_reader/__init__.py +6 -1
- endoreg_db/serializers/__init__.py +1 -1
- endoreg_db/serializers/annotation.py +2 -5
- endoreg_db/serializers/frame.py +1 -5
- endoreg_db/serializers/patient.py +26 -3
- endoreg_db/serializers/prediction.py +2 -7
- endoreg_db/serializers/raw_video_meta_validation.py +13 -0
- endoreg_db/serializers/video.py +6 -13
- endoreg_db/serializers/video_segmentation.py +492 -0
- endoreg_db/templates/admin/patient_finding_intervention.html +253 -0
- endoreg_db/templates/admin/start_examination.html +12 -0
- endoreg_db/templates/timeline.html +176 -0
- endoreg_db/urls.py +173 -0
- endoreg_db/utils/__init__.py +36 -1
- endoreg_db/utils/dataloader.py +45 -19
- endoreg_db/utils/dates.py +39 -0
- endoreg_db/utils/hashs.py +122 -4
- endoreg_db/utils/names.py +74 -0
- endoreg_db/utils/parse_and_generate_yaml.py +46 -0
- endoreg_db/utils/pydantic_models/__init__.py +6 -0
- endoreg_db/utils/pydantic_models/db_config.py +57 -0
- endoreg_db/utils/validate_endo_roi.py +19 -0
- endoreg_db/utils/validate_subcategory_dict.py +91 -0
- endoreg_db/utils/video/__init__.py +13 -0
- endoreg_db/utils/video/extract_frames.py +121 -0
- endoreg_db/utils/video/transcode_videofile.py +111 -0
- endoreg_db/views/__init__.py +2 -0
- endoreg_db/views/csrf.py +7 -0
- endoreg_db/views/patient_views.py +90 -0
- endoreg_db/views/raw_video_meta_validation_views.py +38 -0
- endoreg_db/views/report_views.py +96 -0
- endoreg_db/views/video_segmentation_views.py +149 -0
- endoreg_db/views/views_for_timeline.py +46 -0
- endoreg_db/views.py +0 -3
- endoreg_db-0.6.1.dist-info/METADATA +151 -0
- endoreg_db-0.6.1.dist-info/RECORD +420 -0
- {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/WHEEL +1 -1
- endoreg_db/data/active_model/data.yaml +0 -3
- endoreg_db/data/label/label-set/data.yaml +0 -18
- endoreg_db/management/commands/delete_legacy_images.py +0 -19
- endoreg_db/management/commands/delete_legacy_videos.py +0 -17
- endoreg_db/management/commands/extract_legacy_video_frames.py +0 -18
- endoreg_db/management/commands/import_legacy_images.py +0 -94
- endoreg_db/management/commands/import_legacy_videos.py +0 -76
- endoreg_db/management/commands/load_label_data.py +0 -67
- endoreg_db/migrations/0002_anonymizedimagelabel_anonymousimageannotation_and_more.py +0 -55
- endoreg_db/migrations/0003_anonymousimageannotation_original_image_url_and_more.py +0 -39
- endoreg_db/migrations/0004_alter_rawpdffile_file.py +0 -20
- endoreg_db/migrations/0005_uploadedfile_alter_rawpdffile_file_anonymizedfile.py +0 -40
- endoreg_db/migrations/0006_alter_rawpdffile_file.py +0 -20
- endoreg_db/migrations/0007_networkdevicelogentry_datetime_and_more.py +0 -43
- endoreg_db/migrations/0008_networkdevicelogentry_aglnet_ip_and_more.py +0 -28
- endoreg_db/migrations/0009_alter_networkdevicelogentry_vpn_service_status.py +0 -18
- endoreg_db/migrations/0010_remove_networkdevicelogentry_hostname.py +0 -17
- endoreg_db/models/legacy_data/__init__.py +0 -3
- endoreg_db/models/legacy_data/image.py +0 -34
- endoreg_db/models/patient_examination/__init__.py +0 -35
- endoreg_db/utils/video_metadata.py +0 -87
- endoreg_db-0.5.3.dist-info/METADATA +0 -28
- endoreg_db-0.5.3.dist-info/RECORD +0 -319
- /endoreg_db/{models/persons/patient/case → case_generator}/__init__.py +0 -0
- /endoreg_db/data/{label → ai_model_label}/label-type/data.yaml +0 -0
- /endoreg_db/data/{model_type → ai_model_type}/data.yaml +0 -0
- /endoreg_db/{data/distribution/numeric/.init → management/__init__.py} +0 -0
- /endoreg_db/management/commands/{load_report_reader_flag.py → load_report_reader_flag_data.py} +0 -0
- {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(
|
|
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 =
|
|
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 .
|
|
8
|
+
from .patient_examination_indication import PatientExaminationIndication
|