endoreg-db 0.6.3__py3-none-any.whl → 0.8.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 +26 -26
- endoreg_db/api_urls.py +4 -0
- endoreg_db/apps.py +12 -0
- endoreg_db/assets/dummy_model.ckpt +1 -0
- endoreg_db/codemods/readme.md +88 -0
- endoreg_db/codemods/rename_datetime_fields.py +92 -0
- endoreg_db/config/env.py +101 -0
- endoreg_db/data/__init__.py +26 -0
- endoreg_db/data/ai_model/data.yaml +1 -1
- endoreg_db/data/ai_model_label/label/polyp_classification.yaml +52 -0
- endoreg_db/data/ai_model_label/label-set/data.yaml +20 -1
- endoreg_db/data/ai_model_label/label-set/polyp_classifications.yaml +25 -0
- endoreg_db/data/center/data.yaml +13 -12
- endoreg_db/data/center_shift/ukw.yaml +9 -0
- endoreg_db/data/db_summary.csv +58 -0
- endoreg_db/data/db_summary.xlsx +0 -0
- endoreg_db/data/disease/misc.yaml +1 -2
- endoreg_db/data/disease_classification/chronic_kidney_disease.yaml +2 -2
- endoreg_db/data/disease_classification_choice/chronic_kidney_disease.yaml +6 -6
- endoreg_db/data/endoscopy_processor/data.yaml +3 -0
- endoreg_db/data/event/cardiology.yaml +0 -13
- endoreg_db/data/examination/examinations/data.yaml +34 -28
- endoreg_db/data/examination/type/data.yaml +12 -0
- endoreg_db/data/examination_indication/endoscopy.yaml +418 -2
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +157 -5
- endoreg_db/data/examination_requirement_set/colonoscopy.yaml +15 -0
- endoreg_db/data/finding/anatomy_colon.yaml +128 -0
- endoreg_db/data/finding/colonoscopy.yaml +40 -0
- endoreg_db/data/finding/colonoscopy_bowel_prep.yaml +56 -0
- endoreg_db/data/finding/complication.yaml +16 -0
- endoreg_db/data/finding/data.yaml +8 -44
- endoreg_db/data/finding/examination_setting.yaml +16 -0
- endoreg_db/data/finding/medication_related.yaml +18 -0
- endoreg_db/data/finding/outcome.yaml +12 -0
- endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +95 -0
- endoreg_db/data/finding_classification/colonoscopy_jnet.yaml +22 -0
- endoreg_db/data/finding_classification/colonoscopy_kudo.yaml +25 -0
- endoreg_db/data/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
- endoreg_db/data/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +68 -0
- endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +80 -0
- endoreg_db/data/finding_classification/colonoscopy_lst.yaml +21 -0
- endoreg_db/data/finding_classification/colonoscopy_nice.yaml +20 -0
- endoreg_db/data/finding_classification/colonoscopy_paris.yaml +26 -0
- endoreg_db/data/finding_classification/colonoscopy_sano.yaml +22 -0
- endoreg_db/data/finding_classification/colonoscopy_summary.yaml +53 -0
- endoreg_db/data/finding_classification/complication_generic.yaml +25 -0
- endoreg_db/data/finding_classification/examination_setting_generic.yaml +40 -0
- endoreg_db/data/finding_classification/histology_colo.yaml +51 -0
- endoreg_db/data/finding_classification/intervention_required.yaml +26 -0
- endoreg_db/data/finding_classification/medication_related.yaml +23 -0
- endoreg_db/data/finding_classification/visualized.yaml +33 -0
- endoreg_db/data/finding_classification_choice/bowel_preparation.yaml +78 -0
- endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_circularity_default.yaml +0 -2
- endoreg_db/data/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
- endoreg_db/data/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
- endoreg_db/data/finding_classification_choice/colon_lesion_lst.yaml +15 -0
- endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_nice.yaml +4 -7
- endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_paris.yaml +0 -8
- endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_planarity_default.yaml +6 -13
- endoreg_db/data/finding_classification_choice/colon_lesion_sano.yaml +14 -0
- endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_surface_intact_default.yaml +3 -6
- endoreg_db/data/{finding_location_classification_choice/colonoscopy.yaml → finding_classification_choice/colonoscopy_location.yaml} +11 -22
- endoreg_db/data/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
- endoreg_db/data/finding_classification_choice/colonoscopy_size.yaml +82 -0
- endoreg_db/data/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
- endoreg_db/data/finding_classification_choice/complication_generic_types.yaml +15 -0
- endoreg_db/data/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
- endoreg_db/data/finding_classification_choice/histology.yaml +24 -0
- endoreg_db/data/finding_classification_choice/histology_polyp.yaml +20 -0
- endoreg_db/data/finding_classification_choice/outcome.yaml +19 -0
- endoreg_db/data/finding_classification_choice/yes_no_na.yaml +11 -0
- endoreg_db/data/finding_classification_type/colonoscopy_basic.yaml +48 -0
- endoreg_db/data/finding_intervention/endoscopy.yaml +26 -121
- endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
- endoreg_db/data/finding_intervention/endoscopy_egd.yaml +128 -0
- endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +32 -0
- endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +9 -0
- endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +36 -0
- endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +6 -6
- endoreg_db/data/finding_type/data.yaml +23 -10
- endoreg_db/data/gender/data.yaml +8 -1
- endoreg_db/data/information_source/annotation.yaml +6 -0
- endoreg_db/data/information_source/endoscopy_guidelines.yaml +7 -0
- endoreg_db/data/information_source/prediction.yaml +7 -0
- endoreg_db/data/information_source_type/data.yaml +8 -0
- endoreg_db/data/lab_value/misc.yaml +43 -0
- endoreg_db/data/medication/anticoagulation.yaml +5 -5
- endoreg_db/data/medication/tah.yaml +5 -5
- endoreg_db/data/medication_indication/anticoagulation.yaml +4 -4
- endoreg_db/data/medication_intake_time/base.yaml +4 -4
- endoreg_db/data/names_first/first_names.yaml +3 -0
- endoreg_db/data/pdf_type/data.yaml +27 -10
- endoreg_db/data/qualification/endoscopy.yaml +36 -0
- endoreg_db/data/qualification/m2.yaml +39 -0
- endoreg_db/data/qualification/outpatient_clinic.yaml +35 -0
- endoreg_db/data/qualification/sonography.yaml +36 -0
- endoreg_db/data/qualification_type/base.yaml +29 -0
- endoreg_db/data/report_reader_flag/rkh-histology-generic.yaml +10 -0
- endoreg_db/data/report_reader_flag/ukw-histology-generic.yaml +5 -0
- endoreg_db/data/requirement/age.yaml +26 -0
- endoreg_db/data/requirement/colonoscopy_baseline_austria.yaml +45 -0
- endoreg_db/data/requirement/disease_cardiovascular.yaml +79 -0
- endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
- endoreg_db/data/requirement/disease_hepatology.yaml +12 -0
- endoreg_db/data/requirement/disease_misc.yaml +12 -0
- endoreg_db/data/requirement/disease_renal.yaml +96 -0
- endoreg_db/data/requirement/endoscopy_bleeding_risk.yaml +59 -0
- endoreg_db/data/requirement/event_cardiology.yaml +251 -0
- endoreg_db/data/requirement/event_requirements.yaml +145 -0
- endoreg_db/data/requirement/finding_colon_polyp.yaml +50 -0
- endoreg_db/data/requirement/gender.yaml +25 -0
- endoreg_db/data/requirement/lab_value.yaml +441 -0
- endoreg_db/data/requirement/medication.yaml +93 -0
- endoreg_db/data/requirement_operator/age.yaml +13 -0
- endoreg_db/data/requirement_operator/lab_operators.yaml +129 -0
- endoreg_db/data/requirement_operator/model_operators.yaml +96 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +48 -0
- endoreg_db/data/requirement_set/colonoscopy_austria_screening.yaml +57 -0
- endoreg_db/data/requirement_set/endoscopy_bleeding_risk.yaml +52 -0
- endoreg_db/data/requirement_set_type/data.yaml +20 -0
- endoreg_db/data/requirement_type/requirement_types.yaml +165 -0
- endoreg_db/data/risk/bleeding.yaml +26 -0
- endoreg_db/data/risk/thrombosis.yaml +37 -0
- endoreg_db/data/risk_type/data.yaml +27 -0
- endoreg_db/data/shift/endoscopy.yaml +21 -0
- endoreg_db/data/shift_type/base.yaml +35 -0
- endoreg_db/data/tag/requirement_set_tags.yaml +11 -0
- endoreg_db/data/unit/concentration.yaml +23 -0
- endoreg_db/data/unit/time.yaml +36 -1
- endoreg_db/exceptions.py +19 -0
- endoreg_db/forms/patient_finding_intervention_form.py +4 -5
- endoreg_db/forms/patient_form.py +7 -6
- endoreg_db/forms/questionnaires/__init__.py +1 -1
- endoreg_db/forms/questionnaires/tto_questionnaire.py +19 -19
- endoreg_db/helpers/count_db.py +45 -0
- endoreg_db/helpers/data_loader.py +208 -0
- endoreg_db/helpers/default_objects.py +359 -0
- endoreg_db/helpers/interact.py +6 -0
- endoreg_db/helpers/test_video_helper.py +119 -0
- endoreg_db/logger_conf.py +140 -0
- endoreg_db/management/__init__.py +1 -0
- endoreg_db/management/commands/__init__.py +1 -0
- endoreg_db/management/commands/anonymize_video.py +0 -0
- endoreg_db/management/commands/check_auth.py +125 -0
- endoreg_db/management/commands/create_multilabel_model_meta.py +214 -0
- endoreg_db/management/commands/fix_missing_patient_data.py +172 -0
- endoreg_db/management/commands/fix_video_paths.py +165 -0
- endoreg_db/management/commands/import_fallback_video.py +203 -0
- endoreg_db/management/commands/import_report.py +298 -0
- endoreg_db/management/commands/import_video.py +422 -0
- endoreg_db/management/commands/import_video_with_classification.py +367 -0
- endoreg_db/management/commands/init_default_ai_model.py +112 -0
- endoreg_db/management/commands/load_ai_model_data.py +2 -7
- endoreg_db/management/commands/load_base_db_data.py +15 -1
- endoreg_db/management/commands/load_center_data.py +46 -21
- endoreg_db/management/commands/load_endoscope_data.py +2 -2
- endoreg_db/management/commands/load_examination_indication_data.py +49 -28
- endoreg_db/management/commands/load_finding_data.py +49 -92
- endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +0 -1
- endoreg_db/management/commands/load_information_source.py +13 -7
- endoreg_db/management/commands/load_qualification_data.py +59 -0
- endoreg_db/management/commands/load_requirement_data.py +180 -0
- endoreg_db/management/commands/load_risk_data.py +56 -0
- endoreg_db/management/commands/load_shift_data.py +60 -0
- endoreg_db/management/commands/load_tag_data.py +57 -0
- endoreg_db/management/commands/register_ai_model.py +1 -1
- endoreg_db/management/commands/start_filewatcher.py +106 -0
- endoreg_db/management/commands/storage_management.py +548 -0
- endoreg_db/management/commands/summarize_db_content.py +189 -0
- endoreg_db/management/commands/validate_video.py +204 -0
- endoreg_db/management/commands/validate_video_files.py +161 -0
- endoreg_db/management/commands/video_validation.py +22 -0
- endoreg_db/migrations/0001_initial.py +625 -813
- endoreg_db/migrations/0002_add_video_correction_models.py +52 -0
- endoreg_db/models/__init__.py +274 -291
- endoreg_db/models/administration/__init__.py +116 -0
- endoreg_db/models/{ai_model → administration/ai}/__init__.py +6 -1
- endoreg_db/models/administration/ai/active_model.py +35 -0
- endoreg_db/models/administration/ai/ai_model.py +156 -0
- endoreg_db/models/{ai_model → administration/ai}/model_type.py +6 -1
- endoreg_db/models/administration/case/__init__.py +19 -0
- endoreg_db/models/administration/case/case.py +114 -0
- endoreg_db/models/{case_template → administration/case/case_template}/case_template.py +3 -3
- endoreg_db/models/{case_template → administration/case/case_template}/case_template_rule.py +3 -10
- endoreg_db/models/{case_template → administration/case/case_template}/case_template_rule_value.py +2 -4
- endoreg_db/models/{case_template → administration/case/case_template}/case_template_type.py +1 -3
- endoreg_db/models/{center → administration/center}/__init__.py +3 -1
- endoreg_db/models/administration/center/center.py +61 -0
- endoreg_db/models/administration/center/center_product.py +64 -0
- endoreg_db/models/{center → administration/center}/center_resource.py +19 -3
- endoreg_db/models/administration/center/center_shift.py +88 -0
- endoreg_db/models/administration/center/center_waste.py +30 -0
- endoreg_db/models/administration/permissions/__init__.py +44 -0
- endoreg_db/models/administration/person/__init__.py +24 -0
- endoreg_db/models/administration/person/employee/__init__.py +3 -0
- endoreg_db/models/administration/person/employee/employee.py +35 -0
- endoreg_db/models/administration/person/employee/employee_qualification.py +39 -0
- endoreg_db/models/administration/person/employee/employee_type.py +42 -0
- endoreg_db/models/administration/person/examiner/__init__.py +4 -0
- endoreg_db/models/administration/person/examiner/examiner.py +54 -0
- endoreg_db/models/administration/person/names/__init__.py +0 -0
- endoreg_db/models/{persons → administration/person/names}/first_name.py +1 -1
- endoreg_db/models/{persons → administration/person/names}/last_name.py +2 -3
- endoreg_db/models/administration/person/patient/__init__.py +5 -0
- endoreg_db/models/administration/person/patient/patient.py +460 -0
- endoreg_db/models/administration/person/profession/__init__.py +24 -0
- endoreg_db/models/administration/person/user/__init__.py +5 -0
- endoreg_db/models/administration/person/user/portal_user_information.py +37 -0
- endoreg_db/models/administration/product/product.py +97 -0
- endoreg_db/models/administration/product/product_group.py +39 -0
- endoreg_db/models/administration/product/product_material.py +54 -0
- endoreg_db/models/{product → administration/product}/product_weight.py +9 -0
- endoreg_db/models/{product → administration/product}/reference_product.py +26 -11
- endoreg_db/models/administration/qualification/__init__.py +7 -0
- endoreg_db/models/administration/qualification/qualification.py +37 -0
- endoreg_db/models/administration/qualification/qualification_type.py +35 -0
- endoreg_db/models/administration/shift/__init__.py +9 -0
- endoreg_db/models/administration/shift/scheduled_days.py +69 -0
- endoreg_db/models/administration/shift/shift.py +51 -0
- endoreg_db/models/administration/shift/shift_type.py +108 -0
- endoreg_db/models/label/__init__.py +24 -1
- endoreg_db/models/label/annotation/__init__.py +12 -0
- endoreg_db/models/label/annotation/image_classification.py +84 -0
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +66 -0
- endoreg_db/models/label/label.py +45 -74
- endoreg_db/models/label/label_set.py +53 -0
- endoreg_db/models/label/label_type.py +29 -0
- endoreg_db/models/label/label_video_segment/__init__.py +3 -0
- endoreg_db/models/label/label_video_segment/_create_from_video.py +41 -0
- endoreg_db/models/label/label_video_segment/label_video_segment.py +511 -0
- endoreg_db/models/label/video_segmentation_label.py +31 -0
- endoreg_db/models/{annotation → label}/video_segmentation_labelset.py +7 -0
- endoreg_db/models/media/__init__.py +14 -0
- endoreg_db/models/media/frame/__init__.py +3 -0
- endoreg_db/models/media/frame/frame.py +111 -0
- endoreg_db/models/media/pdf/__init__.py +11 -0
- endoreg_db/models/media/pdf/raw_pdf.py +608 -0
- endoreg_db/models/media/pdf/report_file.py +162 -0
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +77 -0
- endoreg_db/models/media/video/__init__.py +4 -0
- endoreg_db/models/media/video/create_from_file.py +336 -0
- endoreg_db/models/media/video/pipe_1.py +195 -0
- endoreg_db/models/media/video/pipe_2.py +105 -0
- endoreg_db/models/media/video/refactor_plan.md +0 -0
- endoreg_db/models/media/video/video_file.py +680 -0
- endoreg_db/models/media/video/video_file_ai.py +443 -0
- endoreg_db/models/media/video/video_file_anonymize.py +348 -0
- endoreg_db/models/media/video/video_file_frames/__init__.py +47 -0
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +22 -0
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +23 -0
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +104 -0
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +174 -0
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +28 -0
- endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +27 -0
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +20 -0
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +27 -0
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +34 -0
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +27 -0
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +129 -0
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +129 -0
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +65 -0
- endoreg_db/models/media/video/video_file_frames.py +0 -0
- endoreg_db/models/media/video/video_file_io.py +166 -0
- endoreg_db/models/media/video/video_file_meta/__init__.py +22 -0
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +45 -0
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +39 -0
- endoreg_db/models/media/video/video_file_meta/get_fps.py +147 -0
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +143 -0
- endoreg_db/models/media/video/video_file_meta/text_meta.py +134 -0
- endoreg_db/models/media/video/video_file_meta/video_meta.py +70 -0
- endoreg_db/models/media/video/video_file_meta.py +11 -0
- endoreg_db/models/media/video/video_file_segments.py +209 -0
- endoreg_db/models/medical/__init__.py +146 -0
- endoreg_db/models/{contraindication → medical/contraindication}/__init__.py +1 -5
- endoreg_db/models/medical/disease.py +156 -0
- endoreg_db/models/medical/event.py +137 -0
- endoreg_db/models/{examination → medical/examination}/__init__.py +1 -1
- endoreg_db/models/medical/examination/examination.py +148 -0
- endoreg_db/models/medical/examination/examination_indication.py +278 -0
- endoreg_db/models/{examination → medical/examination}/examination_time.py +0 -4
- endoreg_db/models/{examination → medical/examination}/examination_time_type.py +1 -8
- endoreg_db/models/{examination → medical/examination}/examination_type.py +18 -10
- endoreg_db/models/medical/finding/__init__.py +18 -0
- endoreg_db/models/medical/finding/finding.py +96 -0
- endoreg_db/models/medical/finding/finding_classification.py +142 -0
- endoreg_db/models/{finding → medical/finding}/finding_intervention.py +2 -10
- endoreg_db/models/medical/finding/finding_type.py +35 -0
- endoreg_db/models/medical/hardware/__init__.py +8 -0
- endoreg_db/models/{hardware → medical/hardware}/endoscope.py +28 -23
- endoreg_db/models/medical/laboratory/__init__.py +5 -0
- endoreg_db/models/medical/laboratory/lab_value.py +419 -0
- endoreg_db/models/{medication → medical/medication}/medication.py +1 -3
- endoreg_db/models/{medication → medical/medication}/medication_indication_type.py +8 -3
- endoreg_db/models/{medication → medical/medication}/medication_intake_time.py +21 -3
- endoreg_db/models/{medication → medical/medication}/medication_schedule.py +13 -5
- endoreg_db/models/{organ → medical/organ}/__init__.py +3 -6
- endoreg_db/models/medical/patient/__init__.py +56 -0
- endoreg_db/models/medical/patient/medication_examples.py +38 -0
- endoreg_db/models/medical/patient/patient_disease.py +63 -0
- endoreg_db/models/medical/patient/patient_event.py +75 -0
- endoreg_db/models/medical/patient/patient_examination.py +249 -0
- endoreg_db/models/{persons → medical}/patient/patient_examination_indication.py +21 -9
- endoreg_db/models/medical/patient/patient_finding.py +357 -0
- endoreg_db/models/medical/patient/patient_finding_classification.py +207 -0
- endoreg_db/models/{patient → medical/patient}/patient_finding_intervention.py +15 -1
- endoreg_db/models/medical/patient/patient_lab_sample.py +148 -0
- endoreg_db/models/{persons → medical}/patient/patient_lab_value.py +40 -15
- endoreg_db/models/medical/patient/patient_medication.py +104 -0
- endoreg_db/models/medical/patient/patient_medication_schedule.py +136 -0
- endoreg_db/models/medical/risk/__init__.py +7 -0
- endoreg_db/models/medical/risk/risk.py +72 -0
- endoreg_db/models/medical/risk/risk_type.py +51 -0
- endoreg_db/models/{data_file/metadata → metadata}/__init__.py +6 -0
- endoreg_db/models/metadata/frame_ocr_result.py +0 -0
- endoreg_db/models/metadata/model_meta.py +193 -0
- endoreg_db/models/metadata/model_meta_logic.py +236 -0
- endoreg_db/models/{data_file/metadata → metadata}/pdf_meta.py +28 -13
- endoreg_db/models/metadata/sensitive_meta.py +288 -0
- endoreg_db/models/metadata/sensitive_meta_logic.py +643 -0
- endoreg_db/models/metadata/video_meta.py +332 -0
- endoreg_db/models/metadata/video_prediction_logic.py +190 -0
- endoreg_db/models/metadata/video_prediction_meta.py +270 -0
- endoreg_db/models/other/__init__.py +17 -0
- endoreg_db/models/other/distribution/date_value_distribution.py +0 -2
- endoreg_db/models/other/distribution/numeric_value_distribution.py +30 -2
- endoreg_db/models/{emission → other/emission}/emission_factor.py +15 -6
- endoreg_db/models/{persons → other}/gender.py +8 -3
- endoreg_db/models/other/information_source.py +159 -0
- endoreg_db/models/other/material.py +10 -2
- endoreg_db/models/other/resource.py +6 -2
- endoreg_db/models/other/tag.py +27 -0
- endoreg_db/models/other/transport_route.py +13 -2
- endoreg_db/models/{unit.py → other/unit.py} +16 -6
- endoreg_db/models/other/waste.py +10 -3
- endoreg_db/models/requirement/__init__.py +11 -0
- endoreg_db/models/requirement/requirement.py +767 -0
- endoreg_db/models/requirement/requirement_evaluation/__init__.py +6 -0
- endoreg_db/models/requirement/requirement_evaluation/get_values.py +40 -0
- endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +9 -0
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +95 -0
- endoreg_db/models/requirement/requirement_operator.py +176 -0
- endoreg_db/models/requirement/requirement_set.py +287 -0
- endoreg_db/models/rule/__init__.py +13 -0
- endoreg_db/models/{rules → rule}/rule.py +6 -3
- endoreg_db/models/{rules → rule}/rule_attribute_dtype.py +0 -2
- endoreg_db/models/{rules → rule}/rule_type.py +0 -2
- endoreg_db/models/{rules → rule}/ruleset.py +0 -2
- endoreg_db/models/state/__init__.py +12 -0
- endoreg_db/models/state/abstract.py +11 -0
- endoreg_db/models/state/audit_ledger.py +150 -0
- endoreg_db/models/state/label_video_segment.py +22 -0
- endoreg_db/models/state/raw_pdf.py +187 -0
- endoreg_db/models/state/sensitive_meta.py +46 -0
- endoreg_db/models/state/video.py +232 -0
- endoreg_db/models/upload_job.py +99 -0
- endoreg_db/models/utils.py +135 -0
- endoreg_db/models/video_metadata.py +66 -0
- endoreg_db/models/video_processing.py +153 -0
- endoreg_db/renames.yml +8 -0
- endoreg_db/root_urls.py +9 -0
- endoreg_db/schemas/__init__.py +0 -0
- endoreg_db/schemas/examination_evaluation.py +27 -0
- endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +775 -0
- endoreg_db/serializers/__init__.py +147 -10
- endoreg_db/serializers/{raw_pdf_meta_validation.py → _old/raw_pdf_meta_validation.py} +3 -3
- endoreg_db/serializers/{raw_video_meta_validation.py → _old/raw_video_meta_validation.py} +18 -14
- endoreg_db/serializers/_old/video.py +71 -0
- endoreg_db/serializers/administration/__init__.py +14 -0
- endoreg_db/serializers/administration/ai/__init__.py +10 -0
- endoreg_db/serializers/administration/ai/active_model.py +10 -0
- endoreg_db/serializers/administration/ai/ai_model.py +18 -0
- endoreg_db/serializers/administration/ai/model_type.py +10 -0
- endoreg_db/serializers/administration/center.py +9 -0
- endoreg_db/serializers/administration/gender.py +9 -0
- endoreg_db/serializers/anonymization.py +66 -0
- endoreg_db/serializers/evaluation/examination_evaluation.py +1 -0
- endoreg_db/serializers/examination/__init__.py +10 -0
- endoreg_db/serializers/examination/base.py +46 -0
- endoreg_db/serializers/examination/dropdown.py +21 -0
- endoreg_db/serializers/examination_serializer.py +12 -0
- endoreg_db/serializers/finding/__init__.py +5 -0
- endoreg_db/serializers/finding/finding.py +54 -0
- endoreg_db/serializers/finding_classification/__init__.py +7 -0
- endoreg_db/serializers/finding_classification/choice.py +19 -0
- endoreg_db/serializers/finding_classification/classification.py +13 -0
- endoreg_db/serializers/label/__init__.py +7 -0
- endoreg_db/serializers/label/image_classification_annotation.py +62 -0
- endoreg_db/serializers/label/label.py +15 -0
- endoreg_db/serializers/label_video_segment/__init__.py +7 -0
- endoreg_db/serializers/label_video_segment/_lvs_create.py +149 -0
- endoreg_db/serializers/label_video_segment/_lvs_update.py +138 -0
- endoreg_db/serializers/label_video_segment/_lvs_validate.py +149 -0
- endoreg_db/serializers/label_video_segment/label_video_segment.py +344 -0
- endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +99 -0
- endoreg_db/serializers/label_video_segment/label_video_segment_update.py +163 -0
- endoreg_db/serializers/meta/__init__.py +19 -0
- endoreg_db/serializers/meta/pdf_file_meta_extraction.py +115 -0
- endoreg_db/serializers/meta/report_meta.py +53 -0
- endoreg_db/serializers/meta/sensitive_meta_detail.py +162 -0
- endoreg_db/serializers/meta/sensitive_meta_update.py +148 -0
- endoreg_db/serializers/meta/sensitive_meta_verification.py +59 -0
- endoreg_db/serializers/meta/video_meta.py +39 -0
- endoreg_db/serializers/misc/__init__.py +14 -0
- endoreg_db/serializers/misc/file_overview.py +152 -0
- endoreg_db/serializers/misc/stats.py +33 -0
- endoreg_db/serializers/misc/translatable_field_mix_in.py +44 -0
- endoreg_db/serializers/misc/upload_job.py +71 -0
- endoreg_db/serializers/misc/vop_patient_data.py +120 -0
- endoreg_db/serializers/patient/__init__.py +11 -0
- endoreg_db/serializers/patient/patient.py +86 -0
- endoreg_db/serializers/patient/patient_dropdown.py +27 -0
- endoreg_db/serializers/patient_examination/__init__.py +7 -0
- endoreg_db/serializers/patient_examination/patient_examination.py +141 -0
- endoreg_db/serializers/patient_finding/__init__.py +15 -0
- endoreg_db/serializers/patient_finding/patient_finding.py +31 -0
- endoreg_db/serializers/patient_finding/patient_finding_classification.py +39 -0
- endoreg_db/serializers/patient_finding/patient_finding_detail.py +53 -0
- endoreg_db/serializers/patient_finding/patient_finding_intervention.py +26 -0
- endoreg_db/serializers/patient_finding/patient_finding_list.py +41 -0
- endoreg_db/serializers/patient_finding/patient_finding_write.py +126 -0
- endoreg_db/serializers/pdf/__init__.py +5 -0
- endoreg_db/serializers/pdf/anony_text_validation.py +85 -0
- endoreg_db/serializers/report/__init__.py +9 -0
- endoreg_db/serializers/report/mixins.py +45 -0
- endoreg_db/serializers/report/report.py +105 -0
- endoreg_db/serializers/report/report_list.py +22 -0
- endoreg_db/serializers/report/secure_file_url.py +26 -0
- endoreg_db/serializers/requirements/requirement_schema.py +25 -0
- endoreg_db/serializers/requirements/requirement_sets.py +29 -0
- endoreg_db/serializers/sensitive_meta_serializer.py +282 -0
- endoreg_db/serializers/video/__init__.py +7 -0
- endoreg_db/serializers/video/segmentation.py +263 -0
- endoreg_db/serializers/video/video_file_brief.py +10 -0
- endoreg_db/serializers/video/video_file_detail.py +83 -0
- endoreg_db/serializers/video/video_file_list.py +67 -0
- endoreg_db/serializers/video/video_metadata.py +105 -0
- endoreg_db/serializers/video/video_processing_history.py +153 -0
- endoreg_db/services/__init__.py +5 -0
- endoreg_db/services/anonymization.py +223 -0
- endoreg_db/services/examination_evaluation.py +149 -0
- endoreg_db/services/finding_description_service.py +0 -0
- endoreg_db/services/lookup_service.py +241 -0
- endoreg_db/services/lookup_store.py +122 -0
- endoreg_db/services/ollama_api_docs.py +1528 -0
- endoreg_db/services/pdf_import.py +993 -0
- endoreg_db/services/polling_coordinator.py +288 -0
- endoreg_db/services/pseudonym_service.py +89 -0
- endoreg_db/services/requirements_object.py +147 -0
- endoreg_db/services/segment_sync.py +155 -0
- endoreg_db/services/storage_aware_video_processor.py +344 -0
- endoreg_db/services/video_import.py +915 -0
- endoreg_db/tasks/upload_tasks.py +207 -0
- endoreg_db/tasks/video_ingest.py +157 -0
- endoreg_db/tasks/video_processing_tasks.py +327 -0
- endoreg_db/urls/__init__.py +72 -0
- endoreg_db/urls/anonymization.py +32 -0
- endoreg_db/urls/auth.py +16 -0
- endoreg_db/urls/classification.py +39 -0
- endoreg_db/urls/examination.py +54 -0
- endoreg_db/urls/files.py +6 -0
- endoreg_db/urls/label_video_segment_validate.py +33 -0
- endoreg_db/urls/label_video_segments.py +44 -0
- endoreg_db/urls/media.py +32 -0
- endoreg_db/urls/patient.py +19 -0
- endoreg_db/urls/pdf.py +0 -0
- endoreg_db/urls/report.py +78 -0
- endoreg_db/urls/requirements.py +13 -0
- endoreg_db/urls/sensitive_meta.py +36 -0
- endoreg_db/urls/stats.py +46 -0
- endoreg_db/urls/upload.py +20 -0
- endoreg_db/urls/video.py +119 -0
- endoreg_db/urls.py +6 -269
- endoreg_db/utils/__init__.py +68 -16
- endoreg_db/utils/ai/__init__.py +9 -0
- endoreg_db/{models/ai_model/utils.py → utils/ai/get.py} +1 -4
- endoreg_db/{models/ai_model/lightning → utils/ai}/inference_dataset.py +0 -1
- endoreg_db/{models/ai_model/lightning → utils/ai}/multilabel_classification_net.py +14 -10
- endoreg_db/{models/ai_model/lightning → utils/ai}/postprocess.py +15 -5
- endoreg_db/utils/ai/predict.py +291 -0
- endoreg_db/{models/ai_model/lightning → utils/ai}/preprocess.py +1 -1
- endoreg_db/utils/calc_duration_seconds.py +24 -0
- endoreg_db/utils/case_generator/__init__.py +0 -0
- endoreg_db/utils/check_video_files.py +148 -0
- endoreg_db/utils/dataloader.py +88 -31
- endoreg_db/utils/dates.py +21 -0
- endoreg_db/utils/env.py +33 -0
- endoreg_db/utils/extract_specific_frames.py +72 -0
- endoreg_db/utils/file_operations.py +29 -1
- endoreg_db/utils/fix_video_path_direct.py +141 -0
- endoreg_db/utils/frame_anonymization_utils.py +463 -0
- endoreg_db/utils/hashs.py +1 -0
- endoreg_db/utils/links/__init__.py +0 -0
- endoreg_db/utils/links/requirement_link.py +193 -0
- endoreg_db/utils/mime_types.py +0 -0
- endoreg_db/utils/names.py +2 -0
- endoreg_db/utils/paths.py +104 -0
- endoreg_db/utils/permissions.py +143 -0
- endoreg_db/utils/pipelines/Readme.md +235 -0
- endoreg_db/utils/pipelines/__init__.py +0 -0
- endoreg_db/utils/pipelines/process_video_dir.py +120 -0
- endoreg_db/utils/product/__init__.py +0 -0
- endoreg_db/utils/product/sum_emissions.py +20 -0
- endoreg_db/utils/product/sum_weights.py +18 -0
- endoreg_db/utils/pydantic_models/db_config.py +1 -1
- endoreg_db/utils/requirement_helpers.py +0 -0
- endoreg_db/utils/requirement_operator_logic/__init__.py +0 -0
- endoreg_db/utils/requirement_operator_logic/lab_value_operators.py +578 -0
- endoreg_db/utils/requirement_operator_logic/model_evaluators.py +368 -0
- endoreg_db/utils/translation.py +27 -0
- endoreg_db/utils/validate_video_detailed.py +357 -0
- endoreg_db/utils/video/__init__.py +19 -6
- endoreg_db/utils/video/extract_frames.py +37 -70
- endoreg_db/utils/video/ffmpeg_wrapper.py +772 -0
- endoreg_db/utils/video/names.py +42 -0
- endoreg_db/utils/video/streaming_processor.py +312 -0
- endoreg_db/utils/video/video_splitter.py +94 -0
- endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +238 -0
- endoreg_db/views/__init__.py +282 -2
- endoreg_db/views/anonymization/__init__.py +27 -0
- endoreg_db/views/anonymization/media_management.py +454 -0
- endoreg_db/views/anonymization/overview.py +216 -0
- endoreg_db/views/anonymization/validate.py +63 -0
- endoreg_db/views/auth/__init__.py +13 -0
- endoreg_db/views/auth/keycloak.py +113 -0
- endoreg_db/views/examination/__init__.py +33 -0
- endoreg_db/views/examination/examination.py +37 -0
- endoreg_db/views/examination/examination_manifest_cache.py +26 -0
- endoreg_db/views/examination/get_finding_classification_choices.py +59 -0
- endoreg_db/views/examination/get_finding_classifications.py +36 -0
- endoreg_db/views/examination/get_findings.py +41 -0
- endoreg_db/views/examination/get_instruments.py +18 -0
- endoreg_db/views/examination/get_interventions.py +14 -0
- endoreg_db/views/finding/__init__.py +9 -0
- endoreg_db/views/finding/finding.py +112 -0
- endoreg_db/views/finding/get_classifications.py +14 -0
- endoreg_db/views/finding/get_interventions.py +17 -0
- endoreg_db/views/finding_classification/__init__.py +13 -0
- endoreg_db/views/finding_classification/base.py +0 -0
- endoreg_db/views/finding_classification/finding_classification.py +42 -0
- endoreg_db/views/finding_classification/get_classification_choices.py +55 -0
- endoreg_db/views/label/__init__.py +5 -0
- endoreg_db/views/label/label.py +15 -0
- endoreg_db/views/label_video_segment/__init__.py +16 -0
- endoreg_db/views/label_video_segment/create_lvs_from_annotation.py +44 -0
- endoreg_db/views/label_video_segment/get_lvs_by_name_and_video.py +50 -0
- endoreg_db/views/label_video_segment/label_video_segment.py +77 -0
- endoreg_db/views/label_video_segment/label_video_segment_by_label.py +174 -0
- endoreg_db/views/label_video_segment/label_video_segment_detail.py +73 -0
- endoreg_db/views/label_video_segment/update_lvs_from_annotation.py +46 -0
- endoreg_db/views/label_video_segment/validate.py +226 -0
- endoreg_db/views/media/__init__.py +9 -0
- endoreg_db/views/media/pdf_media.py +386 -0
- endoreg_db/views/media/video_media.py +272 -0
- endoreg_db/views/meta/__init__.py +15 -0
- endoreg_db/views/meta/available_files_list.py +146 -0
- endoreg_db/views/meta/report_meta.py +53 -0
- endoreg_db/views/meta/sensitive_meta_detail.py +148 -0
- endoreg_db/views/meta/sensitive_meta_list.py +104 -0
- endoreg_db/views/meta/sensitive_meta_verification.py +71 -0
- endoreg_db/views/misc/__init__.py +63 -0
- endoreg_db/views/misc/center.py +13 -0
- endoreg_db/views/misc/gender.py +14 -0
- endoreg_db/views/misc/secure_file_serving_view.py +80 -0
- endoreg_db/views/misc/secure_file_url_view.py +84 -0
- endoreg_db/views/misc/secure_url_validate.py +79 -0
- endoreg_db/views/misc/stats.py +220 -0
- endoreg_db/views/misc/translation.py +182 -0
- endoreg_db/views/misc/upload_views.py +240 -0
- endoreg_db/views/patient/__init__.py +5 -0
- endoreg_db/views/patient/patient.py +210 -0
- endoreg_db/views/patient_examination/DEPRECATED_video_backup.py +164 -0
- endoreg_db/views/patient_examination/__init__.py +11 -0
- endoreg_db/views/patient_examination/patient_examination.py +140 -0
- endoreg_db/views/patient_examination/patient_examination_create.py +63 -0
- endoreg_db/views/patient_examination/patient_examination_detail.py +66 -0
- endoreg_db/views/patient_examination/patient_examination_list.py +68 -0
- endoreg_db/views/patient_examination/video.py +194 -0
- endoreg_db/views/patient_finding/__init__.py +7 -0
- endoreg_db/views/patient_finding/base.py +0 -0
- endoreg_db/views/patient_finding/patient_finding.py +64 -0
- endoreg_db/views/patient_finding/patient_finding_optimized.py +259 -0
- endoreg_db/views/patient_finding_classification/__init__.py +5 -0
- endoreg_db/views/patient_finding_classification/pfc_create.py +67 -0
- endoreg_db/views/patient_finding_location/__init__.py +5 -0
- endoreg_db/views/patient_finding_location/pfl_create.py +70 -0
- endoreg_db/views/patient_finding_morphology/__init__.py +5 -0
- endoreg_db/views/patient_finding_morphology/pfm_create.py +70 -0
- endoreg_db/views/pdf/__init__.py +11 -0
- endoreg_db/views/pdf/pdf_media.py +239 -0
- endoreg_db/views/pdf/pdf_stream_views.py +127 -0
- endoreg_db/views/pdf/reimport.py +151 -0
- endoreg_db/views/report/__init__.py +9 -0
- endoreg_db/views/report/report_list.py +112 -0
- endoreg_db/views/report/report_with_secure_url.py +28 -0
- endoreg_db/views/report/start_examination.py +7 -0
- endoreg_db/views/requirement/__init__.py +10 -0
- endoreg_db/views/requirement/evaluate.py +279 -0
- endoreg_db/views/requirement/lookup.py +483 -0
- endoreg_db/views/requirement/lookup_store.py +252 -0
- endoreg_db/views/requirement_lookup/lookup.py +0 -0
- endoreg_db/views/requirement_lookup/lookup_store.py +0 -0
- endoreg_db/views/stats/__init__.py +13 -0
- endoreg_db/views/stats/stats_views.py +229 -0
- endoreg_db/views/video/__init__.py +72 -0
- endoreg_db/views/video/correction.py +672 -0
- endoreg_db/views/video/media/__init__.py +23 -0
- endoreg_db/views/video/media/task_status.py +49 -0
- endoreg_db/views/video/media/video_analyze.py +52 -0
- endoreg_db/views/video/media/video_apply_mask.py +48 -0
- endoreg_db/views/video/media/video_correction.py +21 -0
- endoreg_db/views/video/media/video_download_processed.py +58 -0
- endoreg_db/views/video/media/video_media.py +158 -0
- endoreg_db/views/video/media/video_meta.py +29 -0
- endoreg_db/views/video/media/video_processing_history.py +24 -0
- endoreg_db/views/video/media/video_remove_frames.py +48 -0
- endoreg_db/views/video/media/video_reprocess.py +40 -0
- endoreg_db/views/video/reimport.py +192 -0
- endoreg_db/views/video/segmentation.py +274 -0
- endoreg_db/views/{views_for_timeline.py → video/timeline.py} +3 -3
- endoreg_db/views/video/video_examination_viewset.py +329 -0
- endoreg_db/views/video/video_stream.py +188 -0
- endoreg_db-0.8.1.dist-info/METADATA +384 -0
- endoreg_db-0.8.1.dist-info/RECORD +789 -0
- endoreg_db/data/agl_service/data.yaml +0 -19
- endoreg_db/data/finding_location_classification/colonoscopy.yaml +0 -46
- endoreg_db/data/finding_morphology_classification/colonoscopy.yaml +0 -48
- endoreg_db/data/finding_morphology_classification_choice/colonoscopy_size.yaml +0 -57
- endoreg_db/management/commands/_load_model_template.py +0 -41
- endoreg_db/management/commands/delete_all.py +0 -18
- endoreg_db/management/commands/fetch_legacy_image_dataset.py +0 -32
- endoreg_db/management/commands/fix_auth_permission.py +0 -20
- endoreg_db/management/commands/load_active_model_data.py +0 -45
- endoreg_db/management/commands/load_g_play_data.py +0 -113
- endoreg_db/management/commands/load_logging_data.py +0 -39
- endoreg_db/management/commands/load_lx_data.py +0 -64
- endoreg_db/management/commands/load_medication_indication_data.py +0 -63
- endoreg_db/management/commands/load_medication_indication_type_data.py +0 -41
- endoreg_db/management/commands/load_medication_intake_time_data.py +0 -41
- endoreg_db/management/commands/load_medication_schedule_data.py +0 -55
- endoreg_db/management/commands/load_network_data.py +0 -57
- endoreg_db/migrations/0002_alter_frame_image_alter_rawframe_image.py +0 -23
- endoreg_db/migrations/0003_alter_frame_image_alter_rawframe_image.py +0 -23
- endoreg_db/migrations/0004_alter_rawvideofile_file_alter_video_file.py +0 -25
- endoreg_db/migrations/0005_rawvideofile_frame_count_and_more.py +0 -33
- endoreg_db/migrations/0006_frame_extracted_rawframe_extracted.py +0 -23
- endoreg_db/migrations/0007_rename_pseudo_patient_video_patient_and_more.py +0 -24
- endoreg_db/migrations/0008_remove_reportfile_patient_examination_and_more.py +0 -48
- endoreg_db/models/ai_model/active_model.py +0 -9
- endoreg_db/models/ai_model/ai_model.py +0 -103
- endoreg_db/models/ai_model/lightning/__init__.py +0 -3
- endoreg_db/models/ai_model/lightning/predict.py +0 -172
- endoreg_db/models/ai_model/lightning/prediction_visualizer.py +0 -55
- endoreg_db/models/ai_model/lightning/run_visualizer.py +0 -21
- endoreg_db/models/ai_model/model_meta.py +0 -250
- endoreg_db/models/annotation/__init__.py +0 -32
- endoreg_db/models/annotation/anonymized_image_annotation.py +0 -115
- endoreg_db/models/annotation/binary_classification_annotation_task.py +0 -117
- endoreg_db/models/annotation/image_classification.py +0 -86
- endoreg_db/models/annotation/video_segmentation_annotation.py +0 -52
- endoreg_db/models/case/__init__.py +0 -1
- endoreg_db/models/case/case.py +0 -34
- endoreg_db/models/center/center.py +0 -51
- endoreg_db/models/center/center_product.py +0 -33
- endoreg_db/models/center/center_waste.py +0 -16
- endoreg_db/models/data_file/__init__.py +0 -39
- endoreg_db/models/data_file/base_classes/__init__.py +0 -7
- endoreg_db/models/data_file/base_classes/abstract_frame.py +0 -100
- endoreg_db/models/data_file/base_classes/abstract_pdf.py +0 -136
- endoreg_db/models/data_file/base_classes/abstract_video.py +0 -807
- endoreg_db/models/data_file/base_classes/frame_helpers.py +0 -17
- endoreg_db/models/data_file/base_classes/prepare_bulk_frames.py +0 -19
- endoreg_db/models/data_file/base_classes/utils.py +0 -80
- endoreg_db/models/data_file/frame.py +0 -29
- endoreg_db/models/data_file/import_classes/__init__.py +0 -18
- endoreg_db/models/data_file/import_classes/processing_functions/__init__.py +0 -35
- endoreg_db/models/data_file/import_classes/processing_functions/pdf.py +0 -28
- endoreg_db/models/data_file/import_classes/processing_functions/video.py +0 -260
- endoreg_db/models/data_file/import_classes/raw_pdf.py +0 -260
- endoreg_db/models/data_file/import_classes/raw_video.py +0 -288
- endoreg_db/models/data_file/metadata/sensitive_meta.py +0 -290
- endoreg_db/models/data_file/metadata/video_meta.py +0 -199
- endoreg_db/models/data_file/report_file.py +0 -56
- endoreg_db/models/data_file/video/__init__.py +0 -11
- endoreg_db/models/data_file/video/import_meta.py +0 -25
- endoreg_db/models/data_file/video/video.py +0 -196
- endoreg_db/models/data_file/video_segment.py +0 -214
- endoreg_db/models/disease.py +0 -79
- endoreg_db/models/event.py +0 -73
- endoreg_db/models/examination/examination.py +0 -67
- endoreg_db/models/examination/examination_indication.py +0 -170
- endoreg_db/models/finding/__init__.py +0 -11
- endoreg_db/models/finding/finding.py +0 -75
- endoreg_db/models/finding/finding_location_classification.py +0 -94
- endoreg_db/models/finding/finding_morphology_classification.py +0 -89
- endoreg_db/models/finding/finding_type.py +0 -22
- endoreg_db/models/hardware/__init__.py +0 -2
- endoreg_db/models/information_source.py +0 -29
- endoreg_db/models/laboratory/__init__.py +0 -1
- endoreg_db/models/laboratory/lab_value.py +0 -111
- endoreg_db/models/logging/__init__.py +0 -11
- endoreg_db/models/logging/agl_service.py +0 -19
- endoreg_db/models/logging/base.py +0 -22
- endoreg_db/models/logging/log_type.py +0 -23
- endoreg_db/models/logging/network_device.py +0 -27
- endoreg_db/models/lx/__init__.py +0 -4
- endoreg_db/models/lx/client.py +0 -57
- endoreg_db/models/lx/identity.py +0 -34
- endoreg_db/models/lx/permission.py +0 -18
- endoreg_db/models/lx/user.py +0 -16
- endoreg_db/models/network/__init__.py +0 -9
- endoreg_db/models/network/agl_service.py +0 -38
- endoreg_db/models/network/network_device.py +0 -58
- endoreg_db/models/network/network_device_type.py +0 -23
- endoreg_db/models/other/distribution.py +0 -5
- endoreg_db/models/patient/__init__.py +0 -24
- endoreg_db/models/patient/patient_examination.py +0 -182
- endoreg_db/models/patient/patient_finding.py +0 -143
- endoreg_db/models/patient/patient_finding_location.py +0 -120
- endoreg_db/models/patient/patient_finding_morphology.py +0 -166
- endoreg_db/models/permissions/__init__.py +0 -44
- endoreg_db/models/persons/__init__.py +0 -34
- endoreg_db/models/persons/examiner/__init__.py +0 -2
- endoreg_db/models/persons/examiner/examiner.py +0 -60
- endoreg_db/models/persons/examiner/examiner_type.py +0 -2
- endoreg_db/models/persons/patient/__init__.py +0 -8
- endoreg_db/models/persons/patient/patient.py +0 -389
- endoreg_db/models/persons/patient/patient_disease.py +0 -22
- endoreg_db/models/persons/patient/patient_event.py +0 -52
- endoreg_db/models/persons/patient/patient_lab_sample.py +0 -108
- endoreg_db/models/persons/patient/patient_medication.py +0 -59
- endoreg_db/models/persons/patient/patient_medication_schedule.py +0 -88
- endoreg_db/models/persons/portal_user_information.py +0 -27
- endoreg_db/models/prediction/__init__.py +0 -8
- endoreg_db/models/prediction/image_classification.py +0 -51
- endoreg_db/models/prediction/video_prediction_meta.py +0 -306
- endoreg_db/models/product/product.py +0 -110
- endoreg_db/models/product/product_group.py +0 -27
- endoreg_db/models/product/product_material.py +0 -28
- endoreg_db/models/questionnaires/__init__.py +0 -114
- endoreg_db/models/quiz/__init__.py +0 -9
- endoreg_db/models/quiz/quiz_answer.py +0 -41
- endoreg_db/models/quiz/quiz_question.py +0 -54
- endoreg_db/models/report_reader/report_reader_config.py +0 -53
- endoreg_db/models/rules/__init__.py +0 -5
- endoreg_db/queries/get/__init__.py +0 -6
- endoreg_db/queries/get/center.py +0 -42
- endoreg_db/queries/get/model.py +0 -13
- endoreg_db/queries/get/patient.py +0 -14
- endoreg_db/queries/get/patient_examination.py +0 -20
- endoreg_db/queries/get/report_file.py +0 -33
- endoreg_db/queries/get/video.py +0 -31
- endoreg_db/serializers/ai_model.py +0 -19
- endoreg_db/serializers/annotation.py +0 -14
- endoreg_db/serializers/center.py +0 -11
- endoreg_db/serializers/examination.py +0 -33
- endoreg_db/serializers/frame.py +0 -9
- endoreg_db/serializers/hardware.py +0 -21
- endoreg_db/serializers/label.py +0 -22
- endoreg_db/serializers/patient.py +0 -33
- endoreg_db/serializers/prediction.py +0 -10
- endoreg_db/serializers/raw_pdf_anony_text_validation.py +0 -137
- endoreg_db/serializers/report_file.py +0 -7
- endoreg_db/serializers/video.py +0 -20
- endoreg_db/serializers/video_segmentation.py +0 -587
- endoreg_db/tests.py +0 -3
- endoreg_db/utils/legacy_ocr.py +0 -201
- endoreg_db/utils/video/transcode_videofile.py +0 -111
- endoreg_db/views/patient_views.py +0 -90
- endoreg_db/views/raw_pdf_anony_text_validation_views.py +0 -95
- endoreg_db/views/raw_pdf_meta_validation_views.py +0 -111
- endoreg_db/views/raw_video_meta_validation_views.py +0 -148
- endoreg_db/views/report_views.py +0 -96
- endoreg_db/views/video_segmentation_views.py +0 -166
- endoreg_db-0.6.3.dist-info/METADATA +0 -161
- endoreg_db-0.6.3.dist-info/RECORD +0 -435
- /endoreg_db/{case_generator/__init__.py → api/serializers/finding_descriptions.py} +0 -0
- /endoreg_db/{queries/get/annotation.py → api/views/finding_descriptions.py} +0 -0
- /endoreg_db/{queries/get/prediction.py → data/shift/m2.yaml} +0 -0
- /endoreg_db/{queries/get/video_import_meta.py → factories/__init__.py} +0 -0
- /endoreg_db/{queries/get/video_prediction_meta.py → helpers/__init__.py} +0 -0
- /endoreg_db/models/{case_template → administration/case/case_template}/__init__.py +0 -0
- /endoreg_db/models/{persons → administration/person}/person.py +0 -0
- /endoreg_db/models/{product → administration/product}/__init__.py +0 -0
- /endoreg_db/models/{report_reader → media/pdf/report_reader}/__init__.py +0 -0
- /endoreg_db/models/{report_reader → media/pdf/report_reader}/report_reader_flag.py +0 -0
- /endoreg_db/models/{hardware → medical/hardware}/endoscopy_processor.py +0 -0
- /endoreg_db/models/{medication → medical/medication}/__init__.py +0 -0
- /endoreg_db/models/{medication → medical/medication}/medication_indication.py +0 -0
- /endoreg_db/models/{emission → other/emission}/__init__.py +0 -0
- /endoreg_db/models/{rules → rule}/rule_applicator.py +0 -0
- /endoreg_db/{case_generator → utils/case_generator}/case_generator.py +0 -0
- /endoreg_db/{case_generator → utils/case_generator}/lab_sample_factory.py +0 -0
- /endoreg_db/{case_generator → utils/case_generator}/utils.py +0 -0
- /endoreg_db/views/{csrf.py → misc/csrf.py} +0 -0
- {endoreg_db-0.6.3.dist-info → endoreg_db-0.8.1.dist-info}/WHEEL +0 -0
- {endoreg_db-0.6.3.dist-info → endoreg_db-0.8.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Video import service module.
|
|
3
|
+
|
|
4
|
+
Provides high-level functions for importing and anonymizing video files,
|
|
5
|
+
combining VideoFile creation with frame-level anonymization.
|
|
6
|
+
"""
|
|
7
|
+
from datetime import date
|
|
8
|
+
import logging
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Union, Dict, Any, Optional
|
|
14
|
+
from django.db import transaction
|
|
15
|
+
from django.core.exceptions import FieldError
|
|
16
|
+
from endoreg_db.models import VideoFile, SensitiveMeta
|
|
17
|
+
from endoreg_db.utils.paths import STORAGE_DIR, RAW_FRAME_DIR, VIDEO_DIR, ANONYM_VIDEO_DIR
|
|
18
|
+
import random
|
|
19
|
+
from lx_anonymizer.ocr import trocr_full_image_ocr
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VideoImportService():
|
|
23
|
+
"""
|
|
24
|
+
Service for importing and anonymizing video files.
|
|
25
|
+
Uses a central video instance pattern for cleaner state management.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, project_root: Path = None):
|
|
29
|
+
|
|
30
|
+
# Set up project root path
|
|
31
|
+
if project_root:
|
|
32
|
+
self.project_root = Path(project_root)
|
|
33
|
+
else:
|
|
34
|
+
self.project_root = Path(__file__).parent.parent.parent.parent
|
|
35
|
+
|
|
36
|
+
# Track processed files to prevent duplicates
|
|
37
|
+
self.processed_files = set(str(file) for file in os.listdir(ANONYM_VIDEO_DIR))
|
|
38
|
+
|
|
39
|
+
self.STORAGE_DIR = STORAGE_DIR
|
|
40
|
+
|
|
41
|
+
# Central video instance and processing context
|
|
42
|
+
self.current_video = None
|
|
43
|
+
self.processing_context: Dict[str, Any] = {}
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from endoreg_db.models import VideoFile
|
|
47
|
+
|
|
48
|
+
self.logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
def processed(self) -> bool:
|
|
51
|
+
"""Indicates if the current file has already been processed."""
|
|
52
|
+
return getattr(self, '_processed', False)
|
|
53
|
+
|
|
54
|
+
def import_and_anonymize(
|
|
55
|
+
self,
|
|
56
|
+
file_path: Union[Path, str],
|
|
57
|
+
center_name: str,
|
|
58
|
+
processor_name: str,
|
|
59
|
+
save_video: bool = True,
|
|
60
|
+
delete_source: bool = True,
|
|
61
|
+
) -> "VideoFile":
|
|
62
|
+
"""
|
|
63
|
+
High-level helper that orchestrates the complete video import and anonymization process.
|
|
64
|
+
Uses the central video instance pattern for improved state management.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
file_path: Path to the video file to import
|
|
68
|
+
center_name: Name of the center to associate with video
|
|
69
|
+
processor_name: Name of the processor to associate with video
|
|
70
|
+
save_video: Whether to save the video file
|
|
71
|
+
delete_source: Whether to delete the source file after import
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
VideoFile instance after import and anonymization
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
Exception: On any failure during import or anonymization
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
# Initialize processing context
|
|
81
|
+
self._initialize_processing_context(file_path, center_name, processor_name,
|
|
82
|
+
save_video, delete_source)
|
|
83
|
+
|
|
84
|
+
# Validate and prepare file
|
|
85
|
+
self._validate_and_prepare_file()
|
|
86
|
+
|
|
87
|
+
# Create or retrieve video instance
|
|
88
|
+
self._create_or_retrieve_video_instance()
|
|
89
|
+
|
|
90
|
+
# Setup processing environment
|
|
91
|
+
self._setup_processing_environment()
|
|
92
|
+
|
|
93
|
+
# Process frames and metadata
|
|
94
|
+
self._process_frames_and_metadata()
|
|
95
|
+
|
|
96
|
+
# Finalize processing
|
|
97
|
+
self._finalize_processing()
|
|
98
|
+
|
|
99
|
+
# Move files and cleanup
|
|
100
|
+
self._cleanup_and_archive()
|
|
101
|
+
|
|
102
|
+
return self.current_video
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
self.logger.error(f"Video import and anonymization failed for {file_path}: {e}")
|
|
106
|
+
self._cleanup_on_error()
|
|
107
|
+
raise
|
|
108
|
+
finally:
|
|
109
|
+
self._cleanup_processing_context()
|
|
110
|
+
|
|
111
|
+
def _initialize_processing_context(self, file_path: Union[Path, str], center_name: str,
|
|
112
|
+
processor_name: str, save_video: bool, delete_source: bool):
|
|
113
|
+
"""Initialize the processing context for the current video import."""
|
|
114
|
+
self.processing_context = {
|
|
115
|
+
'file_path': Path(file_path),
|
|
116
|
+
'center_name': center_name,
|
|
117
|
+
'processor_name': processor_name,
|
|
118
|
+
'save_video': save_video,
|
|
119
|
+
'delete_source': delete_source,
|
|
120
|
+
'processing_started': False,
|
|
121
|
+
'frames_extracted': False,
|
|
122
|
+
'anonymization_completed': False,
|
|
123
|
+
'error_reason': None
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
self.logger.info(f"Initialized processing context for: {file_path}")
|
|
127
|
+
|
|
128
|
+
def _validate_and_prepare_file(self):
|
|
129
|
+
"""Validate the video file and prepare for processing."""
|
|
130
|
+
file_path = self.processing_context['file_path']
|
|
131
|
+
|
|
132
|
+
# Check if already processed
|
|
133
|
+
if str(file_path) in self.processed_files:
|
|
134
|
+
self.logger.info(f"File {file_path} already processed, skipping")
|
|
135
|
+
self.processed = True
|
|
136
|
+
raise ValueError(f"File already processed: {file_path}")
|
|
137
|
+
|
|
138
|
+
# Check file exists
|
|
139
|
+
if not file_path.exists():
|
|
140
|
+
raise FileNotFoundError(f"Video file not found: {file_path}")
|
|
141
|
+
|
|
142
|
+
self.logger.info(f"File validation completed for: {file_path}")
|
|
143
|
+
|
|
144
|
+
def _create_or_retrieve_video_instance(self):
|
|
145
|
+
"""Create or retrieve the VideoFile instance and move to final storage."""
|
|
146
|
+
from endoreg_db.models import VideoFile
|
|
147
|
+
|
|
148
|
+
self.logger.info("Creating VideoFile instance...")
|
|
149
|
+
|
|
150
|
+
self.current_video = VideoFile.create_from_file_initialized(
|
|
151
|
+
file_path=self.processing_context['file_path'],
|
|
152
|
+
center_name=self.processing_context['center_name'],
|
|
153
|
+
processor_name=self.processing_context['processor_name'],
|
|
154
|
+
delete_source=self.processing_context['delete_source'],
|
|
155
|
+
save_video_file=self.processing_context['save_video'],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if not self.current_video:
|
|
159
|
+
raise RuntimeError("Failed to create VideoFile instance")
|
|
160
|
+
|
|
161
|
+
# Immediately move to final storage locations
|
|
162
|
+
self._move_to_final_storage()
|
|
163
|
+
|
|
164
|
+
self.logger.info(f"Created VideoFile with UUID: {self.current_video.uuid}")
|
|
165
|
+
|
|
166
|
+
# Get and mark processing state
|
|
167
|
+
state = VideoFile.get_or_create_state(self.current_video)
|
|
168
|
+
if not state:
|
|
169
|
+
raise RuntimeError("Failed to create VideoFile state")
|
|
170
|
+
|
|
171
|
+
state.mark_processing_started(save=True)
|
|
172
|
+
self.processing_context['processing_started'] = True
|
|
173
|
+
|
|
174
|
+
def _move_to_final_storage(self):
|
|
175
|
+
"""
|
|
176
|
+
Move video from raw_videos to final storage locations.
|
|
177
|
+
- Raw video → /data/videos (raw_file_path)
|
|
178
|
+
- Processed video will later → /data/anonym_videos (file_path)
|
|
179
|
+
"""
|
|
180
|
+
from endoreg_db.utils import data_paths
|
|
181
|
+
|
|
182
|
+
source_path = self.processing_context['file_path']
|
|
183
|
+
|
|
184
|
+
# Define target directories
|
|
185
|
+
videos_dir = data_paths["video"] # /data/videos for raw files
|
|
186
|
+
videos_dir.mkdir(parents=True, exist_ok=True)
|
|
187
|
+
|
|
188
|
+
# Create target path for raw video in /data/videos
|
|
189
|
+
video_filename = f"{self.current_video.uuid}_{Path(source_path).name}"
|
|
190
|
+
raw_target_path = videos_dir / video_filename
|
|
191
|
+
|
|
192
|
+
# Move source file to raw video storage
|
|
193
|
+
try:
|
|
194
|
+
shutil.move(str(source_path), str(raw_target_path))
|
|
195
|
+
self.logger.info(f"Moved raw video to: {raw_target_path}")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
self.logger.error(f"Failed to move video to final storage: {e}")
|
|
198
|
+
raise
|
|
199
|
+
|
|
200
|
+
# Update the raw_file path in database (relative to storage root)
|
|
201
|
+
try:
|
|
202
|
+
storage_root = data_paths["storage"]
|
|
203
|
+
relative_path = raw_target_path.relative_to(storage_root)
|
|
204
|
+
self.current_video.raw_file.name = str(relative_path)
|
|
205
|
+
self.current_video.save(update_fields=['raw_file'])
|
|
206
|
+
self.logger.info(f"Updated raw_file path to: {relative_path}")
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self.logger.error(f"Failed to update raw_file path: {e}")
|
|
209
|
+
# Fallback to simple relative path
|
|
210
|
+
self.current_video.raw_file.name = f"videos/{video_filename}"
|
|
211
|
+
self.current_video.save(update_fields=['raw_file'])
|
|
212
|
+
self.logger.info(f"Updated raw_file path using fallback: videos/{video_filename}")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# Store paths for later processing
|
|
216
|
+
self.processing_context['raw_video_path'] = raw_target_path
|
|
217
|
+
self.processing_context['video_filename'] = video_filename
|
|
218
|
+
|
|
219
|
+
def _setup_processing_environment(self):
|
|
220
|
+
"""Setup the processing environment without file movement."""
|
|
221
|
+
# Ensure we have a valid video instance
|
|
222
|
+
if not self.current_video:
|
|
223
|
+
raise RuntimeError("No video instance available for processing environment setup")
|
|
224
|
+
|
|
225
|
+
# Initialize video specifications
|
|
226
|
+
self.current_video.initialize_video_specs()
|
|
227
|
+
|
|
228
|
+
# Initialize frame objects in database
|
|
229
|
+
self.current_video.initialize_frames()
|
|
230
|
+
|
|
231
|
+
# Extract frames BEFORE processing to prevent pipeline 1 conflicts
|
|
232
|
+
self.logger.info("Pre-extracting frames to avoid pipeline conflicts...")
|
|
233
|
+
try:
|
|
234
|
+
frames_extracted = self.current_video.extract_frames(overwrite=False)
|
|
235
|
+
if frames_extracted:
|
|
236
|
+
self.processing_context['frames_extracted'] = True
|
|
237
|
+
self.logger.info("Frame extraction completed successfully")
|
|
238
|
+
|
|
239
|
+
# CRITICAL: Immediately save the frames_extracted state to database
|
|
240
|
+
# to prevent refresh_from_db() in pipeline 1 from overriding it
|
|
241
|
+
state = self.current_video.get_or_create_state()
|
|
242
|
+
if not state.frames_extracted:
|
|
243
|
+
state.frames_extracted = True
|
|
244
|
+
state.save(update_fields=['frames_extracted'])
|
|
245
|
+
self.logger.info("Persisted frames_extracted=True to database")
|
|
246
|
+
else:
|
|
247
|
+
self.logger.warning("Frame extraction failed, but continuing...")
|
|
248
|
+
self.processing_context['frames_extracted'] = False
|
|
249
|
+
except Exception as e:
|
|
250
|
+
self.logger.warning(f"Frame extraction failed during setup: {e}, but continuing...")
|
|
251
|
+
self.processing_context['frames_extracted'] = False
|
|
252
|
+
|
|
253
|
+
# Ensure default patient data
|
|
254
|
+
self._ensure_default_patient_data()
|
|
255
|
+
|
|
256
|
+
self.logger.info("Processing environment setup completed")
|
|
257
|
+
|
|
258
|
+
def _process_frames_and_metadata(self):
|
|
259
|
+
"""Process frames and extract metadata with anonymization."""
|
|
260
|
+
# Check frame cleaning availability
|
|
261
|
+
frame_cleaning_available, FrameCleaner, ReportReader = self._ensure_frame_cleaning_available()
|
|
262
|
+
|
|
263
|
+
if not (frame_cleaning_available and self.current_video.raw_file):
|
|
264
|
+
self.logger.warning("Frame cleaning not available or conditions not met, using fallback anonymization.")
|
|
265
|
+
self._fallback_anonymize_video()
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
self.logger.info("Starting frame-level anonymization with processor ROI masking...")
|
|
270
|
+
|
|
271
|
+
# Get processor ROI information
|
|
272
|
+
processor_roi, endoscope_roi = self._get_processor_roi_info()
|
|
273
|
+
|
|
274
|
+
# Perform frame cleaning
|
|
275
|
+
self._perform_frame_cleaning(FrameCleaner, processor_roi, endoscope_roi)
|
|
276
|
+
|
|
277
|
+
self.processing_context['anonymization_completed'] = True
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
self.logger.warning(f"Frame cleaning failed, continuing with original video: {e}")
|
|
281
|
+
self.processing_context['anonymization_completed'] = False
|
|
282
|
+
self.processing_context['error_reason'] = f"Frame cleaning failed: {e}"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _fallback_anonymize_video(self):
|
|
286
|
+
"""Fallback to create anonymized video if lx_anonymizer is not available."""
|
|
287
|
+
try:
|
|
288
|
+
self.logger.info("Attempting to anonymize video using fallback method.")
|
|
289
|
+
# This requires sensitive meta to be verified.
|
|
290
|
+
if self.current_video.sensitive_meta:
|
|
291
|
+
self.current_video.sensitive_meta.is_verified = True
|
|
292
|
+
self.current_video.sensitive_meta.save()
|
|
293
|
+
|
|
294
|
+
if self.current_video.anonymize_video(delete_original_raw=False):
|
|
295
|
+
self.logger.info("Fallback anonymization successful.")
|
|
296
|
+
self.processing_context['anonymization_completed'] = True
|
|
297
|
+
else:
|
|
298
|
+
self.logger.warning("Fallback anonymization failed.")
|
|
299
|
+
self.processing_context['anonymization_completed'] = False
|
|
300
|
+
self.processing_context['error_reason'] = "Fallback anonymization failed"
|
|
301
|
+
except Exception as e:
|
|
302
|
+
self.logger.error(f"Error during fallback anonymization: {e}", exc_info=True)
|
|
303
|
+
self.processing_context['anonymization_completed'] = False
|
|
304
|
+
self.processing_context['error_reason'] = f"Fallback anonymization failed: {e}"
|
|
305
|
+
|
|
306
|
+
def _finalize_processing(self):
|
|
307
|
+
"""Finalize processing and update video state."""
|
|
308
|
+
self.logger.info("Updating video processing state...")
|
|
309
|
+
|
|
310
|
+
with transaction.atomic():
|
|
311
|
+
# Update basic processing states
|
|
312
|
+
# Only mark frames as extracted if they were successfully extracted
|
|
313
|
+
if self.processing_context.get('frames_extracted', False):
|
|
314
|
+
self.current_video.state.frames_extracted = True
|
|
315
|
+
self.logger.info("Marked frames as extracted in state")
|
|
316
|
+
else:
|
|
317
|
+
self.logger.warning("Frames were not extracted, not updating state")
|
|
318
|
+
|
|
319
|
+
self.current_video.state.frames_initialized = True
|
|
320
|
+
self.current_video.state.video_meta_extracted = True
|
|
321
|
+
self.current_video.state.text_meta_extracted = True
|
|
322
|
+
|
|
323
|
+
# Mark sensitive meta as processed
|
|
324
|
+
self.current_video.state.mark_sensitive_meta_processed(save=False)
|
|
325
|
+
|
|
326
|
+
# Update completion status based on anonymization success
|
|
327
|
+
if self.processing_context['anonymization_completed']:
|
|
328
|
+
self.logger.info(f"Video {self.current_video.uuid} successfully anonymized")
|
|
329
|
+
else:
|
|
330
|
+
self.logger.warning(f"Video {self.current_video.uuid} imported but not anonymized")
|
|
331
|
+
|
|
332
|
+
# Save all state changes
|
|
333
|
+
self.current_video.state.save()
|
|
334
|
+
self.current_video.save()
|
|
335
|
+
|
|
336
|
+
# Signal completion
|
|
337
|
+
self._signal_completion()
|
|
338
|
+
|
|
339
|
+
def _cleanup_and_archive(self):
|
|
340
|
+
"""Move processed video to anonym_videos and cleanup."""
|
|
341
|
+
from endoreg_db.utils import data_paths
|
|
342
|
+
|
|
343
|
+
# Define target directory for processed videos
|
|
344
|
+
anonym_videos_dir = data_paths["anonym_video"] # /data/anonym_videos
|
|
345
|
+
anonym_videos_dir.mkdir(parents=True, exist_ok=True)
|
|
346
|
+
|
|
347
|
+
# Check if we have a processed/cleaned video
|
|
348
|
+
processed_video_path = None
|
|
349
|
+
|
|
350
|
+
# Look for cleaned video from frame cleaning process
|
|
351
|
+
if 'cleaned_video_path' in self.processing_context:
|
|
352
|
+
processed_video_path = self.processing_context['cleaned_video_path']
|
|
353
|
+
else:
|
|
354
|
+
# If no processing occurred, copy from raw video location
|
|
355
|
+
raw_video_path = self.processing_context.get('raw_video_path')
|
|
356
|
+
if raw_video_path and Path(raw_video_path).exists():
|
|
357
|
+
video_filename = self.processing_context.get('video_filename', Path(raw_video_path).name)
|
|
358
|
+
processed_filename = f"processed_{video_filename}"
|
|
359
|
+
processed_video_path = Path(raw_video_path).parent / processed_filename
|
|
360
|
+
|
|
361
|
+
# Copy raw to processed location (will be moved to anonym_videos)
|
|
362
|
+
try:
|
|
363
|
+
shutil.copy2(str(raw_video_path), str(processed_video_path))
|
|
364
|
+
self.logger.info(f"Copied raw video for processing: {processed_video_path}")
|
|
365
|
+
except Exception as e:
|
|
366
|
+
self.logger.error(f"Failed to copy raw video: {e}")
|
|
367
|
+
processed_video_path = raw_video_path # Use original as fallback
|
|
368
|
+
|
|
369
|
+
# Move processed video to anonym_videos
|
|
370
|
+
if processed_video_path and Path(processed_video_path).exists():
|
|
371
|
+
try:
|
|
372
|
+
anonym_video_filename = f"anonym_{self.processing_context.get('video_filename', Path(processed_video_path).name)}"
|
|
373
|
+
anonym_target_path = anonym_videos_dir / anonym_video_filename
|
|
374
|
+
|
|
375
|
+
shutil.move(str(processed_video_path), str(anonym_target_path))
|
|
376
|
+
self.logger.info(f"Moved processed video to: {anonym_target_path}")
|
|
377
|
+
|
|
378
|
+
storage_root = data_paths["storage"]
|
|
379
|
+
relative_path = anonym_target_path.relative_to(storage_root)
|
|
380
|
+
|
|
381
|
+
# Update the file field in database (for processed video)
|
|
382
|
+
try:
|
|
383
|
+
self.current_video.processed_file = str(relative_path)
|
|
384
|
+
self.current_video.save(update_fields=['processed_file'])
|
|
385
|
+
self.logger.info(f"Updated file path to: {relative_path}")
|
|
386
|
+
except FieldError:
|
|
387
|
+
self.logger.warning("Field 'processed_file' does not exist on VideoFile, skipping update")
|
|
388
|
+
raise FieldError
|
|
389
|
+
except Exception as e:
|
|
390
|
+
self.logger.error(f"Failed to move processed video to anonym_videos: {e}")
|
|
391
|
+
|
|
392
|
+
# Cleanup temporary directories
|
|
393
|
+
try:
|
|
394
|
+
from endoreg_db.utils.paths import RAW_FRAME_DIR
|
|
395
|
+
shutil.rmtree(RAW_FRAME_DIR, ignore_errors=True)
|
|
396
|
+
self.logger.debug(f"Cleaned up temporary frames directory: {RAW_FRAME_DIR}")
|
|
397
|
+
except Exception as e:
|
|
398
|
+
self.logger.warning(f"Failed to remove directory {RAW_FRAME_DIR}: {e}")
|
|
399
|
+
|
|
400
|
+
# Handle source file deletion - this should already be moved, but check raw_videos
|
|
401
|
+
source_path = self.processing_context['file_path']
|
|
402
|
+
if self.processing_context['delete_source'] and Path(source_path).exists():
|
|
403
|
+
try:
|
|
404
|
+
os.remove(source_path)
|
|
405
|
+
self.logger.info(f"Removed remaining source file: {source_path}")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
self.logger.warning(f"Failed to remove source file {source_path}: {e}")
|
|
408
|
+
|
|
409
|
+
# Mark as processed
|
|
410
|
+
self.processed_files.add(str(self.processing_context['file_path']))
|
|
411
|
+
|
|
412
|
+
# Refresh from database and finalize state
|
|
413
|
+
with transaction.atomic():
|
|
414
|
+
self.current_video.refresh_from_db()
|
|
415
|
+
if hasattr(self.current_video, 'state'):
|
|
416
|
+
self.current_video.state.mark_sensitive_meta_processed(save=True)
|
|
417
|
+
|
|
418
|
+
self.logger.info(f"Import and anonymization completed for VideoFile UUID: {self.current_video.uuid}")
|
|
419
|
+
self.logger.info("Raw video stored in: /data/videos")
|
|
420
|
+
self.logger.info("Processed video stored in: /data/anonym_videos")
|
|
421
|
+
|
|
422
|
+
def _create_sensitive_file(self, video_instance: "VideoFile" = None, file_path: Union[Path, str] = None) -> Path:
|
|
423
|
+
"""
|
|
424
|
+
Create a sensitive file for the given video file by copying the original file and updating the path.
|
|
425
|
+
Uses the central video instance and processing context if parameters not provided.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
video_instance: Optional video instance, defaults to self.current_video
|
|
429
|
+
file_path: Optional file path, defaults to processing_context['file_path']
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Path: The path to the created sensitive file.
|
|
433
|
+
"""
|
|
434
|
+
video_file = video_instance or self.current_video
|
|
435
|
+
# Always use the currently stored raw file path from the model to avoid deleting external source assets
|
|
436
|
+
source_path = None
|
|
437
|
+
try:
|
|
438
|
+
if video_file and hasattr(video_file, 'raw_file') and video_file.raw_file and hasattr(video_file.raw_file, 'path'):
|
|
439
|
+
source_path = Path(video_file.raw_file.path)
|
|
440
|
+
except Exception:
|
|
441
|
+
source_path = None
|
|
442
|
+
# Fallback only if explicitly provided (do NOT default to processing_context input file)
|
|
443
|
+
if source_path is None and file_path is not None:
|
|
444
|
+
source_path = Path(file_path)
|
|
445
|
+
|
|
446
|
+
if not video_file:
|
|
447
|
+
raise ValueError("No video instance available for creating sensitive file")
|
|
448
|
+
if not source_path:
|
|
449
|
+
raise ValueError("No file path available for creating sensitive file")
|
|
450
|
+
|
|
451
|
+
if not video_file.raw_file:
|
|
452
|
+
raise ValueError("VideoFile must have a raw_file to create a sensitive file")
|
|
453
|
+
|
|
454
|
+
# Ensure the target directory exists
|
|
455
|
+
target_dir = VIDEO_DIR / 'sensitive'
|
|
456
|
+
if not target_dir.exists():
|
|
457
|
+
self.logger.info(f"Creating sensitive file directory: {target_dir}")
|
|
458
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
459
|
+
|
|
460
|
+
# Move the stored raw file into the sensitive directory within storage
|
|
461
|
+
target_file_path = target_dir / source_path.name
|
|
462
|
+
try:
|
|
463
|
+
# Prefer a move within the storage to avoid extra disk usage. This does not touch external input files.
|
|
464
|
+
shutil.move(str(source_path), str(target_file_path))
|
|
465
|
+
self.logger.info(f"Moved raw file to sensitive directory: {target_file_path}")
|
|
466
|
+
except Exception as e:
|
|
467
|
+
# Fallback to copy if move fails (e.g., cross-device or permissions), then remove only the original stored raw file
|
|
468
|
+
self.logger.warning(f"Failed to move raw file to sensitive dir, copying instead: {e}")
|
|
469
|
+
shutil.copy(str(source_path), str(target_file_path))
|
|
470
|
+
try:
|
|
471
|
+
# Remove only the stored raw file copy; never touch external input paths here
|
|
472
|
+
os.remove(source_path)
|
|
473
|
+
except FileNotFoundError:
|
|
474
|
+
pass
|
|
475
|
+
|
|
476
|
+
# Update the model to point to the sensitive file location
|
|
477
|
+
# Use relative path from storage root, like in create_from_file.py
|
|
478
|
+
try:
|
|
479
|
+
from endoreg_db.utils import data_paths
|
|
480
|
+
storage_root = data_paths["storage"]
|
|
481
|
+
relative_path = target_file_path.relative_to(storage_root)
|
|
482
|
+
video_file.raw_file.name = str(relative_path)
|
|
483
|
+
video_file.save(update_fields=['raw_file'])
|
|
484
|
+
self.logger.info(f"Updated video.raw_file to point to sensitive location: {relative_path}")
|
|
485
|
+
except Exception as e:
|
|
486
|
+
# Fallback to absolute path conversion if relative path fails
|
|
487
|
+
self.logger.warning(f"Failed to set relative path, using fallback: {e}")
|
|
488
|
+
video_file.raw_file.name = f"videos/sensitive/{target_file_path.name}"
|
|
489
|
+
video_file.save(update_fields=['raw_file'])
|
|
490
|
+
self.logger.info(f"Updated video.raw_file using fallback method: videos/sensitive/{target_file_path.name}")
|
|
491
|
+
|
|
492
|
+
# Important: Do NOT remove the original input asset passed to the service here.
|
|
493
|
+
# Source file cleanup for external inputs is handled by create_from_file via delete_source flag.
|
|
494
|
+
|
|
495
|
+
self.logger.info(f"Created sensitive file for {video_file.uuid} at {target_file_path}")
|
|
496
|
+
return target_file_path
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _ensure_frame_cleaning_available(self):
|
|
502
|
+
"""
|
|
503
|
+
Ensure frame cleaning modules are available by adding lx-anonymizer to path.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Tuple of (availability_flag, FrameCleaner_class, ReportReader_class)
|
|
507
|
+
"""
|
|
508
|
+
try:
|
|
509
|
+
# Check if we can find the lx-anonymizer directory
|
|
510
|
+
from importlib import resources
|
|
511
|
+
lx_anonymizer_path = resources.files("lx_anonymizer")
|
|
512
|
+
|
|
513
|
+
if lx_anonymizer_path.exists():
|
|
514
|
+
# Add to Python path temporarily
|
|
515
|
+
if str(lx_anonymizer_path) not in sys.path:
|
|
516
|
+
sys.path.insert(0, str(lx_anonymizer_path))
|
|
517
|
+
|
|
518
|
+
# Try simple import
|
|
519
|
+
from lx_anonymizer import FrameCleaner, ReportReader
|
|
520
|
+
|
|
521
|
+
self.logger.info("Successfully imported lx_anonymizer modules")
|
|
522
|
+
|
|
523
|
+
# Remove from path to avoid conflicts
|
|
524
|
+
if str(lx_anonymizer_path) in sys.path:
|
|
525
|
+
sys.path.remove(str(lx_anonymizer_path))
|
|
526
|
+
|
|
527
|
+
return True, FrameCleaner, ReportReader
|
|
528
|
+
|
|
529
|
+
else:
|
|
530
|
+
self.logger.warning(f"lx-anonymizer path not found: {lx_anonymizer_path}")
|
|
531
|
+
|
|
532
|
+
except Exception as e:
|
|
533
|
+
self.logger.warning(f"Frame cleaning not available: {e}")
|
|
534
|
+
|
|
535
|
+
return False, None, None
|
|
536
|
+
|
|
537
|
+
def _get_processor_roi_info(self):
|
|
538
|
+
"""Get processor ROI information for masking."""
|
|
539
|
+
processor_roi = None
|
|
540
|
+
endoscope_roi = None
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
if self.current_video.video_meta and self.current_video.video_meta.processor:
|
|
544
|
+
processor = getattr(self.current_video.video_meta, "processor", None)
|
|
545
|
+
|
|
546
|
+
# Get the endoscope ROI for masking
|
|
547
|
+
endoscope_roi = processor.get_roi_endoscope_image()
|
|
548
|
+
|
|
549
|
+
# Get all processor ROIs for comprehensive masking
|
|
550
|
+
processor_roi = {
|
|
551
|
+
'endoscope_image': endoscope_roi,
|
|
552
|
+
'patient_first_name': processor.get_roi_patient_first_name(),
|
|
553
|
+
'patient_last_name': processor.get_roi_patient_last_name(),
|
|
554
|
+
'patient_dob': processor.get_roi_patient_dob(),
|
|
555
|
+
'examination_date': processor.get_roi_examination_date(),
|
|
556
|
+
'examination_time': processor.get_roi_examination_time(),
|
|
557
|
+
'endoscope_type': processor.get_roi_endoscope_type(),
|
|
558
|
+
'endoscopy_sn': processor.get_roi_endoscopy_sn(),
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
self.logger.info(f"Retrieved processor ROI information: endoscope_roi={endoscope_roi}")
|
|
562
|
+
else:
|
|
563
|
+
self.logger.warning(f"No processor found for video {self.current_video.uuid}, proceeding without ROI masking")
|
|
564
|
+
|
|
565
|
+
except Exception as e:
|
|
566
|
+
self.logger.error(f"Failed to retrieve processor ROI information: {e}")
|
|
567
|
+
# Continue without ROI - don't fail the entire import process
|
|
568
|
+
|
|
569
|
+
return processor_roi, endoscope_roi
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _ensure_default_patient_data(self, video_instance: "VideoFile" = None) -> None:
|
|
573
|
+
"""
|
|
574
|
+
Ensure video has minimum required patient data in SensitiveMeta.
|
|
575
|
+
Creates default values if data is missing after OCR processing.
|
|
576
|
+
Uses the central video instance if parameter not provided.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
video_instance: Optional video instance, defaults to self.current_video
|
|
580
|
+
"""
|
|
581
|
+
video_file = video_instance or self.current_video
|
|
582
|
+
|
|
583
|
+
if not video_file:
|
|
584
|
+
raise ValueError("No video instance available for ensuring patient data")
|
|
585
|
+
|
|
586
|
+
if not video_file.sensitive_meta:
|
|
587
|
+
self.logger.info(f"No SensitiveMeta found for video {video_file.uuid}, creating default")
|
|
588
|
+
|
|
589
|
+
# Create default SensitiveMeta with placeholder data
|
|
590
|
+
default_data = {
|
|
591
|
+
"patient_first_name": "Patient",
|
|
592
|
+
"patient_last_name": "Unknown",
|
|
593
|
+
"patient_dob": date(1990, 1, 1), # Default DOB
|
|
594
|
+
"examination_date": date.today(),
|
|
595
|
+
"center_name": video_file.center.name if video_file.center else "university_hospital_wuerzburg"
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try:
|
|
599
|
+
sensitive_meta = SensitiveMeta.create_from_dict(default_data)
|
|
600
|
+
video_file.sensitive_meta = sensitive_meta
|
|
601
|
+
video_file.save(update_fields=['sensitive_meta'])
|
|
602
|
+
|
|
603
|
+
# Mark sensitive meta as processed after creating default data
|
|
604
|
+
state = video_file.get_or_create_state()
|
|
605
|
+
state.mark_sensitive_meta_processed(save=True)
|
|
606
|
+
|
|
607
|
+
self.logger.info(f"Created default SensitiveMeta for video {video_file.uuid}")
|
|
608
|
+
except Exception as e:
|
|
609
|
+
self.logger.error(f"Failed to create default SensitiveMeta for video {video_file.uuid}: {e}")
|
|
610
|
+
return
|
|
611
|
+
|
|
612
|
+
else:
|
|
613
|
+
# Update existing SensitiveMeta with missing fields
|
|
614
|
+
update_needed = False
|
|
615
|
+
update_data = {}
|
|
616
|
+
|
|
617
|
+
if not video_file.sensitive_meta.patient_first_name:
|
|
618
|
+
update_data["patient_first_name"] = "Patient"
|
|
619
|
+
update_needed = True
|
|
620
|
+
|
|
621
|
+
if not video_file.sensitive_meta.patient_last_name:
|
|
622
|
+
update_data["patient_last_name"] = "Unknown"
|
|
623
|
+
update_needed = True
|
|
624
|
+
|
|
625
|
+
if not video_file.sensitive_meta.patient_dob:
|
|
626
|
+
update_data["patient_dob"] = date(1990, 1, 1)
|
|
627
|
+
update_needed = True
|
|
628
|
+
|
|
629
|
+
if not video_file.sensitive_meta.examination_date:
|
|
630
|
+
update_data["examination_date"] = date.today()
|
|
631
|
+
update_needed = True
|
|
632
|
+
|
|
633
|
+
if update_needed:
|
|
634
|
+
try:
|
|
635
|
+
video_file.sensitive_meta.update_from_dict(update_data)
|
|
636
|
+
|
|
637
|
+
# Mark sensitive meta as processed after updating missing fields
|
|
638
|
+
state = video_file.get_or_create_state()
|
|
639
|
+
state.mark_sensitive_meta_processed(save=True)
|
|
640
|
+
|
|
641
|
+
self.logger.info(f"Updated missing SensitiveMeta fields for video {video_file.uuid}: {list(update_data.keys())}")
|
|
642
|
+
except Exception as e:
|
|
643
|
+
self.logger.error(f"Failed to update SensitiveMeta for video {video_file.uuid}: {e}")
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _ensure_frame_cleaning_available(self):
|
|
647
|
+
"""
|
|
648
|
+
Ensure frame cleaning modules are available by adding lx-anonymizer to path.
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
Tuple of (availability_flag, FrameCleaner_class, ReportReader_class)
|
|
652
|
+
"""
|
|
653
|
+
try:
|
|
654
|
+
# Check if we can find the lx-anonymizer directory
|
|
655
|
+
from importlib import resources
|
|
656
|
+
lx_anonymizer_path = resources.files("lx_anonymizer")
|
|
657
|
+
|
|
658
|
+
if lx_anonymizer_path.exists():
|
|
659
|
+
# Add to Python path temporarily
|
|
660
|
+
if str(lx_anonymizer_path) not in sys.path:
|
|
661
|
+
sys.path.insert(0, str(lx_anonymizer_path))
|
|
662
|
+
|
|
663
|
+
# Try simple import
|
|
664
|
+
from lx_anonymizer import FrameCleaner, ReportReader
|
|
665
|
+
|
|
666
|
+
self.logger.info("Successfully imported lx_anonymizer modules")
|
|
667
|
+
|
|
668
|
+
# Remove from path to avoid conflicts
|
|
669
|
+
if str(lx_anonymizer_path) in sys.path:
|
|
670
|
+
sys.path.remove(str(lx_anonymizer_path))
|
|
671
|
+
|
|
672
|
+
return True, FrameCleaner, ReportReader
|
|
673
|
+
|
|
674
|
+
else:
|
|
675
|
+
self.logger.warning(f"lx-anonymizer path not found: {lx_anonymizer_path}")
|
|
676
|
+
|
|
677
|
+
except Exception as e:
|
|
678
|
+
self.logger.warning(f"Frame cleaning not available: {e}")
|
|
679
|
+
|
|
680
|
+
return False, None, None
|
|
681
|
+
|
|
682
|
+
def _get_processor_roi_info(self):
|
|
683
|
+
"""Get processor ROI information for masking."""
|
|
684
|
+
processor_roi = None
|
|
685
|
+
endoscope_roi = None
|
|
686
|
+
|
|
687
|
+
try:
|
|
688
|
+
if self.current_video.video_meta and self.current_video.video_meta.processor:
|
|
689
|
+
processor = getattr(self.current_video.video_meta, "processor", None)
|
|
690
|
+
|
|
691
|
+
# Get the endoscope ROI for masking
|
|
692
|
+
endoscope_roi = processor.get_roi_endoscope_image()
|
|
693
|
+
|
|
694
|
+
# Get all processor ROIs for comprehensive masking
|
|
695
|
+
processor_roi = {
|
|
696
|
+
'endoscope_image': endoscope_roi,
|
|
697
|
+
'patient_first_name': processor.get_roi_patient_first_name(),
|
|
698
|
+
'patient_last_name': processor.get_roi_patient_last_name(),
|
|
699
|
+
'patient_dob': processor.get_roi_patient_dob(),
|
|
700
|
+
'examination_date': processor.get_roi_examination_date(),
|
|
701
|
+
'examination_time': processor.get_roi_examination_time(),
|
|
702
|
+
'endoscope_type': processor.get_roi_endoscope_type(),
|
|
703
|
+
'endoscopy_sn': processor.get_roi_endoscopy_sn(),
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
self.logger.info(f"Retrieved processor ROI information: endoscope_roi={endoscope_roi}")
|
|
707
|
+
else:
|
|
708
|
+
self.logger.warning(f"No processor found for video {self.current_video.uuid}, proceeding without ROI masking")
|
|
709
|
+
|
|
710
|
+
except Exception as e:
|
|
711
|
+
self.logger.error(f"Failed to retrieve processor ROI information: {e}")
|
|
712
|
+
# Continue without ROI - don't fail the entire import process
|
|
713
|
+
|
|
714
|
+
return processor_roi, endoscope_roi
|
|
715
|
+
|
|
716
|
+
def _perform_frame_cleaning(self, FrameCleaner, processor_roi, endoscope_roi):
|
|
717
|
+
"""Perform frame cleaning and anonymization."""
|
|
718
|
+
# Instantiate frame cleaner
|
|
719
|
+
frame_cleaner = FrameCleaner()
|
|
720
|
+
|
|
721
|
+
# Prepare parameters for frame cleaning
|
|
722
|
+
raw_video_path = self.processing_context.get('raw_video_path')
|
|
723
|
+
|
|
724
|
+
if not raw_video_path or not Path(raw_video_path).exists():
|
|
725
|
+
raise RuntimeError(f"Raw video path not found: {raw_video_path}")
|
|
726
|
+
|
|
727
|
+
# Get processor name safely
|
|
728
|
+
processor = getattr(self.current_video.video_meta, "processor", None) if self.current_video.video_meta else None
|
|
729
|
+
device_name = processor.name if processor else self.processing_context['processor_name']
|
|
730
|
+
|
|
731
|
+
tmp_dir = RAW_FRAME_DIR
|
|
732
|
+
|
|
733
|
+
# Create temporary output path for cleaned video
|
|
734
|
+
video_filename = self.processing_context.get('video_filename', Path(raw_video_path).name)
|
|
735
|
+
cleaned_filename = f"cleaned_{video_filename}"
|
|
736
|
+
cleaned_video_path = Path(raw_video_path).parent / cleaned_filename
|
|
737
|
+
|
|
738
|
+
# Clean video with ROI masking (heavy I/O operation)
|
|
739
|
+
actual_cleaned_path, extracted_metadata = frame_cleaner.clean_video(
|
|
740
|
+
Path(raw_video_path),
|
|
741
|
+
self.current_video,
|
|
742
|
+
tmp_dir,
|
|
743
|
+
device_name,
|
|
744
|
+
endoscope_roi,
|
|
745
|
+
processor_roi,
|
|
746
|
+
cleaned_video_path
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
# Optional: enrich metadata using TrOCR+LLM on one random extracted frame
|
|
750
|
+
try:
|
|
751
|
+
# Prefer frames belonging to this video (UUID in path), else pick any frame
|
|
752
|
+
frame_candidates = list(RAW_FRAME_DIR.rglob("*.jpg")) + list(RAW_FRAME_DIR.rglob("*.png"))
|
|
753
|
+
video_uuid = str(self.current_video.uuid)
|
|
754
|
+
filtered = [p for p in frame_candidates if video_uuid in str(p)] or frame_candidates
|
|
755
|
+
if filtered:
|
|
756
|
+
sample_frame = random.choice(filtered)
|
|
757
|
+
ocr_text = trocr_full_image_ocr(sample_frame)
|
|
758
|
+
if ocr_text:
|
|
759
|
+
llm_metadata = frame_cleaner.extract_metadata(ocr_text)
|
|
760
|
+
if llm_metadata:
|
|
761
|
+
# Merge with already extracted frame-level metadata
|
|
762
|
+
extracted_metadata = frame_cleaner.frame_metadata_extractor.merge_metadata(
|
|
763
|
+
extracted_metadata or {}, llm_metadata
|
|
764
|
+
)
|
|
765
|
+
self.logger.info("LLM metadata extraction (random frame) successful")
|
|
766
|
+
else:
|
|
767
|
+
self.logger.info("LLM metadata extraction (random frame) found no data")
|
|
768
|
+
else:
|
|
769
|
+
self.logger.info("No text extracted by TrOCR on random frame")
|
|
770
|
+
except Exception as e:
|
|
771
|
+
self.logger.error(f"LLM metadata enrichment step failed: {e}")
|
|
772
|
+
|
|
773
|
+
# Store cleaned video path for later use in _cleanup_and_archive
|
|
774
|
+
self.processing_context['cleaned_video_path'] = actual_cleaned_path
|
|
775
|
+
self.processing_context['extracted_metadata'] = extracted_metadata
|
|
776
|
+
|
|
777
|
+
# Update sensitive metadata with extracted information
|
|
778
|
+
self._update_sensitive_metadata(extracted_metadata)
|
|
779
|
+
self.logger.info(f"Extracted metadata from frame cleaning: {extracted_metadata}")
|
|
780
|
+
|
|
781
|
+
self.logger.info(f"Frame cleaning with ROI masking completed: {actual_cleaned_path}")
|
|
782
|
+
self.logger.info("Cleaned video will be moved to anonym_videos during cleanup")
|
|
783
|
+
|
|
784
|
+
def _update_sensitive_metadata(self, extracted_metadata):
|
|
785
|
+
"""
|
|
786
|
+
Update sensitive metadata with extracted information.
|
|
787
|
+
|
|
788
|
+
SAFETY MECHANISM: Only updates fields that are empty, default values, or explicitly marked as safe to overwrite.
|
|
789
|
+
This prevents accidentally overwriting valuable manually entered or previously extracted data.
|
|
790
|
+
"""
|
|
791
|
+
if not (self.current_video.sensitive_meta and extracted_metadata):
|
|
792
|
+
return
|
|
793
|
+
|
|
794
|
+
sm = self.current_video.sensitive_meta
|
|
795
|
+
updated_fields = []
|
|
796
|
+
|
|
797
|
+
# Map extracted metadata to SensitiveMeta fields
|
|
798
|
+
metadata_mapping = {
|
|
799
|
+
'patient_first_name': 'patient_first_name',
|
|
800
|
+
'patient_last_name': 'patient_last_name',
|
|
801
|
+
'patient_dob': 'patient_dob',
|
|
802
|
+
'examination_date': 'examination_date',
|
|
803
|
+
'endoscope_type': 'endoscope_type'
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
# Define default/placeholder values that are safe to overwrite
|
|
807
|
+
SAFE_TO_OVERWRITE_VALUES = [
|
|
808
|
+
'Patient', # Default first name
|
|
809
|
+
'Unknown', # Default last name
|
|
810
|
+
date(1990, 1, 1), # Default DOB
|
|
811
|
+
None, # Empty values
|
|
812
|
+
'', # Empty strings
|
|
813
|
+
'N/A', # Placeholder values
|
|
814
|
+
'Unknown Device', # Default device name
|
|
815
|
+
]
|
|
816
|
+
|
|
817
|
+
for meta_key, sm_field in metadata_mapping.items():
|
|
818
|
+
if extracted_metadata.get(meta_key) and hasattr(sm, sm_field):
|
|
819
|
+
old_value = getattr(sm, sm_field)
|
|
820
|
+
new_value = extracted_metadata[meta_key]
|
|
821
|
+
|
|
822
|
+
# Enhanced safety check: Only update if current value is safe to overwrite
|
|
823
|
+
if new_value and (old_value in SAFE_TO_OVERWRITE_VALUES):
|
|
824
|
+
self.logger.info(f"Updating {sm_field} from '{old_value}' to '{new_value}' for video {self.current_video.uuid}")
|
|
825
|
+
setattr(sm, sm_field, new_value)
|
|
826
|
+
updated_fields.append(sm_field)
|
|
827
|
+
elif new_value and old_value and old_value not in SAFE_TO_OVERWRITE_VALUES:
|
|
828
|
+
self.logger.info(f"Preserving existing {sm_field} value '{old_value}' (not overwriting with '{new_value}') for video {self.current_video.uuid}")
|
|
829
|
+
|
|
830
|
+
if updated_fields:
|
|
831
|
+
sm.save(update_fields=updated_fields)
|
|
832
|
+
self.logger.info(f"Updated SensitiveMeta fields for video {self.current_video.uuid}: {updated_fields}")
|
|
833
|
+
|
|
834
|
+
# Mark sensitive meta as processed after successful update
|
|
835
|
+
self.current_video.state.mark_sensitive_meta_processed(save=True)
|
|
836
|
+
self.logger.info(f"Marked sensitive metadata as processed for video {self.current_video.uuid}")
|
|
837
|
+
else:
|
|
838
|
+
self.logger.info(f"No SensitiveMeta fields updated for video {self.current_video.uuid} - all existing values preserved")
|
|
839
|
+
|
|
840
|
+
def _signal_completion(self):
|
|
841
|
+
"""Signal completion to the tracking system."""
|
|
842
|
+
try:
|
|
843
|
+
video_processing_complete = (
|
|
844
|
+
self.current_video.sensitive_meta is not None and
|
|
845
|
+
self.current_video.video_meta is not None and
|
|
846
|
+
self.current_video.raw_file and
|
|
847
|
+
hasattr(self.current_video.raw_file, 'path') and
|
|
848
|
+
Path(self.current_video.raw_file.path).exists()
|
|
849
|
+
)
|
|
850
|
+
|
|
851
|
+
if video_processing_complete:
|
|
852
|
+
self.logger.info(f"Video {self.current_video.uuid} processing completed successfully - ready for validation")
|
|
853
|
+
|
|
854
|
+
# Update completion flags if they exist
|
|
855
|
+
completion_fields = []
|
|
856
|
+
for field_name in ['import_completed', 'processing_complete', 'ready_for_validation']:
|
|
857
|
+
if hasattr(self.current_video, field_name):
|
|
858
|
+
setattr(self.current_video, field_name, True)
|
|
859
|
+
completion_fields.append(field_name)
|
|
860
|
+
|
|
861
|
+
if completion_fields:
|
|
862
|
+
self.current_video.save(update_fields=completion_fields)
|
|
863
|
+
self.logger.info(f"Updated completion flags: {completion_fields}")
|
|
864
|
+
else:
|
|
865
|
+
self.logger.warning(f"Video {self.current_video.uuid} processing incomplete - missing required components")
|
|
866
|
+
|
|
867
|
+
except Exception as e:
|
|
868
|
+
self.logger.warning(f"Failed to signal completion status: {e}")
|
|
869
|
+
|
|
870
|
+
def _cleanup_on_error(self):
|
|
871
|
+
"""Cleanup processing context on error."""
|
|
872
|
+
if self.current_video and hasattr(self.current_video, 'state'):
|
|
873
|
+
try:
|
|
874
|
+
if self.processing_context.get('processing_started'):
|
|
875
|
+
self.current_video.state.frames_extracted = False
|
|
876
|
+
self.current_video.state.frames_initialized = False
|
|
877
|
+
self.current_video.state.video_meta_extracted = False
|
|
878
|
+
self.current_video.state.text_meta_extracted = False
|
|
879
|
+
self.current_video.state.save()
|
|
880
|
+
except Exception as e:
|
|
881
|
+
self.logger.warning(f"Error during cleanup: {e}")
|
|
882
|
+
|
|
883
|
+
def _cleanup_processing_context(self):
|
|
884
|
+
"""Cleanup processing context."""
|
|
885
|
+
try:
|
|
886
|
+
# Clean up any temporary processing artifacts
|
|
887
|
+
if self.processing_context.get('frames_extracted'):
|
|
888
|
+
# Cleanup handled in _cleanup_and_archive
|
|
889
|
+
pass
|
|
890
|
+
except Exception as e:
|
|
891
|
+
self.logger.warning(f"Error during context cleanup: {e}")
|
|
892
|
+
finally:
|
|
893
|
+
# Reset context
|
|
894
|
+
self.current_video = None
|
|
895
|
+
self.processing_context = {}
|
|
896
|
+
|
|
897
|
+
# Convenience function for callers/tests that expect a module-level import_and_anonymize
|
|
898
|
+
def import_and_anonymize(
|
|
899
|
+
file_path,
|
|
900
|
+
center_name: str,
|
|
901
|
+
processor_name: str,
|
|
902
|
+
save_video: bool = True,
|
|
903
|
+
delete_source: bool = False,
|
|
904
|
+
) -> "VideoFile":
|
|
905
|
+
"""Module-level helper that instantiates VideoImportService and runs import_and_anonymize.
|
|
906
|
+
Kept for backward compatibility with callers that import this function directly.
|
|
907
|
+
"""
|
|
908
|
+
service = VideoImportService()
|
|
909
|
+
return service.import_and_anonymize(
|
|
910
|
+
file_path=file_path,
|
|
911
|
+
center_name=center_name,
|
|
912
|
+
processor_name=processor_name,
|
|
913
|
+
save_video=save_video,
|
|
914
|
+
delete_source=delete_source,
|
|
915
|
+
)
|