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,43 +1,32 @@
|
|
|
1
1
|
from rest_framework import serializers
|
|
2
|
-
from typing import List
|
|
3
2
|
from django.core.exceptions import ObjectDoesNotExist
|
|
4
3
|
from django.conf import settings
|
|
5
4
|
from urllib.parse import urljoin
|
|
6
5
|
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from ...models import LabelVideoSegment, VideoFile
|
|
6
|
+
from typing import Any, Literal, Dict
|
|
9
7
|
import logging
|
|
10
|
-
from .
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
_update,
|
|
8
|
+
from django.db.models import Prefetch
|
|
9
|
+
from django.db import models
|
|
10
|
+
|
|
11
|
+
from endoreg_db.models import (
|
|
12
|
+
LabelVideoSegment,
|
|
13
|
+
VideoFile,
|
|
14
|
+
Label,
|
|
15
|
+
InformationSource,
|
|
16
|
+
ImageClassificationAnnotation,
|
|
20
17
|
)
|
|
21
|
-
from .
|
|
22
|
-
|
|
23
|
-
_extract_and_validate_basic_attrs,
|
|
24
|
-
_process_time_data,
|
|
25
|
-
_process_frame_data,
|
|
26
|
-
_validate_time_and_frame_presence,
|
|
27
|
-
_validate_time_constraints,
|
|
28
|
-
_validate_frame_constraints
|
|
18
|
+
from endoreg_db.serializers.label_video_segment.image_classification_annotation import (
|
|
19
|
+
ImageClassificationAnnotationSerializer,
|
|
29
20
|
)
|
|
30
|
-
from endoreg_db.serializers import ImageClassificationAnnotationSerializer
|
|
31
21
|
|
|
32
22
|
logger = logging.getLogger(__name__)
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
|
|
25
|
+
# --- Helper Functions ---
|
|
26
|
+
|
|
27
|
+
|
|
35
28
|
def _media_relpath_from_file_path(file_path) -> str:
|
|
36
|
-
"""
|
|
37
|
-
Return a media-relative path (never an absolute server path).
|
|
38
|
-
If MEDIA_ROOT is a prefix, strip it; otherwise return the basename.
|
|
39
|
-
Accepts str or Path-like.
|
|
40
|
-
"""
|
|
29
|
+
"""Return a media-relative path (never an absolute server path)."""
|
|
41
30
|
p = Path(str(file_path))
|
|
42
31
|
media_root = getattr(settings, "MEDIA_ROOT", None)
|
|
43
32
|
if media_root:
|
|
@@ -48,11 +37,9 @@ def _media_relpath_from_file_path(file_path) -> str:
|
|
|
48
37
|
pass
|
|
49
38
|
return p.name # safe fallback
|
|
50
39
|
|
|
40
|
+
|
|
51
41
|
def _media_url_from_file_path(file_path, request=None) -> str:
|
|
52
|
-
"""
|
|
53
|
-
Build a public URL for the file using MEDIA_URL + relpath.
|
|
54
|
-
If `request` is provided, return an absolute URL.
|
|
55
|
-
"""
|
|
42
|
+
"""Build a public URL for the file using MEDIA_URL + relpath."""
|
|
56
43
|
base = getattr(settings, "MEDIA_URL", "/media/")
|
|
57
44
|
if not base.endswith("/"):
|
|
58
45
|
base += "/"
|
|
@@ -67,251 +54,374 @@ def _media_url_from_file_path(file_path, request=None) -> str:
|
|
|
67
54
|
|
|
68
55
|
|
|
69
56
|
class LabelVideoSegmentSerializer(serializers.ModelSerializer):
|
|
70
|
-
"""Serializer for creating and
|
|
71
|
-
|
|
72
|
-
# Additional fields for convenience - matching frontend expectations
|
|
73
|
-
start_time = serializers.SerializerMethodField()
|
|
74
|
-
end_time = serializers.SerializerMethodField()
|
|
75
|
-
|
|
76
|
-
# Input fields (write_only for creation)
|
|
77
|
-
video_id = serializers.IntegerField(required=False, help_text="Video file ID")
|
|
78
|
-
label_id = serializers.IntegerField(required=False, allow_null=True, help_text="Label ID")
|
|
79
|
-
|
|
80
|
-
# Add support for label names (both Label and VideoSegmentationLabel)
|
|
81
|
-
label_name = serializers.CharField(write_only=True, required=False, allow_null=True, help_text="Label name")
|
|
82
|
-
label_display = serializers.SerializerMethodField()
|
|
83
|
-
|
|
84
|
-
# Read-only fields for response
|
|
85
|
-
video_name = serializers.SerializerMethodField(read_only=True)
|
|
86
|
-
frame_predictions = serializers.SerializerMethodField(read_only=True, help_text="Frame predictions for the video segment")
|
|
87
|
-
manual_frame_annotations = serializers.SerializerMethodField(read_only=True, help_text="Manual frame annotations for the video segment")
|
|
88
|
-
|
|
89
|
-
time_segments = serializers.SerializerMethodField(read_only=True, help_text="Time segments for the video segment")
|
|
57
|
+
"""Serializer for creating, retrieving, and updating LabelVideoSegment instances."""
|
|
90
58
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
get_information_source = _get_information_source
|
|
59
|
+
# Write-only fields for Input (Frontend sends seconds)
|
|
60
|
+
start_time = serializers.FloatField(
|
|
61
|
+
write_only=True, required=False, allow_null=True
|
|
62
|
+
)
|
|
63
|
+
end_time = serializers.FloatField(write_only=True, required=False, allow_null=True)
|
|
97
64
|
|
|
98
|
-
|
|
65
|
+
# Input fields
|
|
66
|
+
video_id = serializers.IntegerField(required=False, help_text="Video file ID")
|
|
67
|
+
label_id = serializers.IntegerField(
|
|
68
|
+
required=False, allow_null=True, help_text="Label ID"
|
|
69
|
+
)
|
|
70
|
+
label_name = serializers.CharField(
|
|
71
|
+
write_only=True, required=False, allow_null=True, help_text="Label name"
|
|
72
|
+
)
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
validate_time_constraints = _validate_time_constraints
|
|
106
|
-
validate_frame_constraints = _validate_frame_constraints
|
|
74
|
+
# Read-only fields for Output
|
|
75
|
+
video_name = serializers.SerializerMethodField(read_only=True)
|
|
76
|
+
frame_predictions = serializers.SerializerMethodField(read_only=True)
|
|
77
|
+
manual_frame_annotations = serializers.SerializerMethodField(read_only=True)
|
|
78
|
+
time_segments = serializers.SerializerMethodField(read_only=True)
|
|
107
79
|
|
|
108
80
|
class Meta:
|
|
109
81
|
model = LabelVideoSegment
|
|
110
82
|
fields = [
|
|
111
|
-
|
|
83
|
+
"id",
|
|
112
84
|
"video_file",
|
|
113
|
-
|
|
85
|
+
"video_name",
|
|
114
86
|
"video_id",
|
|
115
87
|
"label",
|
|
116
|
-
|
|
88
|
+
"label_name",
|
|
117
89
|
"label_id",
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"label_display",
|
|
90
|
+
"start_frame_number",
|
|
91
|
+
"end_frame_number",
|
|
92
|
+
"start_time",
|
|
93
|
+
"end_time",
|
|
123
94
|
"frame_predictions",
|
|
124
95
|
"manual_frame_annotations",
|
|
125
|
-
"time_segments"
|
|
96
|
+
"time_segments",
|
|
126
97
|
]
|
|
127
|
-
read_only_fields = [
|
|
98
|
+
read_only_fields = ["id", "video_name"]
|
|
128
99
|
extra_kwargs = {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
100
|
+
"start_frame_number": {"required": False},
|
|
101
|
+
"end_frame_number": {"required": False},
|
|
102
|
+
"video_file": {"required": False},
|
|
103
|
+
"label": {"required": False},
|
|
133
104
|
}
|
|
134
|
-
|
|
135
105
|
|
|
136
|
-
|
|
106
|
+
# --- Internal Helpers ---
|
|
107
|
+
|
|
108
|
+
def _get_video_file(self, video_id) -> VideoFile:
|
|
109
|
+
try:
|
|
110
|
+
return VideoFile.objects.get(id=video_id)
|
|
111
|
+
except ObjectDoesNotExist:
|
|
112
|
+
raise serializers.ValidationError(
|
|
113
|
+
f"VideoFile with id {video_id} does not exist"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _get_label(self, label_id, label_name):
|
|
117
|
+
if label_id:
|
|
118
|
+
try:
|
|
119
|
+
return Label.objects.get(id=label_id)
|
|
120
|
+
except ObjectDoesNotExist:
|
|
121
|
+
raise serializers.ValidationError(
|
|
122
|
+
f"Label with id {label_id} does not exist"
|
|
123
|
+
)
|
|
124
|
+
elif label_name:
|
|
125
|
+
label, _ = Label.get_or_create_from_name(label_name)
|
|
126
|
+
if not label:
|
|
127
|
+
raise serializers.ValidationError(
|
|
128
|
+
f"Failed to create or retrieve label with name {label_name}"
|
|
129
|
+
)
|
|
130
|
+
return label
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def _validate_fps(self, video_file) -> float:
|
|
134
|
+
"""Helper to get valid FPS from video file."""
|
|
135
|
+
fps = video_file.get_fps()
|
|
136
|
+
if not fps or fps <= 0:
|
|
137
|
+
raise serializers.ValidationError(
|
|
138
|
+
"Video file must have a defined, positive FPS to calculate frames."
|
|
139
|
+
)
|
|
140
|
+
return float(fps)
|
|
141
|
+
|
|
142
|
+
def _convert_time_to_frame(self, time_val, fps):
|
|
143
|
+
return int(round(float(time_val) * fps))
|
|
144
|
+
|
|
145
|
+
def _get_information_source(self) -> InformationSource:
|
|
146
|
+
source, _ = InformationSource.objects.get_or_create(
|
|
147
|
+
name="Manual Annotation",
|
|
148
|
+
defaults={
|
|
149
|
+
"description": "Manually created label segments via web interface"
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
return source
|
|
153
|
+
|
|
154
|
+
# --- DRF Overrides ---
|
|
155
|
+
|
|
156
|
+
def to_internal_value(self, data) -> Any:
|
|
157
|
+
"""Normalize input data keys."""
|
|
158
|
+
# Frontend might send video_file or video_id, label or label_id
|
|
159
|
+
if "video_file" in data:
|
|
160
|
+
data["video_id"] = data["video_file"]
|
|
161
|
+
if "label" in data:
|
|
162
|
+
data["label_id"] = data["label"]
|
|
163
|
+
return super().to_internal_value(data)
|
|
164
|
+
|
|
165
|
+
def validate(self, attrs) -> Any:
|
|
166
|
+
"""
|
|
167
|
+
Validate logical consistency:
|
|
168
|
+
1. Ensure we have Video reference.
|
|
169
|
+
2. Ensure we have EITHER (start_time, end_time) OR (start_frame, end_frame).
|
|
170
|
+
3. Ensure Start < End.
|
|
171
|
+
"""
|
|
172
|
+
# 1. Video Check
|
|
173
|
+
video_id = attrs.get("video_id") or self.initial_data.get("video_id")
|
|
174
|
+
if not video_id and not self.instance:
|
|
175
|
+
raise serializers.ValidationError("video_id is required.")
|
|
176
|
+
|
|
177
|
+
# 2. Time vs Frame Check
|
|
178
|
+
start_time = attrs.get("start_time")
|
|
179
|
+
end_time = attrs.get("end_time")
|
|
180
|
+
start_frame = attrs.get("start_frame_number")
|
|
181
|
+
end_frame = attrs.get("end_frame_number")
|
|
182
|
+
|
|
183
|
+
# If updating, fallback to instance values
|
|
184
|
+
if self.instance:
|
|
185
|
+
if start_time is None and "start_time" not in attrs:
|
|
186
|
+
# We don't have time in attrs, but we might have frames
|
|
187
|
+
pass
|
|
188
|
+
if start_frame is None:
|
|
189
|
+
start_frame = self.instance.start_frame_number
|
|
190
|
+
if end_frame is None:
|
|
191
|
+
end_frame = self.instance.end_frame_number
|
|
192
|
+
|
|
193
|
+
has_time = start_time is not None and end_time is not None
|
|
194
|
+
has_frames = start_frame is not None and end_frame is not None
|
|
195
|
+
|
|
196
|
+
if not has_time and not has_frames:
|
|
197
|
+
# If creating, strictly require one set
|
|
198
|
+
if not self.instance:
|
|
199
|
+
raise serializers.ValidationError(
|
|
200
|
+
"Either (start_time, end_time) OR (start_frame_number, end_frame_number) must be provided."
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# 3. Logical Constraints
|
|
204
|
+
if has_time:
|
|
205
|
+
if start_time < 0:
|
|
206
|
+
raise serializers.ValidationError(
|
|
207
|
+
{"start_time": "Must be non-negative."}
|
|
208
|
+
)
|
|
209
|
+
if end_time <= start_time:
|
|
210
|
+
raise serializers.ValidationError(
|
|
211
|
+
{"end_time": "Must be greater than start_time."}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if has_frames:
|
|
215
|
+
if start_frame < 0:
|
|
216
|
+
raise serializers.ValidationError(
|
|
217
|
+
{"start_frame_number": "Must be non-negative."}
|
|
218
|
+
)
|
|
219
|
+
if end_frame <= start_frame:
|
|
220
|
+
raise serializers.ValidationError(
|
|
221
|
+
{"end_frame_number": "Must be greater than start_frame_number."}
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return attrs
|
|
225
|
+
|
|
226
|
+
def create(self, validated_data) -> LabelVideoSegment:
|
|
227
|
+
"""
|
|
228
|
+
Create logic:
|
|
229
|
+
1. Extract ID/Name/Time data.
|
|
230
|
+
2. Resolve Objects (Video, Label).
|
|
231
|
+
3. Convert Time -> Frames.
|
|
232
|
+
4. Save.
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
# Extract basic data
|
|
236
|
+
video_id = validated_data.pop("video_id")
|
|
237
|
+
label_id = validated_data.pop("label_id", None)
|
|
238
|
+
label_name = validated_data.pop("label_name", None)
|
|
239
|
+
|
|
240
|
+
# Extract time data (might be None if frames were passed directly)
|
|
241
|
+
start_time = validated_data.pop("start_time", None)
|
|
242
|
+
end_time = validated_data.pop("end_time", None)
|
|
243
|
+
|
|
244
|
+
# Resolve Objects
|
|
245
|
+
video_file = self._get_video_file(video_id)
|
|
246
|
+
label = self._get_label(label_id, label_name)
|
|
247
|
+
source = self._get_information_source()
|
|
248
|
+
|
|
249
|
+
# Calculate Frames if time is provided
|
|
250
|
+
if start_time is not None and end_time is not None:
|
|
251
|
+
fps = self._validate_fps(video_file)
|
|
252
|
+
validated_data["start_frame_number"] = self._convert_time_to_frame(
|
|
253
|
+
start_time, fps
|
|
254
|
+
)
|
|
255
|
+
validated_data["end_frame_number"] = self._convert_time_to_frame(
|
|
256
|
+
end_time, fps
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Final check to ensure we have frames (in case validation slipped or logic failed)
|
|
260
|
+
if (
|
|
261
|
+
"start_frame_number" not in validated_data
|
|
262
|
+
or "end_frame_number" not in validated_data
|
|
263
|
+
):
|
|
264
|
+
raise serializers.ValidationError(
|
|
265
|
+
"Could not determine frame numbers. Please provide start_time/end_time."
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Create
|
|
269
|
+
segment = LabelVideoSegment.safe_create(
|
|
270
|
+
video_file=video_file,
|
|
271
|
+
label=label,
|
|
272
|
+
source=source,
|
|
273
|
+
start_frame_number=validated_data["start_frame_number"],
|
|
274
|
+
end_frame_number=validated_data["end_frame_number"],
|
|
275
|
+
prediction_meta=None,
|
|
276
|
+
)
|
|
277
|
+
segment.save()
|
|
278
|
+
|
|
279
|
+
logger.info(f"Created segment {segment.pk} for video {video_id}")
|
|
280
|
+
return segment
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
logger.error(f"Error creating segment: {e}")
|
|
284
|
+
raise serializers.ValidationError(str(e))
|
|
285
|
+
|
|
286
|
+
def update(self, instance, validated_data) -> Any:
|
|
137
287
|
"""
|
|
138
|
-
|
|
288
|
+
Update logic:
|
|
289
|
+
1. Check if Video changed (affects FPS).
|
|
290
|
+
2. Check if Label changed.
|
|
291
|
+
3. Check if Time changed -> Recalculate Frames.
|
|
139
292
|
"""
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
293
|
+
try:
|
|
294
|
+
# Pop fields
|
|
295
|
+
video_id = validated_data.pop("video_id", None)
|
|
296
|
+
label_id = validated_data.pop("label_id", None)
|
|
297
|
+
label_id_present = "label_id" in validated_data
|
|
298
|
+
label_name_present = "label_name" in validated_data
|
|
299
|
+
label_name = validated_data.pop("label_name", None)
|
|
300
|
+
start_time = validated_data.pop("start_time", None)
|
|
301
|
+
end_time = validated_data.pop("end_time", None)
|
|
302
|
+
|
|
303
|
+
# 1. Update Video?
|
|
304
|
+
current_video = instance.video_file
|
|
305
|
+
if video_id and current_video.id != video_id:
|
|
306
|
+
current_video = self._get_video_file(video_id)
|
|
307
|
+
instance.video_file = current_video
|
|
308
|
+
|
|
309
|
+
# 2. Update Label?
|
|
310
|
+
if label_id_present or label_name_present:
|
|
311
|
+
if label_id or label_name:
|
|
312
|
+
instance.label = self._get_label(label_id, label_name)
|
|
313
|
+
else:
|
|
314
|
+
instance.label = None
|
|
315
|
+
|
|
316
|
+
# 3. Update Frames (from Time or direct Frames)
|
|
317
|
+
# We need FPS if we are using time
|
|
318
|
+
fps = None
|
|
319
|
+
if start_time is not None or end_time is not None:
|
|
320
|
+
fps = self._validate_fps(current_video)
|
|
321
|
+
|
|
322
|
+
if start_time is not None:
|
|
323
|
+
instance.start_frame_number = self._convert_time_to_frame(
|
|
324
|
+
start_time, fps
|
|
325
|
+
)
|
|
326
|
+
elif "start_frame_number" in validated_data:
|
|
327
|
+
instance.start_frame_number = validated_data["start_frame_number"]
|
|
328
|
+
|
|
329
|
+
if end_time is not None:
|
|
330
|
+
instance.end_frame_number = self._convert_time_to_frame(end_time, fps)
|
|
331
|
+
elif "end_frame_number" in validated_data:
|
|
332
|
+
instance.end_frame_number = validated_data["end_frame_number"]
|
|
333
|
+
|
|
334
|
+
# Final Frame Safety Check
|
|
335
|
+
if instance.start_frame_number >= instance.end_frame_number:
|
|
336
|
+
raise serializers.ValidationError(
|
|
337
|
+
"start_time/frame must be strictly less than end_time/frame"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
instance.save()
|
|
341
|
+
logger.info(f"Updated segment {instance.pk}")
|
|
342
|
+
return instance
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logger.error(f"Error updating segment {instance.pk}: {e}")
|
|
346
|
+
raise serializers.ValidationError(str(e))
|
|
347
|
+
|
|
348
|
+
# --- Read/Representation Methods (Already existed) ---
|
|
349
|
+
|
|
350
|
+
def to_representation(self, instance) -> dict[str, Any]:
|
|
351
|
+
"""Inject calculated seconds and IDs for frontend convenience."""
|
|
352
|
+
data = super().to_representation(instance)
|
|
353
|
+
|
|
354
|
+
video = instance.video_file
|
|
355
|
+
if video:
|
|
356
|
+
data["start_time"] = video.frame_number_to_s(instance.start_frame_number)
|
|
357
|
+
data["end_time"] = video.frame_number_to_s(instance.end_frame_number)
|
|
358
|
+
data["video_id"] = video.id
|
|
359
|
+
|
|
360
|
+
if instance.label:
|
|
361
|
+
data["label_name"] = instance.label.name
|
|
362
|
+
data["label_id"] = instance.label.id
|
|
363
|
+
else:
|
|
364
|
+
data["label_name"] = None
|
|
365
|
+
data["label_id"] = None
|
|
143
366
|
|
|
367
|
+
return data
|
|
144
368
|
|
|
145
369
|
def get_time_segments(self, obj: LabelVideoSegment) -> dict[str, dict]:
|
|
146
|
-
|
|
370
|
+
annotations_prefetch = Prefetch(
|
|
371
|
+
"image_classification_annotations",
|
|
372
|
+
queryset=ImageClassificationAnnotation.objects.select_related("label"),
|
|
373
|
+
)
|
|
374
|
+
assert isinstance(obj, LabelVideoSegment)
|
|
375
|
+
assert isinstance(obj.frames, models.QuerySet)
|
|
376
|
+
frames = obj.frames.prefetch_related(annotations_prefetch)
|
|
147
377
|
time_segments = {
|
|
148
|
-
"segment_id": obj.
|
|
378
|
+
"segment_id": obj.pk,
|
|
149
379
|
"segment_start": obj.start_frame_number,
|
|
150
380
|
"segment_end": obj.end_frame_number,
|
|
151
381
|
"start_time": obj.start_time,
|
|
152
382
|
"end_time": obj.end_time,
|
|
153
|
-
"frames": []
|
|
383
|
+
"frames": [],
|
|
154
384
|
}
|
|
155
385
|
|
|
156
386
|
request = self.context.get("request") if hasattr(self, "context") else None
|
|
157
387
|
|
|
158
388
|
for frame in frames:
|
|
389
|
+
# Optimization: Use annotations if available to avoid N+1 queries
|
|
159
390
|
all_classifications = ImageClassificationAnnotationSerializer(
|
|
160
391
|
frame.image_classification_annotations.all(), many=True
|
|
161
392
|
).data
|
|
162
|
-
predictions = ImageClassificationAnnotationSerializer(frame.predictions, many=True).data
|
|
163
|
-
manual_annotations = ImageClassificationAnnotationSerializer(
|
|
164
|
-
frame.manual_annotations, many=True
|
|
165
|
-
).data if frame.has_manual_annotations else []
|
|
166
393
|
|
|
167
|
-
#
|
|
394
|
+
# Use safe helpers for paths
|
|
168
395
|
rel = _media_relpath_from_file_path(frame.file_path)
|
|
169
396
|
url = _media_url_from_file_path(frame.file_path, request=request)
|
|
170
397
|
|
|
171
398
|
frame_data = {
|
|
172
399
|
"frame_filename": Path(str(frame.file_path)).name,
|
|
173
|
-
"frame_file_path": rel,
|
|
174
|
-
"frame_url": url,
|
|
400
|
+
"frame_file_path": rel,
|
|
401
|
+
"frame_url": url,
|
|
175
402
|
"all_classifications": all_classifications,
|
|
176
|
-
"
|
|
177
|
-
"frame_id": frame.id,
|
|
178
|
-
"manual_annotations": manual_annotations
|
|
403
|
+
"frame_id": frame.pk,
|
|
179
404
|
}
|
|
180
405
|
time_segments["frames"].append(frame_data)
|
|
181
406
|
|
|
182
407
|
return time_segments
|
|
183
408
|
|
|
184
|
-
def get_label_name(self, obj)
|
|
185
|
-
""
|
|
186
|
-
Return the name of the label associated with the segment, or "unknown" if no label is set.
|
|
187
|
-
"""
|
|
188
|
-
if obj.label:
|
|
189
|
-
return obj.label.name
|
|
190
|
-
return "unknown"
|
|
191
|
-
|
|
192
|
-
def get_manual_frame_annotations(self, obj:LabelVideoSegment):
|
|
193
|
-
"""
|
|
194
|
-
Return serialized manual frame annotations for the given video segment.
|
|
195
|
-
|
|
196
|
-
Parameters:
|
|
197
|
-
obj (LabelVideoSegment): The video segment instance whose manual frame annotations are to be serialized.
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
list: A list of serialized manual frame annotation data.
|
|
201
|
-
"""
|
|
202
|
-
return ImageClassificationAnnotationSerializer(obj.manual_frame_annotations, many=True).data
|
|
203
|
-
|
|
204
|
-
def get_frame_predictions(self, obj:LabelVideoSegment) -> List[dict]:
|
|
205
|
-
"""
|
|
206
|
-
Return serialized frame prediction annotations for the given video segment.
|
|
207
|
-
|
|
208
|
-
Parameters:
|
|
209
|
-
obj (LabelVideoSegment): The video segment instance whose frame predictions are to be serialized.
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
List[dict]: A list of serialized frame prediction annotation data.
|
|
213
|
-
"""
|
|
214
|
-
return ImageClassificationAnnotationSerializer(obj.frame_predictions, many=True).data
|
|
215
|
-
|
|
216
|
-
def get_all_annotations(self, obj:LabelVideoSegment):
|
|
217
|
-
"""
|
|
218
|
-
Retrieve all image classification annotations for every frame in the given video segment.
|
|
219
|
-
|
|
220
|
-
Parameters:
|
|
221
|
-
obj (LabelVideoSegment): The video segment instance whose frame annotations are to be retrieved.
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
list: A list of serialized image classification annotations for all frames in the segment.
|
|
225
|
-
"""
|
|
226
|
-
return ImageClassificationAnnotationSerializer(obj.all_frame_annotations, many=True).data
|
|
409
|
+
def get_label_name(self, obj) -> Any | Literal["Unknown"]:
|
|
410
|
+
return obj.label.name if obj.label else "Unknown"
|
|
227
411
|
|
|
412
|
+
def get_manual_frame_annotations(self, obj: LabelVideoSegment) -> Dict[Any, Any]:
|
|
413
|
+
return ImageClassificationAnnotationSerializer(
|
|
414
|
+
obj.manual_frame_annotations, many=True
|
|
415
|
+
).data
|
|
228
416
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"""
|
|
417
|
+
def get_frame_predictions(self, obj: LabelVideoSegment) -> Dict[Any, Any]:
|
|
418
|
+
return ImageClassificationAnnotationSerializer(
|
|
419
|
+
obj.frame_predictions, many=True
|
|
420
|
+
).data
|
|
421
|
+
|
|
422
|
+
def get_video_name(self, obj) -> Any | str:
|
|
236
423
|
try:
|
|
237
424
|
video = obj.video_file
|
|
238
|
-
return getattr(video,
|
|
425
|
+
return getattr(video, "original_file_name", f"Video {video.id}")
|
|
239
426
|
except (AttributeError, ObjectDoesNotExist):
|
|
240
|
-
return
|
|
241
|
-
|
|
242
|
-
def get_start_time(self, obj:LabelVideoSegment):
|
|
243
|
-
"""
|
|
244
|
-
Return the start time of the video segment in seconds.
|
|
245
|
-
"""
|
|
246
|
-
return obj.start_time
|
|
247
|
-
|
|
248
|
-
def get_end_time(self, obj):
|
|
249
|
-
"""
|
|
250
|
-
Return the end time of the video segment in seconds.
|
|
251
|
-
"""
|
|
252
|
-
return obj.end_time
|
|
253
|
-
|
|
254
|
-
def is_valid(self, raise_exception=False):
|
|
255
|
-
"""
|
|
256
|
-
Validates the serializer input data and logs the validation process.
|
|
257
|
-
|
|
258
|
-
Parameters:
|
|
259
|
-
raise_exception (bool): If True, raises a ValidationError on failure.
|
|
260
|
-
|
|
261
|
-
Returns:
|
|
262
|
-
bool: True if the data is valid, False otherwise.
|
|
263
|
-
"""
|
|
264
|
-
logger.debug("Starting validation")
|
|
265
|
-
result = super().is_valid(raise_exception=raise_exception)
|
|
266
|
-
if not result:
|
|
267
|
-
logger.debug(f"Validation errors: {self.errors}")
|
|
268
|
-
return result
|
|
269
|
-
|
|
270
|
-
def to_internal_value(self, data):
|
|
271
|
-
"""
|
|
272
|
-
Normalizes input data by mapping between `video_id`/`label_id` and `video_file`/`label` keys to ensure consistent internal representation for model creation and validation.
|
|
273
|
-
|
|
274
|
-
This allows the serializer to accept either set of keys and internally synchronize them before further processing.
|
|
275
|
-
"""
|
|
276
|
-
#TODO @coderabbitai create an issue for @Hamzaukw
|
|
277
|
-
# we need to ensure consistent naming across serializers
|
|
278
|
-
# Currently we use it in various places, but it should be consistent
|
|
279
|
-
# Either make the usage consistent or create another serializer that handles this
|
|
280
|
-
# Map video_file → video_id and label → label_id
|
|
281
|
-
if 'video_file' in data:
|
|
282
|
-
data['video_id'] = data['video_file']
|
|
283
|
-
if 'label' in data:
|
|
284
|
-
data['label_id'] = data['label']
|
|
285
|
-
# Map video_id → video_file and label_id → label
|
|
286
|
-
if 'video_id' in data:
|
|
287
|
-
data['video_file'] = data['video_id']
|
|
288
|
-
if 'label_id' in data:
|
|
289
|
-
data['label'] = data['label_id']
|
|
290
|
-
return super().to_internal_value(data)
|
|
291
|
-
|
|
292
|
-
def to_representation(self, instance):
|
|
293
|
-
"""
|
|
294
|
-
Return a JSON-compatible representation of a label video segment with calculated start and end times in seconds, label name, and explicit video and label IDs.
|
|
295
|
-
|
|
296
|
-
Raises:
|
|
297
|
-
ValueError: If the segment is not associated with a video file.
|
|
298
|
-
TypeError: If the associated video file is not a VideoFile instance.
|
|
299
|
-
"""
|
|
300
|
-
data = super().to_representation(instance)
|
|
301
|
-
video_file = instance.video_file
|
|
302
|
-
if video_file is None:
|
|
303
|
-
raise ValueError("Video file must be associated with the segment")
|
|
304
|
-
if not isinstance(video_file, VideoFile):
|
|
305
|
-
raise TypeError("Expected video_file to be an instance of VideoFile")
|
|
306
|
-
# Add calculated time fields for frontend compatibility
|
|
307
|
-
data['start_time'] = video_file.frame_number_to_s(instance.start_frame_number)
|
|
308
|
-
data['end_time'] = video_file.frame_number_to_s(instance.end_frame_number)
|
|
309
|
-
# Ensure label_name is always present in response
|
|
310
|
-
if instance.label:
|
|
311
|
-
data['label_name'] = instance.label.name
|
|
312
|
-
else:
|
|
313
|
-
data['label_name'] = None
|
|
314
|
-
# Explicitly add video_id and label_id to the output for frontend convenience
|
|
315
|
-
data['video_id'] = instance.video_file.id if instance.video_file else None
|
|
316
|
-
data['label_id'] = instance.label.id if instance.label else None
|
|
317
|
-
return data
|
|
427
|
+
return "Unknown Video"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
from .sensitive_meta_detail import SensitiveMetaDetailSerializer
|
|
3
2
|
from .sensitive_meta_update import SensitiveMetaUpdateSerializer
|
|
4
3
|
from .sensitive_meta_verification import SensitiveMetaVerificationSerializer
|
|
@@ -11,4 +10,4 @@ __all__ = [
|
|
|
11
10
|
"SensitiveMetaUpdateSerializer",
|
|
12
11
|
"SensitiveMetaVerificationSerializer",
|
|
13
12
|
"VideoMetaSerializer",
|
|
14
|
-
]
|
|
13
|
+
]
|