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
|
@@ -172,7 +172,9 @@ class Command(BaseCommand):
|
|
|
172
172
|
raise CommandError(str(exc)) from exc
|
|
173
173
|
|
|
174
174
|
self.stdout.write(
|
|
175
|
-
self.style.SUCCESS(
|
|
175
|
+
self.style.SUCCESS(
|
|
176
|
+
f"ModelMeta ready: {model_meta.name} (v{model_meta.version}) for {model_meta.model.name}"
|
|
177
|
+
)
|
|
176
178
|
)
|
|
177
179
|
|
|
178
180
|
def _create_from_local_file(self, options: Dict[str, Any]) -> ModelMeta:
|
|
@@ -213,15 +215,21 @@ class Command(BaseCommand):
|
|
|
213
215
|
|
|
214
216
|
meta_name = fields.get("name") or options["model_name"]
|
|
215
217
|
model_name = fields.get("model") or options["model_name"]
|
|
216
|
-
labelset_name =
|
|
217
|
-
|
|
218
|
+
labelset_name = (
|
|
219
|
+
fields.get("labelset") or options["image_classification_labelset_name"]
|
|
220
|
+
)
|
|
221
|
+
labelset_version = fields.get(
|
|
222
|
+
"labelset_version", options.get("image_classification_labelset_version")
|
|
223
|
+
)
|
|
218
224
|
|
|
219
225
|
self._ensure_ai_model_exists(model_name)
|
|
220
226
|
labelset = self._resolve_labelset(labelset_name, labelset_version)
|
|
221
227
|
|
|
222
228
|
requested_version = options.get("model_meta_version") or fields.get("version")
|
|
223
229
|
if not requested_version:
|
|
224
|
-
raise CommandError(
|
|
230
|
+
raise CommandError(
|
|
231
|
+
"Provide --model_meta_version or include a 'version' in the template entry."
|
|
232
|
+
)
|
|
225
233
|
|
|
226
234
|
hf_config = entry.get("setup_config", {}).get("huggingface_fallback", {})
|
|
227
235
|
repo_id = hf_config.get("repo_id")
|
|
@@ -233,7 +241,9 @@ class Command(BaseCommand):
|
|
|
233
241
|
)
|
|
234
242
|
|
|
235
243
|
if not filename.endswith(".safetensors"):
|
|
236
|
-
raise CommandError(
|
|
244
|
+
raise CommandError(
|
|
245
|
+
"Only .safetensors files are supported when downloading from Hugging Face."
|
|
246
|
+
)
|
|
237
247
|
|
|
238
248
|
token = options.get("huggingface_token")
|
|
239
249
|
|
|
@@ -273,7 +283,9 @@ class Command(BaseCommand):
|
|
|
273
283
|
elif template_name:
|
|
274
284
|
resolved = (AI_MODEL_META_DATA_DIR / f"{template_name}.yaml").resolve()
|
|
275
285
|
else: # pragma: no cover - guarded by caller
|
|
276
|
-
raise CommandError(
|
|
286
|
+
raise CommandError(
|
|
287
|
+
"Template mode requires --template_path or --template_name."
|
|
288
|
+
)
|
|
277
289
|
|
|
278
290
|
if not resolved.exists():
|
|
279
291
|
raise CommandError(f"Template file not found: {resolved}")
|
|
@@ -290,16 +302,22 @@ class Command(BaseCommand):
|
|
|
290
302
|
if isinstance(data, list):
|
|
291
303
|
return [entry for entry in data if isinstance(entry, dict)]
|
|
292
304
|
|
|
293
|
-
raise CommandError(
|
|
305
|
+
raise CommandError(
|
|
306
|
+
f"Template {template_path} must define a mapping or list of mappings."
|
|
307
|
+
)
|
|
294
308
|
|
|
295
|
-
def _select_template_entry(
|
|
309
|
+
def _select_template_entry(
|
|
310
|
+
self, entries: Iterable[Dict[str, Any]], options: Dict[str, Any]
|
|
311
|
+
) -> Dict[str, Any]:
|
|
296
312
|
target = options.get("template_entry_name") or options.get("model_name")
|
|
297
313
|
|
|
298
314
|
for entry in entries:
|
|
299
315
|
fields = entry.get("fields", {})
|
|
300
316
|
if not fields:
|
|
301
317
|
continue
|
|
302
|
-
if target and (
|
|
318
|
+
if target and (
|
|
319
|
+
fields.get("name") == target or fields.get("model") == target
|
|
320
|
+
):
|
|
303
321
|
return entry
|
|
304
322
|
|
|
305
323
|
entries = list(entries)
|
|
@@ -325,20 +343,33 @@ class Command(BaseCommand):
|
|
|
325
343
|
}
|
|
326
344
|
)
|
|
327
345
|
|
|
328
|
-
def _collect_template_kwargs(
|
|
346
|
+
def _collect_template_kwargs(
|
|
347
|
+
self, fields: Dict[str, Any], options: Dict[str, Any]
|
|
348
|
+
) -> Dict[str, Any]:
|
|
329
349
|
def numeric(value):
|
|
330
350
|
return int(value) if value is not None else value
|
|
331
351
|
|
|
332
352
|
return self._filter_none(
|
|
333
353
|
{
|
|
334
|
-
"activation": fields.get("activation")
|
|
335
|
-
|
|
336
|
-
"
|
|
337
|
-
|
|
338
|
-
"
|
|
354
|
+
"activation": fields.get("activation")
|
|
355
|
+
or options.get("activation_function_name"),
|
|
356
|
+
"mean": self._normalise_sequence(fields.get("mean"))
|
|
357
|
+
or options.get("mean"),
|
|
358
|
+
"std": self._normalise_sequence(fields.get("std"))
|
|
359
|
+
or options.get("std"),
|
|
360
|
+
"size_x": numeric(fields.get("size_x"))
|
|
361
|
+
if fields.get("size_x") is not None
|
|
362
|
+
else options.get("size_x"),
|
|
363
|
+
"size_y": numeric(fields.get("size_y"))
|
|
364
|
+
if fields.get("size_y") is not None
|
|
365
|
+
else options.get("size_y"),
|
|
339
366
|
"axes": fields.get("axes") or options.get("axes"),
|
|
340
|
-
"batchsize": numeric(fields.get("batchsize"))
|
|
341
|
-
|
|
367
|
+
"batchsize": numeric(fields.get("batchsize"))
|
|
368
|
+
if fields.get("batchsize") is not None
|
|
369
|
+
else options.get("batchsize"),
|
|
370
|
+
"num_workers": numeric(fields.get("num_workers"))
|
|
371
|
+
if fields.get("num_workers") is not None
|
|
372
|
+
else options.get("num_workers"),
|
|
342
373
|
"description": fields.get("description") or options.get("description"),
|
|
343
374
|
}
|
|
344
375
|
)
|
|
@@ -367,7 +398,9 @@ class Command(BaseCommand):
|
|
|
367
398
|
@staticmethod
|
|
368
399
|
def _ensure_ai_model_exists(model_name: str) -> None:
|
|
369
400
|
if not AiModel.objects.filter(name=model_name).exists():
|
|
370
|
-
raise CommandError(
|
|
401
|
+
raise CommandError(
|
|
402
|
+
f"AiModel not found: {model_name}. Load ai model data before running this command."
|
|
403
|
+
)
|
|
371
404
|
|
|
372
405
|
@staticmethod
|
|
373
406
|
def _resolve_labelset(name: str, version: Any) -> LabelSet:
|
|
@@ -379,6 +412,8 @@ class Command(BaseCommand):
|
|
|
379
412
|
labelset = queryset.filter(version=version).first()
|
|
380
413
|
|
|
381
414
|
if not labelset:
|
|
382
|
-
raise CommandError(
|
|
415
|
+
raise CommandError(
|
|
416
|
+
f"LabelSet not found for name='{name}' and version='{version}'."
|
|
417
|
+
)
|
|
383
418
|
|
|
384
419
|
return labelset
|
|
@@ -3,10 +3,12 @@ Management command to fix missing patient data in existing videos.
|
|
|
3
3
|
Fills in default values for videos that have incomplete SensitiveMeta.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
from datetime import date
|
|
7
|
+
|
|
6
8
|
from django.core.management.base import BaseCommand
|
|
7
9
|
from django.db import transaction
|
|
8
|
-
|
|
9
|
-
from endoreg_db.models import
|
|
10
|
+
|
|
11
|
+
from endoreg_db.models import SensitiveMeta, VideoFile
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Command(BaseCommand):
|
|
@@ -20,128 +22,152 @@ class Command(BaseCommand):
|
|
|
20
22
|
|
|
21
23
|
def add_arguments(self, parser):
|
|
22
24
|
parser.add_argument(
|
|
23
|
-
|
|
24
|
-
action=
|
|
25
|
-
help=
|
|
25
|
+
"--dry-run",
|
|
26
|
+
action="store_true",
|
|
27
|
+
help="Show what would be changed without making actual changes",
|
|
26
28
|
)
|
|
27
29
|
parser.add_argument(
|
|
28
|
-
|
|
29
|
-
action=
|
|
30
|
-
help=
|
|
30
|
+
"--verbose",
|
|
31
|
+
action="store_true",
|
|
32
|
+
help="Show detailed output",
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
def handle(self, *args, **options):
|
|
34
|
-
dry_run = options[
|
|
35
|
-
verbose = options[
|
|
36
|
-
|
|
36
|
+
dry_run = options["dry_run"]
|
|
37
|
+
verbose = options["verbose"]
|
|
38
|
+
|
|
37
39
|
self.stdout.write(self.style.SUCCESS("Starting patient data repair..."))
|
|
38
|
-
|
|
40
|
+
|
|
39
41
|
if dry_run:
|
|
40
|
-
self.stdout.write(
|
|
41
|
-
|
|
42
|
+
self.stdout.write(
|
|
43
|
+
self.style.WARNING("DRY RUN MODE - No changes will be made")
|
|
44
|
+
)
|
|
45
|
+
|
|
42
46
|
# Find videos without SensitiveMeta
|
|
43
47
|
videos_without_meta = VideoFile.objects.filter(sensitive_meta__isnull=True)
|
|
44
48
|
count_without_meta = videos_without_meta.count()
|
|
45
|
-
|
|
49
|
+
|
|
46
50
|
# Find videos with incomplete SensitiveMeta
|
|
47
|
-
videos_with_incomplete_meta =
|
|
48
|
-
sensitive_meta__isnull=False
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
videos_with_incomplete_meta = (
|
|
52
|
+
VideoFile.objects.filter(sensitive_meta__isnull=False).filter(
|
|
53
|
+
# At least one of these fields is missing
|
|
54
|
+
sensitive_meta__patient_first_name__isnull=True
|
|
55
|
+
)
|
|
56
|
+
| VideoFile.objects.filter(
|
|
57
|
+
sensitive_meta__isnull=False,
|
|
58
|
+
sensitive_meta__patient_last_name__isnull=True,
|
|
59
|
+
)
|
|
60
|
+
| VideoFile.objects.filter(
|
|
61
|
+
sensitive_meta__isnull=False, sensitive_meta__patient_dob__isnull=True
|
|
62
|
+
)
|
|
63
|
+
| VideoFile.objects.filter(
|
|
64
|
+
sensitive_meta__isnull=False,
|
|
65
|
+
sensitive_meta__examination_date__isnull=True,
|
|
66
|
+
)
|
|
67
|
+
| VideoFile.objects.filter(
|
|
68
|
+
sensitive_meta__isnull=False,
|
|
69
|
+
sensitive_meta__patient_first_name__exact="",
|
|
70
|
+
)
|
|
71
|
+
| VideoFile.objects.filter(
|
|
72
|
+
sensitive_meta__isnull=False,
|
|
73
|
+
sensitive_meta__patient_last_name__exact="",
|
|
74
|
+
)
|
|
67
75
|
)
|
|
68
|
-
|
|
76
|
+
|
|
69
77
|
count_incomplete = videos_with_incomplete_meta.count()
|
|
70
|
-
|
|
78
|
+
|
|
71
79
|
self.stdout.write(f"Found {count_without_meta} videos without SensitiveMeta")
|
|
72
|
-
self.stdout.write(
|
|
73
|
-
|
|
80
|
+
self.stdout.write(
|
|
81
|
+
f"Found {count_incomplete} videos with incomplete SensitiveMeta"
|
|
82
|
+
)
|
|
83
|
+
|
|
74
84
|
if count_without_meta == 0 and count_incomplete == 0:
|
|
75
|
-
self.stdout.write(
|
|
85
|
+
self.stdout.write(
|
|
86
|
+
self.style.SUCCESS(
|
|
87
|
+
"No repairs needed - all videos have complete patient data!"
|
|
88
|
+
)
|
|
89
|
+
)
|
|
76
90
|
return
|
|
77
|
-
|
|
91
|
+
|
|
78
92
|
fixed_count = 0
|
|
79
93
|
created_count = 0
|
|
80
|
-
|
|
94
|
+
|
|
81
95
|
# Process videos without SensitiveMeta
|
|
82
96
|
if count_without_meta > 0:
|
|
83
|
-
self.stdout.write(
|
|
84
|
-
|
|
97
|
+
self.stdout.write(
|
|
98
|
+
f"\nProcessing {count_without_meta} videos without SensitiveMeta..."
|
|
99
|
+
)
|
|
100
|
+
|
|
85
101
|
for video in videos_without_meta:
|
|
86
102
|
if verbose:
|
|
87
|
-
self.stdout.write(
|
|
88
|
-
|
|
103
|
+
self.stdout.write(
|
|
104
|
+
f"Creating SensitiveMeta for video {video.video_hash}"
|
|
105
|
+
)
|
|
106
|
+
|
|
89
107
|
if not dry_run:
|
|
90
108
|
try:
|
|
91
109
|
with transaction.atomic():
|
|
92
110
|
default_data = {
|
|
93
111
|
"patient_first_name": "Patient",
|
|
94
|
-
"patient_last_name": "Unknown",
|
|
112
|
+
"patient_last_name": "Unknown",
|
|
95
113
|
"patient_dob": date(1990, 1, 1),
|
|
96
114
|
"examination_date": date.today(),
|
|
97
|
-
"center_name": video.center.name
|
|
115
|
+
"center_name": video.center.name
|
|
116
|
+
if video.center
|
|
117
|
+
else "university_hospital_wuerzburg",
|
|
98
118
|
}
|
|
99
|
-
|
|
100
|
-
sensitive_meta = SensitiveMeta.create_from_dict(
|
|
119
|
+
|
|
120
|
+
sensitive_meta = SensitiveMeta.create_from_dict(
|
|
121
|
+
default_data
|
|
122
|
+
)
|
|
101
123
|
video.sensitive_meta = sensitive_meta
|
|
102
|
-
video.save(update_fields=[
|
|
124
|
+
video.save(update_fields=["sensitive_meta"])
|
|
103
125
|
created_count += 1
|
|
104
|
-
|
|
126
|
+
|
|
105
127
|
except Exception as e:
|
|
106
128
|
self.stdout.write(
|
|
107
|
-
self.style.ERROR(
|
|
129
|
+
self.style.ERROR(
|
|
130
|
+
f"Failed to create SensitiveMeta for video {video.video_hash}: {e}"
|
|
131
|
+
)
|
|
108
132
|
)
|
|
109
133
|
else:
|
|
110
134
|
created_count += 1
|
|
111
|
-
|
|
135
|
+
|
|
112
136
|
# Process videos with incomplete SensitiveMeta
|
|
113
137
|
if count_incomplete > 0:
|
|
114
|
-
self.stdout.write(
|
|
115
|
-
|
|
138
|
+
self.stdout.write(
|
|
139
|
+
f"\nProcessing {count_incomplete} videos with incomplete SensitiveMeta..."
|
|
140
|
+
)
|
|
141
|
+
|
|
116
142
|
for video in videos_with_incomplete_meta:
|
|
117
143
|
if not video.sensitive_meta:
|
|
118
144
|
continue # Skip if somehow None (already handled above)
|
|
119
|
-
|
|
145
|
+
|
|
120
146
|
update_data = {}
|
|
121
147
|
missing_fields = []
|
|
122
|
-
|
|
148
|
+
|
|
123
149
|
if not video.sensitive_meta.patient_first_name:
|
|
124
150
|
update_data["patient_first_name"] = "Patient"
|
|
125
151
|
missing_fields.append("first_name")
|
|
126
|
-
|
|
152
|
+
|
|
127
153
|
if not video.sensitive_meta.patient_last_name:
|
|
128
154
|
update_data["patient_last_name"] = "Unknown"
|
|
129
155
|
missing_fields.append("last_name")
|
|
130
|
-
|
|
156
|
+
|
|
131
157
|
if not video.sensitive_meta.patient_dob:
|
|
132
158
|
update_data["patient_dob"] = date(1990, 1, 1)
|
|
133
159
|
missing_fields.append("dob")
|
|
134
|
-
|
|
160
|
+
|
|
135
161
|
if not video.sensitive_meta.examination_date:
|
|
136
162
|
update_data["examination_date"] = date.today()
|
|
137
163
|
missing_fields.append("examination_date")
|
|
138
|
-
|
|
164
|
+
|
|
139
165
|
if update_data:
|
|
140
166
|
if verbose:
|
|
141
167
|
self.stdout.write(
|
|
142
|
-
f"Updating video {video.
|
|
168
|
+
f"Updating video {video.video_hash} - missing fields: {', '.join(missing_fields)}"
|
|
143
169
|
)
|
|
144
|
-
|
|
170
|
+
|
|
145
171
|
if not dry_run:
|
|
146
172
|
try:
|
|
147
173
|
with transaction.atomic():
|
|
@@ -149,24 +175,32 @@ class Command(BaseCommand):
|
|
|
149
175
|
fixed_count += 1
|
|
150
176
|
except Exception as e:
|
|
151
177
|
self.stdout.write(
|
|
152
|
-
self.style.ERROR(
|
|
178
|
+
self.style.ERROR(
|
|
179
|
+
f"Failed to update SensitiveMeta for video {video.video_hash}: {e}"
|
|
180
|
+
)
|
|
153
181
|
)
|
|
154
182
|
else:
|
|
155
183
|
fixed_count += 1
|
|
156
|
-
|
|
184
|
+
|
|
157
185
|
# Summary
|
|
158
|
-
self.stdout.write("\n" + "="*50)
|
|
186
|
+
self.stdout.write("\n" + "=" * 50)
|
|
159
187
|
if dry_run:
|
|
160
188
|
self.stdout.write(self.style.SUCCESS("DRY RUN SUMMARY:"))
|
|
161
189
|
self.stdout.write(f"Would create SensitiveMeta for: {created_count} videos")
|
|
162
190
|
self.stdout.write(f"Would update incomplete data for: {fixed_count} videos")
|
|
163
|
-
self.stdout.write(
|
|
191
|
+
self.stdout.write(
|
|
192
|
+
f"Total videos that would be fixed: {created_count + fixed_count}"
|
|
193
|
+
)
|
|
164
194
|
else:
|
|
165
195
|
self.stdout.write(self.style.SUCCESS("REPAIR COMPLETED:"))
|
|
166
196
|
self.stdout.write(f"Created SensitiveMeta for: {created_count} videos")
|
|
167
197
|
self.stdout.write(f"Updated incomplete data for: {fixed_count} videos")
|
|
168
198
|
self.stdout.write(f"Total videos fixed: {created_count + fixed_count}")
|
|
169
|
-
|
|
199
|
+
|
|
170
200
|
if not dry_run and (created_count > 0 or fixed_count > 0):
|
|
171
|
-
self.stdout.write(
|
|
172
|
-
|
|
201
|
+
self.stdout.write(
|
|
202
|
+
self.style.SUCCESS("\n✅ Patient data repair completed successfully!")
|
|
203
|
+
)
|
|
204
|
+
self.stdout.write(
|
|
205
|
+
"All videos now have the minimum required patient data for annotation."
|
|
206
|
+
)
|
|
@@ -1,77 +1,82 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Django management command to fix video file paths in the database.
|
|
3
3
|
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
4
9
|
from django.core.management.base import BaseCommand
|
|
5
10
|
from django.db import transaction
|
|
11
|
+
|
|
6
12
|
from endoreg_db.models import VideoFile
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
import logging
|
|
9
|
-
import os
|
|
10
13
|
|
|
11
14
|
logger = logging.getLogger(__name__)
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class Command(BaseCommand):
|
|
15
|
-
help =
|
|
18
|
+
help = "Fix video file paths in the database to match actual file locations"
|
|
16
19
|
|
|
17
20
|
def add_arguments(self, parser):
|
|
18
21
|
parser.add_argument(
|
|
19
|
-
|
|
22
|
+
"--video-id",
|
|
20
23
|
type=int,
|
|
21
|
-
help=
|
|
24
|
+
help="Fix specific video ID only",
|
|
22
25
|
)
|
|
23
26
|
parser.add_argument(
|
|
24
|
-
|
|
25
|
-
action=
|
|
26
|
-
help=
|
|
27
|
+
"--dry-run",
|
|
28
|
+
action="store_true",
|
|
29
|
+
help="Show what would be changed without making changes",
|
|
27
30
|
)
|
|
28
31
|
parser.add_argument(
|
|
29
|
-
|
|
30
|
-
action=
|
|
31
|
-
help=
|
|
32
|
+
"--verbose",
|
|
33
|
+
action="store_true",
|
|
34
|
+
help="Enable verbose output",
|
|
32
35
|
)
|
|
33
36
|
parser.add_argument(
|
|
34
|
-
|
|
37
|
+
"--storage-dir",
|
|
35
38
|
type=str,
|
|
36
39
|
default=None,
|
|
37
|
-
help=
|
|
40
|
+
help="Path to the storage directory (default: $ENDOREG_STORAGE_DIR or ./storage)",
|
|
38
41
|
)
|
|
39
42
|
|
|
40
43
|
def handle(self, *args, **options):
|
|
41
44
|
"""
|
|
42
45
|
Synchronizes video file paths in the database with actual files on disk, updating broken or missing paths as needed.
|
|
43
|
-
|
|
46
|
+
|
|
44
47
|
Scans the specified storage directory for video files, matches them to database records by UUID, and updates the `raw_file` field for videos whose stored path is missing or incorrect. Supports dry-run and verbose modes, and can process all videos or a specific video by ID.
|
|
45
48
|
"""
|
|
46
|
-
dry_run = options[
|
|
47
|
-
verbose = options[
|
|
48
|
-
video_id = options.get(
|
|
49
|
+
dry_run = options["dry_run"]
|
|
50
|
+
verbose = options["verbose"]
|
|
51
|
+
video_id = options.get("video_id")
|
|
49
52
|
|
|
50
53
|
# Determine storage_dir from argument, env, or fallback
|
|
51
|
-
storage_dir =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
storage_dir = (
|
|
55
|
+
options.get("storage_dir")
|
|
56
|
+
or os.environ.get("ENDOREG_STORAGE_DIR")
|
|
57
|
+
or "./storage"
|
|
58
|
+
)
|
|
54
59
|
storage_dir = Path(storage_dir)
|
|
55
|
-
|
|
60
|
+
|
|
56
61
|
# Find all actual video files
|
|
57
62
|
actual_files = {}
|
|
58
|
-
for pattern in [
|
|
63
|
+
for pattern in ["**/*.mp4", "**/*.avi", "**/*.mov", "**/*.mkv"]:
|
|
59
64
|
for file_path in storage_dir.glob(pattern):
|
|
60
65
|
if file_path.is_file() and file_path.stat().st_size > 0:
|
|
61
66
|
# Extract UUID from filename
|
|
62
67
|
filename = file_path.name
|
|
63
68
|
# UUID is typically the first part before underscore or the whole name
|
|
64
|
-
if
|
|
65
|
-
uuid_part = filename.split(
|
|
69
|
+
if "_" in filename:
|
|
70
|
+
uuid_part = filename.split("_")[0]
|
|
66
71
|
else:
|
|
67
|
-
uuid_part = filename.split(
|
|
68
|
-
|
|
72
|
+
uuid_part = filename.split(".")[0]
|
|
73
|
+
|
|
69
74
|
# Store relative path from storage directory
|
|
70
75
|
relative_path = file_path.relative_to(storage_dir)
|
|
71
76
|
actual_files[uuid_part] = {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
"absolute_path": file_path,
|
|
78
|
+
"relative_path": relative_path,
|
|
79
|
+
"size_mb": file_path.stat().st_size / (1024 * 1024),
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
self.stdout.write(f"Found {len(actual_files)} video files in storage")
|
|
@@ -82,7 +87,9 @@ class Command(BaseCommand):
|
|
|
82
87
|
videos = [VideoFile.objects.get(pk=video_id)]
|
|
83
88
|
self.stdout.write(f"Processing specific video ID: {video_id}")
|
|
84
89
|
except VideoFile.DoesNotExist:
|
|
85
|
-
self.stdout.write(
|
|
90
|
+
self.stdout.write(
|
|
91
|
+
self.style.ERROR(f"Video with ID {video_id} not found")
|
|
92
|
+
)
|
|
86
93
|
return
|
|
87
94
|
else:
|
|
88
95
|
videos = VideoFile.objects.all()
|
|
@@ -94,52 +101,64 @@ class Command(BaseCommand):
|
|
|
94
101
|
|
|
95
102
|
for video in videos:
|
|
96
103
|
try:
|
|
97
|
-
uuid_str = str(video.
|
|
98
|
-
|
|
104
|
+
uuid_str = str(video.video_hash)
|
|
105
|
+
|
|
99
106
|
# Check if we have a matching file
|
|
100
107
|
if uuid_str in actual_files:
|
|
101
108
|
file_info = actual_files[uuid_str]
|
|
102
|
-
|
|
109
|
+
|
|
103
110
|
# Check current file path
|
|
104
111
|
current_path_exists = False
|
|
105
112
|
current_path = None
|
|
106
|
-
|
|
107
|
-
if hasattr(video,
|
|
113
|
+
|
|
114
|
+
if hasattr(video, "raw_file") and video.raw_file:
|
|
108
115
|
try:
|
|
109
116
|
current_path = Path(video.raw_file.path)
|
|
110
117
|
current_path_exists = current_path.exists()
|
|
111
118
|
except (ValueError, AttributeError, OSError):
|
|
112
119
|
current_path_exists = False
|
|
113
|
-
|
|
120
|
+
|
|
114
121
|
if not current_path_exists:
|
|
115
122
|
# File path is broken, fix it
|
|
116
123
|
if verbose:
|
|
117
124
|
self.stdout.write(f"Video {video.id} ({uuid_str}):")
|
|
118
|
-
self.stdout.write(
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
self.stdout.write(
|
|
126
|
+
f" Current: {current_path or 'None'} (broken)"
|
|
127
|
+
)
|
|
128
|
+
self.stdout.write(
|
|
129
|
+
f" Found: {file_info['absolute_path']} ({file_info['size_mb']:.1f} MB)"
|
|
130
|
+
)
|
|
131
|
+
|
|
121
132
|
if not dry_run:
|
|
122
133
|
with transaction.atomic():
|
|
123
134
|
# Update the raw_file path
|
|
124
|
-
video.raw_file.name = str(file_info[
|
|
125
|
-
video.save(update_fields=[
|
|
126
|
-
|
|
135
|
+
video.raw_file.name = str(file_info["relative_path"])
|
|
136
|
+
video.save(update_fields=["raw_file"])
|
|
137
|
+
|
|
127
138
|
self.stdout.write(
|
|
128
|
-
self.style.SUCCESS(
|
|
139
|
+
self.style.SUCCESS(
|
|
140
|
+
f"✅ Fixed video {video.id}: {file_info['relative_path']}"
|
|
141
|
+
)
|
|
129
142
|
)
|
|
130
143
|
else:
|
|
131
144
|
self.stdout.write(
|
|
132
|
-
self.style.WARNING(
|
|
145
|
+
self.style.WARNING(
|
|
146
|
+
f"🔄 Would fix video {video.id}: {file_info['relative_path']}"
|
|
147
|
+
)
|
|
133
148
|
)
|
|
134
|
-
|
|
149
|
+
|
|
135
150
|
fixed_count += 1
|
|
136
151
|
else:
|
|
137
152
|
if verbose:
|
|
138
|
-
self.stdout.write(
|
|
153
|
+
self.stdout.write(
|
|
154
|
+
f"✅ Video {video.id} ({uuid_str}) already has correct path"
|
|
155
|
+
)
|
|
139
156
|
skipped_count += 1
|
|
140
157
|
else:
|
|
141
158
|
if verbose:
|
|
142
|
-
self.stdout.write(
|
|
159
|
+
self.stdout.write(
|
|
160
|
+
f"❌ Video {video.id} ({uuid_str}): No matching file found"
|
|
161
|
+
)
|
|
143
162
|
error_count += 1
|
|
144
163
|
|
|
145
164
|
except Exception as e:
|
|
@@ -149,17 +168,19 @@ class Command(BaseCommand):
|
|
|
149
168
|
error_count += 1
|
|
150
169
|
|
|
151
170
|
# Summary
|
|
152
|
-
self.stdout.write(f"\n{'='*50}")
|
|
171
|
+
self.stdout.write(f"\n{'=' * 50}")
|
|
153
172
|
self.stdout.write(self.style.SUCCESS("SUMMARY"))
|
|
154
|
-
self.stdout.write(f"{'='*50}")
|
|
155
|
-
|
|
173
|
+
self.stdout.write(f"{'=' * 50}")
|
|
174
|
+
|
|
156
175
|
action_word = "Would fix" if dry_run else "Fixed"
|
|
157
176
|
self.stdout.write(f"🔧 {action_word}: {fixed_count} videos")
|
|
158
177
|
self.stdout.write(f"✅ Already correct: {skipped_count} videos")
|
|
159
178
|
self.stdout.write(f"❌ Errors/Missing files: {error_count} videos")
|
|
160
|
-
|
|
179
|
+
|
|
161
180
|
if dry_run and fixed_count > 0:
|
|
162
181
|
self.stdout.write("\n💡 Run without --dry-run to apply changes")
|
|
163
182
|
elif not dry_run and fixed_count > 0:
|
|
164
|
-
self.stdout.write(
|
|
165
|
-
|
|
183
|
+
self.stdout.write(
|
|
184
|
+
f"\n🎉 Successfully fixed {fixed_count} video file paths!"
|
|
185
|
+
)
|
|
186
|
+
self.stdout.write("🔄 Restart your Django server to reload file paths")
|