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
|
@@ -8,8 +8,7 @@ Provides RESTful endpoints for video segment management:
|
|
|
8
8
|
- Video-specific: GET/POST /api/media/videos/<pk>/segments/
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
|
|
11
|
+
import logging
|
|
13
12
|
|
|
14
13
|
from django.db import transaction
|
|
15
14
|
from django.db.models import Count
|
|
@@ -18,71 +17,65 @@ from rest_framework import status
|
|
|
18
17
|
from rest_framework.decorators import api_view, permission_classes
|
|
19
18
|
from rest_framework.response import Response
|
|
20
19
|
|
|
20
|
+
from endoreg_db.models import Label, LabelVideoSegment, VideoFile
|
|
21
|
+
from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
|
|
21
22
|
from endoreg_db.utils.permissions import EnvironmentAwarePermission
|
|
22
23
|
|
|
23
|
-
import logging
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
@api_view([
|
|
27
|
+
@api_view(["GET"])
|
|
28
28
|
@permission_classes([EnvironmentAwarePermission])
|
|
29
29
|
def video_segments_stats(request):
|
|
30
30
|
"""
|
|
31
31
|
Statistics endpoint for video segments.
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
GET /api/media/videos/segments/stats/
|
|
34
34
|
Returns aggregated statistics about video segments.
|
|
35
35
|
"""
|
|
36
36
|
try:
|
|
37
37
|
# Get all segments queryset
|
|
38
38
|
segments = LabelVideoSegment.objects.all()
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
# Calculate statistics
|
|
41
41
|
total_segments = segments.count()
|
|
42
|
-
|
|
43
|
-
# Segments by status (assuming status field exists)
|
|
44
|
-
status_counts = segments.values('status').annotate(count=Count('id'))
|
|
45
|
-
|
|
42
|
+
|
|
46
43
|
# Segments by label
|
|
47
|
-
label_counts = segments.values(
|
|
48
|
-
|
|
44
|
+
label_counts = segments.values("label__name").annotate(count=Count("id"))
|
|
45
|
+
|
|
49
46
|
# Videos with segments
|
|
50
|
-
videos_with_segments = segments.values(
|
|
51
|
-
|
|
47
|
+
videos_with_segments = segments.values("video_file").distinct().count()
|
|
48
|
+
|
|
52
49
|
stats = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'by_label': {item['label__name']: item['count'] for item in label_counts if item['label__name']},
|
|
50
|
+
"total_segments": total_segments,
|
|
51
|
+
"videos_with_segments": videos_with_segments,
|
|
52
|
+
"by_label": {item["label__name"]: item["count"] for item in label_counts if item["label__name"]},
|
|
57
53
|
}
|
|
58
|
-
|
|
54
|
+
|
|
59
55
|
return Response(stats, status=status.HTTP_200_OK)
|
|
60
|
-
|
|
56
|
+
|
|
61
57
|
except Exception as e:
|
|
62
58
|
logger.error(f"Error fetching video segment stats: {e}")
|
|
63
|
-
return Response(
|
|
64
|
-
{'error': 'Failed to fetch segment statistics'},
|
|
65
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
66
|
-
)
|
|
59
|
+
return Response({"error": "Failed to fetch segment statistics"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
67
60
|
|
|
68
61
|
|
|
69
|
-
@api_view([
|
|
62
|
+
@api_view(["GET", "POST"])
|
|
70
63
|
@permission_classes([EnvironmentAwarePermission])
|
|
71
64
|
def video_segments_collection(request):
|
|
72
65
|
"""
|
|
73
66
|
Collection endpoint for all video segments across all videos.
|
|
74
|
-
|
|
67
|
+
|
|
75
68
|
GET /api/media/videos/segments/
|
|
76
69
|
- Lists all segments, optionally filtered by video_id and/or label_id
|
|
77
70
|
- Query params: video_id, label_id
|
|
78
|
-
|
|
71
|
+
|
|
79
72
|
POST /api/media/videos/segments/
|
|
80
73
|
- Creates a new video segment
|
|
81
74
|
- Requires: video_id, label_id, start_frame_number, end_frame_number
|
|
82
|
-
|
|
75
|
+
|
|
83
76
|
Modern replacement for: /api/video-segments/
|
|
84
77
|
"""
|
|
85
|
-
if request.method ==
|
|
78
|
+
if request.method == "POST":
|
|
86
79
|
logger.info(f"Creating new video segment with data: {request.data}")
|
|
87
80
|
|
|
88
81
|
with transaction.atomic():
|
|
@@ -91,27 +84,18 @@ def video_segments_collection(request):
|
|
|
91
84
|
try:
|
|
92
85
|
segment = serializer.save()
|
|
93
86
|
logger.info(f"Successfully created video segment {segment.pk}")
|
|
94
|
-
return Response(
|
|
95
|
-
LabelVideoSegmentSerializer(segment).data,
|
|
96
|
-
status=status.HTTP_201_CREATED
|
|
97
|
-
)
|
|
87
|
+
return Response(LabelVideoSegmentSerializer(segment).data, status=status.HTTP_201_CREATED)
|
|
98
88
|
except Exception as e:
|
|
99
89
|
logger.error(f"Error creating video segment: {str(e)}")
|
|
100
|
-
return Response(
|
|
101
|
-
{'error': f'Failed to create segment: {str(e)}'},
|
|
102
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
103
|
-
)
|
|
90
|
+
return Response({"error": f"Failed to create segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
104
91
|
else:
|
|
105
92
|
logger.warning(f"Invalid data for video segment creation: {serializer.errors}")
|
|
106
|
-
return Response(
|
|
107
|
-
{'error': 'Invalid data', 'details': serializer.errors},
|
|
108
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
109
|
-
)
|
|
93
|
+
return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|
110
94
|
|
|
111
|
-
elif request.method ==
|
|
95
|
+
elif request.method == "GET":
|
|
112
96
|
# Optional filtering by video_id
|
|
113
|
-
video_id = request.GET.get(
|
|
114
|
-
label_id = request.GET.get(
|
|
97
|
+
video_id = request.GET.get("video_id")
|
|
98
|
+
label_id = request.GET.get("label_id")
|
|
115
99
|
|
|
116
100
|
queryset = LabelVideoSegment.objects.all()
|
|
117
101
|
|
|
@@ -120,140 +104,114 @@ def video_segments_collection(request):
|
|
|
120
104
|
video = VideoFile.objects.get(id=video_id)
|
|
121
105
|
queryset = queryset.filter(video_file=video)
|
|
122
106
|
except VideoFile.DoesNotExist:
|
|
123
|
-
return Response(
|
|
124
|
-
{'error': f'Video with id {video_id} not found'},
|
|
125
|
-
status=status.HTTP_404_NOT_FOUND
|
|
126
|
-
)
|
|
107
|
+
return Response({"error": f"Video with id {video_id} not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
127
108
|
|
|
128
109
|
if label_id:
|
|
129
110
|
try:
|
|
130
111
|
label = Label.objects.get(id=label_id)
|
|
131
112
|
queryset = queryset.filter(label=label)
|
|
132
113
|
except Label.DoesNotExist:
|
|
133
|
-
return Response(
|
|
134
|
-
{'error': f'Label with id {label_id} not found'},
|
|
135
|
-
status=status.HTTP_404_NOT_FOUND
|
|
136
|
-
)
|
|
114
|
+
return Response({"error": f"Label with id {label_id} not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
137
115
|
|
|
138
116
|
# Order by video and start time for consistent results
|
|
139
|
-
segments = queryset.order_by(
|
|
117
|
+
segments = queryset.order_by("video_file__id", "start_frame_number")
|
|
140
118
|
serializer = LabelVideoSegmentSerializer(segments, many=True)
|
|
141
119
|
return Response(serializer.data)
|
|
142
120
|
|
|
143
121
|
|
|
144
|
-
@api_view([
|
|
122
|
+
@api_view(["GET", "POST"])
|
|
145
123
|
@permission_classes([EnvironmentAwarePermission])
|
|
146
124
|
def video_segments_by_video(request, pk):
|
|
147
125
|
"""
|
|
148
126
|
Video-specific segments endpoint.
|
|
149
|
-
|
|
127
|
+
|
|
150
128
|
GET /api/media/videos/<pk>/segments/
|
|
151
129
|
- Lists all segments for a specific video
|
|
152
130
|
- Query params: label (label name filter)
|
|
153
131
|
- Note: This was already implemented in segments.py as video_segments_by_pk
|
|
154
|
-
|
|
132
|
+
|
|
155
133
|
POST /api/media/videos/<pk>/segments/
|
|
156
134
|
- Creates a new segment for this video
|
|
157
135
|
- Automatically sets video_id to pk
|
|
158
136
|
- Requires: label_id, start_frame_number, end_frame_number
|
|
159
|
-
|
|
137
|
+
|
|
160
138
|
Modern replacement for: /api/video-segments/?video_id=<pk>
|
|
161
139
|
"""
|
|
162
140
|
# Verify video exists
|
|
163
141
|
video = get_object_or_404(VideoFile, id=pk)
|
|
164
|
-
|
|
165
|
-
if request.method ==
|
|
142
|
+
|
|
143
|
+
if request.method == "GET":
|
|
166
144
|
# This duplicates video_segments_by_pk functionality
|
|
167
145
|
# We keep both for compatibility during migration
|
|
168
|
-
label_name = request.GET.get(
|
|
169
|
-
|
|
146
|
+
label_name = request.GET.get("label")
|
|
147
|
+
|
|
170
148
|
queryset = LabelVideoSegment.objects.filter(video_file=video)
|
|
171
|
-
|
|
149
|
+
|
|
172
150
|
if label_name:
|
|
173
151
|
try:
|
|
174
152
|
label = Label.objects.get(name=label_name)
|
|
175
153
|
queryset = queryset.filter(label=label)
|
|
176
154
|
except Label.DoesNotExist:
|
|
177
|
-
return Response(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
segments = queryset.order_by('start_frame_number')
|
|
155
|
+
return Response({"error": f'Label "{label_name}" not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
156
|
+
|
|
157
|
+
segments = queryset.order_by("start_frame_number")
|
|
183
158
|
serializer = LabelVideoSegmentSerializer(segments, many=True)
|
|
184
159
|
return Response(serializer.data)
|
|
185
|
-
|
|
186
|
-
elif request.method ==
|
|
160
|
+
|
|
161
|
+
elif request.method == "POST":
|
|
187
162
|
logger.info(f"Creating new segment for video {pk} with data: {request.data}")
|
|
188
|
-
|
|
163
|
+
|
|
189
164
|
# Automatically set video_id to pk
|
|
190
165
|
data = request.data.copy()
|
|
191
|
-
data[
|
|
192
|
-
|
|
166
|
+
data["video_id"] = pk
|
|
167
|
+
|
|
193
168
|
with transaction.atomic():
|
|
194
169
|
serializer = LabelVideoSegmentSerializer(data=data)
|
|
195
170
|
if serializer.is_valid():
|
|
196
171
|
try:
|
|
197
172
|
segment = serializer.save()
|
|
198
173
|
logger.info(f"Successfully created segment {segment.pk} for video {pk}")
|
|
199
|
-
return Response(
|
|
200
|
-
LabelVideoSegmentSerializer(segment).data,
|
|
201
|
-
status=status.HTTP_201_CREATED
|
|
202
|
-
)
|
|
174
|
+
return Response(LabelVideoSegmentSerializer(segment).data, status=status.HTTP_201_CREATED)
|
|
203
175
|
except Exception as e:
|
|
204
176
|
logger.error(f"Error creating segment for video {pk}: {str(e)}")
|
|
205
|
-
return Response(
|
|
206
|
-
{'error': f'Failed to create segment: {str(e)}'},
|
|
207
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
208
|
-
)
|
|
177
|
+
return Response({"error": f"Failed to create segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
209
178
|
else:
|
|
210
179
|
logger.warning(f"Invalid data for segment creation: {serializer.errors}")
|
|
211
|
-
return Response(
|
|
212
|
-
{'error': 'Invalid data', 'details': serializer.errors},
|
|
213
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
214
|
-
)
|
|
180
|
+
return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|
215
181
|
|
|
216
182
|
|
|
217
|
-
@api_view([
|
|
183
|
+
@api_view(["GET", "PATCH", "DELETE"])
|
|
218
184
|
@permission_classes([EnvironmentAwarePermission])
|
|
219
185
|
def video_segment_detail(request, pk, segment_id):
|
|
220
186
|
"""
|
|
221
187
|
Detail endpoint for a specific video segment.
|
|
222
|
-
|
|
188
|
+
|
|
223
189
|
GET /api/media/videos/<pk>/segments/<segment_id>/
|
|
224
190
|
- Returns segment details
|
|
225
|
-
|
|
191
|
+
|
|
226
192
|
PATCH /api/media/videos/<pk>/segments/<segment_id>/
|
|
227
193
|
- Updates segment (partial update)
|
|
228
|
-
|
|
194
|
+
|
|
229
195
|
DELETE /api/media/videos/<pk>/segments/<segment_id>/
|
|
230
196
|
- Deletes segment
|
|
231
|
-
|
|
197
|
+
|
|
232
198
|
Modern replacement for: /api/video-segments/<segment_id>/
|
|
233
199
|
"""
|
|
234
200
|
# Verify video exists
|
|
235
201
|
video = get_object_or_404(VideoFile, id=pk)
|
|
236
|
-
|
|
202
|
+
|
|
237
203
|
# Get segment and verify it belongs to this video
|
|
238
|
-
segment = get_object_or_404(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
video_file=video
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
if request.method == 'GET':
|
|
204
|
+
segment = get_object_or_404(LabelVideoSegment, id=segment_id, video_file=video)
|
|
205
|
+
|
|
206
|
+
if request.method == "GET":
|
|
245
207
|
serializer = LabelVideoSegmentSerializer(segment)
|
|
246
208
|
return Response(serializer.data)
|
|
247
|
-
|
|
248
|
-
elif request.method ==
|
|
209
|
+
|
|
210
|
+
elif request.method == "PATCH":
|
|
249
211
|
logger.info(f"Updating segment {segment_id} for video {pk} with data: {request.data}")
|
|
250
|
-
|
|
212
|
+
|
|
251
213
|
with transaction.atomic():
|
|
252
|
-
serializer = LabelVideoSegmentSerializer(
|
|
253
|
-
segment,
|
|
254
|
-
data=request.data,
|
|
255
|
-
partial=True
|
|
256
|
-
)
|
|
214
|
+
serializer = LabelVideoSegmentSerializer(segment, data=request.data, partial=True)
|
|
257
215
|
if serializer.is_valid():
|
|
258
216
|
try:
|
|
259
217
|
segment = serializer.save()
|
|
@@ -261,33 +219,21 @@ def video_segment_detail(request, pk, segment_id):
|
|
|
261
219
|
return Response(LabelVideoSegmentSerializer(segment).data)
|
|
262
220
|
except Exception as e:
|
|
263
221
|
logger.error(f"Error updating segment {segment_id}: {str(e)}")
|
|
264
|
-
return Response(
|
|
265
|
-
{'error': f'Failed to update segment: {str(e)}'},
|
|
266
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
267
|
-
)
|
|
222
|
+
return Response({"error": f"Failed to update segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
268
223
|
else:
|
|
269
224
|
logger.warning(f"Invalid data for segment update: {serializer.errors}")
|
|
270
|
-
return Response(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
elif request.method == 'DELETE':
|
|
225
|
+
return Response({"error": "Invalid data", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|
226
|
+
|
|
227
|
+
elif request.method == "DELETE":
|
|
276
228
|
logger.info(f"Deleting segment {segment_id} from video {pk}")
|
|
277
229
|
try:
|
|
278
230
|
with transaction.atomic():
|
|
279
231
|
segment.delete()
|
|
280
232
|
logger.info(f"Successfully deleted segment {segment_id}")
|
|
281
|
-
return Response(
|
|
282
|
-
{'message': f'Segment {segment_id} deleted successfully'},
|
|
283
|
-
status=status.HTTP_204_NO_CONTENT
|
|
284
|
-
)
|
|
233
|
+
return Response({"message": f"Segment {segment_id} deleted successfully"}, status=status.HTTP_204_NO_CONTENT)
|
|
285
234
|
except Exception as e:
|
|
286
235
|
logger.error(f"Error deleting segment {segment_id}: {str(e)}")
|
|
287
|
-
return Response(
|
|
288
|
-
{'error': f'Failed to delete segment: {str(e)}'},
|
|
289
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
290
|
-
)
|
|
236
|
+
return Response({"error": f"Failed to delete segment: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
291
237
|
|
|
292
238
|
|
|
293
239
|
# ============================================================================
|
|
@@ -295,23 +241,24 @@ def video_segment_detail(request, pk, segment_id):
|
|
|
295
241
|
# Migrated from /api/label-video-segment/*/validate/ (October 14, 2025)
|
|
296
242
|
# ============================================================================
|
|
297
243
|
|
|
298
|
-
|
|
244
|
+
|
|
245
|
+
@api_view(["POST"])
|
|
299
246
|
@permission_classes([EnvironmentAwarePermission])
|
|
300
247
|
def video_segment_validate(request, pk: int, segment_id: int):
|
|
301
248
|
"""
|
|
302
249
|
Validate a single video segment.
|
|
303
|
-
|
|
250
|
+
|
|
304
251
|
POST /api/media/videos/<pk>/segments/<segment_id>/validate/
|
|
305
|
-
|
|
252
|
+
|
|
306
253
|
Validates a single LabelVideoSegment and marks it as verified.
|
|
307
254
|
Used to confirm user-reviewed segment annotations.
|
|
308
|
-
|
|
255
|
+
|
|
309
256
|
Request Body (optional):
|
|
310
257
|
{
|
|
311
258
|
"is_validated": true, // optional, default true
|
|
312
259
|
"notes": "..." // optional, validation notes
|
|
313
260
|
}
|
|
314
|
-
|
|
261
|
+
|
|
315
262
|
Response:
|
|
316
263
|
{
|
|
317
264
|
"message": "Segment validated successfully",
|
|
@@ -325,69 +272,62 @@ def video_segment_validate(request, pk: int, segment_id: int):
|
|
|
325
272
|
"""
|
|
326
273
|
# Verify video exists
|
|
327
274
|
video = get_object_or_404(VideoFile, pk=pk)
|
|
328
|
-
|
|
275
|
+
|
|
329
276
|
# Get segment and verify it belongs to this video
|
|
330
|
-
segment = get_object_or_404(
|
|
331
|
-
|
|
332
|
-
pk=segment_id,
|
|
333
|
-
video_file=video
|
|
334
|
-
)
|
|
335
|
-
|
|
277
|
+
segment = get_object_or_404(LabelVideoSegment.objects.select_related("state", "video_file", "label"), pk=segment_id, video_file=video)
|
|
278
|
+
|
|
336
279
|
try:
|
|
337
280
|
# Validation status from request (default: True)
|
|
338
|
-
is_validated = request.data.get(
|
|
339
|
-
notes = request.data.get(
|
|
340
|
-
|
|
281
|
+
is_validated = request.data.get("is_validated", True)
|
|
282
|
+
notes = request.data.get("notes", "")
|
|
283
|
+
|
|
341
284
|
# Get or create state object
|
|
342
|
-
if not hasattr(segment,
|
|
343
|
-
return Response({
|
|
344
|
-
|
|
345
|
-
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
346
|
-
|
|
285
|
+
if not hasattr(segment, "state") or segment.state is None:
|
|
286
|
+
return Response({"error": "Segment has no state object. Cannot validate."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
287
|
+
|
|
347
288
|
# Update state
|
|
348
289
|
with transaction.atomic():
|
|
349
290
|
segment.state.is_validated = is_validated
|
|
350
|
-
if notes and hasattr(segment.state, 'validation_notes'):
|
|
351
|
-
segment.state.validation_notes = notes
|
|
352
291
|
segment.state.save()
|
|
353
|
-
|
|
292
|
+
|
|
354
293
|
logger.info(f"Validated segment {segment_id} in video {pk}: {is_validated}")
|
|
355
|
-
|
|
356
|
-
return Response(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
294
|
+
|
|
295
|
+
return Response(
|
|
296
|
+
{
|
|
297
|
+
"message": f"Segment {segment_id} validation status updated",
|
|
298
|
+
"segment_id": segment_id,
|
|
299
|
+
"is_validated": is_validated,
|
|
300
|
+
"label": segment.label.name if segment.label else None,
|
|
301
|
+
"video_id": video.id,
|
|
302
|
+
"start_frame": segment.start_frame_number,
|
|
303
|
+
"end_frame": segment.end_frame_number,
|
|
304
|
+
},
|
|
305
|
+
status=status.HTTP_200_OK,
|
|
306
|
+
)
|
|
307
|
+
|
|
366
308
|
except Exception as e:
|
|
367
309
|
logger.error(f"Error validating segment {segment_id} in video {pk}: {e}")
|
|
368
|
-
return Response({
|
|
369
|
-
"error": f"Validation failed: {str(e)}"
|
|
370
|
-
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
310
|
+
return Response({"error": f"Validation failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
371
311
|
|
|
372
312
|
|
|
373
|
-
@api_view([
|
|
313
|
+
@api_view(["POST"])
|
|
374
314
|
@permission_classes([EnvironmentAwarePermission])
|
|
375
315
|
def video_segments_validate_bulk(request, pk: int):
|
|
376
316
|
"""
|
|
377
317
|
Validate multiple video segments at once.
|
|
378
|
-
|
|
318
|
+
|
|
379
319
|
POST /api/media/videos/<pk>/segments/validate-bulk/
|
|
380
|
-
|
|
320
|
+
|
|
381
321
|
Validates multiple LabelVideoSegments simultaneously.
|
|
382
322
|
Useful for batch validation after review.
|
|
383
|
-
|
|
323
|
+
|
|
384
324
|
Request Body:
|
|
385
325
|
{
|
|
386
326
|
"segment_ids": [1, 2, 3, ...],
|
|
387
327
|
"is_validated": true, // optional, default true
|
|
388
328
|
"notes": "..." // optional, applies to all segments
|
|
389
329
|
}
|
|
390
|
-
|
|
330
|
+
|
|
391
331
|
Response:
|
|
392
332
|
{
|
|
393
333
|
"message": "Bulk validation completed. 3 segments updated.",
|
|
@@ -399,37 +339,30 @@ def video_segments_validate_bulk(request, pk: int):
|
|
|
399
339
|
"""
|
|
400
340
|
# Verify video exists
|
|
401
341
|
video = get_object_or_404(VideoFile, pk=pk)
|
|
402
|
-
|
|
403
|
-
segment_ids = request.data.get(
|
|
404
|
-
is_validated = request.data.get(
|
|
405
|
-
notes = request.data.get(
|
|
406
|
-
|
|
342
|
+
|
|
343
|
+
segment_ids = request.data.get("segment_ids", [])
|
|
344
|
+
is_validated = request.data.get("is_validated", True)
|
|
345
|
+
notes = request.data.get("notes", "")
|
|
346
|
+
|
|
407
347
|
if not segment_ids:
|
|
408
|
-
return Response({
|
|
409
|
-
|
|
410
|
-
}, status=status.HTTP_400_BAD_REQUEST)
|
|
411
|
-
|
|
348
|
+
return Response({"error": "segment_ids is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
349
|
+
|
|
412
350
|
try:
|
|
413
351
|
# Get all segments for this video only
|
|
414
|
-
segments = LabelVideoSegment.objects.filter(
|
|
415
|
-
|
|
416
|
-
video_file=video
|
|
417
|
-
).select_related('state')
|
|
418
|
-
|
|
352
|
+
segments = LabelVideoSegment.objects.filter(pk__in=segment_ids, video_file=video).select_related("state")
|
|
353
|
+
|
|
419
354
|
if not segments.exists():
|
|
420
|
-
return Response({
|
|
421
|
-
|
|
422
|
-
}, status=status.HTTP_404_NOT_FOUND)
|
|
423
|
-
|
|
355
|
+
return Response({"error": "No segments found with provided IDs for this video"}, status=status.HTTP_404_NOT_FOUND)
|
|
356
|
+
|
|
424
357
|
updated_count = 0
|
|
425
358
|
failed_ids = []
|
|
426
|
-
|
|
359
|
+
|
|
427
360
|
with transaction.atomic():
|
|
428
361
|
for segment in segments:
|
|
429
362
|
try:
|
|
430
363
|
if segment.state:
|
|
431
364
|
segment.state.is_validated = is_validated
|
|
432
|
-
if notes and hasattr(segment.state,
|
|
365
|
+
if notes and hasattr(segment.state, "validation_notes"):
|
|
433
366
|
segment.state.validation_notes = notes
|
|
434
367
|
segment.state.save()
|
|
435
368
|
updated_count += 1
|
|
@@ -438,51 +371,49 @@ def video_segments_validate_bulk(request, pk: int):
|
|
|
438
371
|
except Exception as e:
|
|
439
372
|
logger.error(f"Error validating segment {segment.id}: {e}")
|
|
440
373
|
failed_ids.append(segment.id)
|
|
441
|
-
|
|
374
|
+
|
|
442
375
|
logger.info(f"Bulk validated {updated_count} segments in video {pk}")
|
|
443
|
-
|
|
376
|
+
|
|
444
377
|
response_data = {
|
|
445
378
|
"message": f"Bulk validation completed. {updated_count} segments updated.",
|
|
446
379
|
"updated_count": updated_count,
|
|
447
380
|
"requested_count": len(segment_ids),
|
|
448
381
|
"is_validated": is_validated,
|
|
449
|
-
"video_id": pk
|
|
382
|
+
"video_id": pk,
|
|
450
383
|
}
|
|
451
|
-
|
|
384
|
+
|
|
452
385
|
if failed_ids:
|
|
453
386
|
response_data["failed_ids"] = failed_ids
|
|
454
387
|
response_data["warning"] = f"{len(failed_ids)} segments could not be validated"
|
|
455
|
-
|
|
388
|
+
|
|
456
389
|
return Response(response_data, status=status.HTTP_200_OK)
|
|
457
|
-
|
|
390
|
+
|
|
458
391
|
except Exception as e:
|
|
459
392
|
logger.error(f"Error in bulk validation for video {pk}: {e}")
|
|
460
|
-
return Response({
|
|
461
|
-
"error": f"Bulk validation failed: {str(e)}"
|
|
462
|
-
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
393
|
+
return Response({"error": f"Bulk validation failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
463
394
|
|
|
464
395
|
|
|
465
|
-
@api_view([
|
|
396
|
+
@api_view(["GET", "POST"])
|
|
466
397
|
@permission_classes([EnvironmentAwarePermission])
|
|
467
398
|
def video_segments_validation_status(request, pk: int):
|
|
468
399
|
"""
|
|
469
400
|
Get or update validation status for all segments of a video.
|
|
470
|
-
|
|
401
|
+
|
|
471
402
|
GET /api/media/videos/<pk>/segments/validation-status/
|
|
472
403
|
Returns validation statistics for all segments.
|
|
473
|
-
|
|
404
|
+
|
|
474
405
|
POST /api/media/videos/<pk>/segments/validation-status/
|
|
475
406
|
Marks all segments (or filtered by label) as validated.
|
|
476
|
-
|
|
407
|
+
|
|
477
408
|
Query Parameters (GET):
|
|
478
409
|
- label_name: filter by label (optional)
|
|
479
|
-
|
|
410
|
+
|
|
480
411
|
Request Body (POST, optional):
|
|
481
412
|
{
|
|
482
413
|
"label_name": "...", // optional, only validate segments with this label
|
|
483
414
|
"notes": "..." // optional
|
|
484
415
|
}
|
|
485
|
-
|
|
416
|
+
|
|
486
417
|
Response (GET):
|
|
487
418
|
{
|
|
488
419
|
"video_id": 123,
|
|
@@ -492,7 +423,7 @@ def video_segments_validation_status(request, pk: int):
|
|
|
492
423
|
"validation_complete": false,
|
|
493
424
|
"by_label": {...}
|
|
494
425
|
}
|
|
495
|
-
|
|
426
|
+
|
|
496
427
|
Response (POST):
|
|
497
428
|
{
|
|
498
429
|
"message": "Video segment validation completed",
|
|
@@ -504,77 +435,69 @@ def video_segments_validation_status(request, pk: int):
|
|
|
504
435
|
"""
|
|
505
436
|
# Verify video exists
|
|
506
437
|
video = get_object_or_404(VideoFile, pk=pk)
|
|
507
|
-
|
|
508
|
-
if request.method ==
|
|
438
|
+
|
|
439
|
+
if request.method == "GET":
|
|
509
440
|
# Get validation status
|
|
510
|
-
label_name = request.query_params.get(
|
|
511
|
-
|
|
512
|
-
segments_query = LabelVideoSegment.objects.filter(
|
|
513
|
-
|
|
514
|
-
).select_related('state', 'label')
|
|
515
|
-
|
|
441
|
+
label_name = request.query_params.get("label_name")
|
|
442
|
+
|
|
443
|
+
segments_query = LabelVideoSegment.objects.filter(video_file=video).select_related("state", "label")
|
|
444
|
+
|
|
516
445
|
if label_name:
|
|
517
446
|
segments_query = segments_query.filter(label__name=label_name)
|
|
518
|
-
|
|
447
|
+
|
|
519
448
|
segments = segments_query.all()
|
|
520
449
|
total_count = segments.count()
|
|
521
|
-
|
|
450
|
+
|
|
522
451
|
# Count validated segments
|
|
523
|
-
validated_count = sum(
|
|
524
|
-
|
|
525
|
-
if s.state and s.state.is_validated
|
|
526
|
-
)
|
|
527
|
-
|
|
452
|
+
validated_count = sum(1 for s in segments if s.state and s.state.is_validated)
|
|
453
|
+
|
|
528
454
|
# By label breakdown
|
|
529
455
|
by_label = {}
|
|
530
456
|
for segment in segments:
|
|
531
|
-
label = segment.label.name if segment.label else
|
|
457
|
+
label = segment.label.name if segment.label else "unknown"
|
|
532
458
|
if label not in by_label:
|
|
533
|
-
by_label[label] = {
|
|
534
|
-
by_label[label][
|
|
459
|
+
by_label[label] = {"total": 0, "validated": 0}
|
|
460
|
+
by_label[label]["total"] += 1
|
|
535
461
|
if segment.state and segment.state.is_validated:
|
|
536
|
-
by_label[label][
|
|
537
|
-
|
|
538
|
-
return Response(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
462
|
+
by_label[label]["validated"] += 1
|
|
463
|
+
|
|
464
|
+
return Response(
|
|
465
|
+
{
|
|
466
|
+
"video_id": pk,
|
|
467
|
+
"total_segments": total_count,
|
|
468
|
+
"validated_count": validated_count,
|
|
469
|
+
"unvalidated_count": total_count - validated_count,
|
|
470
|
+
"validation_complete": validated_count == total_count and total_count > 0,
|
|
471
|
+
"by_label": by_label,
|
|
472
|
+
"label_filter": label_name,
|
|
473
|
+
},
|
|
474
|
+
status=status.HTTP_200_OK,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
elif request.method == "POST":
|
|
549
478
|
# Mark all segments as validated
|
|
550
|
-
label_name = request.data.get(
|
|
551
|
-
notes = request.data.get(
|
|
552
|
-
|
|
553
|
-
segments_query = LabelVideoSegment.objects.filter(
|
|
554
|
-
|
|
555
|
-
).select_related('state', 'label')
|
|
556
|
-
|
|
479
|
+
label_name = request.data.get("label_name")
|
|
480
|
+
notes = request.data.get("notes", "")
|
|
481
|
+
|
|
482
|
+
segments_query = LabelVideoSegment.objects.filter(video_file=video).select_related("state", "label")
|
|
483
|
+
|
|
557
484
|
if label_name:
|
|
558
485
|
segments_query = segments_query.filter(label__name=label_name)
|
|
559
|
-
|
|
486
|
+
|
|
560
487
|
segments = segments_query.all()
|
|
561
|
-
|
|
488
|
+
|
|
562
489
|
if not segments.exists():
|
|
563
|
-
return Response({
|
|
564
|
-
|
|
565
|
-
"video_id": pk,
|
|
566
|
-
"updated_count": 0
|
|
567
|
-
}, status=status.HTTP_200_OK)
|
|
568
|
-
|
|
490
|
+
return Response({"message": "No segments found to validate", "video_id": pk, "updated_count": 0}, status=status.HTTP_200_OK)
|
|
491
|
+
|
|
569
492
|
updated_count = 0
|
|
570
493
|
failed_count = 0
|
|
571
|
-
|
|
494
|
+
|
|
572
495
|
with transaction.atomic():
|
|
573
496
|
for segment in segments:
|
|
574
497
|
try:
|
|
575
498
|
if segment.state:
|
|
576
499
|
segment.state.is_validated = True
|
|
577
|
-
if notes and hasattr(segment.state,
|
|
500
|
+
if notes and hasattr(segment.state, "validation_notes"):
|
|
578
501
|
segment.state.validation_notes = notes
|
|
579
502
|
segment.state.save()
|
|
580
503
|
updated_count += 1
|
|
@@ -583,14 +506,18 @@ def video_segments_validation_status(request, pk: int):
|
|
|
583
506
|
except Exception as e:
|
|
584
507
|
logger.error(f"Error validating segment {segment.id}: {e}")
|
|
585
508
|
failed_count += 1
|
|
586
|
-
|
|
509
|
+
|
|
587
510
|
logger.info(f"Completed validation for {updated_count} segments in video {pk}")
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
511
|
+
logger.info(f"Removing Outside Segments")
|
|
512
|
+
video_file.label_video_segments.filter(video_file=video, label="outside", state__is_validated=False).delete()
|
|
513
|
+
return Response(
|
|
514
|
+
{
|
|
515
|
+
"message": f"Video segment validation completed for video {pk}",
|
|
516
|
+
"video_id": pk,
|
|
517
|
+
"total_segments": len(segments),
|
|
518
|
+
"updated_count": updated_count,
|
|
519
|
+
"failed_count": failed_count,
|
|
520
|
+
"label_filter": label_name,
|
|
521
|
+
},
|
|
522
|
+
status=status.HTTP_200_OK,
|
|
523
|
+
)
|