endoreg-db 0.8.9.2__py3-none-any.whl → 0.8.9.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of endoreg-db might be problematic. Click here for more details.
- endoreg_db/admin.py +10 -5
- endoreg_db/apps.py +4 -7
- endoreg_db/authz/auth.py +1 -0
- endoreg_db/authz/backends.py +1 -1
- endoreg_db/authz/management/commands/list_routes.py +2 -0
- endoreg_db/authz/middleware.py +8 -7
- endoreg_db/authz/permissions.py +21 -10
- endoreg_db/authz/policy.py +14 -19
- endoreg_db/authz/views_auth.py +14 -10
- endoreg_db/codemods/rename_datetime_fields.py +8 -1
- endoreg_db/exceptions.py +5 -2
- endoreg_db/forms/__init__.py +0 -1
- endoreg_db/forms/examination_form.py +4 -3
- endoreg_db/forms/patient_finding_intervention_form.py +30 -8
- endoreg_db/forms/patient_form.py +9 -13
- endoreg_db/forms/questionnaires/__init__.py +1 -1
- endoreg_db/forms/settings/__init__.py +4 -1
- endoreg_db/forms/unit.py +2 -1
- endoreg_db/helpers/count_db.py +17 -14
- endoreg_db/helpers/default_objects.py +2 -1
- endoreg_db/helpers/download_segmentation_model.py +4 -3
- endoreg_db/helpers/interact.py +0 -5
- endoreg_db/helpers/test_video_helper.py +33 -25
- endoreg_db/import_files/__init__.py +1 -1
- endoreg_db/import_files/context/__init__.py +1 -1
- endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
- endoreg_db/import_files/context/ensure_center.py +4 -4
- endoreg_db/import_files/context/file_lock.py +3 -3
- endoreg_db/import_files/context/import_context.py +11 -12
- endoreg_db/import_files/context/validate_directories.py +1 -0
- endoreg_db/import_files/file_storage/create_report_file.py +57 -34
- endoreg_db/import_files/file_storage/create_video_file.py +64 -35
- endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
- endoreg_db/import_files/file_storage/state_management.py +89 -122
- endoreg_db/import_files/file_storage/storage.py +5 -1
- endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
- endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
- endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
- endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
- endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
- endoreg_db/import_files/report_import_service.py +36 -30
- endoreg_db/import_files/video_import_service.py +27 -23
- endoreg_db/logger_conf.py +56 -40
- endoreg_db/management/__init__.py +1 -1
- endoreg_db/management/commands/__init__.py +1 -1
- endoreg_db/management/commands/check_auth.py +45 -38
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
- endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
- endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
- endoreg_db/management/commands/fix_video_paths.py +75 -54
- endoreg_db/management/commands/import_report.py +1 -3
- endoreg_db/management/commands/list_routes.py +2 -0
- endoreg_db/management/commands/load_ai_model_data.py +8 -2
- endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
- endoreg_db/management/commands/load_center_data.py +3 -3
- endoreg_db/management/commands/load_distribution_data.py +35 -38
- endoreg_db/management/commands/load_endoscope_data.py +0 -3
- endoreg_db/management/commands/load_examination_data.py +20 -4
- endoreg_db/management/commands/load_finding_data.py +18 -3
- endoreg_db/management/commands/load_gender_data.py +17 -24
- endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
- endoreg_db/management/commands/load_information_source.py +0 -3
- endoreg_db/management/commands/load_lab_value_data.py +14 -3
- endoreg_db/management/commands/load_legacy_data.py +303 -0
- endoreg_db/management/commands/load_name_data.py +1 -2
- endoreg_db/management/commands/load_pdf_type_data.py +4 -8
- endoreg_db/management/commands/load_profession_data.py +0 -1
- endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
- endoreg_db/management/commands/load_requirement_data.py +6 -2
- endoreg_db/management/commands/load_unit_data.py +0 -4
- endoreg_db/management/commands/load_user_groups.py +5 -7
- endoreg_db/management/commands/model_input.py +169 -0
- endoreg_db/management/commands/register_ai_model.py +22 -16
- endoreg_db/management/commands/setup_endoreg_db.py +110 -32
- endoreg_db/management/commands/storage_management.py +14 -8
- endoreg_db/management/commands/summarize_db_content.py +154 -63
- endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
- endoreg_db/management/commands/validate_video_files.py +82 -50
- endoreg_db/management/commands/video_validation.py +4 -6
- endoreg_db/migrations/0001_initial.py +112 -63
- endoreg_db/models/__init__.py +8 -0
- endoreg_db/models/administration/ai/active_model.py +5 -5
- endoreg_db/models/administration/ai/ai_model.py +41 -18
- endoreg_db/models/administration/ai/model_type.py +1 -0
- endoreg_db/models/administration/case/case.py +22 -22
- endoreg_db/models/administration/center/__init__.py +5 -5
- endoreg_db/models/administration/center/center.py +6 -2
- endoreg_db/models/administration/center/center_resource.py +18 -4
- endoreg_db/models/administration/center/center_shift.py +3 -1
- endoreg_db/models/administration/center/center_waste.py +6 -2
- endoreg_db/models/administration/person/__init__.py +1 -1
- endoreg_db/models/administration/person/employee/__init__.py +1 -1
- endoreg_db/models/administration/person/employee/employee_type.py +3 -1
- endoreg_db/models/administration/person/examiner/__init__.py +1 -1
- endoreg_db/models/administration/person/examiner/examiner.py +10 -2
- endoreg_db/models/administration/person/names/first_name.py +6 -4
- endoreg_db/models/administration/person/names/last_name.py +4 -3
- endoreg_db/models/administration/person/patient/__init__.py +1 -1
- endoreg_db/models/administration/person/patient/patient.py +0 -1
- endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
- endoreg_db/models/administration/person/person.py +1 -1
- endoreg_db/models/administration/product/__init__.py +7 -6
- endoreg_db/models/administration/product/product.py +6 -2
- endoreg_db/models/administration/product/product_group.py +9 -7
- endoreg_db/models/administration/product/product_material.py +9 -2
- endoreg_db/models/administration/product/reference_product.py +64 -15
- endoreg_db/models/administration/qualification/qualification.py +3 -1
- endoreg_db/models/administration/shift/shift.py +3 -1
- endoreg_db/models/administration/shift/shift_type.py +12 -4
- endoreg_db/models/aidataset/__init__.py +5 -0
- endoreg_db/models/aidataset/aidataset.py +193 -0
- endoreg_db/models/label/__init__.py +1 -1
- endoreg_db/models/label/label.py +10 -2
- endoreg_db/models/label/label_set.py +3 -1
- endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
- endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
- endoreg_db/models/media/__init__.py +12 -5
- endoreg_db/models/media/frame/__init__.py +1 -1
- endoreg_db/models/media/frame/frame.py +34 -8
- endoreg_db/models/media/pdf/__init__.py +2 -1
- endoreg_db/models/media/pdf/raw_pdf.py +11 -4
- endoreg_db/models/media/pdf/report_file.py +6 -2
- endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
- endoreg_db/models/media/video/create_from_file.py +20 -41
- endoreg_db/models/media/video/pipe_1.py +75 -30
- endoreg_db/models/media/video/pipe_2.py +37 -12
- endoreg_db/models/media/video/video_file.py +36 -24
- endoreg_db/models/media/video/video_file_ai.py +235 -70
- endoreg_db/models/media/video/video_file_anonymize.py +240 -65
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
- endoreg_db/models/media/video/video_file_io.py +85 -33
- endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
- endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
- endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
- endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
- endoreg_db/models/media/video/video_file_segments.py +118 -27
- endoreg_db/models/media/video/video_metadata.py +25 -6
- endoreg_db/models/media/video/video_processing.py +54 -15
- endoreg_db/models/medical/__init__.py +3 -13
- endoreg_db/models/medical/contraindication/__init__.py +3 -1
- endoreg_db/models/medical/disease.py +18 -6
- endoreg_db/models/medical/event.py +6 -2
- endoreg_db/models/medical/examination/__init__.py +5 -1
- endoreg_db/models/medical/examination/examination.py +22 -6
- endoreg_db/models/medical/examination/examination_indication.py +23 -7
- endoreg_db/models/medical/examination/examination_time.py +6 -2
- endoreg_db/models/medical/finding/__init__.py +3 -1
- endoreg_db/models/medical/finding/finding.py +37 -12
- endoreg_db/models/medical/finding/finding_classification.py +27 -8
- endoreg_db/models/medical/finding/finding_intervention.py +19 -6
- endoreg_db/models/medical/finding/finding_type.py +3 -1
- endoreg_db/models/medical/hardware/__init__.py +1 -1
- endoreg_db/models/medical/hardware/endoscope.py +14 -2
- endoreg_db/models/medical/laboratory/__init__.py +1 -1
- endoreg_db/models/medical/laboratory/lab_value.py +139 -39
- endoreg_db/models/medical/medication/__init__.py +7 -3
- endoreg_db/models/medical/medication/medication.py +3 -1
- endoreg_db/models/medical/medication/medication_indication.py +3 -1
- endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
- endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
- endoreg_db/models/medical/medication/medication_schedule.py +3 -1
- endoreg_db/models/medical/patient/__init__.py +2 -10
- endoreg_db/models/medical/patient/medication_examples.py +3 -14
- endoreg_db/models/medical/patient/patient_disease.py +17 -5
- endoreg_db/models/medical/patient/patient_event.py +12 -4
- endoreg_db/models/medical/patient/patient_examination.py +52 -15
- endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
- endoreg_db/models/medical/patient/patient_finding.py +105 -29
- endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
- endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
- endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
- endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
- endoreg_db/models/medical/patient/patient_medication.py +25 -7
- endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
- endoreg_db/models/metadata/model_meta.py +40 -12
- endoreg_db/models/metadata/model_meta_logic.py +51 -16
- endoreg_db/models/metadata/sensitive_meta.py +65 -28
- endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
- endoreg_db/models/metadata/video_meta.py +146 -39
- endoreg_db/models/metadata/video_prediction_logic.py +70 -21
- endoreg_db/models/metadata/video_prediction_meta.py +80 -27
- endoreg_db/models/operation_log.py +63 -0
- endoreg_db/models/other/__init__.py +10 -10
- endoreg_db/models/other/distribution/__init__.py +9 -7
- endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
- endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
- endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
- endoreg_db/models/other/emission/__init__.py +1 -1
- endoreg_db/models/other/emission/emission_factor.py +9 -3
- endoreg_db/models/other/information_source.py +15 -5
- endoreg_db/models/other/material.py +3 -1
- endoreg_db/models/other/transport_route.py +3 -1
- endoreg_db/models/other/unit.py +6 -2
- endoreg_db/models/report/report.py +0 -1
- endoreg_db/models/requirement/requirement.py +84 -27
- endoreg_db/models/requirement/requirement_error.py +5 -6
- endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
- endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
- endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
- endoreg_db/models/requirement/requirement_operator.py +28 -8
- endoreg_db/models/requirement/requirement_set.py +34 -11
- endoreg_db/models/state/__init__.py +1 -0
- endoreg_db/models/state/audit_ledger.py +9 -2
- endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
- endoreg_db/models/state/processing_history/processing_history.py +136 -0
- endoreg_db/models/state/raw_pdf.py +0 -1
- endoreg_db/models/state/video.py +2 -4
- endoreg_db/models/utils.py +4 -2
- endoreg_db/queries/__init__.py +2 -6
- endoreg_db/queries/annotations/__init__.py +1 -3
- endoreg_db/queries/annotations/legacy.py +37 -26
- endoreg_db/root_urls.py +3 -4
- endoreg_db/schemas/examination_evaluation.py +3 -0
- endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
- endoreg_db/serializers/__init__.py +2 -8
- endoreg_db/serializers/administration/__init__.py +1 -2
- endoreg_db/serializers/administration/ai/__init__.py +0 -1
- endoreg_db/serializers/administration/ai/active_model.py +3 -1
- endoreg_db/serializers/administration/ai/ai_model.py +5 -3
- endoreg_db/serializers/administration/ai/model_type.py +3 -1
- endoreg_db/serializers/administration/center.py +7 -2
- endoreg_db/serializers/administration/gender.py +4 -2
- endoreg_db/serializers/anonymization.py +13 -13
- endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
- endoreg_db/serializers/examination/__init__.py +1 -1
- endoreg_db/serializers/examination/base.py +12 -13
- endoreg_db/serializers/examination/dropdown.py +6 -7
- endoreg_db/serializers/examination_serializer.py +3 -6
- endoreg_db/serializers/finding/__init__.py +1 -1
- endoreg_db/serializers/finding/finding.py +14 -7
- endoreg_db/serializers/finding_classification/__init__.py +3 -3
- endoreg_db/serializers/finding_classification/choice.py +3 -3
- endoreg_db/serializers/finding_classification/classification.py +2 -4
- endoreg_db/serializers/label_video_segment/__init__.py +5 -3
- endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
- endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
- endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
- endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
- endoreg_db/serializers/meta/__init__.py +1 -2
- endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
- endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
- endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
- endoreg_db/serializers/misc/__init__.py +2 -2
- endoreg_db/serializers/misc/file_overview.py +11 -7
- endoreg_db/serializers/misc/stats.py +10 -8
- endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
- endoreg_db/serializers/misc/upload_job.py +32 -29
- endoreg_db/serializers/patient/__init__.py +2 -1
- endoreg_db/serializers/patient/patient.py +32 -15
- endoreg_db/serializers/patient/patient_dropdown.py +11 -3
- endoreg_db/serializers/patient_examination/__init__.py +1 -1
- endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
- endoreg_db/serializers/patient_finding/__init__.py +1 -1
- endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
- endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
- endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
- endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
- endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
- endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
- endoreg_db/serializers/pdf/__init__.py +1 -3
- endoreg_db/serializers/requirements/requirement_schema.py +1 -6
- endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
- endoreg_db/serializers/video/__init__.py +2 -2
- endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
- endoreg_db/serializers/video/video_file_brief.py +6 -2
- endoreg_db/serializers/video/video_file_detail.py +36 -23
- endoreg_db/serializers/video/video_file_list.py +4 -2
- endoreg_db/serializers/video/video_processing_history.py +54 -50
- endoreg_db/services/__init__.py +1 -1
- endoreg_db/services/anonymization.py +2 -2
- endoreg_db/services/examination_evaluation.py +40 -17
- endoreg_db/services/model_meta_from_hf.py +76 -0
- endoreg_db/services/polling_coordinator.py +101 -70
- endoreg_db/services/pseudonym_service.py +27 -22
- endoreg_db/services/report_import.py +6 -3
- endoreg_db/services/segment_sync.py +75 -59
- endoreg_db/services/video_import.py +6 -7
- endoreg_db/urls/__init__.py +2 -2
- endoreg_db/urls/ai.py +7 -25
- endoreg_db/urls/anonymization.py +61 -15
- endoreg_db/urls/auth.py +4 -4
- endoreg_db/urls/classification.py +4 -9
- endoreg_db/urls/examination.py +27 -18
- endoreg_db/urls/media.py +27 -34
- endoreg_db/urls/patient.py +11 -7
- endoreg_db/urls/requirements.py +3 -1
- endoreg_db/urls/root_urls.py +2 -3
- endoreg_db/urls/stats.py +24 -16
- endoreg_db/urls/upload.py +3 -11
- endoreg_db/utils/__init__.py +14 -15
- endoreg_db/utils/ai/__init__.py +1 -1
- endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
- endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
- endoreg_db/utils/ai/get.py +2 -1
- endoreg_db/utils/ai/inference_dataset.py +14 -15
- endoreg_db/utils/ai/model_training/config.py +117 -0
- endoreg_db/utils/ai/model_training/dataset.py +74 -0
- endoreg_db/utils/ai/model_training/losses.py +68 -0
- endoreg_db/utils/ai/model_training/metrics.py +78 -0
- endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
- endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
- endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
- endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
- endoreg_db/utils/ai/predict.py +4 -4
- endoreg_db/utils/ai/preprocess.py +19 -11
- endoreg_db/utils/calc_duration_seconds.py +4 -4
- endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
- endoreg_db/utils/check_video_files.py +74 -47
- endoreg_db/utils/cropping.py +10 -9
- endoreg_db/utils/dataloader.py +11 -3
- endoreg_db/utils/dates.py +3 -4
- endoreg_db/utils/defaults/set_default_center.py +7 -6
- endoreg_db/utils/env.py +6 -2
- endoreg_db/utils/extract_specific_frames.py +24 -9
- endoreg_db/utils/file_operations.py +30 -18
- endoreg_db/utils/fix_video_path_direct.py +57 -41
- endoreg_db/utils/frame_anonymization_utils.py +157 -157
- endoreg_db/utils/hashs.py +3 -18
- endoreg_db/utils/links/requirement_link.py +96 -52
- endoreg_db/utils/ocr.py +30 -25
- endoreg_db/utils/operation_log.py +61 -0
- endoreg_db/utils/parse_and_generate_yaml.py +12 -13
- endoreg_db/utils/paths.py +6 -6
- endoreg_db/utils/permissions.py +40 -24
- endoreg_db/utils/pipelines/process_video_dir.py +50 -26
- endoreg_db/utils/product/sum_emissions.py +5 -3
- endoreg_db/utils/product/sum_weights.py +4 -2
- endoreg_db/utils/pydantic_models/__init__.py +3 -4
- endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
- endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
- endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
- endoreg_db/utils/setup_config.py +21 -5
- endoreg_db/utils/storage.py +3 -1
- endoreg_db/utils/translation.py +19 -15
- endoreg_db/utils/uuid.py +1 -0
- endoreg_db/utils/validate_endo_roi.py +12 -4
- endoreg_db/utils/validate_subcategory_dict.py +26 -24
- endoreg_db/utils/validate_video_detailed.py +207 -149
- endoreg_db/utils/video/__init__.py +7 -3
- endoreg_db/utils/video/extract_frames.py +30 -18
- endoreg_db/utils/video/names.py +11 -6
- endoreg_db/utils/video/streaming_processor.py +175 -101
- endoreg_db/utils/video/video_splitter.py +30 -19
- endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
- endoreg_db/views/__init__.py +0 -20
- endoreg_db/views/anonymization/__init__.py +6 -2
- endoreg_db/views/anonymization/media_management.py +2 -6
- endoreg_db/views/anonymization/overview.py +34 -1
- endoreg_db/views/anonymization/validate.py +79 -18
- endoreg_db/views/auth/__init__.py +1 -1
- endoreg_db/views/auth/keycloak.py +16 -14
- endoreg_db/views/examination/__init__.py +12 -15
- endoreg_db/views/examination/examination.py +5 -5
- endoreg_db/views/examination/examination_manifest_cache.py +5 -5
- endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
- endoreg_db/views/examination/get_finding_classifications.py +9 -7
- endoreg_db/views/examination/get_findings.py +8 -10
- endoreg_db/views/examination/get_instruments.py +3 -2
- endoreg_db/views/examination/get_interventions.py +1 -1
- endoreg_db/views/finding/__init__.py +2 -2
- endoreg_db/views/finding/finding.py +58 -54
- endoreg_db/views/finding/get_classifications.py +1 -1
- endoreg_db/views/finding/get_interventions.py +1 -1
- endoreg_db/views/finding_classification/__init__.py +5 -5
- endoreg_db/views/finding_classification/finding_classification.py +5 -6
- endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
- endoreg_db/views/media/__init__.py +13 -13
- endoreg_db/views/media/pdf_media.py +9 -9
- endoreg_db/views/media/sensitive_metadata.py +10 -7
- endoreg_db/views/media/video_media.py +4 -4
- endoreg_db/views/meta/__init__.py +1 -1
- endoreg_db/views/meta/sensitive_meta_list.py +20 -22
- endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
- endoreg_db/views/misc/__init__.py +6 -34
- endoreg_db/views/misc/center.py +2 -1
- endoreg_db/views/misc/csrf.py +2 -1
- endoreg_db/views/misc/gender.py +2 -1
- endoreg_db/views/misc/stats.py +141 -106
- endoreg_db/views/patient/__init__.py +1 -3
- endoreg_db/views/patient/patient.py +141 -99
- endoreg_db/views/patient_examination/__init__.py +5 -5
- endoreg_db/views/patient_examination/patient_examination.py +43 -42
- endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
- endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
- endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
- endoreg_db/views/patient_examination/video.py +114 -80
- endoreg_db/views/patient_finding/__init__.py +1 -1
- endoreg_db/views/patient_finding/patient_finding.py +17 -10
- endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
- endoreg_db/views/patient_finding_classification/__init__.py +1 -1
- endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
- endoreg_db/views/report/reimport.py +1 -1
- endoreg_db/views/report/report_stream.py +5 -8
- endoreg_db/views/requirement/__init__.py +2 -1
- endoreg_db/views/requirement/evaluate.py +7 -9
- endoreg_db/views/requirement/lookup.py +2 -3
- endoreg_db/views/requirement/lookup_store.py +0 -1
- endoreg_db/views/requirement/requirement_utils.py +2 -4
- endoreg_db/views/stats/__init__.py +4 -4
- endoreg_db/views/stats/stats_views.py +152 -115
- endoreg_db/views/video/__init__.py +18 -27
- endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
- endoreg_db/views/{ai → video/ai}/label.py +20 -16
- endoreg_db/views/video/correction.py +5 -6
- endoreg_db/views/video/reimport.py +134 -99
- endoreg_db/views/video/segments_crud.py +134 -44
- endoreg_db/views/video/video_apply_mask.py +13 -12
- endoreg_db/views/video/video_correction.py +2 -1
- endoreg_db/views/video/video_download_processed.py +15 -15
- endoreg_db/views/video/video_meta_stats.py +7 -6
- endoreg_db/views/video/video_processing_history.py +3 -2
- endoreg_db/views/video/video_remove_frames.py +13 -12
- endoreg_db/views/video/video_stream.py +110 -82
- {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
- {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +434 -431
- endoreg_db/management/commands/import_fallback_video.py +0 -203
- endoreg_db/management/commands/import_video.py +0 -422
- endoreg_db/management/commands/import_video_with_classification.py +0 -367
- endoreg_db/models/media/processing_history/processing_history.py +0 -96
- endoreg_db/serializers/label/__init__.py +0 -7
- endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
- endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
- endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
- endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
- endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
- endoreg_db/services/__old/pdf_import.py +0 -1487
- endoreg_db/services/__old/video_import.py +0 -1306
- endoreg_db/tasks/upload_tasks.py +0 -216
- endoreg_db/tasks/video_ingest.py +0 -161
- endoreg_db/tasks/video_processing_tasks.py +0 -327
- endoreg_db/views/misc/translation.py +0 -182
- {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
|
|
2
3
|
from rest_framework import serializers
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
from ...models import VideoFile
|
|
6
|
+
|
|
4
7
|
try:
|
|
5
8
|
import cv2
|
|
6
9
|
except ImportError:
|
|
7
10
|
cv2 = None
|
|
8
|
-
from django.conf import settings
|
|
9
11
|
# from django.conf import settings
|
|
10
12
|
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from django.conf import settings
|
|
11
15
|
from rest_framework.exceptions import ValidationError
|
|
16
|
+
|
|
12
17
|
if TYPE_CHECKING:
|
|
13
18
|
from endoreg_db.models import VideoFile
|
|
14
|
-
|
|
19
|
+
|
|
20
|
+
|
|
15
21
|
class VideoFileSerializer(serializers.ModelSerializer):
|
|
16
22
|
"""
|
|
17
23
|
Serializer that dynamically handles video retrieval and streaming.
|
|
@@ -55,24 +61,24 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
55
61
|
|
|
56
62
|
# @staticmethod #using @staticmethod makes it reusable without needing to create a serializer instance.
|
|
57
63
|
# Without @staticmethod, you would need to instantiate the serializer before calling the method, which is unnecessary her
|
|
58
|
-
def get_video_selection_field(self, obj:"
|
|
64
|
+
def get_video_selection_field(self, obj: "VideoFile"):
|
|
59
65
|
"""
|
|
60
66
|
Return the UUID of the video for use as a selection value in frontend dropdowns.
|
|
61
|
-
|
|
67
|
+
|
|
62
68
|
Parameters:
|
|
63
69
|
obj (Video): The video instance being serialized.
|
|
64
|
-
|
|
70
|
+
|
|
65
71
|
Returns:
|
|
66
72
|
str: The UUID of the video.
|
|
67
73
|
"""
|
|
68
|
-
return obj.
|
|
74
|
+
return obj.video_hash
|
|
69
75
|
|
|
70
76
|
def get_video_url(
|
|
71
77
|
self, obj
|
|
72
78
|
): # when we serialize a RawVideoFile object (video metadata), the get_video_url method is automatically invoked by DRF
|
|
73
79
|
"""
|
|
74
80
|
Return the absolute API URL for accessing the video file.
|
|
75
|
-
|
|
81
|
+
|
|
76
82
|
If the video ID is invalid or the request context is missing, returns a dictionary with an error message.
|
|
77
83
|
"""
|
|
78
84
|
if not obj.id:
|
|
@@ -82,14 +88,16 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
82
88
|
"request"
|
|
83
89
|
) # Gets the request object (provided by DRF).
|
|
84
90
|
if request:
|
|
85
|
-
return request.build_absolute_uri(
|
|
91
|
+
return request.build_absolute_uri(
|
|
92
|
+
f"/api/video/{obj.id}/"
|
|
93
|
+
) # Added api/ prefix
|
|
86
94
|
|
|
87
95
|
return {"error": "Video URL not available"}
|
|
88
96
|
|
|
89
|
-
def get_duration(self, obj:"
|
|
97
|
+
def get_duration(self, obj: "VideoFile"):
|
|
90
98
|
"""
|
|
91
99
|
Return the duration of the video in seconds, using the stored value if available or extracting it dynamically with OpenCV.
|
|
92
|
-
|
|
100
|
+
|
|
93
101
|
If the duration is not present in the database, the method opens the video file, retrieves its frame count and frames per second (FPS), and calculates the duration. Returns `None` if the video cannot be opened or FPS is zero.
|
|
94
102
|
"""
|
|
95
103
|
if hasattr(obj, "duration") and obj.duration:
|
|
@@ -99,7 +107,7 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
99
107
|
|
|
100
108
|
# Dynamically extract duration if not stored
|
|
101
109
|
video_path = obj.active_file.path
|
|
102
|
-
|
|
110
|
+
|
|
103
111
|
cap = cv2.VideoCapture(video_path)
|
|
104
112
|
try:
|
|
105
113
|
if not cap.isOpened():
|
|
@@ -114,13 +122,13 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
114
122
|
finally:
|
|
115
123
|
cap.release()
|
|
116
124
|
|
|
117
|
-
def get_file(self, obj:"
|
|
125
|
+
def get_file(self, obj: "VideoFile"):
|
|
118
126
|
"""
|
|
119
127
|
Returns the relative file path of the active video file, or an error message if the file is missing or invalid.
|
|
120
|
-
|
|
128
|
+
|
|
121
129
|
Parameters:
|
|
122
130
|
obj (Video): The video instance whose file path is to be retrieved.
|
|
123
|
-
|
|
131
|
+
|
|
124
132
|
Returns:
|
|
125
133
|
str or dict: The relative file path as a string, or a dictionary with an error message if the file is missing or invalid.
|
|
126
134
|
"""
|
|
@@ -134,10 +142,10 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
134
142
|
obj.active_file.name
|
|
135
143
|
).strip() # Only return the file path, no URL,#obj.active_file returning a FieldFile object instead of a string
|
|
136
144
|
|
|
137
|
-
def get_full_video_path(self, obj:"
|
|
145
|
+
def get_full_video_path(self, obj: "VideoFile"):
|
|
138
146
|
"""
|
|
139
147
|
Return the absolute filesystem path to the video's active file.
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
If the file does not exist or an error occurs during path construction, returns a dictionary with an error message.
|
|
142
150
|
"""
|
|
143
151
|
if not obj.active_file:
|
|
@@ -145,26 +153,34 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
145
153
|
|
|
146
154
|
try:
|
|
147
155
|
# Use the active_file_path property which handles both processed and raw files
|
|
148
|
-
if hasattr(obj,
|
|
156
|
+
if hasattr(obj, "active_file_path") and obj.active_file_path:
|
|
149
157
|
full_path = obj.active_file_path
|
|
150
|
-
return
|
|
158
|
+
return (
|
|
159
|
+
str(full_path)
|
|
160
|
+
if full_path.exists()
|
|
161
|
+
else {"error": f"file not found at: {full_path}"}
|
|
162
|
+
)
|
|
151
163
|
else:
|
|
152
164
|
# Fallback: construct path manually
|
|
153
165
|
video_relative_path = str(obj.active_file.name).strip()
|
|
154
166
|
if not video_relative_path:
|
|
155
167
|
return {"error": "Video file path is empty or invalid"}
|
|
156
|
-
|
|
168
|
+
|
|
157
169
|
# Construct the path using the file's actual path
|
|
158
170
|
full_path = obj.active_file.path
|
|
159
|
-
return
|
|
160
|
-
|
|
171
|
+
return (
|
|
172
|
+
str(full_path)
|
|
173
|
+
if Path(full_path).exists()
|
|
174
|
+
else {"error": f"file not found at: {full_path}"}
|
|
175
|
+
)
|
|
176
|
+
|
|
161
177
|
except Exception as e:
|
|
162
178
|
return {"error": f"Error constructing file path: {str(e)}"}
|
|
163
179
|
|
|
164
|
-
def get_sequences(self, obj:"
|
|
180
|
+
def get_sequences(self, obj: "VideoFile"):
|
|
165
181
|
"""
|
|
166
182
|
Retrieve frame sequences for each label from the video object.
|
|
167
|
-
|
|
183
|
+
|
|
168
184
|
Returns:
|
|
169
185
|
dict: A mapping of label names to lists of frame ranges, or an error message if no sequences are found.
|
|
170
186
|
"""
|
|
@@ -172,25 +188,25 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
172
188
|
"error": "no sequence found, check database first"
|
|
173
189
|
} # Get from sequences, return {} if missing
|
|
174
190
|
|
|
175
|
-
def get_label_names(self, obj:"
|
|
191
|
+
def get_label_names(self, obj: "VideoFile"):
|
|
176
192
|
"""
|
|
177
193
|
Return a list of label names present in the video's frame sequences.
|
|
178
|
-
|
|
194
|
+
|
|
179
195
|
Parameters:
|
|
180
196
|
obj (Video): The video instance to extract label names from.
|
|
181
|
-
|
|
197
|
+
|
|
182
198
|
Returns:
|
|
183
199
|
list[str]: List of label names, or an empty list if no sequences are found.
|
|
184
200
|
"""
|
|
185
201
|
sequences = self.get_sequences(obj)
|
|
186
202
|
return list(sequences.keys()) if sequences else []
|
|
187
203
|
|
|
188
|
-
def get_label_time_segments(self, obj:"
|
|
204
|
+
def get_label_time_segments(self, obj: "VideoFile"):
|
|
189
205
|
"""
|
|
190
206
|
Convert frame sequences for each label into time segments with frame-level metadata.
|
|
191
|
-
|
|
207
|
+
|
|
192
208
|
For each label in the video, this method generates a list of time segments based on frame index ranges, converting them to seconds using the video's FPS. Each segment includes the raw frame indices, start and end times in seconds, and detailed information for each frame in the segment, such as filename, full file path, and a placeholder for predictions.
|
|
193
|
-
|
|
209
|
+
|
|
194
210
|
Returns:
|
|
195
211
|
dict: A dictionary mapping each label to its list of time segments and associated frame metadata.
|
|
196
212
|
"""
|
|
@@ -201,13 +217,18 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
201
217
|
|
|
202
218
|
if not fps or fps <= 0:
|
|
203
219
|
# Strict by default — only use fallback if explicitly enabled and > 0
|
|
204
|
-
if
|
|
220
|
+
if (
|
|
221
|
+
getattr(settings, "VIDEO_ALLOW_FPS_FALLBACK", False)
|
|
222
|
+
and getattr(settings, "VIDEO_DEFAULT_FPS", 0) > 0
|
|
223
|
+
):
|
|
205
224
|
fps = settings.VIDEO_DEFAULT_FPS
|
|
206
225
|
else:
|
|
207
|
-
raise ValidationError(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
raise ValidationError(
|
|
227
|
+
{
|
|
228
|
+
"label_time_segments": "FPS unavailable — cannot calculate time segments",
|
|
229
|
+
"video_id": getattr(obj, "id", None),
|
|
230
|
+
}
|
|
231
|
+
)
|
|
211
232
|
|
|
212
233
|
sequences = self.get_sequences(obj) # Fetch sequence data
|
|
213
234
|
frame_dir = Path(obj.frame_dir) # Get the correct directory from the model
|
|
@@ -229,17 +250,16 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
229
250
|
|
|
230
251
|
# Fetch predictions for frames within this range
|
|
231
252
|
for frame_num in range(start_frame, end_frame + 1):
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
253
|
+
frame_filename = (
|
|
254
|
+
f"frame_{str(frame_num).zfill(7)}.jpg" # Frame filename format
|
|
255
|
+
)
|
|
256
|
+
frame_path = frame_dir / frame_filename # Full path to the frame
|
|
257
|
+
|
|
258
|
+
frame_data[frame_num] = {
|
|
259
|
+
"frame_filename": frame_filename,
|
|
260
|
+
"frame_file_path": str(frame_path),
|
|
261
|
+
"predictions": None,
|
|
262
|
+
}
|
|
243
263
|
|
|
244
264
|
# Append the converted time segment
|
|
245
265
|
label_times.append(
|
|
@@ -261,4 +281,3 @@ class VideoFileSerializer(serializers.ModelSerializer):
|
|
|
261
281
|
}
|
|
262
282
|
|
|
263
283
|
return time_segments
|
|
264
|
-
|
|
@@ -6,5 +6,9 @@ from rest_framework import serializers
|
|
|
6
6
|
|
|
7
7
|
class VideoBriefSerializer(serializers.ModelSerializer):
|
|
8
8
|
class Meta:
|
|
9
|
-
model
|
|
10
|
-
fields = [
|
|
9
|
+
model = VideoFile
|
|
10
|
+
fields = [
|
|
11
|
+
"id",
|
|
12
|
+
"original_file_name",
|
|
13
|
+
"sensitive_meta_id",
|
|
14
|
+
] # for tables/overview
|
|
@@ -6,23 +6,32 @@ from endoreg_db.models.media.video.video_file import VideoFile
|
|
|
6
6
|
from endoreg_db.serializers.video.video_file_brief import VideoBriefSerializer
|
|
7
7
|
from ...utils.calc_duration_seconds import _calc_duration_vf
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
class VideoDetailSerializer(VideoBriefSerializer):
|
|
10
11
|
# pull selected fields from SensitiveMeta (READ-ONLY) - using SerializerMethodField to handle datetime->date conversion
|
|
11
|
-
patient_first_name = serializers.CharField(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
patient_first_name = serializers.CharField(
|
|
13
|
+
source="sensitive_meta.patient_first_name", read_only=True
|
|
14
|
+
)
|
|
15
|
+
patient_last_name = serializers.CharField(
|
|
16
|
+
source="sensitive_meta.patient_last_name", read_only=True
|
|
17
|
+
)
|
|
18
|
+
patient_dob = serializers.SerializerMethodField()
|
|
19
|
+
examination_date = serializers.SerializerMethodField()
|
|
15
20
|
|
|
16
|
-
file
|
|
17
|
-
full_path
|
|
18
|
-
duration
|
|
19
|
-
video_url
|
|
21
|
+
file = serializers.SerializerMethodField()
|
|
22
|
+
full_path = serializers.SerializerMethodField()
|
|
23
|
+
duration = serializers.SerializerMethodField()
|
|
24
|
+
video_url = serializers.SerializerMethodField()
|
|
20
25
|
|
|
21
26
|
class Meta(VideoBriefSerializer.Meta):
|
|
22
27
|
fields = VideoBriefSerializer.Meta.fields + [
|
|
23
|
-
"file",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
28
|
+
"file",
|
|
29
|
+
"full_path",
|
|
30
|
+
"video_url",
|
|
31
|
+
"patient_first_name",
|
|
32
|
+
"patient_last_name",
|
|
33
|
+
"patient_dob",
|
|
34
|
+
"examination_date",
|
|
26
35
|
"duration",
|
|
27
36
|
]
|
|
28
37
|
|
|
@@ -38,46 +47,50 @@ class VideoDetailSerializer(VideoBriefSerializer):
|
|
|
38
47
|
def get_video_url(self, obj):
|
|
39
48
|
"""
|
|
40
49
|
Return the absolute URL for accessing the video streaming resource.
|
|
41
|
-
|
|
50
|
+
|
|
42
51
|
Returns:
|
|
43
52
|
str or None: The absolute URL to the video streaming endpoint if a request context is available; otherwise, None.
|
|
44
53
|
"""
|
|
45
54
|
request = self.context.get("request")
|
|
46
55
|
# Use video streaming endpoint (VideoStreamView)
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
return (
|
|
57
|
+
request.build_absolute_uri(f"/api/media/videos/{obj.pk}/")
|
|
58
|
+
if request
|
|
59
|
+
else None
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def get_duration(self, obj: VideoFile):
|
|
50
63
|
"""
|
|
51
64
|
Return the duration of the video, using the stored value if available or calculating it if not.
|
|
52
|
-
|
|
65
|
+
|
|
53
66
|
Parameters:
|
|
54
67
|
obj (VideoFile): The video file instance.
|
|
55
|
-
|
|
68
|
+
|
|
56
69
|
Returns:
|
|
57
70
|
float or None: Duration of the video in seconds, or None if unavailable.
|
|
58
71
|
"""
|
|
59
72
|
return obj.duration or _calc_duration_vf(obj)
|
|
60
|
-
|
|
73
|
+
|
|
61
74
|
def get_patient_dob(self, obj):
|
|
62
75
|
"""
|
|
63
76
|
Returns the patient's date of birth as a date object if available, or None if not present.
|
|
64
|
-
|
|
77
|
+
|
|
65
78
|
Extracts the date part from the patient's date of birth field in the sensitive metadata, handling both datetime and date types.
|
|
66
79
|
"""
|
|
67
80
|
if obj.sensitive_meta and obj.sensitive_meta.patient_dob:
|
|
68
81
|
# If it's a datetime, extract the date part
|
|
69
82
|
dob = obj.sensitive_meta.patient_dob
|
|
70
|
-
return dob.date() if hasattr(dob,
|
|
83
|
+
return dob.date() if hasattr(dob, "date") else dob
|
|
71
84
|
return None
|
|
72
|
-
|
|
85
|
+
|
|
73
86
|
def get_examination_date(self, obj):
|
|
74
87
|
"""
|
|
75
88
|
Returns the examination date as a date object from the sensitive metadata, or None if unavailable.
|
|
76
|
-
|
|
89
|
+
|
|
77
90
|
If the examination date is a datetime, only the date part is returned.
|
|
78
91
|
"""
|
|
79
92
|
if obj.sensitive_meta and obj.sensitive_meta.examination_date:
|
|
80
93
|
# If it's a datetime, extract the date part
|
|
81
94
|
exam_date = obj.sensitive_meta.examination_date
|
|
82
|
-
return exam_date.date() if hasattr(exam_date,
|
|
95
|
+
return exam_date.date() if hasattr(exam_date, "date") else exam_date
|
|
83
96
|
return None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# endoreg_db/serializers/video/video_file_list.py
|
|
2
|
-
from typing import Literal
|
|
2
|
+
from typing import Literal
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
from rest_framework import serializers
|
|
@@ -38,7 +38,9 @@ class VideoFileListSerializer(serializers.ModelSerializer):
|
|
|
38
38
|
"""
|
|
39
39
|
try:
|
|
40
40
|
return getattr(obj, "state", None)
|
|
41
|
-
except
|
|
41
|
+
except (
|
|
42
|
+
Exception
|
|
43
|
+
) as exc: # pragma: no cover - type of error is DB/backend-specific
|
|
42
44
|
logger.warning(
|
|
43
45
|
"VideoFileListSerializer: unable to access state for VideoFile(id=%s): %s",
|
|
44
46
|
getattr(obj, "id", "unknown"),
|
|
@@ -4,6 +4,7 @@ Video Processing History Serializer
|
|
|
4
4
|
Serializes VideoProcessingHistory model for API responses.
|
|
5
5
|
Created as part of Phase 1.1: Video Correction API Endpoints.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from collections.abc import Mapping
|
|
8
9
|
|
|
9
10
|
from rest_framework import serializers
|
|
@@ -14,60 +15,61 @@ from endoreg_db.models import VideoProcessingHistory
|
|
|
14
15
|
class VideoProcessingHistorySerializer(serializers.ModelSerializer):
|
|
15
16
|
"""
|
|
16
17
|
Serializer for VideoProcessingHistory model.
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
Provides operation audit trail (masking, frame removal, analysis)
|
|
19
20
|
with download URLs for processed files.
|
|
20
21
|
"""
|
|
22
|
+
|
|
21
23
|
download_url = serializers.SerializerMethodField()
|
|
22
24
|
operation_display = serializers.SerializerMethodField()
|
|
23
25
|
status_display = serializers.SerializerMethodField()
|
|
24
26
|
duration = serializers.ReadOnlyField()
|
|
25
27
|
is_complete = serializers.ReadOnlyField()
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
class Meta:
|
|
28
30
|
model = VideoProcessingHistory
|
|
29
31
|
fields = [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
"id",
|
|
33
|
+
"video",
|
|
34
|
+
"operation",
|
|
35
|
+
"operation_display",
|
|
36
|
+
"status",
|
|
37
|
+
"status_display",
|
|
38
|
+
"config",
|
|
39
|
+
"output_file",
|
|
40
|
+
"download_url",
|
|
41
|
+
"details",
|
|
42
|
+
"task_id",
|
|
43
|
+
"created_at",
|
|
44
|
+
"completed_at",
|
|
45
|
+
"duration",
|
|
46
|
+
"is_complete",
|
|
45
47
|
]
|
|
46
|
-
read_only_fields = [
|
|
47
|
-
|
|
48
|
+
read_only_fields = ["id", "created_at", "completed_at"]
|
|
49
|
+
|
|
48
50
|
def get_download_url(self, obj) -> str | None:
|
|
49
51
|
"""
|
|
50
52
|
Generate download URL for processed video file.
|
|
51
|
-
|
|
53
|
+
|
|
52
54
|
Args:
|
|
53
55
|
obj: VideoProcessingHistory instance
|
|
54
|
-
|
|
56
|
+
|
|
55
57
|
Returns:
|
|
56
58
|
str: URL to download processed file, or None if not available
|
|
57
59
|
"""
|
|
58
60
|
if not obj.output_file or obj.status != VideoProcessingHistory.STATUS_SUCCESS:
|
|
59
61
|
return None
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
# Build URL to download endpoint (to be implemented)
|
|
62
64
|
# Format: /api/media/processed-videos/{video_id}/{history_id}/
|
|
63
65
|
context = self.context if isinstance(self.context, Mapping) else None
|
|
64
|
-
request = context.get(
|
|
66
|
+
request = context.get("request") if context else None
|
|
65
67
|
if request:
|
|
66
68
|
return request.build_absolute_uri(
|
|
67
|
-
f
|
|
69
|
+
f"/api/media/processed-videos/{obj.video.id}/{obj.id}/"
|
|
68
70
|
)
|
|
69
|
-
|
|
70
|
-
return f
|
|
71
|
+
|
|
72
|
+
return f"/api/media/processed-videos/{obj.video.id}/{obj.id}/"
|
|
71
73
|
|
|
72
74
|
def get_operation_display(self, obj) -> str:
|
|
73
75
|
display = getattr(obj, "get_operation_display", None)
|
|
@@ -78,37 +80,39 @@ class VideoProcessingHistorySerializer(serializers.ModelSerializer):
|
|
|
78
80
|
display = getattr(obj, "get_status_display", None)
|
|
79
81
|
result = display() if callable(display) else obj.status
|
|
80
82
|
return str(result)
|
|
81
|
-
|
|
83
|
+
|
|
82
84
|
def validate_operation(self, value):
|
|
83
85
|
"""
|
|
84
86
|
Validate operation is one of the defined choices.
|
|
85
|
-
|
|
87
|
+
|
|
86
88
|
Args:
|
|
87
89
|
value: Operation type
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
Returns:
|
|
90
92
|
str: Validated operation
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
Raises:
|
|
93
95
|
ValidationError: If operation is invalid
|
|
94
96
|
"""
|
|
95
|
-
valid_operations = [
|
|
97
|
+
valid_operations = [
|
|
98
|
+
choice[0] for choice in VideoProcessingHistory.OPERATION_CHOICES
|
|
99
|
+
]
|
|
96
100
|
if value not in valid_operations:
|
|
97
101
|
raise serializers.ValidationError(
|
|
98
102
|
f"Invalid operation. Must be one of: {', '.join(valid_operations)}"
|
|
99
103
|
)
|
|
100
104
|
return value
|
|
101
|
-
|
|
105
|
+
|
|
102
106
|
def validate_status(self, value):
|
|
103
107
|
"""
|
|
104
108
|
Validate status is one of the defined choices.
|
|
105
|
-
|
|
109
|
+
|
|
106
110
|
Args:
|
|
107
111
|
value: Status type
|
|
108
|
-
|
|
112
|
+
|
|
109
113
|
Returns:
|
|
110
114
|
str: Validated status
|
|
111
|
-
|
|
115
|
+
|
|
112
116
|
Raises:
|
|
113
117
|
ValidationError: If status is invalid
|
|
114
118
|
"""
|
|
@@ -118,17 +122,17 @@ class VideoProcessingHistorySerializer(serializers.ModelSerializer):
|
|
|
118
122
|
f"Invalid status. Must be one of: {', '.join(valid_statuses)}"
|
|
119
123
|
)
|
|
120
124
|
return value
|
|
121
|
-
|
|
125
|
+
|
|
122
126
|
def validate_config(self, value):
|
|
123
127
|
"""
|
|
124
128
|
Validate config based on operation type.
|
|
125
|
-
|
|
129
|
+
|
|
126
130
|
Args:
|
|
127
131
|
value: Config dictionary
|
|
128
|
-
|
|
132
|
+
|
|
129
133
|
Returns:
|
|
130
134
|
dict: Validated config
|
|
131
|
-
|
|
135
|
+
|
|
132
136
|
Raises:
|
|
133
137
|
ValidationError: If config is invalid for operation
|
|
134
138
|
"""
|
|
@@ -136,33 +140,33 @@ class VideoProcessingHistorySerializer(serializers.ModelSerializer):
|
|
|
136
140
|
raise serializers.ValidationError("config must be a dictionary")
|
|
137
141
|
|
|
138
142
|
initial = self.initial_data if isinstance(self.initial_data, Mapping) else {}
|
|
139
|
-
operation = initial.get(
|
|
140
|
-
|
|
143
|
+
operation = initial.get("operation")
|
|
144
|
+
|
|
141
145
|
# Validate masking config
|
|
142
146
|
if operation == VideoProcessingHistory.OPERATION_MASKING:
|
|
143
|
-
required_fields = [
|
|
144
|
-
if
|
|
147
|
+
required_fields = ["mask_type"]
|
|
148
|
+
if "mask_type" not in value:
|
|
145
149
|
raise serializers.ValidationError(
|
|
146
150
|
f"Masking config must include: {', '.join(required_fields)}"
|
|
147
151
|
)
|
|
148
|
-
|
|
152
|
+
|
|
149
153
|
# If device mask, require device_name
|
|
150
|
-
if value[
|
|
154
|
+
if value["mask_type"] == "device" and "device_name" not in value:
|
|
151
155
|
raise serializers.ValidationError(
|
|
152
156
|
"Device mask requires 'device_name' in config"
|
|
153
157
|
)
|
|
154
|
-
|
|
158
|
+
|
|
155
159
|
# If custom ROI, require roi coordinates
|
|
156
|
-
if value[
|
|
160
|
+
if value["mask_type"] == "custom" and "roi" not in value:
|
|
157
161
|
raise serializers.ValidationError(
|
|
158
162
|
"Custom mask requires 'roi' coordinates in config"
|
|
159
163
|
)
|
|
160
|
-
|
|
164
|
+
|
|
161
165
|
# Validate frame removal config
|
|
162
166
|
elif operation == VideoProcessingHistory.OPERATION_FRAME_REMOVAL:
|
|
163
|
-
if
|
|
167
|
+
if "frame_list" not in value and "detection_method" not in value:
|
|
164
168
|
raise serializers.ValidationError(
|
|
165
169
|
"Frame removal config must include 'frame_list' (manual) or 'detection_method' (automatic)"
|
|
166
170
|
)
|
|
167
|
-
|
|
171
|
+
|
|
168
172
|
return value
|
endoreg_db/services/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
|
6
6
|
from django.db import transaction
|
|
7
7
|
|
|
8
8
|
from endoreg_db.models import RawPdfFile, VideoFile
|
|
9
|
-
from endoreg_db.services.
|
|
9
|
+
from endoreg_db.services.video_import import VideoImportService
|
|
10
10
|
from endoreg_db.services.report_import import ReportImportService
|
|
11
11
|
from endoreg_db.utils.paths import STORAGE_DIR
|
|
12
12
|
from endoreg_db.utils.storage import ensure_local_file, file_exists
|
|
@@ -52,7 +52,7 @@ class AnonymizationService:
|
|
|
52
52
|
if vf.state
|
|
53
53
|
else "not_started",
|
|
54
54
|
"fileExists": file_exists(vf.raw_file),
|
|
55
|
-
"uuid": str(vf.
|
|
55
|
+
"uuid": str(vf.video_hash) if vf.video_hash else None,
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
pdf = (
|