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,367 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Management command to import a video file and automatically run NICE/PARIS classifications.
|
|
3
|
-
This command extends the basic import_video functionality with automatic classification.
|
|
4
|
-
"""
|
|
5
|
-
from django.core.management import BaseCommand, call_command
|
|
6
|
-
from django.db import connection
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from endoreg_db.models import VideoFile, ModelMeta
|
|
9
|
-
from endoreg_db.models.administration.center import Center
|
|
10
|
-
from endoreg_db.models.medical.hardware import EndoscopyProcessor
|
|
11
|
-
|
|
12
|
-
# TODO Migrate
|
|
13
|
-
from endoreg_db.serializers.Frames_NICE_and_PARIS_classifications import (
|
|
14
|
-
ForNiceClassificationSerializer,
|
|
15
|
-
ForParisClassificationSerializer
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
from ...helpers.default_objects import (
|
|
19
|
-
get_latest_segmentation_model
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
from endoreg_db.utils.video.ffmpeg_wrapper import check_ffmpeg_availability
|
|
23
|
-
|
|
24
|
-
from endoreg_db.helpers.data_loader import (
|
|
25
|
-
load_disease_data,
|
|
26
|
-
load_gender_data,
|
|
27
|
-
load_event_data,
|
|
28
|
-
load_information_source,
|
|
29
|
-
load_examination_data,
|
|
30
|
-
load_center_data,
|
|
31
|
-
load_endoscope_data,
|
|
32
|
-
load_ai_model_label_data,
|
|
33
|
-
load_ai_model_data,
|
|
34
|
-
load_default_ai_model
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class Command(BaseCommand):
|
|
39
|
-
help = """
|
|
40
|
-
Imports a video file and automatically runs NICE/PARIS classifications.
|
|
41
|
-
|
|
42
|
-
Workflow:
|
|
43
|
-
1. Validates dependencies (FFMPEG, center, processor)
|
|
44
|
-
2. Loads reference data
|
|
45
|
-
3. Imports video using VideoFile.create_from_file_initialized()
|
|
46
|
-
4. Runs VideoFile.pipe_1() for AI segmentation
|
|
47
|
-
5. Automatically runs NICE classification
|
|
48
|
-
6. Automatically runs PARIS classification (if frame_dir available)
|
|
49
|
-
7. Reports results of all steps
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def add_arguments(self, parser):
|
|
53
|
-
parser.add_argument(
|
|
54
|
-
"--verbose",
|
|
55
|
-
action="store_true",
|
|
56
|
-
help="Display verbose output",
|
|
57
|
-
)
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
"--center_name",
|
|
60
|
-
type=str,
|
|
61
|
-
default="university_hospital_wuerzburg",
|
|
62
|
-
help="Name of the center to associate with video",
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
parser.add_argument(
|
|
66
|
-
"video_file",
|
|
67
|
-
type=Path,
|
|
68
|
-
help="Path to the video file to import",
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
parser.add_argument(
|
|
72
|
-
"--frame_dir_root",
|
|
73
|
-
type=str,
|
|
74
|
-
default="~/test-data/raw_frame_dir",
|
|
75
|
-
help="Path to the frame directory",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
parser.add_argument(
|
|
79
|
-
"--video_dir_root",
|
|
80
|
-
type=str,
|
|
81
|
-
default="~/test-data/raw_video_dir",
|
|
82
|
-
help="Path to the video directory",
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
parser.add_argument(
|
|
86
|
-
"--delete_source",
|
|
87
|
-
action="store_true",
|
|
88
|
-
default=False,
|
|
89
|
-
help="Delete the source video file after importing",
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
parser.add_argument(
|
|
93
|
-
"--save_video_file",
|
|
94
|
-
action="store_true",
|
|
95
|
-
default=True,
|
|
96
|
-
help="Save the video file to the video directory",
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
parser.add_argument(
|
|
100
|
-
"--model_path",
|
|
101
|
-
type=str,
|
|
102
|
-
default="./data/models/colo_segmentation_RegNetX800MF_6.safetensors",
|
|
103
|
-
help="Path to the model file",
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
parser.add_argument(
|
|
107
|
-
"--segmentation",
|
|
108
|
-
action="store_true",
|
|
109
|
-
help="Whether to use segmentation",
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
parser.add_argument(
|
|
113
|
-
"--processor_name",
|
|
114
|
-
type=str,
|
|
115
|
-
default="olympus_cv_1500",
|
|
116
|
-
help="Name of the processor to associate with video",
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
# NEW: Classification options
|
|
120
|
-
parser.add_argument(
|
|
121
|
-
"--skip_classification",
|
|
122
|
-
action="store_true",
|
|
123
|
-
default=False,
|
|
124
|
-
help="Skip automatic NICE/PARIS classification after import",
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
parser.add_argument(
|
|
128
|
-
"--classification_only",
|
|
129
|
-
type=str,
|
|
130
|
-
choices=['nice', 'paris', 'both'],
|
|
131
|
-
default='both',
|
|
132
|
-
help="Which classification types to run (default: both)",
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
def handle(self, *args, **options):
|
|
136
|
-
"""
|
|
137
|
-
Main command handler that orchestrates the complete workflow.
|
|
138
|
-
"""
|
|
139
|
-
# Check FFMPEG availability
|
|
140
|
-
try:
|
|
141
|
-
check_ffmpeg_availability()
|
|
142
|
-
self.stdout.write(self.style.SUCCESS("✓ FFMPEG is available"))
|
|
143
|
-
except FileNotFoundError as e:
|
|
144
|
-
self.stderr.write(self.style.ERROR(f"✗ FFMPEG not found: {str(e)}"))
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
self.stdout.write(f"Database: {connection.alias}")
|
|
148
|
-
self.stdout.write(self.style.SUCCESS("=== Starting Video Import and Classification Workflow ==="))
|
|
149
|
-
|
|
150
|
-
# Load reference data
|
|
151
|
-
self._load_reference_data()
|
|
152
|
-
|
|
153
|
-
# Parse options
|
|
154
|
-
segmentation = options["segmentation"]
|
|
155
|
-
skip_classification = options["skip_classification"]
|
|
156
|
-
classification_only = options["classification_only"]
|
|
157
|
-
|
|
158
|
-
# Get AI model if segmentation is enabled
|
|
159
|
-
self.ai_model_meta = None
|
|
160
|
-
if segmentation:
|
|
161
|
-
load_ai_model_label_data()
|
|
162
|
-
load_ai_model_data()
|
|
163
|
-
load_default_ai_model()
|
|
164
|
-
try:
|
|
165
|
-
self.ai_model_meta = get_latest_segmentation_model()
|
|
166
|
-
except ModelMeta.DoesNotExist as exc:
|
|
167
|
-
raise AssertionError("No ModelMeta found for the latest default segmentation AiModel") from exc
|
|
168
|
-
|
|
169
|
-
# Validate input parameters
|
|
170
|
-
video_file = Path(options["video_file"]).expanduser()
|
|
171
|
-
if not video_file.exists():
|
|
172
|
-
self.stdout.write(self.style.ERROR(f"✗ Video file not found: {video_file}"))
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
# Validate center and processor
|
|
176
|
-
center, processor = self._validate_center_and_processor(options)
|
|
177
|
-
if not center or not processor:
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
# Step 1: Import Video
|
|
181
|
-
self.stdout.write(self.style.SUCCESS("\n=== Step 1: Importing Video ==="))
|
|
182
|
-
video_file_obj = self._import_video(options, video_file)
|
|
183
|
-
if not video_file_obj:
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
# Step 2: Run Pipeline 1 (AI Segmentation)
|
|
187
|
-
self.stdout.write(self.style.SUCCESS("\n=== Step 2: Running AI Segmentation (Pipeline 1) ==="))
|
|
188
|
-
pipeline_success = self._run_pipeline_1(video_file_obj)
|
|
189
|
-
if not pipeline_success:
|
|
190
|
-
self.stdout.write(self.style.WARNING("⚠ Pipeline 1 failed, but continuing with classification..."))
|
|
191
|
-
|
|
192
|
-
# Step 3: Run Classifications (if not skipped)
|
|
193
|
-
if not skip_classification:
|
|
194
|
-
self.stdout.write(self.style.SUCCESS("\n=== Step 3: Running Polyp Classifications ==="))
|
|
195
|
-
self._run_classifications(video_file_obj, classification_only)
|
|
196
|
-
else:
|
|
197
|
-
self.stdout.write(self.style.WARNING("⚠ Skipping classifications as requested"))
|
|
198
|
-
|
|
199
|
-
self.stdout.write(self.style.SUCCESS("\n=== Workflow Complete ==="))
|
|
200
|
-
self.stdout.write(f"✓ Video imported with ID: {video_file_obj.id}")
|
|
201
|
-
self.stdout.write(f"✓ Video available at: {video_file_obj.processed_file}")
|
|
202
|
-
|
|
203
|
-
def _load_reference_data(self):
|
|
204
|
-
"""Load all necessary reference data."""
|
|
205
|
-
self.stdout.write("Loading reference data...")
|
|
206
|
-
load_gender_data()
|
|
207
|
-
load_disease_data()
|
|
208
|
-
load_event_data()
|
|
209
|
-
load_information_source()
|
|
210
|
-
load_examination_data()
|
|
211
|
-
load_center_data()
|
|
212
|
-
load_endoscope_data()
|
|
213
|
-
self.stdout.write(self.style.SUCCESS("✓ Reference data loaded"))
|
|
214
|
-
|
|
215
|
-
def _validate_center_and_processor(self, options):
|
|
216
|
-
"""Validate that center and processor exist."""
|
|
217
|
-
center_name = options["center_name"]
|
|
218
|
-
processor_name = options["processor_name"]
|
|
219
|
-
|
|
220
|
-
# Validate center
|
|
221
|
-
try:
|
|
222
|
-
center = Center.objects.get(name=center_name)
|
|
223
|
-
self.stdout.write(self.style.SUCCESS(f"✓ Using center: {center.name_en}"))
|
|
224
|
-
except Center.DoesNotExist:
|
|
225
|
-
self.stdout.write(self.style.ERROR(f"✗ Center not found: {center_name}"))
|
|
226
|
-
return None, None
|
|
227
|
-
|
|
228
|
-
# Validate processor
|
|
229
|
-
try:
|
|
230
|
-
processors_qs = EndoscopyProcessor.objects.filter(centers=center)
|
|
231
|
-
proc_count = processors_qs.count()
|
|
232
|
-
|
|
233
|
-
if proc_count == 0:
|
|
234
|
-
processor = EndoscopyProcessor.objects.filter(name=processor_name).first()
|
|
235
|
-
if processor is None:
|
|
236
|
-
self.stderr.write(self.style.ERROR(f"✗ No processors found for center or fallback"))
|
|
237
|
-
return None, None
|
|
238
|
-
processor.centers.add(center)
|
|
239
|
-
self.stdout.write(self.style.WARNING(f"⚠ Linked fallback processor '{processor.name}' to center"))
|
|
240
|
-
elif proc_count == 1:
|
|
241
|
-
processor = processors_qs.first()
|
|
242
|
-
else:
|
|
243
|
-
processor = self._choose_processor_interactively(processors_qs)
|
|
244
|
-
|
|
245
|
-
self.stdout.write(self.style.SUCCESS(f"✓ Using processor: {processor.name}"))
|
|
246
|
-
return center, processor
|
|
247
|
-
|
|
248
|
-
except EndoscopyProcessor.DoesNotExist:
|
|
249
|
-
self.stdout.write(self.style.ERROR(f"✗ Processor not found: {processor_name}"))
|
|
250
|
-
return None, None
|
|
251
|
-
|
|
252
|
-
def _import_video(self, options, video_file):
|
|
253
|
-
"""Import the video file into the database."""
|
|
254
|
-
try:
|
|
255
|
-
video_file_obj = VideoFile.create_from_file_initialized(
|
|
256
|
-
file_path=video_file,
|
|
257
|
-
center_name=options["center_name"],
|
|
258
|
-
processor_name=options["processor_name"],
|
|
259
|
-
delete_source=options["delete_source"],
|
|
260
|
-
save_video_file=options["save_video_file"],
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
if video_file_obj:
|
|
264
|
-
self.stdout.write(self.style.SUCCESS(f"✓ Video imported successfully (ID: {video_file_obj.id})"))
|
|
265
|
-
return video_file_obj
|
|
266
|
-
else:
|
|
267
|
-
self.stdout.write(self.style.ERROR("✗ Failed to create VideoFile instance"))
|
|
268
|
-
return None
|
|
269
|
-
|
|
270
|
-
except Exception as e:
|
|
271
|
-
self.stdout.write(self.style.ERROR(f"✗ Error importing video: {str(e)}"))
|
|
272
|
-
return None
|
|
273
|
-
|
|
274
|
-
def _run_pipeline_1(self, video_file_obj):
|
|
275
|
-
"""Run the AI segmentation pipeline."""
|
|
276
|
-
try:
|
|
277
|
-
if self.ai_model_meta:
|
|
278
|
-
success = video_file_obj.pipe_1(model_name=self.ai_model_meta.model.name)
|
|
279
|
-
else:
|
|
280
|
-
# Get the default model meta if segmentation is not enabled
|
|
281
|
-
ai_model_meta = ModelMeta.objects.filter(
|
|
282
|
-
model__name="colo_segmentation_RegNetX800MF_6"
|
|
283
|
-
).first()
|
|
284
|
-
|
|
285
|
-
if ai_model_meta:
|
|
286
|
-
success = video_file_obj.pipe_1(model_name=ai_model_meta.model.name)
|
|
287
|
-
else:
|
|
288
|
-
success = video_file_obj.pipe_1(model_name="colo_segmentation_RegNetX800MF_6")
|
|
289
|
-
|
|
290
|
-
if success:
|
|
291
|
-
self.stdout.write(self.style.SUCCESS("✓ Pipeline 1 completed successfully"))
|
|
292
|
-
return True
|
|
293
|
-
else:
|
|
294
|
-
self.stdout.write(self.style.ERROR("✗ Pipeline 1 failed"))
|
|
295
|
-
return False
|
|
296
|
-
|
|
297
|
-
except Exception as e:
|
|
298
|
-
self.stdout.write(self.style.ERROR(f"✗ Pipeline 1 error: {str(e)}"))
|
|
299
|
-
return False
|
|
300
|
-
|
|
301
|
-
def _run_classifications(self, video_file_obj, classification_only):
|
|
302
|
-
"""Run NICE and/or PARIS classifications."""
|
|
303
|
-
video_list = [video_file_obj]
|
|
304
|
-
|
|
305
|
-
# Run NICE Classification
|
|
306
|
-
if classification_only in ['nice', 'both']:
|
|
307
|
-
self.stdout.write("Running NICE classification...")
|
|
308
|
-
try:
|
|
309
|
-
nice_serializer = ForNiceClassificationSerializer()
|
|
310
|
-
nice_results = nice_serializer.to_representation(video_list)
|
|
311
|
-
|
|
312
|
-
if nice_results and isinstance(nice_results, dict) and 'data' in nice_results:
|
|
313
|
-
segments_count = sum(len(item.get('frames', [])) for item in nice_results['data'] if 'frames' in item)
|
|
314
|
-
self.stdout.write(self.style.SUCCESS(f"✓ NICE classification completed ({segments_count} segments found)"))
|
|
315
|
-
else:
|
|
316
|
-
self.stdout.write(self.style.WARNING("⚠ NICE classification completed but no valid segments found"))
|
|
317
|
-
|
|
318
|
-
except Exception as e:
|
|
319
|
-
self.stdout.write(self.style.ERROR(f"✗ NICE classification failed: {str(e)}"))
|
|
320
|
-
|
|
321
|
-
# Run PARIS Classification
|
|
322
|
-
if classification_only in ['paris', 'both']:
|
|
323
|
-
self.stdout.write("Running PARIS classification...")
|
|
324
|
-
try:
|
|
325
|
-
# Check if video has frame_dir (required for PARIS)
|
|
326
|
-
if not getattr(video_file_obj, "frame_dir", None):
|
|
327
|
-
self.stdout.write(self.style.WARNING("⚠ PARIS classification skipped: no frame_dir available"))
|
|
328
|
-
else:
|
|
329
|
-
paris_serializer = ForParisClassificationSerializer()
|
|
330
|
-
paris_results = paris_serializer.to_representation(video_list)
|
|
331
|
-
|
|
332
|
-
if paris_results and isinstance(paris_results, dict) and 'data' in paris_results:
|
|
333
|
-
segments_count = sum(len(item.get('frames', [])) for item in paris_results['data'] if 'frames' in item)
|
|
334
|
-
self.stdout.write(self.style.SUCCESS(f"✓ PARIS classification completed ({segments_count} segments found)"))
|
|
335
|
-
else:
|
|
336
|
-
self.stdout.write(self.style.WARNING("⚠ PARIS classification completed but no valid segments found"))
|
|
337
|
-
|
|
338
|
-
except Exception as e:
|
|
339
|
-
self.stdout.write(self.style.ERROR(f"✗ PARIS classification failed: {str(e)}"))
|
|
340
|
-
|
|
341
|
-
def _choose_processor_interactively(self, processors_qs):
|
|
342
|
-
"""Interactively choose a processor from multiple options."""
|
|
343
|
-
processors = list(processors_qs)
|
|
344
|
-
|
|
345
|
-
self.stdout.write(self.style.ERROR(
|
|
346
|
-
f"\nThe center has {len(processors)} endoscopy processors.\n"
|
|
347
|
-
"Choose one for this import:\n"
|
|
348
|
-
))
|
|
349
|
-
for idx, proc in enumerate(processors, 1):
|
|
350
|
-
self.stdout.write(f" [{idx}] {proc.name}")
|
|
351
|
-
|
|
352
|
-
while True:
|
|
353
|
-
try:
|
|
354
|
-
choice = input("Processor number › ").strip()
|
|
355
|
-
except (EOFError, KeyboardInterrupt):
|
|
356
|
-
self.stderr.write("\nAborted.")
|
|
357
|
-
raise SystemExit(1)
|
|
358
|
-
|
|
359
|
-
try:
|
|
360
|
-
index = int(choice) - 1
|
|
361
|
-
if not 0 <= index < len(processors):
|
|
362
|
-
raise ValueError
|
|
363
|
-
except ValueError:
|
|
364
|
-
self.stderr.write("❌ Please enter a number in the list above.\n")
|
|
365
|
-
continue
|
|
366
|
-
|
|
367
|
-
return processors[index]
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
4
|
-
from django.contrib.contenttypes.models import ContentType
|
|
5
|
-
from django.db import models
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class ProcessingHistory(models.Model):
|
|
9
|
-
"""
|
|
10
|
-
Generic processing history for media files (video, pdf, ...).
|
|
11
|
-
|
|
12
|
-
Stores:
|
|
13
|
-
- which object (VideoFile/RawPdfFile/other) this entry belongs to
|
|
14
|
-
- whether processing/anonymization succeeded
|
|
15
|
-
- timestamps
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
|
19
|
-
success = models.BooleanField(default=False, blank=True)
|
|
20
|
-
|
|
21
|
-
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
|
22
|
-
object_id = models.PositiveBigIntegerField()
|
|
23
|
-
content_object = GenericForeignKey("content_type", "object_id")
|
|
24
|
-
|
|
25
|
-
class Meta:
|
|
26
|
-
ordering = ["-created_at"]
|
|
27
|
-
|
|
28
|
-
def __str__(self) -> str:
|
|
29
|
-
return f"{self.content_type} #{self.object_id}, Success: {self.success}"
|
|
30
|
-
|
|
31
|
-
# --- public helpers that work with *objects* ---
|
|
32
|
-
|
|
33
|
-
@classmethod
|
|
34
|
-
def get_or_create_for_object(
|
|
35
|
-
cls,
|
|
36
|
-
*,
|
|
37
|
-
obj: models.Model,
|
|
38
|
-
success: Optional[bool],
|
|
39
|
-
) -> "ProcessingHistory":
|
|
40
|
-
"""
|
|
41
|
-
Get or create a ProcessingHistory entry for a concrete model instance.
|
|
42
|
-
|
|
43
|
-
- Returns existing entry for (object_id, content_type) if present
|
|
44
|
-
- Otherwise creates one with the given success flag (default False)
|
|
45
|
-
- If an entry exists and `success` is provided, it updates `success`
|
|
46
|
-
"""
|
|
47
|
-
ct = ContentType.objects.get_for_model(obj, for_concrete_model=False)
|
|
48
|
-
|
|
49
|
-
ph, created = cls.objects.get_or_create(
|
|
50
|
-
object_id=obj.pk,
|
|
51
|
-
content_type=ct,
|
|
52
|
-
defaults={"success": success},
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
if not created and success is not None and ph.success != success:
|
|
56
|
-
ph.success = success
|
|
57
|
-
ph.save(update_fields=["success"])
|
|
58
|
-
|
|
59
|
-
return ph
|
|
60
|
-
|
|
61
|
-
@classmethod
|
|
62
|
-
def has_history(
|
|
63
|
-
cls,
|
|
64
|
-
*,
|
|
65
|
-
object_id: int,
|
|
66
|
-
content_type: ContentType,
|
|
67
|
-
success: Optional[bool] = None,
|
|
68
|
-
) -> bool:
|
|
69
|
-
"""
|
|
70
|
-
Return True if there is at least one ProcessingHistory entry for the
|
|
71
|
-
given (object_id, content_type) combination.
|
|
72
|
-
|
|
73
|
-
If `success` is not None, only entries matching that success flag are
|
|
74
|
-
considered.
|
|
75
|
-
"""
|
|
76
|
-
qs = cls.objects.filter(object_id=object_id, content_type=content_type)
|
|
77
|
-
if success is not None:
|
|
78
|
-
qs = qs.filter(success=success)
|
|
79
|
-
return qs.exists()
|
|
80
|
-
|
|
81
|
-
@classmethod
|
|
82
|
-
def has_history_for_object(
|
|
83
|
-
cls,
|
|
84
|
-
*,
|
|
85
|
-
obj: models.Model,
|
|
86
|
-
success: Optional[bool] = None,
|
|
87
|
-
) -> bool:
|
|
88
|
-
"""
|
|
89
|
-
Convenience wrapper for model instances.
|
|
90
|
-
"""
|
|
91
|
-
ct = ContentType.objects.get_for_model(obj, for_concrete_model=False)
|
|
92
|
-
return cls.has_history(
|
|
93
|
-
object_id=obj.pk,
|
|
94
|
-
content_type=ct,
|
|
95
|
-
success=success,
|
|
96
|
-
)
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
from django.core.exceptions import ObjectDoesNotExist
|
|
3
|
-
from rest_framework import serializers
|
|
4
|
-
from endoreg_db.models import (
|
|
5
|
-
Label,
|
|
6
|
-
VideoFile,
|
|
7
|
-
LabelVideoSegment,
|
|
8
|
-
InformationSource
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
import logging
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from endoreg_db.serializers import LabelVideoSegmentSerializer
|
|
16
|
-
|
|
17
|
-
def _get_video_file(self, video_id):
|
|
18
|
-
"""
|
|
19
|
-
Retrieve a VideoFile instance by its ID.
|
|
20
|
-
|
|
21
|
-
Raises a serializers.ValidationError if the VideoFile does not exist.
|
|
22
|
-
"""
|
|
23
|
-
try:
|
|
24
|
-
return VideoFile.objects.get(id=video_id)
|
|
25
|
-
except ObjectDoesNotExist as exc:
|
|
26
|
-
raise serializers.ValidationError(f"VideoFile with id {video_id} does not exist") from exc
|
|
27
|
-
|
|
28
|
-
def _get_label(self, label_id, label_name):
|
|
29
|
-
"""
|
|
30
|
-
Retrieve a Label instance by ID or name, creating it if necessary.
|
|
31
|
-
|
|
32
|
-
Raises a ValidationError if the label cannot be found, created, or if neither identifier is provided.
|
|
33
|
-
"""
|
|
34
|
-
if label_id:
|
|
35
|
-
try:
|
|
36
|
-
return Label.objects.get(id=label_id)
|
|
37
|
-
except ObjectDoesNotExist as exc:
|
|
38
|
-
raise serializers.ValidationError(f"Label with id {label_id} does not exist") from exc
|
|
39
|
-
elif label_name:
|
|
40
|
-
label, _ = Label.get_or_create_from_name(label_name)
|
|
41
|
-
if not label:
|
|
42
|
-
raise serializers.ValidationError(f"Failed to create or retrieve label with name {label_name}")
|
|
43
|
-
return label
|
|
44
|
-
else:
|
|
45
|
-
raise serializers.ValidationError("Either label_id or label_name must be provided")
|
|
46
|
-
|
|
47
|
-
def _validate_fps(self, video_file):
|
|
48
|
-
"""
|
|
49
|
-
Validate and return the frames per second (FPS) value from a video file.
|
|
50
|
-
|
|
51
|
-
Raises a validation error if the FPS is missing, not a positive number, or cannot be converted to a float.
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
float: The validated FPS value.
|
|
55
|
-
"""
|
|
56
|
-
fps_raw = video_file.get_fps()
|
|
57
|
-
if fps_raw is None:
|
|
58
|
-
raise serializers.ValidationError("Video file must have a defined FPS")
|
|
59
|
-
try:
|
|
60
|
-
fps = float(fps_raw)
|
|
61
|
-
if fps <= 0:
|
|
62
|
-
raise ValueError("FPS must be a positive number")
|
|
63
|
-
except (ValueError, TypeError):
|
|
64
|
-
raise serializers.ValidationError("Invalid FPS format in video file")
|
|
65
|
-
return fps
|
|
66
|
-
|
|
67
|
-
def _calculate_frame_numbers(self, validated_data, fps):
|
|
68
|
-
"""
|
|
69
|
-
Convert start and end times in the input data to corresponding frame numbers using the provided FPS value.
|
|
70
|
-
|
|
71
|
-
Parameters:
|
|
72
|
-
validated_data (dict): Input data containing 'start_time' and 'end_time' keys.
|
|
73
|
-
fps (float): Frames per second of the video.
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
dict: The input data dictionary updated with 'start_frame_number' and 'end_frame_number' keys.
|
|
77
|
-
|
|
78
|
-
Raises:
|
|
79
|
-
serializers.ValidationError: If frame numbers cannot be determined from the provided data.
|
|
80
|
-
"""
|
|
81
|
-
if validated_data.get('start_time') is not None:
|
|
82
|
-
validated_data['start_frame_number'] = int(float(validated_data['start_time']) * fps)
|
|
83
|
-
if validated_data.get('end_time') is not None:
|
|
84
|
-
validated_data['end_frame_number'] = int(float(validated_data['end_time']) * fps)
|
|
85
|
-
if 'start_frame_number' not in validated_data or 'end_frame_number' not in validated_data:
|
|
86
|
-
raise serializers.ValidationError("Could not determine frame numbers from provided data")
|
|
87
|
-
return validated_data
|
|
88
|
-
|
|
89
|
-
def _get_information_source(self):
|
|
90
|
-
"""
|
|
91
|
-
Retrieve or create the information source for manual annotation.
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
InformationSource: The source object representing manual annotation.
|
|
95
|
-
"""
|
|
96
|
-
source, _ = InformationSource.objects.get_or_create(
|
|
97
|
-
name='Manual Annotation',
|
|
98
|
-
defaults={
|
|
99
|
-
'description': 'Manually created label segments via web interface',
|
|
100
|
-
}
|
|
101
|
-
)
|
|
102
|
-
return source
|
|
103
|
-
|
|
104
|
-
def _create(self:"LabelVideoSegmentSerializer", validated_data):
|
|
105
|
-
"""
|
|
106
|
-
Create and persist a new LabelVideoSegment instance from validated input data.
|
|
107
|
-
|
|
108
|
-
This method extracts relevant fields from the input, retrieves or creates related objects (video file, label, information source), validates video FPS, calculates frame numbers from time values, and creates a new LabelVideoSegment. Raises a validation error if required data is missing or invalid, or if creation fails.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
LabelVideoSegment: The newly created and saved segment instance.
|
|
112
|
-
"""
|
|
113
|
-
try:
|
|
114
|
-
# Extract convenience fields
|
|
115
|
-
video_id = validated_data.pop('video_id')
|
|
116
|
-
label_id = validated_data.pop('label_id', None)
|
|
117
|
-
label_name = validated_data.pop('label_name', None)
|
|
118
|
-
start_time = validated_data.pop('start_time', None)
|
|
119
|
-
end_time = validated_data.pop('end_time', None)
|
|
120
|
-
|
|
121
|
-
video_file = self.get_video_file(video_id)
|
|
122
|
-
label = self.get_label(label_id, label_name)
|
|
123
|
-
fps = self.validate_fps(video_file)
|
|
124
|
-
validated_data['start_time'] = start_time
|
|
125
|
-
validated_data['end_time'] = end_time
|
|
126
|
-
validated_data = self.calculate_frame_numbers(validated_data, fps)
|
|
127
|
-
source = self.get_information_source()
|
|
128
|
-
|
|
129
|
-
# Create the segment directly
|
|
130
|
-
segment = LabelVideoSegment.safe_create(
|
|
131
|
-
video_file=video_file,
|
|
132
|
-
label=label,
|
|
133
|
-
source=source,
|
|
134
|
-
start_frame_number=validated_data['start_frame_number'],
|
|
135
|
-
end_frame_number=validated_data['end_frame_number'],
|
|
136
|
-
prediction_meta=None # No prediction meta for manual annotations
|
|
137
|
-
)
|
|
138
|
-
segment.save()
|
|
139
|
-
|
|
140
|
-
logger.info("Created new video segment: %s for video %s with label %s",
|
|
141
|
-
segment.pk, video_id, label.name if label else 'None')
|
|
142
|
-
return segment
|
|
143
|
-
|
|
144
|
-
except serializers.ValidationError as e:
|
|
145
|
-
logger.error("Validation error while creating video segment: %s", str(e))
|
|
146
|
-
raise
|
|
147
|
-
except Exception as e:
|
|
148
|
-
logger.error("Unexpected error while creating video segment: %s", str(e))
|
|
149
|
-
raise serializers.ValidationError(f"Failed to create segment: {str(e)}")
|