endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.0__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/authz/auth.py +74 -0
- endoreg_db/authz/backends.py +168 -0
- endoreg_db/authz/management/commands/list_routes.py +18 -0
- endoreg_db/authz/middleware.py +83 -0
- endoreg_db/authz/permissions.py +127 -0
- endoreg_db/authz/policy.py +218 -0
- endoreg_db/authz/views_auth.py +66 -0
- endoreg_db/config/env.py +13 -8
- endoreg_db/data/__init__.py +8 -31
- endoreg_db/data/_examples/disease.yaml +55 -0
- endoreg_db/data/_examples/disease_classification.yaml +13 -0
- endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
- endoreg_db/data/_examples/event.yaml +64 -0
- endoreg_db/data/_examples/examination.yaml +72 -0
- endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
- endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
- endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
- endoreg_db/data/_examples/finding/complication.yaml +16 -0
- endoreg_db/data/_examples/finding/data.yaml +105 -0
- endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
- endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
- endoreg_db/data/_examples/finding/outcome.yaml +12 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
- endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
- endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
- endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
- endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
- endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
- endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
- endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
- endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
- endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
- endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
- endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
- endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
- endoreg_db/data/_examples/finding_type/data.yaml +43 -0
- endoreg_db/data/_examples/requirement/age.yaml +26 -0
- endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
- endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
- endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
- endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
- endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
- endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
- endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
- endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
- endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
- endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
- endoreg_db/data/_examples/requirement/gender.yaml +25 -0
- endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
- endoreg_db/data/_examples/requirement/medication.yaml +93 -0
- endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
- endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
- endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
- endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
- endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
- endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
- endoreg_db/data/event_classification/data.yaml +4 -0
- endoreg_db/data/event_classification_choice/data.yaml +9 -0
- endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
- endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
- endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
- endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
- endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
- endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
- endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
- endoreg_db/data/requirement_set/_old_ +109 -0
- endoreg_db/data/requirement_set_type/data.yaml +21 -0
- endoreg_db/data/setup_config.yaml +4 -4
- endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
- endoreg_db/exceptions.py +5 -2
- endoreg_db/helpers/data_loader.py +1 -1
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
- endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
- endoreg_db/management/commands/import_video.py +9 -10
- endoreg_db/management/commands/import_video_with_classification.py +1 -1
- endoreg_db/management/commands/init_default_ai_model.py +1 -1
- endoreg_db/management/commands/list_routes.py +18 -0
- endoreg_db/management/commands/load_center_data.py +12 -12
- endoreg_db/management/commands/load_requirement_data.py +60 -31
- endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
- endoreg_db/management/commands/setup_endoreg_db.py +3 -3
- endoreg_db/management/commands/storage_management.py +271 -203
- endoreg_db/migrations/0001_initial.py +1799 -1300
- endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
- endoreg_db/migrations/_old/0001_initial.py +1857 -0
- endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
- endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
- endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
- endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
- endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
- endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
- endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
- endoreg_db/models/__init__.py +78 -123
- endoreg_db/models/administration/__init__.py +21 -42
- endoreg_db/models/administration/ai/active_model.py +2 -2
- endoreg_db/models/administration/ai/ai_model.py +7 -6
- endoreg_db/models/administration/case/__init__.py +1 -15
- endoreg_db/models/administration/case/case.py +3 -3
- endoreg_db/models/administration/case/case_template/__init__.py +2 -14
- endoreg_db/models/administration/case/case_template/case_template.py +2 -124
- endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
- endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
- endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
- endoreg_db/models/administration/center/center.py +33 -19
- endoreg_db/models/administration/center/center_product.py +12 -9
- endoreg_db/models/administration/center/center_resource.py +25 -19
- endoreg_db/models/administration/center/center_shift.py +21 -17
- endoreg_db/models/administration/center/center_waste.py +16 -8
- endoreg_db/models/administration/person/__init__.py +2 -0
- endoreg_db/models/administration/person/employee/employee.py +10 -5
- endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
- endoreg_db/models/administration/person/employee/employee_type.py +12 -6
- endoreg_db/models/administration/person/examiner/examiner.py +13 -11
- endoreg_db/models/administration/person/patient/__init__.py +2 -0
- endoreg_db/models/administration/person/patient/patient.py +103 -100
- endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
- endoreg_db/models/administration/person/person.py +4 -0
- endoreg_db/models/administration/person/profession/__init__.py +8 -4
- endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
- endoreg_db/models/administration/product/product.py +20 -15
- endoreg_db/models/administration/product/product_material.py +17 -18
- endoreg_db/models/administration/product/product_weight.py +12 -8
- endoreg_db/models/administration/product/reference_product.py +23 -55
- endoreg_db/models/administration/qualification/qualification.py +7 -3
- endoreg_db/models/administration/qualification/qualification_type.py +7 -3
- endoreg_db/models/administration/shift/scheduled_days.py +8 -5
- endoreg_db/models/administration/shift/shift.py +16 -12
- endoreg_db/models/administration/shift/shift_type.py +23 -31
- endoreg_db/models/label/__init__.py +7 -8
- endoreg_db/models/label/annotation/image_classification.py +10 -9
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
- endoreg_db/models/label/label.py +15 -15
- endoreg_db/models/label/label_set.py +19 -6
- endoreg_db/models/label/label_type.py +1 -1
- endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
- endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
- endoreg_db/models/label/video_segmentation_label.py +4 -0
- endoreg_db/models/label/video_segmentation_labelset.py +4 -3
- endoreg_db/models/media/frame/frame.py +22 -22
- endoreg_db/models/media/pdf/raw_pdf.py +110 -182
- endoreg_db/models/media/pdf/report_file.py +25 -29
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
- endoreg_db/models/media/video/__init__.py +1 -0
- endoreg_db/models/media/video/create_from_file.py +48 -56
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +150 -108
- endoreg_db/models/media/video/video_file_ai.py +288 -74
- endoreg_db/models/media/video/video_file_anonymize.py +38 -38
- endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
- endoreg_db/models/media/video/video_file_io.py +109 -62
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
- endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
- endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
- endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
- endoreg_db/models/media/video/video_file_segments.py +24 -17
- endoreg_db/models/media/video/video_metadata.py +19 -35
- endoreg_db/models/media/video/video_processing.py +96 -95
- endoreg_db/models/medical/contraindication/__init__.py +13 -3
- endoreg_db/models/medical/disease.py +22 -16
- endoreg_db/models/medical/event.py +31 -18
- endoreg_db/models/medical/examination/__init__.py +13 -6
- endoreg_db/models/medical/examination/examination.py +17 -18
- endoreg_db/models/medical/examination/examination_indication.py +26 -25
- endoreg_db/models/medical/examination/examination_time.py +16 -6
- endoreg_db/models/medical/examination/examination_time_type.py +9 -6
- endoreg_db/models/medical/examination/examination_type.py +3 -4
- endoreg_db/models/medical/finding/finding.py +38 -39
- endoreg_db/models/medical/finding/finding_classification.py +37 -48
- endoreg_db/models/medical/finding/finding_intervention.py +27 -22
- endoreg_db/models/medical/finding/finding_type.py +13 -12
- endoreg_db/models/medical/hardware/endoscope.py +20 -26
- endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
- endoreg_db/models/medical/laboratory/lab_value.py +62 -91
- endoreg_db/models/medical/medication/medication.py +22 -10
- endoreg_db/models/medical/medication/medication_indication.py +29 -3
- endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
- endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
- endoreg_db/models/medical/medication/medication_schedule.py +27 -16
- endoreg_db/models/medical/organ/__init__.py +15 -12
- endoreg_db/models/medical/patient/medication_examples.py +1 -5
- endoreg_db/models/medical/patient/patient_disease.py +20 -23
- endoreg_db/models/medical/patient/patient_event.py +19 -22
- endoreg_db/models/medical/patient/patient_examination.py +48 -54
- endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
- endoreg_db/models/medical/patient/patient_finding.py +122 -139
- endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
- endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
- endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
- endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
- endoreg_db/models/medical/patient/patient_medication.py +27 -38
- endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
- endoreg_db/models/medical/risk/risk.py +7 -6
- endoreg_db/models/medical/risk/risk_type.py +8 -5
- endoreg_db/models/metadata/model_meta.py +60 -29
- endoreg_db/models/metadata/model_meta_logic.py +125 -18
- endoreg_db/models/metadata/pdf_meta.py +19 -24
- endoreg_db/models/metadata/sensitive_meta.py +102 -85
- endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
- endoreg_db/models/metadata/video_meta.py +51 -31
- endoreg_db/models/metadata/video_prediction_logic.py +16 -23
- endoreg_db/models/metadata/video_prediction_meta.py +29 -33
- endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
- endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
- endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
- endoreg_db/models/other/emission/emission_factor.py +18 -8
- endoreg_db/models/other/gender.py +10 -5
- endoreg_db/models/other/information_source.py +25 -25
- endoreg_db/models/other/material.py +9 -5
- endoreg_db/models/other/resource.py +6 -4
- endoreg_db/models/other/tag.py +10 -5
- endoreg_db/models/other/transport_route.py +13 -8
- endoreg_db/models/other/unit.py +10 -6
- endoreg_db/models/other/waste.py +6 -5
- endoreg_db/models/requirement/requirement.py +580 -272
- endoreg_db/models/requirement/requirement_error.py +85 -0
- endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
- endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
- endoreg_db/models/requirement/requirement_operator.py +36 -33
- endoreg_db/models/requirement/requirement_set.py +74 -57
- endoreg_db/models/state/__init__.py +4 -4
- endoreg_db/models/state/abstract.py +2 -2
- endoreg_db/models/state/anonymization.py +12 -0
- endoreg_db/models/state/audit_ledger.py +46 -47
- endoreg_db/models/state/label_video_segment.py +9 -0
- endoreg_db/models/state/raw_pdf.py +40 -46
- endoreg_db/models/state/sensitive_meta.py +6 -2
- endoreg_db/models/state/video.py +58 -53
- endoreg_db/models/upload_job.py +32 -55
- endoreg_db/models/utils.py +1 -2
- endoreg_db/root_urls.py +21 -2
- endoreg_db/serializers/__init__.py +0 -2
- endoreg_db/serializers/anonymization.py +18 -10
- endoreg_db/serializers/meta/report_meta.py +1 -1
- endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
- endoreg_db/serializers/misc/file_overview.py +11 -99
- endoreg_db/serializers/requirements/requirement_sets.py +92 -22
- endoreg_db/serializers/video/segmentation.py +2 -1
- endoreg_db/serializers/video/video_processing_history.py +20 -5
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +37 -24
- endoreg_db/services/pdf_import.py +166 -68
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +193 -283
- endoreg_db/urls/__init__.py +7 -20
- endoreg_db/urls/media.py +108 -67
- endoreg_db/urls/root_urls.py +29 -0
- endoreg_db/utils/__init__.py +15 -5
- endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
- endoreg_db/utils/case_generator/__init__.py +3 -0
- endoreg_db/utils/dataloader.py +88 -16
- endoreg_db/utils/defaults/set_default_center.py +32 -0
- endoreg_db/utils/names.py +22 -16
- endoreg_db/utils/permissions.py +2 -1
- endoreg_db/utils/pipelines/process_video_dir.py +1 -1
- endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
- endoreg_db/utils/setup_config.py +8 -5
- endoreg_db/utils/storage.py +115 -0
- endoreg_db/utils/validate_endo_roi.py +8 -2
- endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
- endoreg_db/views/__init__.py +0 -10
- endoreg_db/views/anonymization/media_management.py +198 -163
- endoreg_db/views/anonymization/overview.py +4 -1
- endoreg_db/views/anonymization/validate.py +174 -40
- endoreg_db/views/media/__init__.py +2 -0
- endoreg_db/views/media/pdf_media.py +131 -152
- endoreg_db/views/media/sensitive_metadata.py +46 -6
- endoreg_db/views/media/video_media.py +89 -82
- endoreg_db/views/media/video_segments.py +2 -3
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/pdf_stream.py +20 -21
- endoreg_db/views/pdf/reimport.py +11 -32
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +17 -3
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -2
- endoreg_db/views/video/correction.py +2 -2
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/video_file_frames.py +0 -0
- endoreg_db/models/metadata/frame_ocr_result.py +0 -0
- endoreg_db/models/rule/__init__.py +0 -13
- endoreg_db/models/rule/rule.py +0 -27
- endoreg_db/models/rule/rule_applicator.py +0 -224
- endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
- endoreg_db/models/rule/rule_type.py +0 -20
- endoreg_db/models/rule/ruleset.py +0 -17
- endoreg_db/serializers/video/video_metadata.py +0 -105
- endoreg_db/urls/report.py +0 -48
- endoreg_db/urls/video.py +0 -61
- endoreg_db/utils/case_generator/case_generator.py +0 -159
- endoreg_db/utils/case_generator/utils.py +0 -30
- endoreg_db/views/report/__init__.py +0 -9
- endoreg_db/views/report/report_list.py +0 -112
- endoreg_db/views/report/report_with_secure_url.py +0 -28
- endoreg_db/views/report/start_examination.py +0 -7
- endoreg_db/views.py +0 -0
- /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
- /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
- /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
- /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,30 +11,26 @@ Changelog:
|
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
import os
|
|
14
|
-
import random
|
|
15
14
|
import shutil
|
|
16
|
-
import sys
|
|
17
15
|
import time
|
|
18
16
|
from contextlib import contextmanager
|
|
19
17
|
from datetime import date
|
|
20
18
|
from pathlib import Path
|
|
21
19
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
22
|
-
|
|
20
|
+
import subprocess
|
|
23
21
|
from django.db import transaction
|
|
24
22
|
from django.db.models.fields.files import FieldFile
|
|
25
|
-
from lx_anonymizer import FrameCleaner
|
|
26
|
-
from moviepy import video
|
|
27
23
|
|
|
28
24
|
from endoreg_db.models import EndoscopyProcessor, SensitiveMeta, VideoFile
|
|
29
25
|
from endoreg_db.models.media.video.video_file_anonymize import _cleanup_raw_assets
|
|
26
|
+
from endoreg_db.utils import ensure_local_file, storage_file_exists
|
|
30
27
|
from endoreg_db.utils.hashs import get_video_hash
|
|
31
28
|
from endoreg_db.utils.paths import ANONYM_VIDEO_DIR, STORAGE_DIR, VIDEO_DIR
|
|
29
|
+
from endoreg_db.models.state import VideoState
|
|
32
30
|
|
|
33
31
|
# File lock configuration (matches PDF import)
|
|
34
32
|
STALE_LOCK_SECONDS = 6000 # 100 minutes - reclaim locks older than this
|
|
35
|
-
MAX_LOCK_WAIT_SECONDS =
|
|
36
|
-
90 # New: wait up to 90s for a non-stale lock to clear before skipping
|
|
37
|
-
)
|
|
33
|
+
MAX_LOCK_WAIT_SECONDS = 90 # New: wait up to 90s for a non-stale lock to clear before skipping
|
|
38
34
|
|
|
39
35
|
logger = logging.getLogger(__name__)
|
|
40
36
|
|
|
@@ -63,10 +59,7 @@ class VideoImportService:
|
|
|
63
59
|
# Ensure anonym_video directory exists before listing files
|
|
64
60
|
anonym_video_dir = Path(ANONYM_VIDEO_DIR)
|
|
65
61
|
if anonym_video_dir.exists():
|
|
66
|
-
self.processed_files = set(
|
|
67
|
-
str(anonym_video_dir / file)
|
|
68
|
-
for file in os.listdir(ANONYM_VIDEO_DIR)
|
|
69
|
-
)
|
|
62
|
+
self.processed_files = set(str(anonym_video_dir / file) for file in os.listdir(ANONYM_VIDEO_DIR))
|
|
70
63
|
else:
|
|
71
64
|
logger.info(f"Creating anonym_videos directory: {anonym_video_dir}")
|
|
72
65
|
anonym_video_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -80,12 +73,13 @@ class VideoImportService:
|
|
|
80
73
|
self.processing_context: Dict[str, Any] = {}
|
|
81
74
|
|
|
82
75
|
self.delete_source = True
|
|
76
|
+
self.original_file_path = None
|
|
83
77
|
|
|
84
78
|
self.logger = logging.getLogger(__name__)
|
|
79
|
+
|
|
80
|
+
self.current_video_id = Optional[int]
|
|
85
81
|
|
|
86
|
-
self.cleaner =
|
|
87
|
-
None # This gets instantiated in the perform_frame_cleaning method
|
|
88
|
-
)
|
|
82
|
+
self.cleaner = None # This gets instantiated in the perform_frame_cleaning method
|
|
89
83
|
|
|
90
84
|
def _require_current_video(self) -> VideoFile:
|
|
91
85
|
"""Return the current VideoFile or raise if it has not been initialized."""
|
|
@@ -131,9 +125,7 @@ class VideoImportService:
|
|
|
131
125
|
)
|
|
132
126
|
lock_path.unlink()
|
|
133
127
|
except Exception as e:
|
|
134
|
-
logger.warning(
|
|
135
|
-
"Failed to remove stale lock %s: %s", lock_path, e
|
|
136
|
-
)
|
|
128
|
+
logger.warning("Failed to remove stale lock %s: %s", lock_path, e)
|
|
137
129
|
# Loop continues and retries acquire immediately
|
|
138
130
|
continue
|
|
139
131
|
|
|
@@ -176,9 +168,7 @@ class VideoImportService:
|
|
|
176
168
|
|
|
177
169
|
try:
|
|
178
170
|
# Initialize processing context
|
|
179
|
-
self._initialize_processing_context(
|
|
180
|
-
file_path, center_name, processor_name, save_video, delete_source
|
|
181
|
-
)
|
|
171
|
+
self._initialize_processing_context(file_path, center_name, processor_name, save_video, delete_source)
|
|
182
172
|
|
|
183
173
|
# Validate and prepare file (may raise ValueError if another worker holds a non-stale lock)
|
|
184
174
|
try:
|
|
@@ -212,15 +202,11 @@ class VideoImportService:
|
|
|
212
202
|
|
|
213
203
|
except Exception as e:
|
|
214
204
|
# Safe file path access - handles cases where processing_context wasn't initialized
|
|
215
|
-
safe_file_path = getattr(self, "processing_context", {}).get(
|
|
216
|
-
"file_path", file_path
|
|
217
|
-
)
|
|
205
|
+
safe_file_path = getattr(self, "processing_context", {}).get("file_path", file_path)
|
|
218
206
|
# Debug: Log context state for troubleshooting
|
|
219
207
|
context_keys = list(getattr(self, "processing_context", {}).keys())
|
|
220
208
|
self.logger.debug(f"Context keys during error: {context_keys}")
|
|
221
|
-
self.logger.error(
|
|
222
|
-
f"Video import and anonymization failed for {safe_file_path}: {e}"
|
|
223
|
-
)
|
|
209
|
+
self.logger.error(f"Video import and anonymization failed for {safe_file_path}: {e}")
|
|
224
210
|
self._cleanup_on_error()
|
|
225
211
|
raise
|
|
226
212
|
finally:
|
|
@@ -246,6 +232,7 @@ class VideoImportService:
|
|
|
246
232
|
"anonymization_completed": False,
|
|
247
233
|
"error_reason": None,
|
|
248
234
|
}
|
|
235
|
+
self.original_file_path = str(file_path)
|
|
249
236
|
|
|
250
237
|
self.logger.info(f"Initialized processing context for: {file_path}")
|
|
251
238
|
|
|
@@ -297,6 +284,7 @@ class VideoImportService:
|
|
|
297
284
|
delete_source=self.processing_context["delete_source"],
|
|
298
285
|
save_video_file=self.processing_context["save_video"],
|
|
299
286
|
)
|
|
287
|
+
self.current_video_id = self.current_video.pk
|
|
300
288
|
|
|
301
289
|
if not self.current_video:
|
|
302
290
|
raise RuntimeError("Failed to create VideoFile instance")
|
|
@@ -358,9 +346,7 @@ class VideoImportService:
|
|
|
358
346
|
|
|
359
347
|
timestamp = int(time.time())
|
|
360
348
|
filename = f"video_{timestamp}{suffix}"
|
|
361
|
-
self.logger.warning(
|
|
362
|
-
"No UUID available, using timestamp-based filename: %s", filename
|
|
363
|
-
)
|
|
349
|
+
self.logger.warning("No UUID available, using timestamp-based filename: %s", filename)
|
|
364
350
|
stored_raw_path = videos_dir / filename
|
|
365
351
|
self.logger.debug("Using UUID-based raw filename: %s", filename)
|
|
366
352
|
|
|
@@ -377,9 +363,7 @@ class VideoImportService:
|
|
|
377
363
|
except Exception:
|
|
378
364
|
shutil.copy2(source_path, stored_raw_path)
|
|
379
365
|
os.remove(source_path)
|
|
380
|
-
self.logger.info(
|
|
381
|
-
"Copied & removed raw video to: %s", stored_raw_path
|
|
382
|
-
)
|
|
366
|
+
self.logger.info("Copied & removed raw video to: %s", stored_raw_path)
|
|
383
367
|
else:
|
|
384
368
|
shutil.copy2(source_path, stored_raw_path)
|
|
385
369
|
self.logger.info("Copied raw video to: %s", stored_raw_path)
|
|
@@ -409,8 +393,6 @@ class VideoImportService:
|
|
|
409
393
|
# Initialize video specifications
|
|
410
394
|
video.initialize_video_specs()
|
|
411
395
|
|
|
412
|
-
|
|
413
|
-
|
|
414
396
|
# Extract frames BEFORE processing to prevent pipeline 1 conflicts
|
|
415
397
|
self.logger.info("Pre-extracting frames to avoid pipeline conflicts...")
|
|
416
398
|
try:
|
|
@@ -418,7 +400,7 @@ class VideoImportService:
|
|
|
418
400
|
if frames_extracted:
|
|
419
401
|
self.processing_context["frames_extracted"] = True
|
|
420
402
|
self.logger.info("Frame extraction completed successfully")
|
|
421
|
-
|
|
403
|
+
# Initialize frame objects in database
|
|
422
404
|
video.initialize_frames(video.get_frame_paths())
|
|
423
405
|
|
|
424
406
|
# CRITICAL: Immediately save the frames_extracted state to database
|
|
@@ -432,9 +414,7 @@ class VideoImportService:
|
|
|
432
414
|
self.logger.warning("Frame extraction failed, but continuing...")
|
|
433
415
|
self.processing_context["frames_extracted"] = False
|
|
434
416
|
except Exception as e:
|
|
435
|
-
self.logger.warning(
|
|
436
|
-
f"Frame extraction failed during setup: {e}, but continuing..."
|
|
437
|
-
)
|
|
417
|
+
self.logger.warning(f"Frame extraction failed during setup: {e}, but continuing...")
|
|
438
418
|
self.processing_context["frames_extracted"] = False
|
|
439
419
|
|
|
440
420
|
# Ensure default patient data
|
|
@@ -445,32 +425,22 @@ class VideoImportService:
|
|
|
445
425
|
def _process_frames_and_metadata(self):
|
|
446
426
|
"""Process frames and extract metadata with anonymization."""
|
|
447
427
|
# Check frame cleaning availability
|
|
448
|
-
frame_cleaning_available, frame_cleaner = (
|
|
449
|
-
self._ensure_frame_cleaning_available()
|
|
450
|
-
)
|
|
428
|
+
frame_cleaning_available, frame_cleaner = self._ensure_frame_cleaning_available()
|
|
451
429
|
video = self._require_current_video()
|
|
452
430
|
|
|
453
431
|
raw_file_field = video.raw_file
|
|
454
|
-
has_raw_file = isinstance(raw_file_field, FieldFile) and bool(
|
|
455
|
-
raw_file_field.name
|
|
456
|
-
)
|
|
432
|
+
has_raw_file = isinstance(raw_file_field, FieldFile) and bool(raw_file_field.name)
|
|
457
433
|
|
|
458
434
|
if not (frame_cleaning_available and has_raw_file):
|
|
459
|
-
self.logger.warning(
|
|
460
|
-
"Frame cleaning not available or conditions not met, using fallback anonymization."
|
|
461
|
-
)
|
|
435
|
+
self.logger.warning("Frame cleaning not available or conditions not met, using fallback anonymization.")
|
|
462
436
|
self._fallback_anonymize_video()
|
|
463
437
|
return
|
|
464
438
|
|
|
465
439
|
try:
|
|
466
|
-
self.logger.info(
|
|
467
|
-
"Starting frame-level anonymization with processor ROI masking..."
|
|
468
|
-
)
|
|
440
|
+
self.logger.info("Starting frame-level anonymization with processor ROI masking...")
|
|
469
441
|
|
|
470
442
|
# Get processor ROI information
|
|
471
|
-
endoscope_data_roi_nested, endoscope_image_roi = (
|
|
472
|
-
self._get_processor_roi_info()
|
|
473
|
-
)
|
|
443
|
+
endoscope_data_roi_nested, endoscope_image_roi = self._get_processor_roi_info()
|
|
474
444
|
|
|
475
445
|
# Perform frame cleaning with timeout to prevent blocking
|
|
476
446
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -484,21 +454,12 @@ class VideoImportService:
|
|
|
484
454
|
)
|
|
485
455
|
try:
|
|
486
456
|
# Increased timeout to better accommodate ffmpeg + OCR
|
|
487
|
-
future.result(timeout=
|
|
457
|
+
future.result(timeout=5000)
|
|
488
458
|
self.processing_context["anonymization_completed"] = True
|
|
489
|
-
self.logger.info(
|
|
490
|
-
"Frame cleaning completed successfully within timeout"
|
|
491
|
-
)
|
|
459
|
+
self.logger.info("Frame cleaning completed successfully within timeout")
|
|
492
460
|
except FutureTimeoutError:
|
|
493
|
-
self.logger.warning(
|
|
494
|
-
"Frame cleaning timed out; entering grace period check for cleaned output"
|
|
495
|
-
)
|
|
461
|
+
self.logger.warning("Frame cleaning timed out; entering grace period check for cleaned output")
|
|
496
462
|
# Grace period: detect if cleaned file appears shortly after timeout
|
|
497
|
-
raw_video_path = self.processing_context.get("raw_video_path")
|
|
498
|
-
video_filename = self.processing_context.get(
|
|
499
|
-
"video_filename",
|
|
500
|
-
Path(raw_video_path).name if raw_video_path else "video.mp4",
|
|
501
|
-
)
|
|
502
463
|
grace_seconds = 60
|
|
503
464
|
expected_cleaned_path: Optional[Path] = None
|
|
504
465
|
processed_field = video.processed_file
|
|
@@ -511,12 +472,8 @@ class VideoImportService:
|
|
|
511
472
|
if expected_cleaned_path is not None:
|
|
512
473
|
for _ in range(grace_seconds):
|
|
513
474
|
if expected_cleaned_path.exists():
|
|
514
|
-
self.processing_context["cleaned_video_path"] =
|
|
515
|
-
|
|
516
|
-
)
|
|
517
|
-
self.processing_context["anonymization_completed"] = (
|
|
518
|
-
True
|
|
519
|
-
)
|
|
475
|
+
self.processing_context["cleaned_video_path"] = expected_cleaned_path
|
|
476
|
+
self.processing_context["anonymization_completed"] = True
|
|
520
477
|
self.logger.info(
|
|
521
478
|
"Detected cleaned video during grace period: %s",
|
|
522
479
|
expected_cleaned_path,
|
|
@@ -527,26 +484,18 @@ class VideoImportService:
|
|
|
527
484
|
else:
|
|
528
485
|
self._fallback_anonymize_video()
|
|
529
486
|
if not found:
|
|
530
|
-
raise TimeoutError(
|
|
531
|
-
"Frame cleaning operation timed out - likely Ollama connection issue"
|
|
532
|
-
)
|
|
487
|
+
raise TimeoutError("Frame cleaning operation timed out - likely Ollama connection issue")
|
|
533
488
|
|
|
534
489
|
except Exception as e:
|
|
535
|
-
self.logger.warning(
|
|
536
|
-
"Frame cleaning failed (reason: %s), falling back to simple copy", e
|
|
537
|
-
)
|
|
490
|
+
self.logger.warning("Frame cleaning failed (reason: %s), falling back to simple copy", e)
|
|
538
491
|
# Try fallback anonymization when frame cleaning fails
|
|
539
492
|
try:
|
|
540
493
|
self._fallback_anonymize_video()
|
|
541
494
|
except Exception as fallback_error:
|
|
542
|
-
self.logger.error(
|
|
543
|
-
"Fallback anonymization also failed: %s", fallback_error
|
|
544
|
-
)
|
|
495
|
+
self.logger.error("Fallback anonymization also failed: %s", fallback_error)
|
|
545
496
|
# If even fallback fails, mark as not anonymized but continue import
|
|
546
497
|
self.processing_context["anonymization_completed"] = False
|
|
547
|
-
self.processing_context["error_reason"] =
|
|
548
|
-
f"Frame cleaning failed: {e}, Fallback failed: {fallback_error}"
|
|
549
|
-
)
|
|
498
|
+
self.processing_context["error_reason"] = f"Frame cleaning failed: {e}, Fallback failed: {fallback_error}"
|
|
550
499
|
|
|
551
500
|
def _save_anonymized_video(self):
|
|
552
501
|
original_raw_file_path_to_delete = None
|
|
@@ -555,24 +504,14 @@ class VideoImportService:
|
|
|
555
504
|
anonymized_video_path = video.get_target_anonymized_video_path()
|
|
556
505
|
|
|
557
506
|
if not anonymized_video_path.exists():
|
|
558
|
-
raise RuntimeError(
|
|
559
|
-
f"Processed video file not found after assembly for {video.uuid}: {anonymized_video_path}"
|
|
560
|
-
)
|
|
507
|
+
raise RuntimeError(f"Processed video file not found after assembly for {video.uuid}: {anonymized_video_path}")
|
|
561
508
|
|
|
562
509
|
new_processed_hash = get_video_hash(anonymized_video_path)
|
|
563
|
-
if (
|
|
564
|
-
video.
|
|
565
|
-
.exclude(pk=video.pk)
|
|
566
|
-
.exists()
|
|
567
|
-
):
|
|
568
|
-
raise ValueError(
|
|
569
|
-
f"Processed video hash {new_processed_hash} already exists for another video (Video: {video.uuid})."
|
|
570
|
-
)
|
|
510
|
+
if video.__class__.objects.filter(processed_video_hash=new_processed_hash).exclude(pk=video.pk).exists():
|
|
511
|
+
raise ValueError(f"Processed video hash {new_processed_hash} already exists for another video (Video: {video.uuid}).")
|
|
571
512
|
|
|
572
513
|
video.processed_video_hash = new_processed_hash
|
|
573
|
-
video.processed_file.name = anonymized_video_path.relative_to(
|
|
574
|
-
STORAGE_DIR
|
|
575
|
-
).as_posix()
|
|
514
|
+
video.processed_file.name = anonymized_video_path.relative_to(STORAGE_DIR).as_posix()
|
|
576
515
|
|
|
577
516
|
update_fields = [
|
|
578
517
|
"processed_video_hash",
|
|
@@ -584,7 +523,7 @@ class VideoImportService:
|
|
|
584
523
|
original_raw_file_path_to_delete = video.get_raw_file_path()
|
|
585
524
|
original_raw_frame_dir_to_delete = video.get_frame_dir_path()
|
|
586
525
|
|
|
587
|
-
video.raw_file.name =
|
|
526
|
+
video.raw_file.name = ""
|
|
588
527
|
|
|
589
528
|
update_fields.extend(["raw_file", "video_hash"])
|
|
590
529
|
|
|
@@ -597,10 +536,18 @@ class VideoImportService:
|
|
|
597
536
|
)
|
|
598
537
|
|
|
599
538
|
video.save(update_fields=update_fields)
|
|
600
|
-
video.state
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
539
|
+
if not isinstance(video.state, VideoState):
|
|
540
|
+
try:
|
|
541
|
+
video.get_or_create_state()
|
|
542
|
+
except ValueError as e:
|
|
543
|
+
raise RuntimeError(f"Video state not found for video {video.uuid}. Error {e}")
|
|
544
|
+
|
|
545
|
+
else:
|
|
546
|
+
video.state.mark_anonymized(save=True)
|
|
547
|
+
video.refresh_from_db()
|
|
548
|
+
self.current_video = video
|
|
549
|
+
|
|
550
|
+
return True
|
|
604
551
|
|
|
605
552
|
def _fallback_anonymize_video(self):
|
|
606
553
|
"""
|
|
@@ -610,23 +557,15 @@ class VideoImportService:
|
|
|
610
557
|
self.logger.info("Attempting fallback video anonymization...")
|
|
611
558
|
video = self.current_video
|
|
612
559
|
if video is None:
|
|
613
|
-
self.logger.warning(
|
|
614
|
-
"No VideoFile instance available for fallback anonymization"
|
|
615
|
-
)
|
|
560
|
+
self.logger.warning("No VideoFile instance available for fallback anonymization")
|
|
616
561
|
|
|
617
562
|
# Strategy 2: Simple copy (no processing, just copy raw to processed)
|
|
618
|
-
self.logger.info(
|
|
619
|
-
"Using simple copy fallback (raw video will be used as 'processed' video)"
|
|
620
|
-
)
|
|
563
|
+
self.logger.info("Using simple copy fallback (raw video will be used as 'processed' video)")
|
|
621
564
|
self.processing_context["anonymization_completed"] = False
|
|
622
565
|
self.processing_context["use_raw_as_processed"] = True
|
|
623
|
-
self.logger.warning(
|
|
624
|
-
"Fallback: Video will be imported without anonymization (raw copy used)"
|
|
625
|
-
)
|
|
566
|
+
self.logger.warning("Fallback: Video will be imported without anonymization (raw copy used)")
|
|
626
567
|
except Exception as e:
|
|
627
|
-
self.logger.error(
|
|
628
|
-
f"Error during fallback anonymization: {e}", exc_info=True
|
|
629
|
-
)
|
|
568
|
+
self.logger.error(f"Error during fallback anonymization: {e}", exc_info=True)
|
|
630
569
|
self.processing_context["anonymization_completed"] = False
|
|
631
570
|
self.processing_context["error_reason"] = str(e)
|
|
632
571
|
|
|
@@ -660,18 +599,12 @@ class VideoImportService:
|
|
|
660
599
|
state.text_meta_extracted = True
|
|
661
600
|
|
|
662
601
|
# ✅ FIX: Only mark as processed if anonymization actually completed
|
|
663
|
-
anonymization_completed = self.processing_context.get(
|
|
664
|
-
"anonymization_completed", False
|
|
665
|
-
)
|
|
602
|
+
anonymization_completed = self.processing_context.get("anonymization_completed", False)
|
|
666
603
|
if anonymization_completed:
|
|
667
604
|
state.mark_sensitive_meta_processed(save=False)
|
|
668
|
-
self.logger.info(
|
|
669
|
-
"Anonymization completed - marking sensitive meta as processed"
|
|
670
|
-
)
|
|
605
|
+
self.logger.info("Anonymization completed - marking sensitive meta as processed")
|
|
671
606
|
else:
|
|
672
|
-
self.logger.warning(
|
|
673
|
-
f"Anonymization NOT completed - NOT marking as processed. Reason: {self.processing_context.get('error_reason', 'Unknown')}"
|
|
674
|
-
)
|
|
607
|
+
self.logger.warning(f"Anonymization NOT completed - NOT marking as processed. Reason: {self.processing_context.get('error_reason', 'Unknown')}")
|
|
675
608
|
# Explicitly mark as NOT processed
|
|
676
609
|
state.sensitive_meta_processed = False
|
|
677
610
|
|
|
@@ -703,9 +636,7 @@ class VideoImportService:
|
|
|
703
636
|
processed_video_path = Path(raw_video_path).parent / processed_filename
|
|
704
637
|
try:
|
|
705
638
|
shutil.copy2(str(raw_video_path), str(processed_video_path))
|
|
706
|
-
self.logger.info(
|
|
707
|
-
"Copied raw video for processing: %s", processed_video_path
|
|
708
|
-
)
|
|
639
|
+
self.logger.info("Copied raw video for processing: %s", processed_video_path)
|
|
709
640
|
except Exception as exc:
|
|
710
641
|
self.logger.error("Failed to copy raw video: %s", exc)
|
|
711
642
|
processed_video_path = None
|
|
@@ -725,16 +656,10 @@ class VideoImportService:
|
|
|
725
656
|
relative_path = anonym_target_path.relative_to(storage_root)
|
|
726
657
|
video.processed_file.name = str(relative_path)
|
|
727
658
|
video.save(update_fields=["processed_file"])
|
|
728
|
-
self.logger.info(
|
|
729
|
-
"Updated processed_file path to: %s", relative_path
|
|
730
|
-
)
|
|
659
|
+
self.logger.info("Updated processed_file path to: %s", relative_path)
|
|
731
660
|
except Exception as exc:
|
|
732
|
-
self.logger.error(
|
|
733
|
-
|
|
734
|
-
)
|
|
735
|
-
video.processed_file.name = (
|
|
736
|
-
f"anonym_videos/{anonym_video_filename}"
|
|
737
|
-
)
|
|
661
|
+
self.logger.error("Failed to update processed_file path: %s", exc)
|
|
662
|
+
video.processed_file.name = f"anonym_videos/{anonym_video_filename}"
|
|
738
663
|
video.save(update_fields=["processed_file"])
|
|
739
664
|
self.logger.info(
|
|
740
665
|
"Updated processed_file path using fallback: %s",
|
|
@@ -748,21 +673,15 @@ class VideoImportService:
|
|
|
748
673
|
anonym_target_path,
|
|
749
674
|
)
|
|
750
675
|
except Exception as exc:
|
|
751
|
-
self.logger.error(
|
|
752
|
-
"Failed to move processed video to anonym_videos: %s", exc
|
|
753
|
-
)
|
|
676
|
+
self.logger.error("Failed to move processed video to anonym_videos: %s", exc)
|
|
754
677
|
else:
|
|
755
|
-
self.logger.warning(
|
|
756
|
-
"No processed video available - processed_file will remain empty"
|
|
757
|
-
)
|
|
678
|
+
self.logger.warning("No processed video available - processed_file will remain empty")
|
|
758
679
|
|
|
759
680
|
try:
|
|
760
681
|
from endoreg_db.utils.paths import RAW_FRAME_DIR
|
|
761
682
|
|
|
762
683
|
shutil.rmtree(RAW_FRAME_DIR, ignore_errors=True)
|
|
763
|
-
self.logger.debug(
|
|
764
|
-
"Cleaned up temporary frames directory: %s", RAW_FRAME_DIR
|
|
765
|
-
)
|
|
684
|
+
self.logger.debug("Cleaned up temporary frames directory: %s", RAW_FRAME_DIR)
|
|
766
685
|
except Exception as exc:
|
|
767
686
|
self.logger.warning("Failed to remove directory %s: %s", RAW_FRAME_DIR, exc)
|
|
768
687
|
|
|
@@ -772,14 +691,10 @@ class VideoImportService:
|
|
|
772
691
|
os.remove(source_path)
|
|
773
692
|
self.logger.info("Removed remaining source file: %s", source_path)
|
|
774
693
|
except Exception as exc:
|
|
775
|
-
self.logger.warning(
|
|
776
|
-
"Failed to remove source file %s: %s", source_path, exc
|
|
777
|
-
)
|
|
694
|
+
self.logger.warning("Failed to remove source file %s: %s", source_path, exc)
|
|
778
695
|
|
|
779
|
-
if not video.processed_file or not
|
|
780
|
-
self.logger.warning(
|
|
781
|
-
"No processed_file found after cleanup - video will be unprocessed"
|
|
782
|
-
)
|
|
696
|
+
if not video.processed_file or not storage_file_exists(video.processed_file):
|
|
697
|
+
self.logger.warning("No processed_file found after cleanup - video will be unprocessed")
|
|
783
698
|
try:
|
|
784
699
|
video.anonymize(delete_original_raw=self.delete_source)
|
|
785
700
|
video.save(update_fields=["processed_file"])
|
|
@@ -794,14 +709,16 @@ class VideoImportService:
|
|
|
794
709
|
|
|
795
710
|
with transaction.atomic():
|
|
796
711
|
video.refresh_from_db()
|
|
797
|
-
if hasattr(video, "state") and self.processing_context.get(
|
|
798
|
-
|
|
799
|
-
|
|
712
|
+
if hasattr(video, "state") and self.processing_context.get("anonymization_completed"):
|
|
713
|
+
if not isinstance(video.state, VideoState):
|
|
714
|
+
try:
|
|
715
|
+
video.get_or_create_state()
|
|
716
|
+
except:
|
|
717
|
+
raise RuntimeError(f"Video state not found for video {video.uuid}")
|
|
718
|
+
|
|
800
719
|
video.state.mark_sensitive_meta_processed(save=True)
|
|
801
720
|
|
|
802
|
-
self.logger.info(
|
|
803
|
-
"Import and anonymization completed for VideoFile UUID: %s", video.uuid
|
|
804
|
-
)
|
|
721
|
+
self.logger.info("Import and anonymization completed for VideoFile UUID: %s", video.uuid)
|
|
805
722
|
self.logger.info("Raw video stored in: /data/videos")
|
|
806
723
|
self.logger.info("Processed video stored in: /data/anonym_videos")
|
|
807
724
|
|
|
@@ -813,52 +730,77 @@ class VideoImportService:
|
|
|
813
730
|
"""Create or move a sensitive copy of the raw video file inside storage."""
|
|
814
731
|
|
|
815
732
|
video = video_instance or self._require_current_video()
|
|
816
|
-
|
|
817
733
|
raw_field: FieldFile | None = getattr(video, "raw_file", None)
|
|
818
|
-
source_path: Path | None = None
|
|
819
|
-
try:
|
|
820
|
-
if raw_field and raw_field.path:
|
|
821
|
-
source_path = Path(raw_field.path)
|
|
822
|
-
except Exception:
|
|
823
|
-
source_path = None
|
|
824
734
|
|
|
825
|
-
|
|
826
|
-
|
|
735
|
+
def copy_into_sensitive(source: Path) -> Path:
|
|
736
|
+
target_dir = VIDEO_DIR / "sensitive"
|
|
737
|
+
if not target_dir.exists():
|
|
738
|
+
self.logger.info("Creating sensitive file directory: %s", target_dir)
|
|
739
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
827
740
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if not raw_field:
|
|
831
|
-
raise ValueError(
|
|
832
|
-
"VideoFile must have a raw_file to create a sensitive file"
|
|
833
|
-
)
|
|
741
|
+
target_name = source.name or "raw_video"
|
|
742
|
+
target_file_path = target_dir / target_name
|
|
834
743
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
744
|
+
if source != target_file_path:
|
|
745
|
+
try:
|
|
746
|
+
shutil.copy2(source, target_file_path)
|
|
747
|
+
self.logger.info("Copied raw file to sensitive directory: %s", target_file_path)
|
|
748
|
+
except Exception as exc:
|
|
749
|
+
self.logger.warning("Failed to copy raw file to sensitive dir: %s", exc)
|
|
750
|
+
shutil.copy(source, target_file_path)
|
|
751
|
+
self.logger.info(
|
|
752
|
+
"Fallback copy succeeded for sensitive directory: %s",
|
|
753
|
+
target_file_path,
|
|
754
|
+
)
|
|
755
|
+
else:
|
|
756
|
+
self.logger.debug(
|
|
757
|
+
"Source path already in sensitive directory: %s",
|
|
758
|
+
target_file_path,
|
|
759
|
+
)
|
|
839
760
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
except Exception as exc:
|
|
847
|
-
self.logger.warning(
|
|
848
|
-
"Failed to move raw file to sensitive dir, copying instead: %s", exc
|
|
849
|
-
)
|
|
850
|
-
shutil.copy(str(source_path), str(target_file_path))
|
|
761
|
+
return target_file_path
|
|
762
|
+
|
|
763
|
+
target_file_path: Path | None = None
|
|
764
|
+
|
|
765
|
+
# Prefer an on-disk path from the FieldFile when available
|
|
766
|
+
if raw_field:
|
|
851
767
|
try:
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
768
|
+
local_candidate = Path(raw_field.path)
|
|
769
|
+
if local_candidate.exists():
|
|
770
|
+
target_file_path = copy_into_sensitive(local_candidate)
|
|
771
|
+
except Exception:
|
|
772
|
+
target_file_path = None
|
|
773
|
+
|
|
774
|
+
if target_file_path is None and storage_file_exists(raw_field):
|
|
775
|
+
try:
|
|
776
|
+
with ensure_local_file(raw_field) as temp_source:
|
|
777
|
+
target_file_path = copy_into_sensitive(Path(temp_source))
|
|
778
|
+
except Exception as exc:
|
|
779
|
+
self.logger.warning("Failed to download raw_field for sensitive copy: %s", exc)
|
|
780
|
+
|
|
781
|
+
if target_file_path is None and file_path is not None:
|
|
782
|
+
file_candidate = Path(file_path)
|
|
783
|
+
if file_candidate.exists():
|
|
784
|
+
target_file_path = copy_into_sensitive(file_candidate)
|
|
785
|
+
|
|
786
|
+
if target_file_path is None:
|
|
787
|
+
context_path = self.processing_context.get("raw_video_path")
|
|
788
|
+
if context_path:
|
|
789
|
+
context_candidate = Path(context_path)
|
|
790
|
+
if context_candidate.exists():
|
|
791
|
+
target_file_path = copy_into_sensitive(context_candidate)
|
|
792
|
+
|
|
793
|
+
if target_file_path is None:
|
|
794
|
+
raise ValueError("No file path available for creating sensitive file")
|
|
795
|
+
if not raw_field:
|
|
796
|
+
raise ValueError("VideoFile must have a raw_file to create a sensitive file")
|
|
855
797
|
|
|
856
798
|
try:
|
|
857
799
|
from endoreg_db.utils import data_paths
|
|
858
800
|
|
|
859
801
|
storage_root = data_paths["storage"]
|
|
860
802
|
relative_path = target_file_path.relative_to(storage_root)
|
|
861
|
-
video.raw_file.name =
|
|
803
|
+
video.raw_file.name = relative_path.as_posix()
|
|
862
804
|
video.save(update_fields=["raw_file"])
|
|
863
805
|
self.logger.info(
|
|
864
806
|
"Updated video.raw_file to point to sensitive location: %s",
|
|
@@ -876,9 +818,7 @@ class VideoImportService:
|
|
|
876
818
|
self.processing_context["raw_video_path"] = target_file_path
|
|
877
819
|
self.processing_context["video_filename"] = target_file_path.name
|
|
878
820
|
|
|
879
|
-
self.logger.info(
|
|
880
|
-
"Created sensitive file for %s at %s", video.uuid, target_file_path
|
|
881
|
-
)
|
|
821
|
+
self.logger.info("Created sensitive file for %s at %s", video.uuid, target_file_path)
|
|
882
822
|
return target_file_path
|
|
883
823
|
|
|
884
824
|
def _get_processor_roi_info(
|
|
@@ -894,9 +834,7 @@ class VideoImportService:
|
|
|
894
834
|
video_meta = getattr(video, "video_meta", None)
|
|
895
835
|
processor = getattr(video_meta, "processor", None) if video_meta else None
|
|
896
836
|
if processor:
|
|
897
|
-
assert isinstance(processor, EndoscopyProcessor),
|
|
898
|
-
"Processor is not of type EndoscopyProcessor"
|
|
899
|
-
)
|
|
837
|
+
assert isinstance(processor, EndoscopyProcessor), "Processor is not of type EndoscopyProcessor"
|
|
900
838
|
endoscope_image_roi = processor.get_roi_endoscope_image()
|
|
901
839
|
endoscope_data_roi_nested = processor.get_sensitive_rois()
|
|
902
840
|
self.logger.info(
|
|
@@ -924,34 +862,26 @@ class VideoImportService:
|
|
|
924
862
|
|
|
925
863
|
return endoscope_data_roi_nested, endoscope_image_roi
|
|
926
864
|
|
|
927
|
-
def _ensure_default_patient_data(
|
|
928
|
-
self, video_instance: VideoFile | None = None
|
|
929
|
-
) -> None:
|
|
865
|
+
def _ensure_default_patient_data(self, video_instance: VideoFile | None = None) -> None:
|
|
930
866
|
"""Ensure minimum patient data is present on the video's SensitiveMeta."""
|
|
931
867
|
|
|
932
868
|
video = video_instance or self._require_current_video()
|
|
933
869
|
|
|
934
870
|
sensitive_meta = getattr(video, "sensitive_meta", None)
|
|
935
871
|
if not sensitive_meta:
|
|
936
|
-
self.logger.info(
|
|
937
|
-
"No SensitiveMeta found for video %s, creating default", video.uuid
|
|
938
|
-
)
|
|
872
|
+
self.logger.info("No SensitiveMeta found for video %s, creating default", video.uuid)
|
|
939
873
|
default_data = {
|
|
940
874
|
"patient_first_name": "Patient",
|
|
941
875
|
"patient_last_name": "Unknown",
|
|
942
876
|
"patient_dob": date(1990, 1, 1),
|
|
943
877
|
"examination_date": date.today(),
|
|
944
|
-
"center_name": video.center.name
|
|
945
|
-
if video.center
|
|
946
|
-
else "university_hospital_wuerzburg",
|
|
878
|
+
"center_name": video.center.name if video.center else "university_hospital_wuerzburg",
|
|
947
879
|
}
|
|
948
880
|
try:
|
|
949
881
|
sensitive_meta = SensitiveMeta.create_from_dict(default_data)
|
|
950
882
|
video.sensitive_meta = sensitive_meta
|
|
951
883
|
video.save(update_fields=["sensitive_meta"])
|
|
952
|
-
self.logger.info(
|
|
953
|
-
"Created default SensitiveMeta for video %s", video.uuid
|
|
954
|
-
)
|
|
884
|
+
self.logger.info("Created default SensitiveMeta for video %s", video.uuid)
|
|
955
885
|
except Exception as exc:
|
|
956
886
|
self.logger.error(
|
|
957
887
|
"Failed to create default SensitiveMeta for video %s: %s",
|
|
@@ -960,32 +890,9 @@ class VideoImportService:
|
|
|
960
890
|
)
|
|
961
891
|
return
|
|
962
892
|
else:
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if not sensitive_meta.patient_last_name:
|
|
967
|
-
update_data["patient_last_name"] = "Unknown"
|
|
968
|
-
if not sensitive_meta.patient_dob:
|
|
969
|
-
update_data["patient_dob"] = date(1990, 1, 1)
|
|
970
|
-
if not sensitive_meta.examination_date:
|
|
971
|
-
update_data["examination_date"] = date.today()
|
|
972
|
-
|
|
973
|
-
if update_data:
|
|
974
|
-
try:
|
|
975
|
-
sensitive_meta.update_from_dict(update_data)
|
|
976
|
-
state = video.get_or_create_state()
|
|
977
|
-
state.mark_sensitive_meta_processed(save=True)
|
|
978
|
-
self.logger.info(
|
|
979
|
-
"Updated missing SensitiveMeta fields for video %s: %s",
|
|
980
|
-
video.uuid,
|
|
981
|
-
list(update_data.keys()),
|
|
982
|
-
)
|
|
983
|
-
except Exception as exc:
|
|
984
|
-
self.logger.error(
|
|
985
|
-
"Failed to update SensitiveMeta for video %s: %s",
|
|
986
|
-
video.uuid,
|
|
987
|
-
exc,
|
|
988
|
-
)
|
|
893
|
+
state = video.get_or_create_state()
|
|
894
|
+
state.mark_sensitive_meta_processed(save=True)
|
|
895
|
+
|
|
989
896
|
|
|
990
897
|
def _ensure_frame_cleaning_available(self):
|
|
991
898
|
"""
|
|
@@ -995,25 +902,24 @@ class VideoImportService:
|
|
|
995
902
|
Tuple of (availability_flag, FrameCleaner_class, ReportReader_class)
|
|
996
903
|
"""
|
|
997
904
|
try:
|
|
998
|
-
|
|
999
|
-
from lx_anonymizer import FrameCleaner # type: ignore[import]
|
|
1000
|
-
|
|
1001
|
-
if FrameCleaner:
|
|
1002
|
-
return True, FrameCleaner()
|
|
1003
|
-
|
|
905
|
+
from lx_anonymizer import FrameCleaner
|
|
1004
906
|
except Exception as e:
|
|
1005
|
-
self.logger.warning(
|
|
1006
|
-
|
|
1007
|
-
|
|
907
|
+
self.logger.warning(f"Frame cleaning not available: {e} Please install or update lx_anonymizer.")
|
|
908
|
+
_available = False
|
|
909
|
+
FrameCleaner = None
|
|
1008
910
|
|
|
1009
|
-
|
|
911
|
+
assert FrameCleaner is not None
|
|
912
|
+
frame_cleaner = FrameCleaner()
|
|
913
|
+
_available = True
|
|
914
|
+
|
|
915
|
+
return _available, frame_cleaner
|
|
1010
916
|
|
|
1011
917
|
def _perform_frame_cleaning(self, endoscope_data_roi_nested, endoscope_image_roi):
|
|
1012
918
|
"""Perform frame cleaning and anonymization."""
|
|
1013
919
|
# Instantiate frame cleaner
|
|
1014
920
|
is_available, frame_cleaner = self._ensure_frame_cleaning_available()
|
|
1015
921
|
|
|
1016
|
-
if not is_available:
|
|
922
|
+
if not is_available or frame_cleaner is None:
|
|
1017
923
|
raise RuntimeError("Frame cleaning not available")
|
|
1018
924
|
|
|
1019
925
|
# Prepare parameters for frame cleaning
|
|
@@ -1030,9 +936,7 @@ class VideoImportService:
|
|
|
1030
936
|
video = self._require_current_video()
|
|
1031
937
|
# Ensure raw_video_path is not None
|
|
1032
938
|
if not raw_video_path:
|
|
1033
|
-
raise RuntimeError(
|
|
1034
|
-
"raw_video_path is None, cannot construct cleaned_video_path"
|
|
1035
|
-
)
|
|
939
|
+
raise RuntimeError("raw_video_path is None, cannot construct cleaned_video_path")
|
|
1036
940
|
suffix = Path(raw_video_path).suffix or ".mp4"
|
|
1037
941
|
cleaned_filename = f"cleaned_{video.uuid}{suffix}"
|
|
1038
942
|
cleaned_video_path = Path(raw_video_path).parent / cleaned_filename
|
|
@@ -1053,13 +957,9 @@ class VideoImportService:
|
|
|
1053
957
|
|
|
1054
958
|
# Update sensitive metadata with extracted information
|
|
1055
959
|
self._update_sensitive_metadata(extracted_metadata)
|
|
1056
|
-
self.logger.info(
|
|
1057
|
-
f"Extracted metadata from frame cleaning: {extracted_metadata}"
|
|
1058
|
-
)
|
|
960
|
+
self.logger.info(f"Extracted metadata from frame cleaning: {extracted_metadata}")
|
|
1059
961
|
|
|
1060
|
-
self.logger.info(
|
|
1061
|
-
f"Frame cleaning with ROI masking completed: {actual_cleaned_path}"
|
|
1062
|
-
)
|
|
962
|
+
self.logger.info(f"Frame cleaning with ROI masking completed: {actual_cleaned_path}")
|
|
1063
963
|
self.logger.info("Cleaned video will be moved to anonym_videos during cleanup")
|
|
1064
964
|
|
|
1065
965
|
def _update_sensitive_metadata(self, extracted_metadata: Dict[str, Any]):
|
|
@@ -1096,21 +996,15 @@ class VideoImportService:
|
|
|
1096
996
|
|
|
1097
997
|
center_obj = Center.objects.get(name=center_name)
|
|
1098
998
|
metadata_to_update["center"] = center_obj
|
|
1099
|
-
self.logger.debug(
|
|
1100
|
-
"Loaded center object '%s' from center_name", center_name
|
|
1101
|
-
)
|
|
999
|
+
self.logger.debug("Loaded center object '%s' from center_name", center_name)
|
|
1102
1000
|
metadata_to_update.pop("center_name", None)
|
|
1103
1001
|
except Center.DoesNotExist:
|
|
1104
|
-
self.logger.error(
|
|
1105
|
-
"Center '%s' not found in database", center_name
|
|
1106
|
-
)
|
|
1002
|
+
self.logger.error("Center '%s' not found in database", center_name)
|
|
1107
1003
|
return
|
|
1108
1004
|
|
|
1109
1005
|
try:
|
|
1110
1006
|
sm.update_from_dict(metadata_to_update)
|
|
1111
|
-
updated_fields = list(
|
|
1112
|
-
extracted_metadata.keys()
|
|
1113
|
-
) # Only log originally extracted fields
|
|
1007
|
+
updated_fields = list(extracted_metadata.keys()) # Only log originally extracted fields
|
|
1114
1008
|
except KeyError as e:
|
|
1115
1009
|
self.logger.warning(f"Failed to update SensitiveMeta field {e}")
|
|
1116
1010
|
return
|
|
@@ -1126,9 +1020,7 @@ class VideoImportService:
|
|
|
1126
1020
|
|
|
1127
1021
|
state = video.get_or_create_state()
|
|
1128
1022
|
state.mark_sensitive_meta_processed(save=True)
|
|
1129
|
-
self.logger.info(
|
|
1130
|
-
"Marked sensitive metadata as processed for video %s", video.uuid
|
|
1131
|
-
)
|
|
1023
|
+
self.logger.info("Marked sensitive metadata as processed for video %s", video.uuid)
|
|
1132
1024
|
except Exception as e:
|
|
1133
1025
|
self.logger.error(f"Failed to save SensitiveMeta: {e}")
|
|
1134
1026
|
raise # Re-raise to trigger fallback in calling method
|
|
@@ -1144,18 +1036,9 @@ class VideoImportService:
|
|
|
1144
1036
|
video = self._require_current_video()
|
|
1145
1037
|
|
|
1146
1038
|
raw_field: FieldFile | None = getattr(video, "raw_file", None)
|
|
1147
|
-
raw_exists =
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
raw_exists = Path(raw_field.path).exists()
|
|
1151
|
-
except (ValueError, OSError):
|
|
1152
|
-
raw_exists = False
|
|
1153
|
-
|
|
1154
|
-
video_processing_complete = (
|
|
1155
|
-
video.sensitive_meta is not None
|
|
1156
|
-
and video.video_meta is not None
|
|
1157
|
-
and raw_exists
|
|
1158
|
-
)
|
|
1039
|
+
raw_exists = storage_file_exists(raw_field)
|
|
1040
|
+
|
|
1041
|
+
video_processing_complete = video.sensitive_meta is not None and video.video_meta is not None and raw_exists
|
|
1159
1042
|
|
|
1160
1043
|
if video_processing_complete:
|
|
1161
1044
|
self.logger.info(
|
|
@@ -1189,15 +1072,44 @@ class VideoImportService:
|
|
|
1189
1072
|
def _cleanup_on_error(self):
|
|
1190
1073
|
"""Cleanup processing context on error."""
|
|
1191
1074
|
if self.current_video and hasattr(self.current_video, "state"):
|
|
1075
|
+
if self.current_video.state is None:
|
|
1076
|
+
try:
|
|
1077
|
+
self.current_video.get_or_create_state()
|
|
1078
|
+
except Exception as e:
|
|
1079
|
+
self.logger.warning(f"Video state not found for video {self.current_video.uuid} during error cleanup {e}")
|
|
1080
|
+
return
|
|
1081
|
+
self.current_video.state = self.current_video.get_or_create_state()
|
|
1082
|
+
try:
|
|
1083
|
+
if self.original_file_path is not None:
|
|
1084
|
+
assert Path(self.original_file_path).exists()
|
|
1085
|
+
else:
|
|
1086
|
+
self.logger.warning("Original file path is None")
|
|
1087
|
+
self.logger.info("Marked video import as failed in state")
|
|
1088
|
+
raw_file_path = getattr(self.current_video.raw_file, "path", None)
|
|
1089
|
+
original_file_path = self.original_file_path
|
|
1090
|
+
if raw_file_path and original_file_path:
|
|
1091
|
+
shutil.copy2(str(raw_file_path), str(original_file_path))
|
|
1092
|
+
else:
|
|
1093
|
+
self.logger.warning("Cannot restore original raw file: path is None")
|
|
1094
|
+
except AssertionError:
|
|
1095
|
+
self.logger.warning("Original file path does not exist")
|
|
1192
1096
|
try:
|
|
1097
|
+
|
|
1098
|
+
if not isinstance(self.current_video.state, VideoState):
|
|
1099
|
+
logger.error("Current video is none after Assertion for Video File")
|
|
1100
|
+
raise AssertionError
|
|
1101
|
+
|
|
1102
|
+
|
|
1193
1103
|
if self.processing_context.get("processing_started"):
|
|
1194
1104
|
self.current_video.state.frames_extracted = False
|
|
1195
1105
|
self.current_video.state.frames_initialized = False
|
|
1196
1106
|
self.current_video.state.video_meta_extracted = False
|
|
1197
1107
|
self.current_video.state.text_meta_extracted = False
|
|
1198
1108
|
self.current_video.state.save()
|
|
1109
|
+
|
|
1199
1110
|
except Exception as e:
|
|
1200
1111
|
self.logger.warning(f"Error during cleanup: {e}")
|
|
1112
|
+
|
|
1201
1113
|
|
|
1202
1114
|
def _cleanup_processing_context(self):
|
|
1203
1115
|
"""
|
|
@@ -1226,9 +1138,7 @@ class VideoImportService:
|
|
|
1226
1138
|
file_path_str = str(file_path)
|
|
1227
1139
|
if file_path_str in self.processed_files:
|
|
1228
1140
|
self.processed_files.remove(file_path_str)
|
|
1229
|
-
self.logger.info(
|
|
1230
|
-
f"Removed {file_path_str} from processed files (failed processing)"
|
|
1231
|
-
)
|
|
1141
|
+
self.logger.info(f"Removed {file_path_str} from processed files (failed processing)")
|
|
1232
1142
|
|
|
1233
1143
|
except Exception as e:
|
|
1234
1144
|
self.logger.warning(f"Error during context cleanup: {e}")
|