endoreg-db 0.8.4.4__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_ai_model_data.py +2 -1
- 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 +14 -10
- 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 +249 -177
- 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_1.py +30 -33
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +359 -204
- 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 +139 -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 +383 -43
- 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 +26 -57
- 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/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +33 -91
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- 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/serializers/video_examination.py +198 -0
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +256 -73
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +711 -310
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +266 -117
- endoreg_db/urls/__init__.py +27 -27
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +108 -66
- 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 +5 -12
- 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 -150
- 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 +187 -260
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +186 -0
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/pdf/reimport.py +86 -91
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +186 -288
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -4
- endoreg_db/views/video/correction.py +2 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/refactor_plan.md +0 -0
- 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/pdf/pdf_media.py +0 -239
- 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/video/video_media.py +0 -158
- 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-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import shutil
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING, List, Tuple, Dict, Optional, Set
|
|
5
3
|
import uuid
|
|
6
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
|
6
|
+
|
|
7
7
|
import cv2
|
|
8
|
+
from django.db import transaction
|
|
8
9
|
from tqdm import tqdm
|
|
9
|
-
from django.conf import settings
|
|
10
|
-
|
|
11
|
-
|
|
12
10
|
|
|
13
11
|
from endoreg_db.utils.hashs import get_video_hash
|
|
14
|
-
from endoreg_db.utils.validate_endo_roi import validate_endo_roi
|
|
15
12
|
from endoreg_db.utils.paths import STORAGE_DIR
|
|
13
|
+
from endoreg_db.utils.validate_endo_roi import validate_endo_roi
|
|
14
|
+
|
|
16
15
|
from ....utils.video.ffmpeg_wrapper import assemble_video_from_frames
|
|
17
16
|
from ...utils import anonymize_frame # Import from models.utils
|
|
18
|
-
from .video_file_segments import
|
|
17
|
+
from .video_file_segments import _get_outside_frame_numbers, _get_outside_frames
|
|
19
18
|
|
|
20
19
|
if TYPE_CHECKING:
|
|
21
|
-
from .video_file import VideoFile
|
|
22
|
-
from ..frame import Frame
|
|
23
20
|
from django.db.models import QuerySet
|
|
24
21
|
|
|
22
|
+
from ..frame import Frame
|
|
23
|
+
from .video_file import VideoFile
|
|
24
|
+
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
26
|
|
|
27
27
|
|
|
@@ -66,7 +66,9 @@ def _create_anonymized_frame_files(
|
|
|
66
66
|
if not isinstance(source_path, Path):
|
|
67
67
|
raise TypeError(f"Frame.file_path did not return a Path object for frame {frame_obj.frame_number}")
|
|
68
68
|
except (AttributeError, TypeError, Exception) as path_err:
|
|
69
|
-
logger.error(
|
|
69
|
+
logger.error(
|
|
70
|
+
"Could not determine source path for Frame %d (PK: %s) using frame_obj.file_path: %s", frame_obj.frame_number, frame_obj.pk, path_err
|
|
71
|
+
)
|
|
70
72
|
raise RuntimeError(f"Failed to get source path for frame {frame_obj.frame_number}") from path_err
|
|
71
73
|
|
|
72
74
|
if not source_path.exists():
|
|
@@ -78,13 +80,7 @@ def _create_anonymized_frame_files(
|
|
|
78
80
|
logger.error(error_msg)
|
|
79
81
|
raise FileNotFoundError(error_msg)
|
|
80
82
|
|
|
81
|
-
anonymize_frame(
|
|
82
|
-
raw_frame_path=source_path,
|
|
83
|
-
target_frame_path=target_path,
|
|
84
|
-
endo_roi=endo_roi,
|
|
85
|
-
all_black=make_all_black,
|
|
86
|
-
censor_color=censor_color
|
|
87
|
-
)
|
|
83
|
+
anonymize_frame(raw_frame_path=source_path, target_frame_path=target_path, endo_roi=endo_roi, all_black=make_all_black, censor_color=censor_color)
|
|
88
84
|
|
|
89
85
|
generated_paths.append(target_path)
|
|
90
86
|
except (FileNotFoundError, IOError, ValueError, AttributeError, TypeError, Exception) as e:
|
|
@@ -146,15 +142,14 @@ def _censor_outside_frames(video: "VideoFile", outside_label_name: str = "outsid
|
|
|
146
142
|
error_count += 1
|
|
147
143
|
|
|
148
144
|
except Exception as e:
|
|
149
|
-
logger.error("Error censoring frame %d (%s): %s",
|
|
150
|
-
frame_obj.frame_number, getattr(frame_obj, 'relative_path', 'N/A'), e, exc_info=True)
|
|
145
|
+
logger.error("Error censoring frame %d (%s): %s", frame_obj.frame_number, getattr(frame_obj, "relative_path", "N/A"), e, exc_info=True)
|
|
151
146
|
error_count += 1
|
|
152
147
|
|
|
153
148
|
logger.info("Finished censoring for video %s. Censored: %d, Errors: %d", video.uuid, censored_count, error_count)
|
|
154
149
|
return error_count == 0
|
|
155
150
|
|
|
156
151
|
|
|
157
|
-
def _make_temporary_anonymized_frames(video: "VideoFile") -> Tuple[Path, List[Path]]:
|
|
152
|
+
def _make_temporary_anonymized_frames(video: "VideoFile", roi_processing = True) -> Tuple[Path, List[Path]]:
|
|
158
153
|
"""
|
|
159
154
|
Creates temporary anonymized frames in a separate directory.
|
|
160
155
|
Requires raw file and extracted frames. Raises ValueError or RuntimeError on failure.
|
|
@@ -171,10 +166,15 @@ def _make_temporary_anonymized_frames(video: "VideoFile") -> Tuple[Path, List[Pa
|
|
|
171
166
|
temp_anonym_frame_dir = video.get_temp_anonymized_frame_dir()
|
|
172
167
|
temp_anonym_frame_dir.mkdir(parents=True, exist_ok=True)
|
|
173
168
|
logger.info("Creating temporary anonymized frames for video %s in %s", video.uuid, temp_anonym_frame_dir)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
if roi_processing:
|
|
170
|
+
endo_roi = video.get_endo_roi()
|
|
171
|
+
if not validate_endo_roi(endo_roi_dict=endo_roi):
|
|
172
|
+
raise ValueError(f"Endoscope ROI is not valid for video {video.uuid}")
|
|
173
|
+
else:
|
|
174
|
+
endo_roi = {"x": 0, "y": 0, "width": 0, "height": 0} # Dummy ROI to skip processing
|
|
175
|
+
assert endo_roi is not None # For type checker
|
|
176
|
+
|
|
177
|
+
|
|
178
178
|
|
|
179
179
|
state = video.get_or_create_state()
|
|
180
180
|
if not state.frames_extracted:
|
|
@@ -211,12 +211,12 @@ def _make_temporary_anonymized_frames(video: "VideoFile") -> Tuple[Path, List[Pa
|
|
|
211
211
|
def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
|
|
212
212
|
"""
|
|
213
213
|
Performs full anonymization of a video by censoring frames, assembling a processed video file, updating database records, and optionally deleting original raw assets.
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
Raises:
|
|
216
216
|
ValueError: If required preconditions are not met (e.g., frames not extracted, sensitive metadata not validated).
|
|
217
217
|
FileNotFoundError: If the raw video file is missing.
|
|
218
218
|
RuntimeError: If anonymization or video assembly fails.
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
Returns:
|
|
221
221
|
bool: True if anonymization completes successfully.
|
|
222
222
|
"""
|
|
@@ -233,8 +233,6 @@ def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
|
|
|
233
233
|
raise ValueError(f"Sensitive metadata for video {video.uuid} is not validated. Cannot anonymize.")
|
|
234
234
|
# outside_segments = video.get_outside_segments(only_validated=False)
|
|
235
235
|
# unvalidated_outside = outside_segments.filter(state__is_validated=False)
|
|
236
|
-
|
|
237
|
-
|
|
238
236
|
|
|
239
237
|
logger.info("Starting anonymization process for video %s", video.uuid)
|
|
240
238
|
|
|
@@ -281,17 +279,18 @@ def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
|
|
|
281
279
|
original_raw_file_path_to_delete = video.get_raw_file_path()
|
|
282
280
|
original_raw_frame_dir_to_delete = video.get_frame_dir_path()
|
|
283
281
|
|
|
284
|
-
video.raw_file.name =
|
|
282
|
+
video.raw_file.name = ""
|
|
285
283
|
|
|
286
284
|
update_fields.extend(["raw_file", "video_hash"])
|
|
287
285
|
|
|
288
|
-
transaction.on_commit(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
)
|
|
286
|
+
transaction.on_commit(
|
|
287
|
+
lambda: _cleanup_raw_assets(
|
|
288
|
+
video_uuid=video.uuid, raw_file_path=original_raw_file_path_to_delete, raw_frame_dir=original_raw_frame_dir_to_delete
|
|
289
|
+
)
|
|
290
|
+
)
|
|
293
291
|
|
|
294
292
|
video.save(update_fields=update_fields)
|
|
293
|
+
assert video.state is not None # For type checker
|
|
295
294
|
video.state.mark_anonymized(save=True)
|
|
296
295
|
video.refresh_from_db()
|
|
297
296
|
return True
|
|
@@ -309,7 +308,7 @@ def _anonymize(video: "VideoFile", delete_original_raw: bool = True) -> bool:
|
|
|
309
308
|
shutil.rmtree(temp_anonym_frame_dir)
|
|
310
309
|
|
|
311
310
|
|
|
312
|
-
def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path]=None, raw_frame_dir: Optional[Path]=None):
|
|
311
|
+
def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path] = None, raw_frame_dir: Optional[Path] = None):
|
|
313
312
|
"""
|
|
314
313
|
Deletes the original raw video file and its extracted frames directory.
|
|
315
314
|
Called via transaction.on_commit after successful anonymization.
|
|
@@ -318,9 +317,10 @@ def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path]=Non
|
|
|
318
317
|
- Sets state.frames_extracted=False.
|
|
319
318
|
"""
|
|
320
319
|
from endoreg_db.models import VideoFile, VideoState
|
|
320
|
+
|
|
321
321
|
logger.info("Performing post-commit cleanup of raw assets for video %s.", video_uuid)
|
|
322
322
|
try:
|
|
323
|
-
video_file = VideoFile.objects.select_related(
|
|
323
|
+
video_file = VideoFile.objects.select_related("state").filter(uuid=video_uuid).first()
|
|
324
324
|
if not video_file:
|
|
325
325
|
logger.error("VideoFile %s not found during post-commit cleanup.", video_uuid)
|
|
326
326
|
return
|
|
@@ -342,7 +342,7 @@ def _cleanup_raw_assets(video_uuid: uuid.UUID, raw_file_path: Optional[Path]=Non
|
|
|
342
342
|
|
|
343
343
|
if video_file.state.frames_extracted:
|
|
344
344
|
video_file.state.frames_extracted = False
|
|
345
|
-
video_file.state.save(update_fields=[
|
|
345
|
+
video_file.state.save(update_fields=["frames_extracted"])
|
|
346
346
|
logger.info("Set state.frames_extracted=False for video %s after raw asset cleanup.", video_uuid)
|
|
347
347
|
|
|
348
348
|
except Exception as e:
|
|
@@ -19,18 +19,20 @@ Submodules:
|
|
|
19
19
|
Usage:
|
|
20
20
|
Import the required functions directly from this module to perform specific video frame operations.
|
|
21
21
|
"""
|
|
22
|
+
|
|
22
23
|
from ._bulk_create_frames import _bulk_create_frames
|
|
23
24
|
from ._create_frame_object import _create_frame_object
|
|
24
25
|
from ._delete_frames import _delete_frames
|
|
25
26
|
from ._extract_frames import _extract_frames
|
|
27
|
+
from ._get_frame import _get_frame
|
|
26
28
|
from ._get_frame_number import _get_frame_number
|
|
27
29
|
from ._get_frame_path import _get_frame_path
|
|
28
30
|
from ._get_frame_paths import _get_frame_paths
|
|
29
31
|
from ._get_frame_range import _get_frame_range
|
|
30
|
-
from ._get_frame import _get_frame
|
|
31
32
|
from ._get_frames import _get_frames
|
|
32
33
|
from ._initialize_frames import _initialize_frames
|
|
33
34
|
from ._mark_frames_extracted_status import _mark_frames_extracted_status
|
|
35
|
+
|
|
34
36
|
__all__ = [
|
|
35
37
|
"_bulk_create_frames",
|
|
36
38
|
"_create_frame_object",
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path, _get_temp_anonymized_frame_dir
|
|
3
|
-
|
|
4
|
-
from typing import TYPE_CHECKING, List
|
|
5
|
-
|
|
6
1
|
import logging
|
|
2
|
+
from typing import TYPE_CHECKING, List
|
|
7
3
|
|
|
8
4
|
if TYPE_CHECKING:
|
|
9
|
-
from endoreg_db.models import
|
|
10
|
-
|
|
5
|
+
from endoreg_db.models import Frame, VideoFile
|
|
6
|
+
|
|
11
7
|
logger = logging.getLogger(__name__)
|
|
12
8
|
|
|
9
|
+
__all__ = ["_bulk_create_frames"]
|
|
13
10
|
|
|
14
11
|
|
|
15
12
|
def _bulk_create_frames(video: "VideoFile", frames_to_create: List["Frame"]):
|
|
16
13
|
"""Helper function to perform bulk_create with ignore_conflicts."""
|
|
17
14
|
from endoreg_db.models import Frame
|
|
15
|
+
|
|
18
16
|
try:
|
|
19
17
|
Frame.objects.bulk_create(frames_to_create, ignore_conflicts=True)
|
|
20
18
|
except Exception as e:
|
|
21
19
|
logger.error("Error during bulk creation of frames for video %s: %s", video.uuid, e, exc_info=True)
|
|
22
|
-
raise
|
|
20
|
+
raise
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
1
|
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
6
3
|
|
|
7
4
|
if TYPE_CHECKING:
|
|
8
|
-
from endoreg_db.models import
|
|
9
|
-
|
|
5
|
+
from endoreg_db.models import Frame, VideoFile
|
|
6
|
+
|
|
10
7
|
logger = logging.getLogger(__name__)
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
__all__ = ["_create_frame_object"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _create_frame_object(video: "VideoFile", frame_number: int, relative_path: str, extracted: bool = False) -> "Frame":
|
|
15
13
|
"""Instantiates a Frame object (does not save it)."""
|
|
16
14
|
from endoreg_db.models import Frame
|
|
17
15
|
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path, _get_temp_anonymized_frame_dir
|
|
3
|
-
from django.db import transaction
|
|
4
|
-
|
|
1
|
+
import logging
|
|
5
2
|
import shutil
|
|
6
3
|
from typing import TYPE_CHECKING
|
|
7
4
|
|
|
8
|
-
import
|
|
5
|
+
from django.db import transaction
|
|
6
|
+
|
|
7
|
+
from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path, _get_temp_anonymized_frame_dir
|
|
9
8
|
|
|
10
9
|
if TYPE_CHECKING:
|
|
11
10
|
from endoreg_db.models import VideoFile, VideoState
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger(__name__)
|
|
14
13
|
|
|
14
|
+
__all__ = ["_delete_frames"]
|
|
15
|
+
|
|
16
|
+
|
|
15
17
|
@transaction.atomic
|
|
16
18
|
def _delete_frames(video: "VideoFile") -> str:
|
|
17
19
|
"""
|
|
@@ -21,6 +23,7 @@ def _delete_frames(video: "VideoFile") -> str:
|
|
|
21
23
|
Raises RuntimeError if state update fails.
|
|
22
24
|
"""
|
|
23
25
|
from endoreg_db.models.media.frame import Frame
|
|
26
|
+
|
|
24
27
|
deleted_messages = []
|
|
25
28
|
error_messages = []
|
|
26
29
|
state_updated = False
|
|
@@ -44,7 +47,6 @@ def _delete_frames(video: "VideoFile") -> str:
|
|
|
44
47
|
msg = f"Frame directory path not set for video {video.uuid}, cannot delete standard frames."
|
|
45
48
|
logger.warning(msg)
|
|
46
49
|
|
|
47
|
-
|
|
48
50
|
temp_anonym_frame_dir = None
|
|
49
51
|
try:
|
|
50
52
|
temp_anonym_frame_dir = _get_temp_anonymized_frame_dir(video)
|
|
@@ -58,13 +60,12 @@ def _delete_frames(video: "VideoFile") -> str:
|
|
|
58
60
|
logger.error(msg, exc_info=True)
|
|
59
61
|
error_messages.append(msg)
|
|
60
62
|
|
|
61
|
-
|
|
62
63
|
try:
|
|
63
64
|
state: "VideoState" = video.get_or_create_state()
|
|
64
65
|
update_fields_state = []
|
|
65
66
|
if state.frames_extracted:
|
|
66
67
|
state.frames_extracted = False
|
|
67
|
-
update_fields_state.append(
|
|
68
|
+
update_fields_state.append("frames_extracted")
|
|
68
69
|
|
|
69
70
|
if update_fields_state:
|
|
70
71
|
state.save(update_fields=update_fields_state)
|
|
@@ -1,53 +1,62 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
2
4
|
from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path
|
|
3
5
|
from endoreg_db.utils.video.ffmpeg_wrapper import extract_frames as ffmpeg_extract_frames
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
if TYPE_CHECKING:
|
|
7
8
|
from endoreg_db.models import VideoFile
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
import shutil
|
|
10
11
|
|
|
12
|
+
from django.db import transaction
|
|
11
13
|
|
|
12
|
-
import shutil
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
16
|
+
|
|
15
17
|
def _extract_frames(
|
|
16
18
|
video: "VideoFile",
|
|
17
19
|
quality: int = 2,
|
|
18
20
|
overwrite: bool = False,
|
|
19
21
|
ext="jpg",
|
|
20
22
|
verbose=False,
|
|
23
|
+
from_processed: bool = False,
|
|
21
24
|
) -> bool:
|
|
22
25
|
"""
|
|
23
26
|
Extract frames from a raw video file, update frame extraction status in the database, and manage related file system operations.
|
|
24
|
-
|
|
27
|
+
|
|
25
28
|
This function checks for existing extracted frames and skips extraction if appropriate, unless overwriting is requested. It handles deletion of existing frames when overwriting, invokes ffmpeg to extract frames, parses extracted frame numbers, updates corresponding database records, and manages video extraction state. Robust error handling ensures cleanup and state rollback on failure.
|
|
26
|
-
|
|
29
|
+
|
|
27
30
|
Parameters:
|
|
28
31
|
video (VideoFile): The video object from which frames are to be extracted.
|
|
29
32
|
quality (int, optional): Quality parameter for ffmpeg extraction. Defaults to 2.
|
|
30
33
|
overwrite (bool, optional): Whether to overwrite existing extracted frames. Defaults to False.
|
|
31
34
|
ext (str, optional): File extension for extracted frames. Defaults to "jpg".
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
Returns:
|
|
34
37
|
bool: True if extraction and updates succeed.
|
|
35
|
-
|
|
38
|
+
|
|
36
39
|
Raises:
|
|
37
40
|
FileNotFoundError: If the raw video file is missing.
|
|
38
41
|
RuntimeError: If extraction or database update fails.
|
|
39
42
|
ValueError: If the frame directory path cannot be determined.
|
|
40
43
|
"""
|
|
41
|
-
from ._delete_frames import _delete_frames
|
|
42
44
|
from endoreg_db.models.media.frame import Frame
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
from ._delete_frames import _delete_frames
|
|
47
|
+
|
|
48
|
+
if from_processed:
|
|
49
|
+
raw_file_path = video.get_processed_file_path()
|
|
50
|
+
if not raw_file_path or not raw_file_path.exists():
|
|
51
|
+
raise FileNotFoundError(f"Processed video file not found at {raw_file_path} for video {video.uuid}. Cannot extract frames.")
|
|
52
|
+
else:
|
|
53
|
+
# Pre-validation checks (outside any transaction)
|
|
54
|
+
if not video.has_raw:
|
|
55
|
+
raise FileNotFoundError(f"Raw video file not available for {video.uuid}. Cannot extract frames.")
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
raw_file_path = video.get_raw_file_path()
|
|
58
|
+
if not raw_file_path or not raw_file_path.exists():
|
|
59
|
+
raise FileNotFoundError(f"Raw video file not found at {raw_file_path} for video {video.uuid}. Cannot extract frames.")
|
|
51
60
|
|
|
52
61
|
frame_dir = _get_frame_dir_path(video)
|
|
53
62
|
if not frame_dir:
|
|
@@ -63,33 +72,22 @@ def _extract_frames(
|
|
|
63
72
|
"Frames already extracted or files exist for video %s, and overwrite=False. Skipping extraction.",
|
|
64
73
|
video.uuid,
|
|
65
74
|
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if updated_count > 0:
|
|
70
|
-
logger.info(
|
|
71
|
-
"Marked %d existing Frame objects as extracted for video %s based on state.",
|
|
72
|
-
updated_count,
|
|
73
|
-
video.uuid,
|
|
74
|
-
)
|
|
75
|
-
elif not state.frames_extracted and files_exist_on_disk:
|
|
76
|
-
logger.warning(
|
|
77
|
-
"Files exist on disk for video %s but state.frames_extracted is False. Correcting state to match disk.",
|
|
78
|
-
video.uuid,
|
|
79
|
-
)
|
|
80
|
-
# Fix inconsistent state: files exist but state.frames_extracted is False
|
|
81
|
-
state.frames_extracted = True
|
|
82
|
-
state.save(update_fields=['frames_extracted'])
|
|
83
|
-
|
|
84
|
-
# Also update Frame objects to be consistent
|
|
85
|
-
with transaction.atomic():
|
|
75
|
+
with transaction.atomic():
|
|
76
|
+
state.refresh_from_db()
|
|
77
|
+
if frames_exist_in_db:
|
|
86
78
|
updated_count = Frame.objects.filter(video=video, is_extracted=False).update(is_extracted=True)
|
|
87
79
|
if updated_count > 0:
|
|
88
80
|
logger.info(
|
|
89
|
-
"Marked %d existing Frame objects as extracted for video %s
|
|
81
|
+
"Marked %d existing Frame objects as extracted for video %s based on current records.",
|
|
90
82
|
updated_count,
|
|
91
83
|
video.uuid,
|
|
92
84
|
)
|
|
85
|
+
if files_exist_on_disk and not state.frames_extracted:
|
|
86
|
+
logger.warning(
|
|
87
|
+
"Files exist on disk for video %s but state.frames_extracted is False. Persisting corrected state.",
|
|
88
|
+
video.uuid,
|
|
89
|
+
)
|
|
90
|
+
state.mark_frames_extracted(save=True)
|
|
93
91
|
return True
|
|
94
92
|
|
|
95
93
|
# Overwrite: delete existing frames/files before re-extraction.
|
|
@@ -122,16 +120,14 @@ def _extract_frames(
|
|
|
122
120
|
video.uuid,
|
|
123
121
|
)
|
|
124
122
|
if video.frame_count is not None and video.frame_count > 0:
|
|
125
|
-
raise RuntimeError(
|
|
126
|
-
f"ffmpeg_extract_frames returned no paths for video {video.uuid}, but {video.frame_count} frames were expected."
|
|
127
|
-
)
|
|
123
|
+
raise RuntimeError(f"ffmpeg_extract_frames returned no paths for video {video.uuid}, but {video.frame_count} frames were expected.")
|
|
128
124
|
|
|
129
125
|
logger.info("Successfully extracted %d frames using ffmpeg for video %s.", len(extracted_paths), video.uuid)
|
|
130
126
|
|
|
131
127
|
extracted_frame_numbers = []
|
|
132
128
|
for frame_path in extracted_paths:
|
|
133
129
|
try:
|
|
134
|
-
frame_number = int(frame_path.stem.split(
|
|
130
|
+
frame_number = int(frame_path.stem.split("_")[-1])
|
|
135
131
|
extracted_frame_numbers.append(frame_number)
|
|
136
132
|
except (ValueError, IndexError) as e:
|
|
137
133
|
logger.warning("Could not parse frame number from extracted file %s: %s", frame_path.name, e)
|
|
@@ -140,9 +136,7 @@ def _extract_frames(
|
|
|
140
136
|
with transaction.atomic():
|
|
141
137
|
if extracted_frame_numbers:
|
|
142
138
|
try:
|
|
143
|
-
update_count = Frame.objects.filter(
|
|
144
|
-
video=video, frame_number__in=extracted_frame_numbers
|
|
145
|
-
).update(is_extracted=True)
|
|
139
|
+
update_count = Frame.objects.filter(video=video, frame_number__in=extracted_frame_numbers).update(is_extracted=True)
|
|
146
140
|
logger.info("Marked %d Frame objects as is_extracted=True for video %s.", update_count, video.uuid)
|
|
147
141
|
if update_count != len(extracted_frame_numbers):
|
|
148
142
|
logger.warning(
|
|
@@ -153,9 +147,8 @@ def _extract_frames(
|
|
|
153
147
|
)
|
|
154
148
|
except Exception as update_e:
|
|
155
149
|
logger.error("Failed to update is_extracted flag for frames of video %s: %s", video.uuid, update_e, exc_info=True)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
state.mark_frames_extracted()
|
|
150
|
+
state.refresh_from_db()
|
|
151
|
+
state.mark_frames_extracted()
|
|
159
152
|
return True
|
|
160
153
|
|
|
161
154
|
except Exception as e:
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
|
|
3
1
|
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
|
-
from endoreg_db.models import
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
5
|
+
from endoreg_db.models import Frame, VideoFile
|
|
10
6
|
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
def _get_frame(video: "VideoFile", frame_number: int) -> "Frame":
|
|
14
11
|
"""Gets a specific Frame object by its frame number."""
|
|
15
12
|
from endoreg_db.models import Frame
|
|
13
|
+
|
|
16
14
|
try:
|
|
17
15
|
# Access related manager directly
|
|
18
16
|
return video.frames.get(frame_number=frame_number)
|
|
@@ -22,7 +20,7 @@ def _get_frame(video: "VideoFile", frame_number: int) -> "Frame":
|
|
|
22
20
|
return Frame.objects.get(video_file=video, frame_number=frame_number)
|
|
23
21
|
except Frame.DoesNotExist:
|
|
24
22
|
logger.error("Frame %d not found for video %s.", frame_number, video.uuid)
|
|
25
|
-
raise
|
|
23
|
+
raise # Re-raise DoesNotExist
|
|
26
24
|
except Exception as e:
|
|
27
25
|
logger.error("Error getting frame %d for video %s: %s", frame_number, video.uuid, e, exc_info=True)
|
|
28
|
-
raise
|
|
26
|
+
raise # Re-raise other exceptions
|
|
@@ -1,27 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
|
-
|
|
4
1
|
import logging
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
5
3
|
|
|
6
4
|
if TYPE_CHECKING:
|
|
7
5
|
from endoreg_db.models import VideoFile
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
6
|
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
12
8
|
|
|
13
9
|
|
|
14
10
|
def _get_frame_number(video: "VideoFile") -> int:
|
|
15
11
|
"""Counts the number of associated Frame objects in the database."""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return video.frames.count()
|
|
19
|
-
except AttributeError:
|
|
20
|
-
logger.error("Could not access frame count for video %s. 'frames' related manager not found.", str(video))
|
|
21
|
-
# Fallback query (less efficient)
|
|
22
|
-
frame_model = video.get_frame_model()
|
|
23
|
-
return frame_model.objects.filter(video_file=video).count()
|
|
24
|
-
except Exception as e:
|
|
25
|
-
logger.error("Error counting frames for video %s: %s", video.uuid, e, exc_info=True)
|
|
26
|
-
return 0
|
|
27
|
-
|
|
12
|
+
# Access related manager directly
|
|
13
|
+
return video.frames.count()
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# --- Frame Creation/Deletion ---
|
|
2
2
|
import logging
|
|
3
|
-
from typing import TYPE_CHECKING, Optional
|
|
4
3
|
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
7
|
from endoreg_db.models import VideoFile
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
def _get_frame_path(video: "VideoFile", frame_number: int) -> Optional[Path]:
|
|
12
13
|
"""Constructs the expected path for a given frame number."""
|
|
13
|
-
target_dir = video.get_frame_dir_path()
|
|
14
|
+
target_dir = video.get_frame_dir_path() # Use IO helper
|
|
14
15
|
if not target_dir:
|
|
15
16
|
logger.warning("Cannot get frame path for video %s: Frame directory not set.", video.uuid)
|
|
16
17
|
return None
|
|
17
18
|
|
|
18
19
|
frame_filename = f"frame_{frame_number:07d}.jpg"
|
|
19
20
|
path = target_dir / frame_filename
|
|
20
|
-
return path
|
|
21
|
+
return path
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
1
|
import logging
|
|
5
|
-
|
|
2
|
+
from pathlib import Path
|
|
6
3
|
from typing import TYPE_CHECKING, List
|
|
4
|
+
|
|
5
|
+
from endoreg_db.models.media.video.video_file_io import _get_frame_dir_path
|
|
6
|
+
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from endoreg_db.models import VideoFile
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
def _get_frame_paths(video: "VideoFile") -> List[Path]:
|
|
13
14
|
"""Returns a sorted list of Path objects for extracted frame image files."""
|
|
14
15
|
frame_dir = _get_frame_dir_path(video)
|
|
@@ -16,10 +17,10 @@ def _get_frame_paths(video: "VideoFile") -> List[Path]:
|
|
|
16
17
|
logger.warning("Frame directory %s does not exist for video %s.", frame_dir, video.uuid)
|
|
17
18
|
return []
|
|
18
19
|
|
|
19
|
-
frame_paths = list(frame_dir.glob(
|
|
20
|
+
frame_paths = list(frame_dir.glob("frame_*.jpg"))
|
|
20
21
|
|
|
21
22
|
try:
|
|
22
|
-
frame_paths.sort(key=lambda p: int(p.stem.split(
|
|
23
|
+
frame_paths.sort(key=lambda p: int(p.stem.split("_")[-1]))
|
|
23
24
|
except (ValueError, IndexError) as e:
|
|
24
25
|
logger.error("Error sorting frame paths in %s: %s. Found paths: %s", frame_dir, e, [p.name for p in frame_paths], exc_info=True)
|
|
25
26
|
logger.warning("Falling back to unsorted frame paths to preserve available data.")
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import logging
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
3
4
|
from django.db.models import QuerySet
|
|
4
|
-
import logging
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
|
-
from endoreg_db.models import
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
from endoreg_db.models import Frame, VideoFile
|
|
12
8
|
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
13
10
|
|
|
14
11
|
|
|
15
12
|
def _get_frame_range(video: "VideoFile", start_frame_number: int, end_frame_number: int) -> "QuerySet[Frame]":
|
|
16
13
|
"""Gets a QuerySet of Frame objects within a specific range, ordered by frame number."""
|
|
17
14
|
from endoreg_db.models import Frame
|
|
15
|
+
|
|
18
16
|
try:
|
|
19
17
|
# Access related manager directly
|
|
20
18
|
return video.frames.filter(
|
|
@@ -31,4 +29,4 @@ def _get_frame_range(video: "VideoFile", start_frame_number: int, end_frame_numb
|
|
|
31
29
|
).order_by("frame_number")
|
|
32
30
|
except Exception as e:
|
|
33
31
|
logger.error("Error getting frame range (%d-%d) for video %s: %s", start_frame_number, end_frame_number, video.uuid, e, exc_info=True)
|
|
34
|
-
return Frame.objects.none()
|
|
32
|
+
return Frame.objects.none() # Return empty queryset on error
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import logging
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
|
-
import
|
|
4
|
+
from django.db.models import QuerySet
|
|
6
5
|
|
|
7
6
|
if TYPE_CHECKING:
|
|
8
|
-
from endoreg_db.models import
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
7
|
+
from endoreg_db.models import Frame, VideoFile
|
|
12
8
|
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
13
10
|
|
|
14
11
|
|
|
15
12
|
def _get_frames(video: "VideoFile") -> "QuerySet[Frame]":
|
|
16
13
|
"""Gets a QuerySet of all associated Frame objects, ordered by frame number."""
|
|
17
14
|
from endoreg_db.models import Frame
|
|
15
|
+
|
|
18
16
|
try:
|
|
19
17
|
# Access related manager directly
|
|
20
18
|
return video.frames.order_by("frame_number")
|
|
@@ -24,4 +22,4 @@ def _get_frames(video: "VideoFile") -> "QuerySet[Frame]":
|
|
|
24
22
|
return Frame.objects.filter(video_file=video).order_by("frame_number")
|
|
25
23
|
except Exception as e:
|
|
26
24
|
logger.error("Error getting frames for video %s: %s", video.uuid, e, exc_info=True)
|
|
27
|
-
return Frame.objects.none()
|
|
25
|
+
return Frame.objects.none() # Return empty queryset on error
|