endoreg-db 0.8.8.0__py3-none-any.whl → 0.8.8.9__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/data/__init__.py +22 -8
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +0 -1
- endoreg_db/data/examination/examinations/data.yaml +114 -14
- endoreg_db/data/examination/time-type/data.yaml +0 -3
- endoreg_db/data/examination_indication/endoscopy.yaml +108 -173
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +0 -70
- endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +33 -37
- endoreg_db/data/finding/00_generic.yaml +35 -0
- endoreg_db/data/finding/00_generic_complication.yaml +9 -0
- endoreg_db/data/finding/01_gastroscopy_baseline.yaml +88 -0
- endoreg_db/data/finding/01_gastroscopy_observation.yaml +113 -0
- endoreg_db/data/finding/02_colonoscopy_baseline.yaml +53 -0
- endoreg_db/data/finding/02_colonoscopy_hidden.yaml +119 -0
- endoreg_db/data/finding/02_colonoscopy_observation.yaml +152 -0
- endoreg_db/data/finding_classification/00_generic.yaml +44 -0
- endoreg_db/data/finding_classification/00_generic_histology.yaml +28 -0
- endoreg_db/data/finding_classification/00_generic_lesion.yaml +52 -0
- endoreg_db/data/finding_classification/{colonoscopy_bowel_preparation.yaml → 02_colonoscopy_baseline.yaml} +35 -20
- endoreg_db/data/finding_classification/02_colonoscopy_histology.yaml +13 -0
- endoreg_db/data/finding_classification/02_colonoscopy_other.yaml +12 -0
- endoreg_db/data/finding_classification/02_colonoscopy_polyp.yaml +101 -0
- endoreg_db/data/finding_classification_choice/{yes_no_na.yaml → 00_generic.yaml} +5 -1
- endoreg_db/data/finding_classification_choice/{examination_setting_generic_types.yaml → 00_generic_baseline.yaml} +10 -2
- endoreg_db/data/finding_classification_choice/{complication_generic_types.yaml → 00_generic_complication.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology.yaml → 00_generic_histology.yaml} +1 -4
- endoreg_db/data/finding_classification_choice/00_generic_lesion.yaml +158 -0
- endoreg_db/data/finding_classification_choice/{bowel_preparation.yaml → 02_colonoscopy_bowel_preparation.yaml} +1 -30
- endoreg_db/data/{_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml → finding_classification_choice/02_colonoscopy_generic.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology_polyp.yaml → 02_colonoscopy_histology.yaml} +1 -1
- endoreg_db/data/{_examples/finding_classification_choice/colonoscopy_location.yaml → finding_classification_choice/02_colonoscopy_location.yaml} +23 -4
- endoreg_db/data/finding_classification_choice/02_colonoscopy_other.yaml +34 -0
- endoreg_db/data/finding_classification_choice/02_colonoscopy_polyp_advanced_imaging.yaml +76 -0
- endoreg_db/data/{_examples/finding_classification_choice/colon_lesion_paris.yaml → finding_classification_choice/02_colonoscopy_polyp_morphology.yaml} +26 -8
- endoreg_db/data/finding_classification_choice/02_colonoscopy_size.yaml +27 -0
- endoreg_db/data/finding_classification_type/{colonoscopy_basic.yaml → 00_generic.yaml} +18 -13
- endoreg_db/data/finding_classification_type/02_colonoscopy.yaml +9 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy.yaml +59 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_ablation.yaml +44 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_bleeding.yaml +55 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_resection.yaml +85 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_stenosis.yaml +17 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_stent.yaml +9 -0
- endoreg_db/data/finding_intervention/01_gastroscopy.yaml +19 -0
- endoreg_db/data/finding_intervention/04_eus.yaml +39 -0
- endoreg_db/data/finding_intervention/05_ercp.yaml +3 -0
- endoreg_db/data/finding_type/data.yaml +8 -12
- endoreg_db/data/requirement/01_patient_data.yaml +93 -0
- endoreg_db/data/requirement_operator/new_operators.yaml +36 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +0 -2
- endoreg_db/data/requirement_set/90_coloreg.yaml +20 -8
- endoreg_db/exceptions.py +0 -1
- endoreg_db/forms/examination_form.py +1 -1
- endoreg_db/helpers/data_loader.py +124 -52
- endoreg_db/helpers/default_objects.py +116 -81
- endoreg_db/import_files/__init__.py +27 -0
- endoreg_db/import_files/context/__init__.py +7 -0
- endoreg_db/import_files/context/default_sensitive_meta.py +81 -0
- endoreg_db/import_files/context/ensure_center.py +17 -0
- endoreg_db/import_files/context/file_lock.py +66 -0
- endoreg_db/import_files/context/import_context.py +43 -0
- endoreg_db/import_files/context/validate_directories.py +56 -0
- endoreg_db/import_files/file_storage/__init__.py +15 -0
- endoreg_db/import_files/file_storage/create_report_file.py +76 -0
- endoreg_db/import_files/file_storage/create_video_file.py +75 -0
- endoreg_db/import_files/file_storage/sensitive_meta_storage.py +39 -0
- endoreg_db/import_files/file_storage/state_management.py +400 -0
- endoreg_db/import_files/file_storage/storage.py +36 -0
- endoreg_db/import_files/import_service.md +26 -0
- endoreg_db/import_files/processing/__init__.py +11 -0
- endoreg_db/import_files/processing/report_processing/report_anonymization.py +94 -0
- endoreg_db/import_files/processing/sensitive_meta_adapter.py +51 -0
- endoreg_db/import_files/processing/video_processing/video_anonymization.py +107 -0
- endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +119 -0
- endoreg_db/import_files/pseudonymization/fake.py +52 -0
- endoreg_db/import_files/pseudonymization/k_anonymity.py +182 -0
- endoreg_db/import_files/pseudonymization/k_pseudonymity.py +128 -0
- endoreg_db/import_files/report_import_service.py +141 -0
- endoreg_db/import_files/video_import_service.py +150 -0
- endoreg_db/management/commands/import_report.py +130 -65
- endoreg_db/management/commands/import_video_with_classification.py +1 -1
- endoreg_db/management/commands/load_ai_model_data.py +5 -5
- endoreg_db/management/commands/load_ai_model_label_data.py +9 -7
- endoreg_db/management/commands/load_base_db_data.py +5 -134
- endoreg_db/management/commands/load_contraindication_data.py +14 -16
- endoreg_db/management/commands/load_disease_classification_choices_data.py +15 -18
- endoreg_db/management/commands/load_disease_classification_data.py +15 -18
- endoreg_db/management/commands/load_disease_data.py +25 -28
- endoreg_db/management/commands/load_endoscope_data.py +20 -27
- endoreg_db/management/commands/load_event_data.py +14 -16
- endoreg_db/management/commands/load_examination_data.py +31 -44
- endoreg_db/management/commands/load_examination_indication_data.py +20 -21
- endoreg_db/management/commands/load_finding_data.py +52 -80
- endoreg_db/management/commands/load_information_source.py +21 -23
- endoreg_db/management/commands/load_lab_value_data.py +17 -26
- endoreg_db/management/commands/load_medication_data.py +13 -12
- endoreg_db/management/commands/load_organ_data.py +15 -19
- endoreg_db/management/commands/load_pdf_type_data.py +19 -18
- endoreg_db/management/commands/load_profession_data.py +14 -17
- endoreg_db/management/commands/load_qualification_data.py +20 -23
- endoreg_db/management/commands/load_report_reader_flag_data.py +17 -19
- endoreg_db/management/commands/load_requirement_data.py +14 -20
- endoreg_db/management/commands/load_risk_data.py +7 -6
- endoreg_db/management/commands/load_shift_data.py +20 -23
- endoreg_db/management/commands/load_tag_data.py +8 -11
- endoreg_db/management/commands/load_unit_data.py +17 -19
- endoreg_db/management/commands/start_filewatcher.py +46 -37
- endoreg_db/management/commands/validate_video_files.py +1 -5
- endoreg_db/migrations/0001_initial.py +1360 -1812
- endoreg_db/models/administration/person/patient/patient.py +72 -46
- endoreg_db/models/label/__init__.py +2 -2
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +18 -26
- endoreg_db/models/label/label_video_segment/label_video_segment.py +23 -1
- endoreg_db/models/media/pdf/raw_pdf.py +136 -64
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +34 -10
- endoreg_db/models/media/processing_history/__init__.py +5 -0
- endoreg_db/models/media/processing_history/processing_history.py +96 -0
- endoreg_db/models/media/video/create_from_file.py +101 -31
- endoreg_db/models/media/video/video_file.py +125 -105
- endoreg_db/models/media/video/video_file_io.py +31 -26
- endoreg_db/models/medical/contraindication/README.md +1 -0
- endoreg_db/models/medical/examination/examination.py +28 -8
- endoreg_db/models/medical/examination/examination_indication.py +13 -79
- endoreg_db/models/medical/examination/examination_time.py +8 -3
- endoreg_db/models/medical/finding/finding.py +5 -12
- endoreg_db/models/medical/finding/finding_classification.py +18 -37
- endoreg_db/models/medical/finding/finding_intervention.py +7 -9
- endoreg_db/models/medical/hardware/endoscope.py +6 -0
- endoreg_db/models/medical/patient/medication_examples.py +5 -1
- endoreg_db/models/medical/patient/patient_finding.py +1 -1
- endoreg_db/models/metadata/pdf_meta.py +22 -10
- endoreg_db/models/metadata/sensitive_meta.py +3 -0
- endoreg_db/models/metadata/sensitive_meta_logic.py +200 -124
- endoreg_db/models/other/information_source.py +27 -6
- endoreg_db/models/report/__init__.py +0 -0
- endoreg_db/models/report/images.py +0 -0
- endoreg_db/models/report/report.py +6 -0
- endoreg_db/models/requirement/requirement.py +59 -399
- endoreg_db/models/requirement/requirement_operator.py +86 -98
- endoreg_db/models/state/audit_ledger.py +4 -5
- endoreg_db/models/state/raw_pdf.py +69 -30
- endoreg_db/models/state/video.py +64 -49
- endoreg_db/models/upload_job.py +33 -9
- endoreg_db/models/utils.py +27 -23
- endoreg_db/queries/__init__.py +3 -1
- endoreg_db/schemas/examination_evaluation.py +1 -1
- endoreg_db/serializers/__init__.py +2 -8
- endoreg_db/serializers/label_video_segment/label_video_segment.py +2 -29
- endoreg_db/serializers/meta/__init__.py +1 -6
- endoreg_db/serializers/misc/sensitive_patient_data.py +50 -26
- endoreg_db/serializers/patient_examination/patient_examination.py +3 -3
- endoreg_db/serializers/pdf/anony_text_validation.py +39 -23
- endoreg_db/serializers/video/video_file_list.py +65 -34
- endoreg_db/services/__old/pdf_import.py +1487 -0
- endoreg_db/services/__old/video_import.py +1306 -0
- endoreg_db/services/anonymization.py +63 -26
- endoreg_db/services/lookup_service.py +28 -28
- endoreg_db/services/lookup_store.py +2 -2
- endoreg_db/services/pdf_import.py +0 -1480
- endoreg_db/services/report_import.py +10 -0
- endoreg_db/services/video_import.py +6 -1165
- endoreg_db/tasks/upload_tasks.py +79 -70
- endoreg_db/tasks/video_ingest.py +8 -4
- endoreg_db/urls/__init__.py +0 -14
- endoreg_db/urls/ai.py +32 -0
- endoreg_db/urls/media.py +21 -24
- endoreg_db/utils/dataloader.py +87 -57
- endoreg_db/utils/paths.py +110 -46
- endoreg_db/utils/pipelines/Readme.md +1 -1
- endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +97 -0
- endoreg_db/views/__init__.py +85 -173
- endoreg_db/views/ai/__init__.py +8 -0
- endoreg_db/views/ai/label.py +155 -0
- endoreg_db/views/anonymization/media_management.py +8 -7
- endoreg_db/views/anonymization/overview.py +97 -68
- endoreg_db/views/anonymization/validate.py +25 -21
- endoreg_db/views/media/__init__.py +5 -20
- endoreg_db/views/media/pdf_media.py +109 -65
- endoreg_db/views/media/sensitive_metadata.py +163 -148
- endoreg_db/views/meta/__init__.py +0 -8
- endoreg_db/views/misc/__init__.py +1 -7
- endoreg_db/views/misc/upload_views.py +94 -93
- endoreg_db/views/report/__init__.py +7 -0
- endoreg_db/views/{pdf → report}/reimport.py +45 -24
- endoreg_db/views/{pdf/pdf_stream.py → report/report_stream.py} +40 -32
- endoreg_db/views/requirement/lookup_store.py +22 -90
- endoreg_db/views/video/__init__.py +23 -22
- endoreg_db/views/video/correction.py +201 -172
- endoreg_db/views/video/reimport.py +1 -1
- endoreg_db/views/{media/video_segments.py → video/segments_crud.py} +75 -37
- endoreg_db/views/video/{video_meta.py → video_meta_stats.py} +2 -2
- endoreg_db/views/video/video_stream.py +7 -8
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.8.9.dist-info}/METADATA +2 -2
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.8.9.dist-info}/RECORD +217 -335
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.8.9.dist-info}/WHEEL +1 -1
- endoreg_db/data/_examples/disease.yaml +0 -55
- endoreg_db/data/_examples/disease_classification.yaml +0 -13
- endoreg_db/data/_examples/disease_classification_choice.yaml +0 -62
- endoreg_db/data/_examples/event.yaml +0 -64
- endoreg_db/data/_examples/examination.yaml +0 -72
- endoreg_db/data/_examples/finding/anatomy_colon.yaml +0 -128
- endoreg_db/data/_examples/finding/colonoscopy.yaml +0 -40
- endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +0 -56
- endoreg_db/data/_examples/finding/complication.yaml +0 -16
- endoreg_db/data/_examples/finding/data.yaml +0 -105
- endoreg_db/data/_examples/finding/examination_setting.yaml +0 -16
- endoreg_db/data/_examples/finding/medication_related.yaml +0 -18
- endoreg_db/data/_examples/finding/outcome.yaml +0 -12
- endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +0 -68
- endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +0 -22
- endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +0 -25
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +0 -68
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
- endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +0 -80
- endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +0 -21
- endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +0 -20
- endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +0 -26
- endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +0 -22
- endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +0 -53
- endoreg_db/data/_examples/finding_classification/complication_generic.yaml +0 -25
- endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +0 -40
- endoreg_db/data/_examples/finding_classification/histology_colo.yaml +0 -51
- endoreg_db/data/_examples/finding_classification/intervention_required.yaml +0 -26
- endoreg_db/data/_examples/finding_classification/medication_related.yaml +0 -23
- endoreg_db/data/_examples/finding_classification/visualized.yaml +0 -33
- endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +0 -78
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +0 -17
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +0 -14
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +0 -82
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/histology.yaml +0 -24
- endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +0 -20
- endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +0 -19
- endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +0 -11
- endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +0 -48
- endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +0 -43
- endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
- endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +0 -128
- endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +0 -32
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +0 -9
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +0 -36
- endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +0 -15
- endoreg_db/data/_examples/finding_type/data.yaml +0 -43
- endoreg_db/data/_examples/requirement/age.yaml +0 -26
- endoreg_db/data/_examples/requirement/gender.yaml +0 -25
- endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +0 -48
- endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +0 -57
- endoreg_db/data/_examples/requirement_set/endoscopy_bleeding_risk.yaml +0 -52
- endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
- endoreg_db/data/finding/anatomy_colon.yaml +0 -128
- endoreg_db/data/finding/colonoscopy.yaml +0 -40
- endoreg_db/data/finding/colonoscopy_bowel_prep.yaml +0 -56
- endoreg_db/data/finding/complication.yaml +0 -16
- endoreg_db/data/finding/data.yaml +0 -105
- endoreg_db/data/finding/examination_setting.yaml +0 -16
- endoreg_db/data/finding/medication_related.yaml +0 -18
- endoreg_db/data/finding/outcome.yaml +0 -12
- endoreg_db/data/finding_classification/colonoscopy_jnet.yaml +0 -22
- endoreg_db/data/finding_classification/colonoscopy_kudo.yaml +0 -25
- endoreg_db/data/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +0 -38
- endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +0 -49
- endoreg_db/data/finding_classification/colonoscopy_lst.yaml +0 -21
- endoreg_db/data/finding_classification/colonoscopy_nice.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_paris.yaml +0 -26
- endoreg_db/data/finding_classification/colonoscopy_sano.yaml +0 -22
- endoreg_db/data/finding_classification/colonoscopy_summary.yaml +0 -53
- endoreg_db/data/finding_classification/complication_generic.yaml +0 -25
- endoreg_db/data/finding_classification/examination_setting_generic.yaml +0 -40
- endoreg_db/data/finding_classification/histology_colo.yaml +0 -43
- endoreg_db/data/finding_classification/intervention_required.yaml +0 -26
- endoreg_db/data/finding_classification/medication_related.yaml +0 -23
- endoreg_db/data/finding_classification/visualized.yaml +0 -33
- endoreg_db/data/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
- endoreg_db/data/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
- endoreg_db/data/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
- endoreg_db/data/finding_classification_choice/colon_lesion_lst.yaml +0 -15
- endoreg_db/data/finding_classification_choice/colon_lesion_nice.yaml +0 -17
- endoreg_db/data/finding_classification_choice/colon_lesion_paris.yaml +0 -57
- endoreg_db/data/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
- endoreg_db/data/finding_classification_choice/colon_lesion_sano.yaml +0 -14
- endoreg_db/data/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
- endoreg_db/data/finding_classification_choice/colonoscopy_location.yaml +0 -229
- endoreg_db/data/finding_classification_choice/colonoscopy_not_complete_reason.yaml +0 -19
- endoreg_db/data/finding_classification_choice/colonoscopy_size.yaml +0 -82
- endoreg_db/data/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
- endoreg_db/data/finding_classification_choice/outcome.yaml +0 -19
- endoreg_db/data/finding_intervention/endoscopy.yaml +0 -43
- endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
- endoreg_db/data/finding_intervention/endoscopy_egd.yaml +0 -128
- endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +0 -32
- endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +0 -9
- endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +0 -36
- endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +0 -79
- endoreg_db/data/requirement/age.yaml +0 -26
- endoreg_db/data/requirement/colonoscopy_baseline_austria.yaml +0 -45
- endoreg_db/data/requirement/disease_cardiovascular.yaml +0 -79
- endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +0 -41
- endoreg_db/data/requirement/disease_hepatology.yaml +0 -12
- endoreg_db/data/requirement/disease_misc.yaml +0 -12
- endoreg_db/data/requirement/disease_renal.yaml +0 -96
- endoreg_db/data/requirement/endoscopy_bleeding_risk.yaml +0 -59
- endoreg_db/data/requirement/event_cardiology.yaml +0 -251
- endoreg_db/data/requirement/event_requirements.yaml +0 -145
- endoreg_db/data/requirement/finding_colon_polyp.yaml +0 -50
- endoreg_db/data/requirement/gender.yaml +0 -25
- endoreg_db/data/requirement/lab_value.yaml +0 -441
- endoreg_db/data/requirement/medication.yaml +0 -93
- endoreg_db/data/requirement_operator/age.yaml +0 -13
- endoreg_db/data/requirement_operator/lab_operators.yaml +0 -129
- endoreg_db/data/requirement_operator/model_operators.yaml +0 -96
- endoreg_db/management/commands/init_default_ai_model.py +0 -112
- endoreg_db/management/commands/reset_celery_schedule.py +0 -9
- endoreg_db/management/commands/validate_video.py +0 -204
- endoreg_db/migrations/0002_requirementset_depends_on.py +0 -18
- endoreg_db/migrations/_old/0001_initial.py +0 -1857
- endoreg_db/migrations/_old/0002_add_video_correction_models.py +0 -52
- endoreg_db/migrations/_old/0003_add_center_display_name.py +0 -30
- endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +0 -68
- endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +0 -77
- endoreg_db/migrations/_old/0005_merge_20251111_1003.py +0 -14
- endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +0 -68
- endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +0 -89
- endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +0 -27
- endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +0 -21
- endoreg_db/renames.yml +0 -8
- endoreg_db/serializers/_old/raw_pdf_meta_validation.py +0 -223
- endoreg_db/serializers/_old/raw_video_meta_validation.py +0 -179
- endoreg_db/serializers/_old/video.py +0 -71
- endoreg_db/serializers/meta/pdf_file_meta_extraction.py +0 -115
- endoreg_db/serializers/meta/report_meta.py +0 -53
- endoreg_db/serializers/report/__init__.py +0 -9
- endoreg_db/serializers/report/mixins.py +0 -45
- endoreg_db/serializers/report/report.py +0 -105
- endoreg_db/serializers/report/report_list.py +0 -22
- endoreg_db/serializers/report/secure_file_url.py +0 -26
- endoreg_db/services/requirements_object.py +0 -147
- endoreg_db/services/storage_aware_video_processor.py +0 -370
- endoreg_db/urls/files.py +0 -6
- endoreg_db/urls/label_video_segment_validate.py +0 -33
- endoreg_db/urls/label_video_segments.py +0 -46
- endoreg_db/views/label/__init__.py +0 -5
- endoreg_db/views/label/label.py +0 -15
- endoreg_db/views/label_video_segment/__init__.py +0 -16
- endoreg_db/views/label_video_segment/create_lvs_from_annotation.py +0 -44
- endoreg_db/views/label_video_segment/get_lvs_by_name_and_video.py +0 -50
- endoreg_db/views/label_video_segment/label_video_segment.py +0 -77
- endoreg_db/views/label_video_segment/label_video_segment_by_label.py +0 -174
- endoreg_db/views/label_video_segment/label_video_segment_detail.py +0 -73
- endoreg_db/views/label_video_segment/update_lvs_from_annotation.py +0 -46
- endoreg_db/views/label_video_segment/validate.py +0 -226
- endoreg_db/views/media/segments.py +0 -71
- endoreg_db/views/meta/available_files_list.py +0 -146
- endoreg_db/views/meta/report_meta.py +0 -53
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -85
- endoreg_db/views/misc/secure_file_serving_view.py +0 -80
- endoreg_db/views/misc/secure_file_url_view.py +0 -84
- endoreg_db/views/misc/secure_url_validate.py +0 -79
- endoreg_db/views/patient_examination/DEPRECATED_video_backup.py +0 -164
- endoreg_db/views/patient_finding_location/__init__.py +0 -5
- endoreg_db/views/patient_finding_location/pfl_create.py +0 -70
- endoreg_db/views/patient_finding_morphology/__init__.py +0 -5
- endoreg_db/views/patient_finding_morphology/pfm_create.py +0 -70
- endoreg_db/views/pdf/__init__.py +0 -8
- endoreg_db/views/video/segmentation.py +0 -274
- endoreg_db/views/video/task_status.py +0 -49
- endoreg_db/views/video/timeline.py +0 -46
- endoreg_db/views/video/video_analyze.py +0 -52
- /endoreg_db/data/requirement/{colon_polyp_intervention.yaml → old/colon_polyp_intervention.yaml} +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/colonoscopy_baseline_austria.yaml +0 -0
- /endoreg_db/data/requirement/{coloreg_colon_polyp.yaml → old/coloreg_colon_polyp.yaml} +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_cardiovascular.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_classification_choice_cardiovascular.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_hepatology.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_misc.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_renal.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/endoscopy_bleeding_risk.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/event_cardiology.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/event_requirements.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/finding_colon_polyp.yaml +0 -0
- /endoreg_db/{migrations/__init__.py → data/requirement/old/gender.yaml} +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/lab_value.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/medication.yaml +0 -0
- /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/age.yaml +0 -0
- /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/lab_operators.yaml +0 -0
- /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/model_operators.yaml +0 -0
- /endoreg_db/{urls/sensitive_meta.py → import_files/pseudonymization/__init__.py} +0 -0
- /endoreg_db/{views/pdf/pdf_stream_views.py → import_files/pseudonymization/pseudonymize.py} +0 -0
- /endoreg_db/utils/requirement_operator_logic/{lab_value_operators.py → _old/lab_value_operators.py} +0 -0
- /endoreg_db/utils/requirement_operator_logic/{model_evaluators.py → _old/model_evaluators.py} +0 -0
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.8.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# File Import and Anonymization
|
|
2
|
+
|
|
3
|
+
Endoreg-Db imports are guarded by a anonymization step, that is supposed to ensure most data is redacted from the input. Here, fake patients are generated to pseudonymize the sensitive information in the data. This ensures, that videos as well as pdfs are not distributed using sensitive data, and if they are by some accident it is harder to know what data is actually real.
|
|
4
|
+
|
|
5
|
+
The Import is handled by two orchestration files:
|
|
6
|
+
|
|
7
|
+
Report import service (RIS)
|
|
8
|
+
|
|
9
|
+
and
|
|
10
|
+
|
|
11
|
+
Video import Service (VIS)
|
|
12
|
+
|
|
13
|
+
The orchestration is abstracted out by the base import service (BIS), to ensure newly implemented data imports follow the same structure and to ensure tests run agnostically of the actual media being processed.
|
|
14
|
+
|
|
15
|
+
## Import Order of Execution
|
|
16
|
+
|
|
17
|
+
The Import starts, when files are dropped into the corresponding media import folders. The locations need to be passed to the import service logic. To ensure atomic processing without overwhelming the server or double processing on parallelization, a file lock is added to the files that are currently processed.
|
|
18
|
+
|
|
19
|
+
### File Lock
|
|
20
|
+
|
|
21
|
+
File Lock is implemented as a context manager. Per default, this means during the execution the files are marked by adding a additional .lock file path inside the folder. Once the code wrapped in the context manager of file lock stops execution, the .lock file is removed only after error processing. This ensures, the full pipeline is executed on each run even when interrupted.
|
|
22
|
+
https://book.pythontips.com/en/latest/context_managers.html
|
|
23
|
+
|
|
24
|
+
### Error Cleanup
|
|
25
|
+
|
|
26
|
+
The ErrorCleanup class is called from inside the file lock context manager to avoid leaving half processed files laying around. It passes file type to the class instance and then runs the correct processing logic.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Callable, Literal, NoReturn
|
|
6
|
+
|
|
7
|
+
from lx_anonymizer import ReportReader
|
|
8
|
+
from lx_anonymizer.sensitive_meta_interface import SensitiveMeta as LxSM
|
|
9
|
+
|
|
10
|
+
from endoreg_db.import_files.context import ImportContext
|
|
11
|
+
from endoreg_db.import_files.file_storage.sensitive_meta_storage import sensitive_meta_storage
|
|
12
|
+
from endoreg_db.utils.paths import ANONYM_REPORT_DIR
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ReportAnonymizer:
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._report_reader_class = None
|
|
21
|
+
self._ensure_report_reading_available()
|
|
22
|
+
self.storage = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def anonymize_report(self, ctx: ImportContext):
|
|
26
|
+
|
|
27
|
+
# Setup anonymized directory
|
|
28
|
+
anonymized_dir = ANONYM_REPORT_DIR
|
|
29
|
+
anonymized_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
assert ctx.current_report is not None
|
|
31
|
+
# Generate output path for anonymized report
|
|
32
|
+
pdf_hash = ctx.current_report.pdf_hash
|
|
33
|
+
anonymized_output_path = anonymized_dir / f"{pdf_hash}.pdf"
|
|
34
|
+
self._report_reader_class = ReportReader()
|
|
35
|
+
|
|
36
|
+
assert isinstance(self._report_reader_class, ReportReader)
|
|
37
|
+
|
|
38
|
+
# Process with enhanced process_report method (returns 4-tuple now)
|
|
39
|
+
ctx.original_text, ctx.anonymized_text, extracted_metadata, ctx.anonymized_path = self._report_reader_class.process_report(
|
|
40
|
+
pdf_path=ctx.file_path,
|
|
41
|
+
create_anonymized_pdf=True,
|
|
42
|
+
anonymized_pdf_output_path=str(anonymized_output_path),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if ctx.anonymized_path:
|
|
46
|
+
logger.info("DEBUG: after anonymizer, ctx.anonymized_path=%s (exists=%s)",
|
|
47
|
+
ctx.anonymized_path, isinstance(ctx.anonymized_path, str))
|
|
48
|
+
|
|
49
|
+
sm = LxSM()
|
|
50
|
+
sm.safe_update(extracted_metadata)
|
|
51
|
+
|
|
52
|
+
self.storage = sensitive_meta_storage(sm, ctx.current_report)
|
|
53
|
+
return ctx
|
|
54
|
+
|
|
55
|
+
def _ensure_report_reading_available(
|
|
56
|
+
self
|
|
57
|
+
) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Ensure report reading modules are available by adding lx-anonymizer to path.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Tuple of (availability_flag, ReportReader_class)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Try direct import first
|
|
67
|
+
from lx_anonymizer import ReportReader
|
|
68
|
+
|
|
69
|
+
logger.info("Successfully imported lx_anonymizer ReportReader module")
|
|
70
|
+
self._report_reader_available = True
|
|
71
|
+
self._report_reader_class = ReportReader
|
|
72
|
+
|
|
73
|
+
except ImportError:
|
|
74
|
+
# Optional: honor LX_ANONYMIZER_PATH=/abs/path/to/src
|
|
75
|
+
import importlib
|
|
76
|
+
|
|
77
|
+
extra = os.getenv("LX_ANONYMIZER_PATH")
|
|
78
|
+
if extra and extra not in sys.path and Path(extra).exists():
|
|
79
|
+
sys.path.insert(0, extra)
|
|
80
|
+
try:
|
|
81
|
+
mod = importlib.import_module("lx_anonymizer")
|
|
82
|
+
ReportReader = getattr(mod, "ReportReader")
|
|
83
|
+
logger.info(
|
|
84
|
+
"Imported lx_anonymizer.ReportReader via LX_ANONYMIZER_PATH"
|
|
85
|
+
)
|
|
86
|
+
self._report_reader_available = True
|
|
87
|
+
self._report_reader_class = ReportReader
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.warning(
|
|
90
|
+
"Failed importing lx_anonymizer via LX_ANONYMIZER_PATH: %s", e
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
self._report_reader_available = False
|
|
94
|
+
self._report_reader_class = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# endoreg_db/import_files/processing/sensitive_meta_adapter.py
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from lx_anonymizer.sensitive_meta_interface import SensitiveMeta as LxSensitiveMeta
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def normalize_lx_sensitive_meta(meta: LxSensitiveMeta) -> Dict[str, Any]:
|
|
8
|
+
"""
|
|
9
|
+
Convert lx_anonymizer.SensitiveMeta into a dict suitable for
|
|
10
|
+
endoreg_db SensitiveMeta.update_from_dict / create_from_dict.
|
|
11
|
+
|
|
12
|
+
- Renames fields where necessary (center -> center_name, patient_gender_name -> patient_gender)
|
|
13
|
+
- Drops None/blank values (your update logic already handles blanks carefully)
|
|
14
|
+
- Leaves dates as strings; your logic layer already parses them
|
|
15
|
+
"""
|
|
16
|
+
raw = meta.to_dict()
|
|
17
|
+
out: Dict[str, Any] = {}
|
|
18
|
+
|
|
19
|
+
# 1:1 fields (same names in model logic)
|
|
20
|
+
direct_keys = [
|
|
21
|
+
"file_path",
|
|
22
|
+
"patient_first_name",
|
|
23
|
+
"patient_last_name",
|
|
24
|
+
"patient_dob", # string; logic has parsing
|
|
25
|
+
"casenumber",
|
|
26
|
+
"examination_date", # string; logic has parsing
|
|
27
|
+
"examination_time", # string "HH:MM" is fine
|
|
28
|
+
"examiner_first_name",
|
|
29
|
+
"examiner_last_name",
|
|
30
|
+
"text",
|
|
31
|
+
"anonymized_text",
|
|
32
|
+
"endoscope_type",
|
|
33
|
+
"endoscope_sn",
|
|
34
|
+
]
|
|
35
|
+
for k in direct_keys:
|
|
36
|
+
v = raw.get(k)
|
|
37
|
+
if v not in (None, "", []):
|
|
38
|
+
out[k] = v
|
|
39
|
+
|
|
40
|
+
# Map patient_gender_name (interface) -> patient_gender (logic)
|
|
41
|
+
gender_name = raw.get("patient_gender_name")
|
|
42
|
+
if gender_name not in (None, ""):
|
|
43
|
+
# Your logic.update_* can handle strings for patient_gender
|
|
44
|
+
out["patient_gender"] = gender_name
|
|
45
|
+
|
|
46
|
+
# Map center (string) -> center_name (logic)
|
|
47
|
+
center_name = raw.get("center")
|
|
48
|
+
if center_name not in (None, ""):
|
|
49
|
+
out["center_name"] = center_name
|
|
50
|
+
|
|
51
|
+
return out
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Tuple, Optional
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
from lx_anonymizer import FrameCleaner
|
|
7
|
+
from lx_anonymizer.sensitive_meta_interface import SensitiveMeta as LxSM
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from endoreg_db.import_files.file_storage.sensitive_meta_storage import sensitive_meta_storage
|
|
11
|
+
from endoreg_db.import_files.context import ImportContext
|
|
12
|
+
from endoreg_db.utils.paths import ANONYM_VIDEO_DIR
|
|
13
|
+
from endoreg_db.models import EndoscopyProcessor, VideoFile
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VideoAnonymizer:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self._ensure_frame_cleaning_available()
|
|
19
|
+
self._frame_cleaning_available = None
|
|
20
|
+
self._frame_cleaning_class = None
|
|
21
|
+
self.storage = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def anonymize_video(self, ctx: ImportContext):
|
|
25
|
+
# Setup anonymized directory
|
|
26
|
+
anonymized_dir = ANONYM_VIDEO_DIR
|
|
27
|
+
anonymized_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
assert ctx.current_video is not None
|
|
29
|
+
# Generate output path for anonymized report
|
|
30
|
+
|
|
31
|
+
video_hash = ctx.current_video.video_hash
|
|
32
|
+
anonymized_output_path = anonymized_dir / f"{video_hash}.mp4"
|
|
33
|
+
|
|
34
|
+
self._frame_cleaning_class = FrameCleaner()
|
|
35
|
+
|
|
36
|
+
assert isinstance(self._frame_cleaning_class, FrameCleaner)
|
|
37
|
+
endoscope_roi, endoscope_roi_nested = self._get_processor_roi_info(ctx)
|
|
38
|
+
# Process with enhanced process_report method (returns 4-tuple now)
|
|
39
|
+
ctx.anonymized_path, extracted_metadata = self._frame_cleaning_class.clean_video(
|
|
40
|
+
video_path=ctx.file_path,
|
|
41
|
+
endoscope_image_roi=endoscope_roi,
|
|
42
|
+
endoscope_data_roi_nested=endoscope_roi_nested,
|
|
43
|
+
output_path=anonymized_output_path
|
|
44
|
+
|
|
45
|
+
)
|
|
46
|
+
sm = LxSM()
|
|
47
|
+
sm.safe_update(extracted_metadata)
|
|
48
|
+
|
|
49
|
+
self.storage = sensitive_meta_storage(sm, ctx.current_video)
|
|
50
|
+
return ctx
|
|
51
|
+
|
|
52
|
+
def _ensure_frame_cleaning_available(self):
|
|
53
|
+
"""
|
|
54
|
+
Ensure frame cleaning modules are available by adding lx-anonymizer to path.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Tuple of (availability_flag, FrameCleaner_class, ReportReader_class)
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
from lx_anonymizer import FrameCleaner
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.warning(
|
|
63
|
+
f"Frame cleaning not available: {e} Please install or update lx_anonymizer."
|
|
64
|
+
)
|
|
65
|
+
raise
|
|
66
|
+
|
|
67
|
+
assert FrameCleaner is not None
|
|
68
|
+
self._frame_cleaning_class = FrameCleaner()
|
|
69
|
+
self._frame_cleaning_available = True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _get_processor_roi_info(
|
|
73
|
+
self,
|
|
74
|
+
ctx: ImportContext,
|
|
75
|
+
) -> tuple[dict[str, int | None] | None,
|
|
76
|
+
dict[str, dict[str, int | None] | None] | None]:
|
|
77
|
+
"""Get processor ROI information for masking and data extraction."""
|
|
78
|
+
endoscope_data_roi_nested = None
|
|
79
|
+
endoscope_image_roi = None
|
|
80
|
+
|
|
81
|
+
video = ctx.current_video
|
|
82
|
+
assert isinstance(video, VideoFile)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
processor_name = ctx.processor_name if ctx.processor_name else None
|
|
86
|
+
if processor_name:
|
|
87
|
+
pr = EndoscopyProcessor()
|
|
88
|
+
processor = pr.get_by_name(processor_name)
|
|
89
|
+
assert isinstance(processor, EndoscopyProcessor), (
|
|
90
|
+
"Processor is not of type EndoscopyProcessor"
|
|
91
|
+
)
|
|
92
|
+
endoscope_image_roi = processor.get_roi_endoscope_image()
|
|
93
|
+
endoscope_data_roi_nested = processor.get_sensitive_rois()
|
|
94
|
+
logger.info(
|
|
95
|
+
"Retrieved processor ROI information: endoscope_image_roi=%s",
|
|
96
|
+
endoscope_image_roi,
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
logger.warning(
|
|
100
|
+
"No processor found for video %s, proceeding without ROI masking",
|
|
101
|
+
video.uuid,
|
|
102
|
+
)
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
logger.error("Failed to retrieve processor ROI information: %s", exc)
|
|
105
|
+
|
|
106
|
+
# IMPORTANT: return order must match clean_video signature
|
|
107
|
+
return endoscope_image_roi, endoscope_data_roi_nested
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# endoreg_db/services/video_processing/video_cleanup_on_error.py
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, MutableSet, Optional
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def cleanup_video_on_error(
|
|
11
|
+
*,
|
|
12
|
+
current_video: Any,
|
|
13
|
+
original_file_path: Optional[str | Path],
|
|
14
|
+
processing_context: Dict[str, Any],
|
|
15
|
+
) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Cleanup processing context on error for video imports.
|
|
18
|
+
|
|
19
|
+
This is extracted from VideoImportService._cleanup_on_error and kept as
|
|
20
|
+
close as possible to the original behavior.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
if not current_video or not hasattr(current_video, "state"):
|
|
24
|
+
# Nothing we can sensibly do here
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
# Ensure state exists
|
|
28
|
+
if current_video.state is None:
|
|
29
|
+
try:
|
|
30
|
+
current_video.get_or_create_state()
|
|
31
|
+
except Exception as e:
|
|
32
|
+
logger.warning(
|
|
33
|
+
"Video state not found for video %s during error cleanup: %s",
|
|
34
|
+
getattr(current_video, "uuid", None),
|
|
35
|
+
e,
|
|
36
|
+
)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
current_video.state = current_video.get_or_create_state()
|
|
40
|
+
|
|
41
|
+
# Try to restore original raw file
|
|
42
|
+
try:
|
|
43
|
+
if original_file_path is not None:
|
|
44
|
+
original_path = Path(original_file_path)
|
|
45
|
+
if not original_path.exists():
|
|
46
|
+
raise AssertionError("Original file path does not exist")
|
|
47
|
+
|
|
48
|
+
logger.info("Marked video import as failed in state")
|
|
49
|
+
raw_file_path = getattr(getattr(current_video, "raw_file", None), "path", None)
|
|
50
|
+
if raw_file_path and original_file_path:
|
|
51
|
+
shutil.copy2(str(raw_file_path), str(original_file_path))
|
|
52
|
+
else:
|
|
53
|
+
logger.warning("Cannot restore original raw file: path is None")
|
|
54
|
+
else:
|
|
55
|
+
logger.warning("Original file path is None")
|
|
56
|
+
except AssertionError:
|
|
57
|
+
logger.warning("Original file path does not exist")
|
|
58
|
+
|
|
59
|
+
# Reset state flags if processing had started
|
|
60
|
+
try:
|
|
61
|
+
from endoreg_db.models.state import VideoState # local import to avoid cycles
|
|
62
|
+
|
|
63
|
+
if not isinstance(current_video.state, VideoState):
|
|
64
|
+
logger.error("Current video state is not a VideoState instance during cleanup")
|
|
65
|
+
raise AssertionError
|
|
66
|
+
|
|
67
|
+
if processing_context.get("processing_started"):
|
|
68
|
+
current_video.state.frames_extracted = False
|
|
69
|
+
current_video.state.frames_initialized = False
|
|
70
|
+
current_video.state.video_meta_extracted = False
|
|
71
|
+
current_video.state.text_meta_extracted = False
|
|
72
|
+
current_video.state.save()
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.warning("Error during video error cleanup: %s", e)
|
|
75
|
+
except Exception as outer_exc:
|
|
76
|
+
logger.warning("Unexpected error in cleanup_video_on_error: %s", outer_exc)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cleanup_video_processing_context(
|
|
80
|
+
*,
|
|
81
|
+
processing_context: Dict[str, Any],
|
|
82
|
+
processed_files: MutableSet[str],
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Cleanup processing context and release file lock for video imports.
|
|
86
|
+
|
|
87
|
+
Extracted from VideoImportService._cleanup_processing_context.
|
|
88
|
+
"""
|
|
89
|
+
# DEFENSIVE: ensure dict
|
|
90
|
+
if processing_context is None:
|
|
91
|
+
processing_context = {}
|
|
92
|
+
|
|
93
|
+
# Release file lock if it was acquired
|
|
94
|
+
try:
|
|
95
|
+
lock_context = processing_context.get("_lock_context")
|
|
96
|
+
if lock_context is not None:
|
|
97
|
+
try:
|
|
98
|
+
lock_context.__exit__(None, None, None)
|
|
99
|
+
logger.info("Released file lock")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.warning("Error releasing file lock during context cleanup: %s", e)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.warning("Error while handling lock release in context cleanup: %s", e)
|
|
104
|
+
|
|
105
|
+
# Remove file from processed_files set if processing failed
|
|
106
|
+
try:
|
|
107
|
+
file_path = processing_context.get("file_path")
|
|
108
|
+
anonymization_completed = processing_context.get("anonymization_completed")
|
|
109
|
+
|
|
110
|
+
if file_path and not anonymization_completed:
|
|
111
|
+
file_path_str = str(file_path)
|
|
112
|
+
if file_path_str in processed_files:
|
|
113
|
+
processed_files.remove(file_path_str)
|
|
114
|
+
logger.info(
|
|
115
|
+
"Removed %s from processed files (failed processing)",
|
|
116
|
+
file_path_str,
|
|
117
|
+
)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning("Error while cleaning processed_files set: %s", e)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
from typing import Tuple, Optional
|
|
3
|
+
from faker import Faker
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def fake_name_with_similar_dob_and_gender(
|
|
8
|
+
gender: str,
|
|
9
|
+
dob: date,
|
|
10
|
+
*,
|
|
11
|
+
year_tolerance: int = 3,
|
|
12
|
+
locale: str = "de_DE",
|
|
13
|
+
seed: Optional[int] = None,
|
|
14
|
+
) -> Tuple[str, str, date]:
|
|
15
|
+
"""
|
|
16
|
+
Generate a fake name with the same gender and a similar date of birth.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
gender: "male" or "female"
|
|
20
|
+
dob: Original date of birth
|
|
21
|
+
year_tolerance: Maximum age difference in years
|
|
22
|
+
locale: Faker locale (default: German)
|
|
23
|
+
seed: Optional reproducible seed
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
(full_name, fake_dob)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if gender not in {"male", "female"}:
|
|
30
|
+
raise ValueError("gender must be 'male' or 'female'")
|
|
31
|
+
|
|
32
|
+
fake = Faker(locale)
|
|
33
|
+
|
|
34
|
+
if seed is not None:
|
|
35
|
+
Faker.seed(seed)
|
|
36
|
+
random.seed(seed)
|
|
37
|
+
|
|
38
|
+
# --- Generate gender-safe name ---
|
|
39
|
+
if gender == "male":
|
|
40
|
+
first_name = fake.first_name_male()
|
|
41
|
+
else:
|
|
42
|
+
first_name = fake.first_name_female()
|
|
43
|
+
|
|
44
|
+
last_name = fake.last_name()
|
|
45
|
+
full_name = f"{first_name} {last_name}"
|
|
46
|
+
|
|
47
|
+
# --- Generate similar DOB ---
|
|
48
|
+
days_range = year_tolerance * 365
|
|
49
|
+
offset_days = random.randint(-days_range, days_range)
|
|
50
|
+
fake_dob = dob + timedelta(days=offset_days)
|
|
51
|
+
|
|
52
|
+
return first_name, last_name, fake_dob
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from endoreg_db.models import Patient, SensitiveMeta, Center, Gender
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import timedelta
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
from django.db.models import QuerySet
|
|
7
|
+
|
|
8
|
+
from itertools import combinations
|
|
9
|
+
from typing import Dict, Tuple, List
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
QI_FLAGS = ["first_name", "last_name", "center", "gender", "dob_band"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_k_profile_for_instance(
|
|
18
|
+
instance: SensitiveMeta,
|
|
19
|
+
*,
|
|
20
|
+
dob_year_tolerance: int = 1,
|
|
21
|
+
include_self: bool = True,
|
|
22
|
+
) -> Dict[Tuple[str, ...], int]:
|
|
23
|
+
"""
|
|
24
|
+
For a given SensitiveMeta instance, compute k (equivalence class size)
|
|
25
|
+
for all non-empty subsets of the quasi-identifiers defined in QI_FLAGS.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
{
|
|
29
|
+
('first_name',): 12,
|
|
30
|
+
('center', 'gender'): 45,
|
|
31
|
+
('first_name', 'last_name', 'dob_band'): 3,
|
|
32
|
+
...
|
|
33
|
+
}
|
|
34
|
+
"""
|
|
35
|
+
result: Dict[Tuple[str, ...], int] = {}
|
|
36
|
+
|
|
37
|
+
for r in range(1, len(QI_FLAGS) + 1):
|
|
38
|
+
for subset in combinations(QI_FLAGS, r):
|
|
39
|
+
use_first_name = "first_name" in subset
|
|
40
|
+
use_last_name = "last_name" in subset
|
|
41
|
+
use_center = "center" in subset
|
|
42
|
+
use_gender = "gender" in subset
|
|
43
|
+
use_dob_band = "dob_band" in subset
|
|
44
|
+
|
|
45
|
+
qs = _build_sensitive_meta_qi_queryset(
|
|
46
|
+
instance,
|
|
47
|
+
dob_year_tolerance=dob_year_tolerance,
|
|
48
|
+
include_self=include_self,
|
|
49
|
+
use_first_name=use_first_name,
|
|
50
|
+
use_last_name=use_last_name,
|
|
51
|
+
use_center=use_center,
|
|
52
|
+
use_gender=use_gender,
|
|
53
|
+
use_dob_band=use_dob_band,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
k_value = qs.count()
|
|
57
|
+
result[subset] = k_value
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_k_anonymity(pk, k=3):
|
|
63
|
+
"""
|
|
64
|
+
How anonymized is a patient?
|
|
65
|
+
Get the k value for how many patients can be matched to the current patients attributes.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
pk (_type_): _description_
|
|
69
|
+
k (int, optional): _description_. Defaults to 3.
|
|
70
|
+
"""
|
|
71
|
+
return get_k_anonymity_for_sensitive_meta(pk=pk, k=k, dob_year_tolerance=1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _build_sensitive_meta_qi_queryset(
|
|
75
|
+
instance: SensitiveMeta,
|
|
76
|
+
*,
|
|
77
|
+
dob_year_tolerance: int = 1,
|
|
78
|
+
include_self: bool = True,
|
|
79
|
+
use_first_name: bool = True,
|
|
80
|
+
use_last_name: bool = True,
|
|
81
|
+
use_center: bool = True,
|
|
82
|
+
use_gender: bool = True,
|
|
83
|
+
use_dob_band: bool = True,
|
|
84
|
+
) -> QuerySet[SensitiveMeta]:
|
|
85
|
+
"""
|
|
86
|
+
Build a queryset of SensitiveMeta records that are indistinguishable from
|
|
87
|
+
`instance` on the chosen quasi-identifiers:
|
|
88
|
+
|
|
89
|
+
- same center
|
|
90
|
+
- same patient_gender
|
|
91
|
+
- patient_dob within ±dob_year_tolerance years (approx via days)
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
instance: The SensitiveMeta instance we evaluate.
|
|
95
|
+
dob_year_tolerance: Allowed +- years around patient_dob.
|
|
96
|
+
include_self: Whether to include `instance` itself in the result.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A Django QuerySet for further aggregation.
|
|
100
|
+
"""
|
|
101
|
+
qs = SensitiveMeta.objects.all()
|
|
102
|
+
|
|
103
|
+
if use_first_name and instance.patient_first_name is not None:
|
|
104
|
+
qs = qs.filter(patient_first_name=instance.patient_first_name)
|
|
105
|
+
|
|
106
|
+
if use_last_name and instance.patient_first_name is not None:
|
|
107
|
+
qs = qs.filter(patient_first_name=instance.patient_first_name)
|
|
108
|
+
|
|
109
|
+
# --- Center ---
|
|
110
|
+
if use_center and instance.center is not None:
|
|
111
|
+
if instance.center.pk is not None:
|
|
112
|
+
qs = qs.filter(center=instance.center.pk)
|
|
113
|
+
|
|
114
|
+
# --- Gender ---
|
|
115
|
+
if use_gender and instance.patient_gender is not None
|
|
116
|
+
if instance.patient_gender.pk is not None:
|
|
117
|
+
qs = qs.filter(patient_gender_id=instance.patient_gender)
|
|
118
|
+
|
|
119
|
+
# --- DOB (approximate ±N years using days) ---
|
|
120
|
+
if use_dob_band and instance.patient_dob is not None:
|
|
121
|
+
days = dob_year_tolerance * 365
|
|
122
|
+
ref_date = instance.patient_dob.date()
|
|
123
|
+
start = ref_date - timedelta(days=days)
|
|
124
|
+
end = ref_date + timedelta(days=days)
|
|
125
|
+
qs = qs.filter(patient_dob__date__range=(start, end))
|
|
126
|
+
|
|
127
|
+
# --- Exclude self if requested ---
|
|
128
|
+
if not include_self and instance.pk is not None:
|
|
129
|
+
qs = qs.exclude(pk=instance.pk)
|
|
130
|
+
|
|
131
|
+
return qs
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_k_anonymity_for_sensitive_meta(
|
|
135
|
+
pk: int,
|
|
136
|
+
*,
|
|
137
|
+
k: int = 3,
|
|
138
|
+
dob_year_tolerance: int = 1,
|
|
139
|
+
) -> Tuple[int, bool]:
|
|
140
|
+
"""
|
|
141
|
+
Compute the k-anonymity (equivalence class size) for a SensitiveMeta record.
|
|
142
|
+
|
|
143
|
+
k-anonymity here is defined as the number of SensitiveMeta rows that share
|
|
144
|
+
the same quasi-identifiers as the given record:
|
|
145
|
+
|
|
146
|
+
- center
|
|
147
|
+
- patient_gender
|
|
148
|
+
- patient_dob within ±dob_year_tolerance years (approximate)
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
pk: Primary key of the SensitiveMeta instance to evaluate.
|
|
152
|
+
k: Desired anonymity threshold (e.g. 3 for 3-anonymity).
|
|
153
|
+
dob_year_tolerance: Allowed age window in years around patient_dob.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
(k_value, is_k_anonymous) where:
|
|
157
|
+
k_value = size of the equivalence class
|
|
158
|
+
is_k_anonymous = True if k_value >= k
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
sm = SensitiveMeta.objects.get(pk=pk)
|
|
162
|
+
except SensitiveMeta.DoesNotExist:
|
|
163
|
+
raise ValueError(f"SensitiveMeta with pk={pk} does not exist")
|
|
164
|
+
|
|
165
|
+
qs = _build_sensitive_meta_qi_queryset(
|
|
166
|
+
sm,
|
|
167
|
+
dob_year_tolerance=dob_year_tolerance,
|
|
168
|
+
include_self=True,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
k_value = qs.count()
|
|
172
|
+
is_k_anon = k_value >= k
|
|
173
|
+
|
|
174
|
+
logger.info(
|
|
175
|
+
"k-anonymity for SensitiveMeta pk=%s -> k=%s (threshold=%s, dob_tol=%s years)",
|
|
176
|
+
pk,
|
|
177
|
+
k_value,
|
|
178
|
+
k,
|
|
179
|
+
dob_year_tolerance,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return k_value, is_k_anon
|