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
|
@@ -34,7 +34,9 @@ class LabelVideoSegment(models.Model):
|
|
|
34
34
|
|
|
35
35
|
start_frame_number = models.IntegerField()
|
|
36
36
|
end_frame_number = models.IntegerField()
|
|
37
|
-
source = models.ForeignKey(
|
|
37
|
+
source = models.ForeignKey(
|
|
38
|
+
"InformationSource", on_delete=models.SET_NULL, null=True
|
|
39
|
+
)
|
|
38
40
|
label = models.ForeignKey("Label", on_delete=models.SET_NULL, null=True, blank=True)
|
|
39
41
|
|
|
40
42
|
# Single ForeignKey to the unified VideoFile model
|
|
@@ -68,13 +70,18 @@ class LabelVideoSegment(models.Model):
|
|
|
68
70
|
source: models.ForeignKey["InformationSource|None"]
|
|
69
71
|
prediction_meta: models.ForeignKey["VideoPredictionMeta|None"]
|
|
70
72
|
|
|
71
|
-
patient_findings = cast(
|
|
73
|
+
patient_findings = cast(
|
|
74
|
+
models.manager.RelatedManager["PatientFinding"], patient_findings
|
|
75
|
+
)
|
|
72
76
|
model_meta: models.ForeignKey["ModelMeta|None"]
|
|
73
77
|
state: models.OneToOneField["LabelVideoSegmentState"]
|
|
74
78
|
|
|
75
79
|
class Meta:
|
|
76
80
|
constraints = [
|
|
77
|
-
CheckConstraint(
|
|
81
|
+
CheckConstraint(
|
|
82
|
+
condition=Q(start_frame_number__lt=F("end_frame_number")),
|
|
83
|
+
name="segment_start_lt_end",
|
|
84
|
+
),
|
|
78
85
|
]
|
|
79
86
|
indexes = [
|
|
80
87
|
models.Index(fields=["video_file", "label", "start_frame_number"]),
|
|
@@ -125,13 +132,18 @@ class LabelVideoSegment(models.Model):
|
|
|
125
132
|
return self.state.is_validated
|
|
126
133
|
except ObjectDoesNotExist:
|
|
127
134
|
# This might happen if the state wasn't created yet, though the save method tries to prevent this.
|
|
128
|
-
logger.warning(
|
|
135
|
+
logger.warning(
|
|
136
|
+
"LabelVideoSegmentState not found for LabelVideoSegment %s.", self.pk
|
|
137
|
+
)
|
|
129
138
|
return False
|
|
130
139
|
except AttributeError:
|
|
131
140
|
# Should not happen if self.state exists and has the is_validated attribute.
|
|
132
|
-
logger.error(
|
|
141
|
+
logger.error(
|
|
142
|
+
"AttributeError accessing 'state.is_validated' for LabelVideoSegment %s.",
|
|
143
|
+
self.pk,
|
|
144
|
+
)
|
|
133
145
|
return False
|
|
134
|
-
|
|
146
|
+
|
|
135
147
|
def mark_validated(
|
|
136
148
|
self,
|
|
137
149
|
is_validated: bool = True,
|
|
@@ -163,7 +175,12 @@ class LabelVideoSegment(models.Model):
|
|
|
163
175
|
|
|
164
176
|
if not isinstance(self.video_file, VideoFile):
|
|
165
177
|
raise ValueError("Cannot extract frame files: No associated VideoFile.")
|
|
166
|
-
return self.video_file.extract_specific_frame_range(
|
|
178
|
+
return self.video_file.extract_specific_frame_range(
|
|
179
|
+
start_frame=self.start_frame_number,
|
|
180
|
+
end_frame=self.end_frame_number,
|
|
181
|
+
overwrite=overwrite,
|
|
182
|
+
**kwargs,
|
|
183
|
+
)
|
|
167
184
|
|
|
168
185
|
def delete_frame_files(self) -> None:
|
|
169
186
|
"""
|
|
@@ -176,10 +193,14 @@ class LabelVideoSegment(models.Model):
|
|
|
176
193
|
|
|
177
194
|
if not isinstance(self.video_file, VideoFile):
|
|
178
195
|
raise ValueError("Cannot delete frame files: No associated VideoFile.")
|
|
179
|
-
self.video_file.delete_specific_frame_range(
|
|
196
|
+
self.video_file.delete_specific_frame_range(
|
|
197
|
+
start_frame=self.start_frame_number, end_frame=self.end_frame_number
|
|
198
|
+
)
|
|
180
199
|
|
|
181
200
|
@classmethod
|
|
182
|
-
def safe_create(
|
|
201
|
+
def safe_create(
|
|
202
|
+
cls, video_file, label, start_frame_number, end_frame_number, **kwargs
|
|
203
|
+
):
|
|
183
204
|
"""
|
|
184
205
|
Create a new LabelVideoSegment instance after validating the frame range.
|
|
185
206
|
|
|
@@ -188,8 +209,16 @@ class LabelVideoSegment(models.Model):
|
|
|
188
209
|
Returns:
|
|
189
210
|
LabelVideoSegment: The newly created segment instance.
|
|
190
211
|
"""
|
|
191
|
-
cls.validate_frame_range(
|
|
192
|
-
|
|
212
|
+
cls.validate_frame_range(
|
|
213
|
+
start_frame_number, end_frame_number, video_file=video_file
|
|
214
|
+
)
|
|
215
|
+
return cls.objects.create(
|
|
216
|
+
video_file=video_file,
|
|
217
|
+
label=label,
|
|
218
|
+
start_frame_number=start_frame_number,
|
|
219
|
+
end_frame_number=end_frame_number,
|
|
220
|
+
**kwargs,
|
|
221
|
+
)
|
|
193
222
|
|
|
194
223
|
def save(self, *args, **kwargs):
|
|
195
224
|
"""
|
|
@@ -232,7 +261,9 @@ class LabelVideoSegment(models.Model):
|
|
|
232
261
|
"""
|
|
233
262
|
Create a LabelVideoSegment instance from a VideoFile.
|
|
234
263
|
"""
|
|
235
|
-
return _create_from_video(
|
|
264
|
+
return _create_from_video(
|
|
265
|
+
cls, source, prediction_meta, label, start_frame_number, end_frame_number
|
|
266
|
+
)
|
|
236
267
|
|
|
237
268
|
def get_video(self) -> "VideoFile":
|
|
238
269
|
"""Returns the associated VideoFile instance."""
|
|
@@ -243,15 +274,21 @@ class LabelVideoSegment(models.Model):
|
|
|
243
274
|
return self.video_file
|
|
244
275
|
except ObjectDoesNotExist:
|
|
245
276
|
# This might occur if the related VideoFile was deleted unexpectedly.
|
|
246
|
-
logger.error(
|
|
247
|
-
|
|
277
|
+
logger.error(
|
|
278
|
+
"Associated VideoFile not found for LabelVideoSegment %s.", self.pk
|
|
279
|
+
)
|
|
280
|
+
raise ValueError(
|
|
281
|
+
f"LabelVideoSegment {self.pk} is not associated with a valid VideoFile."
|
|
282
|
+
)
|
|
248
283
|
|
|
249
284
|
def __str__(self):
|
|
250
285
|
try:
|
|
251
286
|
video_obj = self.get_video()
|
|
252
287
|
label_name = self.label.name if self.label else "No Label"
|
|
253
288
|
active_path = video_obj.active_file_path
|
|
254
|
-
video_identifier =
|
|
289
|
+
video_identifier = (
|
|
290
|
+
active_path.name if active_path else f"UUID {video_obj.video_hash}"
|
|
291
|
+
)
|
|
255
292
|
|
|
256
293
|
str_repr = f"{video_identifier} Label - {label_name} - {self.start_frame_number} - {self.end_frame_number}"
|
|
257
294
|
except ObjectDoesNotExist: # More specific exception
|
|
@@ -259,7 +296,11 @@ class LabelVideoSegment(models.Model):
|
|
|
259
296
|
except ValueError as e: # Catch specific error from get_video
|
|
260
297
|
str_repr = f"Segment {self.pk} (Error: {e})"
|
|
261
298
|
except Exception as e:
|
|
262
|
-
logger.warning(
|
|
299
|
+
logger.warning(
|
|
300
|
+
"Error generating string representation for LabelVideoSegment %s: %s",
|
|
301
|
+
self.pk,
|
|
302
|
+
e,
|
|
303
|
+
)
|
|
263
304
|
str_repr = f"Segment {self.pk} (Error: {e})"
|
|
264
305
|
|
|
265
306
|
return str_repr
|
|
@@ -296,12 +337,20 @@ class LabelVideoSegment(models.Model):
|
|
|
296
337
|
|
|
297
338
|
try:
|
|
298
339
|
video_obj = self.get_video()
|
|
299
|
-
return video_obj.frames.filter(
|
|
340
|
+
return video_obj.frames.filter(
|
|
341
|
+
frame_number__gte=self.start_frame_number,
|
|
342
|
+
frame_number__lt=self.end_frame_number,
|
|
343
|
+
).order_by("frame_number")
|
|
300
344
|
except ValueError:
|
|
301
|
-
logger.error(
|
|
345
|
+
logger.error(
|
|
346
|
+
"Cannot get frames for segment %s: No associated VideoFile.", self.pk
|
|
347
|
+
)
|
|
302
348
|
return Frame.objects.none()
|
|
303
349
|
except AttributeError:
|
|
304
|
-
logger.error(
|
|
350
|
+
logger.error(
|
|
351
|
+
"Cannot get frames for segment %s: 'frames' related manager not found on VideoFile.",
|
|
352
|
+
self.pk,
|
|
353
|
+
)
|
|
305
354
|
return Frame.objects.none()
|
|
306
355
|
|
|
307
356
|
@property
|
|
@@ -323,7 +372,10 @@ class LabelVideoSegment(models.Model):
|
|
|
323
372
|
label=self.label,
|
|
324
373
|
)
|
|
325
374
|
except ValueError:
|
|
326
|
-
logger.error(
|
|
375
|
+
logger.error(
|
|
376
|
+
"Cannot get annotations for segment %s: No associated VideoFile.",
|
|
377
|
+
self.pk,
|
|
378
|
+
)
|
|
327
379
|
return ImageClassificationAnnotation.objects.none()
|
|
328
380
|
|
|
329
381
|
@property
|
|
@@ -346,11 +398,16 @@ class LabelVideoSegment(models.Model):
|
|
|
346
398
|
information_source__information_source_types__name="prediction",
|
|
347
399
|
)
|
|
348
400
|
except ValueError:
|
|
349
|
-
logger.error(
|
|
401
|
+
logger.error(
|
|
402
|
+
"Cannot get predictions for segment %s: No associated VideoFile.",
|
|
403
|
+
self.pk,
|
|
404
|
+
)
|
|
350
405
|
return ImageClassificationAnnotation.objects.none()
|
|
351
406
|
|
|
352
407
|
@property
|
|
353
|
-
def manual_frame_annotations(
|
|
408
|
+
def manual_frame_annotations(
|
|
409
|
+
self,
|
|
410
|
+
) -> models.QuerySet["ImageClassificationAnnotation"]:
|
|
354
411
|
"""
|
|
355
412
|
Return manual image classification annotations for frames within this segment and matching the segment's label.
|
|
356
413
|
|
|
@@ -369,7 +426,10 @@ class LabelVideoSegment(models.Model):
|
|
|
369
426
|
information_source__information_source_types__name="manual_annotation",
|
|
370
427
|
)
|
|
371
428
|
except ValueError:
|
|
372
|
-
logger.error(
|
|
429
|
+
logger.error(
|
|
430
|
+
"Cannot get manual annotations for segment %s: No associated VideoFile.",
|
|
431
|
+
self.pk,
|
|
432
|
+
)
|
|
373
433
|
return ImageClassificationAnnotation.objects.none()
|
|
374
434
|
|
|
375
435
|
def get_segment_len_in_s(self) -> float:
|
|
@@ -383,14 +443,21 @@ class LabelVideoSegment(models.Model):
|
|
|
383
443
|
video_obj = self.get_video()
|
|
384
444
|
fps = video_obj.get_fps()
|
|
385
445
|
if fps is None or fps <= 0:
|
|
386
|
-
logger.warning(
|
|
446
|
+
logger.warning(
|
|
447
|
+
"Could not determine valid FPS for %s. Cannot calculate segment length in seconds.",
|
|
448
|
+
video_obj,
|
|
449
|
+
)
|
|
387
450
|
return 0.0
|
|
388
451
|
return (self.end_frame_number - self.start_frame_number) / fps
|
|
389
452
|
except ValueError as e: # Catch error from get_video
|
|
390
|
-
logger.error(
|
|
453
|
+
logger.error(
|
|
454
|
+
"Cannot calculate segment length for segment %s: %s", self.pk, e
|
|
455
|
+
)
|
|
391
456
|
return 0.0
|
|
392
457
|
|
|
393
|
-
def get_frames_without_annotation(
|
|
458
|
+
def get_frames_without_annotation(
|
|
459
|
+
self, n_frames: int
|
|
460
|
+
) -> Union[list["Frame"], list]:
|
|
394
461
|
"""
|
|
395
462
|
Get up to n frames within the segment that do not have an ImageClassificationAnnotation
|
|
396
463
|
for this segment's label.
|
|
@@ -402,14 +469,19 @@ class LabelVideoSegment(models.Model):
|
|
|
402
469
|
return []
|
|
403
470
|
|
|
404
471
|
if not self.label:
|
|
405
|
-
logger.warning(
|
|
472
|
+
logger.warning(
|
|
473
|
+
"Segment %s has no label. Cannot find frames without annotation.",
|
|
474
|
+
self.pk,
|
|
475
|
+
)
|
|
406
476
|
return []
|
|
407
477
|
|
|
408
|
-
annotated_frame_ids = ImageClassificationAnnotation.objects.filter(
|
|
409
|
-
"
|
|
410
|
-
)
|
|
478
|
+
annotated_frame_ids = ImageClassificationAnnotation.objects.filter(
|
|
479
|
+
frame__in=frames_qs.values_list("id", flat=True), label=self.label
|
|
480
|
+
).values_list("frame_id", flat=True)
|
|
411
481
|
|
|
412
|
-
frames_without_annotation = list(
|
|
482
|
+
frames_without_annotation = list(
|
|
483
|
+
frames_qs.exclude(id__in=annotated_frame_ids)[:n_frames]
|
|
484
|
+
)
|
|
413
485
|
return frames_without_annotation
|
|
414
486
|
|
|
415
487
|
def generate_annotations(self):
|
|
@@ -419,25 +491,35 @@ class LabelVideoSegment(models.Model):
|
|
|
419
491
|
Annotations are generated only if the segment has associated prediction metadata, model metadata, and label. Existing annotations for the same frame, label, model, and information source are not duplicated. Uses bulk creation for efficiency.
|
|
420
492
|
"""
|
|
421
493
|
if not self.prediction_meta:
|
|
422
|
-
logger.info(
|
|
494
|
+
logger.info(
|
|
495
|
+
"Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.",
|
|
496
|
+
self.pk,
|
|
497
|
+
)
|
|
423
498
|
return
|
|
424
499
|
|
|
425
500
|
from endoreg_db.models import ImageClassificationAnnotation, InformationSource
|
|
426
501
|
|
|
427
502
|
information_source = self.source
|
|
428
503
|
if not information_source:
|
|
429
|
-
information_source, _ = InformationSource.objects.get_or_create(
|
|
504
|
+
information_source, _ = InformationSource.objects.get_or_create(
|
|
505
|
+
name="prediction"
|
|
506
|
+
)
|
|
430
507
|
|
|
431
508
|
model_meta = self.get_model_meta()
|
|
432
509
|
label = self.label
|
|
433
510
|
|
|
434
511
|
if not model_meta or not label:
|
|
435
|
-
logger.warning(
|
|
512
|
+
logger.warning(
|
|
513
|
+
"Missing model_meta or label for segment %s. Skipping annotation generation.",
|
|
514
|
+
self.pk,
|
|
515
|
+
)
|
|
436
516
|
return
|
|
437
517
|
|
|
438
518
|
frames_queryset = self.get_frames().only("id")
|
|
439
519
|
if not isinstance(frames_queryset, models.QuerySet):
|
|
440
|
-
logger.error(
|
|
520
|
+
logger.error(
|
|
521
|
+
"Could not get frame queryset for segment %s. Skipping.", self.pk
|
|
522
|
+
)
|
|
441
523
|
return
|
|
442
524
|
|
|
443
525
|
existing_annotation_frame_ids = set(
|
|
@@ -450,9 +532,15 @@ class LabelVideoSegment(models.Model):
|
|
|
450
532
|
)
|
|
451
533
|
|
|
452
534
|
annotations_to_create = []
|
|
453
|
-
frames_to_annotate = frames_queryset.exclude(
|
|
535
|
+
frames_to_annotate = frames_queryset.exclude(
|
|
536
|
+
id__in=existing_annotation_frame_ids
|
|
537
|
+
)
|
|
454
538
|
|
|
455
|
-
for frame in tqdm(
|
|
539
|
+
for frame in tqdm(
|
|
540
|
+
frames_to_annotate.iterator(),
|
|
541
|
+
total=frames_to_annotate.count(),
|
|
542
|
+
desc=f"Preparing annotations for segment {self.pk} ({label.name})",
|
|
543
|
+
):
|
|
456
544
|
annotations_to_create.append(
|
|
457
545
|
ImageClassificationAnnotation(
|
|
458
546
|
frame=frame,
|
|
@@ -464,8 +552,14 @@ class LabelVideoSegment(models.Model):
|
|
|
464
552
|
)
|
|
465
553
|
|
|
466
554
|
if annotations_to_create:
|
|
467
|
-
logger.info(
|
|
468
|
-
|
|
555
|
+
logger.info(
|
|
556
|
+
"Bulk creating %d annotations for segment %s...",
|
|
557
|
+
len(annotations_to_create),
|
|
558
|
+
self.pk,
|
|
559
|
+
)
|
|
560
|
+
ImageClassificationAnnotation.objects.bulk_create(
|
|
561
|
+
annotations_to_create, ignore_conflicts=True
|
|
562
|
+
)
|
|
469
563
|
logger.info("Bulk creation complete.")
|
|
470
564
|
else:
|
|
471
565
|
logger.info("No new annotations needed for segment %s.", self.pk)
|
|
@@ -483,7 +577,9 @@ class LabelVideoSegment(models.Model):
|
|
|
483
577
|
return video_obj.get_fps()
|
|
484
578
|
|
|
485
579
|
@staticmethod
|
|
486
|
-
def validate_frame_range(
|
|
580
|
+
def validate_frame_range(
|
|
581
|
+
start_frame_number: int, end_frame_number: int, video_file=None
|
|
582
|
+
):
|
|
487
583
|
"""
|
|
488
584
|
Validate that the provided frame numbers define a valid segment range, optionally checking against a video's frame count.
|
|
489
585
|
|
|
@@ -495,13 +591,21 @@ class LabelVideoSegment(models.Model):
|
|
|
495
591
|
Raises:
|
|
496
592
|
ValueError: If frame numbers are not integers, are negative, are out of order, or exceed the video's frame count.
|
|
497
593
|
"""
|
|
498
|
-
if not isinstance(start_frame_number, int) or not isinstance(
|
|
499
|
-
|
|
594
|
+
if not isinstance(start_frame_number, int) or not isinstance(
|
|
595
|
+
end_frame_number, int
|
|
596
|
+
):
|
|
597
|
+
raise ValueError(
|
|
598
|
+
"start_frame_number and end_frame_number must be integers."
|
|
599
|
+
)
|
|
500
600
|
if start_frame_number < 0:
|
|
501
601
|
raise ValueError("start_frame_number must be non-negative.")
|
|
502
602
|
if end_frame_number < start_frame_number:
|
|
503
|
-
raise ValueError(
|
|
603
|
+
raise ValueError(
|
|
604
|
+
"end_frame_number must be equal or greater than start_frame_number."
|
|
605
|
+
)
|
|
504
606
|
if video_file is not None:
|
|
505
607
|
frame_count = getattr(video_file, "frame_count", None)
|
|
506
608
|
if frame_count is not None and end_frame_number > frame_count:
|
|
507
|
-
raise ValueError(
|
|
609
|
+
raise ValueError(
|
|
610
|
+
f"end_frame_number ({end_frame_number}) exceeds video frame count ({frame_count})."
|
|
611
|
+
)
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
from .video import VideoFile, VideoMetadata, VideoProcessingHistory
|
|
2
2
|
from .frame import Frame
|
|
3
|
-
from .pdf import
|
|
3
|
+
from .pdf import (
|
|
4
|
+
RawPdfFile,
|
|
5
|
+
DocumentType,
|
|
6
|
+
AnonymExaminationReport,
|
|
7
|
+
ReportReaderConfig,
|
|
8
|
+
ReportReaderFlag,
|
|
9
|
+
AnonymHistologyReport,
|
|
10
|
+
)
|
|
4
11
|
|
|
5
12
|
__all__ = [
|
|
6
13
|
"VideoFile",
|
|
@@ -9,8 +16,8 @@ __all__ = [
|
|
|
9
16
|
"DocumentType",
|
|
10
17
|
"AnonymExaminationReport",
|
|
11
18
|
"AnonymHistologyReport",
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
"ReportReaderConfig",
|
|
20
|
+
"ReportReaderFlag",
|
|
21
|
+
"VideoMetadata",
|
|
22
|
+
"VideoProcessingHistory",
|
|
16
23
|
]
|
|
@@ -24,10 +24,13 @@ class Frame(models.Model):
|
|
|
24
24
|
frame_number = models.PositiveIntegerField()
|
|
25
25
|
relative_path = models.CharField(max_length=512)
|
|
26
26
|
timestamp = models.FloatField(null=True, blank=True)
|
|
27
|
+
|
|
27
28
|
is_extracted = models.BooleanField(default=False)
|
|
28
29
|
|
|
29
30
|
if TYPE_CHECKING:
|
|
30
|
-
image_classification_annotations: models.QuerySet[
|
|
31
|
+
image_classification_annotations: models.QuerySet[
|
|
32
|
+
"ImageClassificationAnnotation"
|
|
33
|
+
]
|
|
31
34
|
video: models.ForeignKey["VideoFile"]
|
|
32
35
|
|
|
33
36
|
class Meta:
|
|
@@ -54,7 +57,9 @@ class Frame(models.Model):
|
|
|
54
57
|
Returns:
|
|
55
58
|
QuerySet: A queryset of related ImageClassificationAnnotation objects filtered to those whose information source type is "prediction".
|
|
56
59
|
"""
|
|
57
|
-
return self.image_classification_annotations.filter(
|
|
60
|
+
return self.image_classification_annotations.filter(
|
|
61
|
+
information_source__information_source_types__name="prediction"
|
|
62
|
+
)
|
|
58
63
|
|
|
59
64
|
@property
|
|
60
65
|
def manual_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
|
|
@@ -64,7 +69,9 @@ class Frame(models.Model):
|
|
|
64
69
|
Returns:
|
|
65
70
|
QuerySet: A queryset of related ImageClassificationAnnotation objects whose information source type is "manual_annotation".
|
|
66
71
|
"""
|
|
67
|
-
return self.image_classification_annotations.filter(
|
|
72
|
+
return self.image_classification_annotations.filter(
|
|
73
|
+
information_source__information_source_types__name="manual_annotation"
|
|
74
|
+
)
|
|
68
75
|
|
|
69
76
|
@property
|
|
70
77
|
def has_predictions(self) -> bool:
|
|
@@ -93,19 +100,38 @@ class Frame(models.Model):
|
|
|
93
100
|
"""
|
|
94
101
|
frame_path = self.file_path
|
|
95
102
|
if not frame_path.exists():
|
|
96
|
-
logger.warning(
|
|
103
|
+
logger.warning(
|
|
104
|
+
"Frame file not found at %s for Frame %s (Video %s)",
|
|
105
|
+
frame_path,
|
|
106
|
+
self.pk,
|
|
107
|
+
self.video.video_hash,
|
|
108
|
+
)
|
|
97
109
|
return None
|
|
98
110
|
try:
|
|
99
111
|
image = cv2.imread(str(frame_path))
|
|
100
112
|
if image is None:
|
|
101
|
-
logger.warning(
|
|
113
|
+
logger.warning(
|
|
114
|
+
"cv2.imread returned None for frame file %s (Frame %s, Video %s)",
|
|
115
|
+
frame_path,
|
|
116
|
+
self.pk,
|
|
117
|
+
self.video.video_hash,
|
|
118
|
+
)
|
|
102
119
|
return image
|
|
103
120
|
except Exception as e:
|
|
104
|
-
logger.error(
|
|
121
|
+
logger.error(
|
|
122
|
+
"Error reading frame file %s (Frame %s, Video %s): %s",
|
|
123
|
+
frame_path,
|
|
124
|
+
self.pk,
|
|
125
|
+
self.video.video_hash,
|
|
126
|
+
e,
|
|
127
|
+
exc_info=True,
|
|
128
|
+
)
|
|
105
129
|
return None
|
|
106
130
|
|
|
107
131
|
def __str__(self):
|
|
108
|
-
return f"Frame {self.frame_number} of Video {self.video.
|
|
132
|
+
return f"Frame {self.frame_number} of Video {self.video.video_hash}"
|
|
109
133
|
|
|
110
|
-
def get_classification_annotations(
|
|
134
|
+
def get_classification_annotations(
|
|
135
|
+
self,
|
|
136
|
+
) -> models.QuerySet["ImageClassificationAnnotation"]:
|
|
111
137
|
return self.image_classification_annotations.all()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .raw_pdf import RawPdfFile
|
|
2
2
|
from .report_file import DocumentType, AnonymExaminationReport, AnonymHistologyReport
|
|
3
3
|
from .report_reader import ReportReaderConfig, ReportReaderFlag
|
|
4
|
+
|
|
4
5
|
__all__ = [
|
|
5
6
|
"RawPdfFile",
|
|
6
7
|
"DocumentType",
|
|
@@ -8,4 +9,4 @@ __all__ = [
|
|
|
8
9
|
"AnonymHistologyReport",
|
|
9
10
|
"ReportReaderConfig",
|
|
10
11
|
"ReportReaderFlag",
|
|
11
|
-
]
|
|
12
|
+
]
|
|
@@ -11,7 +11,7 @@ from django.core.files import File
|
|
|
11
11
|
from django.core.validators import FileExtensionValidator
|
|
12
12
|
from django.db import models
|
|
13
13
|
|
|
14
|
-
from endoreg_db.utils.file_operations import
|
|
14
|
+
from endoreg_db.utils.file_operations import get_content_hash_filename
|
|
15
15
|
from endoreg_db.utils.hashs import get_pdf_hash
|
|
16
16
|
from endoreg_db.utils.paths import (
|
|
17
17
|
ANONYM_REPORT_DIR,
|
|
@@ -515,7 +515,7 @@ class RawPdfFile(models.Model):
|
|
|
515
515
|
raise
|
|
516
516
|
|
|
517
517
|
# Generate a unique filename (e.g., using UUID)
|
|
518
|
-
new_file_name, _uuid =
|
|
518
|
+
new_file_name, _uuid = get_content_hash_filename(file_path)
|
|
519
519
|
logger.info(f"Generated new filename: {new_file_name}")
|
|
520
520
|
|
|
521
521
|
# Create model instance via manager so creation can be intercepted/mocked during tests
|
|
@@ -750,8 +750,15 @@ class RawPdfFile(models.Model):
|
|
|
750
750
|
return settings_dict
|
|
751
751
|
|
|
752
752
|
@staticmethod
|
|
753
|
-
def
|
|
753
|
+
def get_report_by_pk(pk: int) -> "RawPdfFile":
|
|
754
754
|
try:
|
|
755
755
|
return RawPdfFile.objects.get(pk=pk)
|
|
756
756
|
except RawPdfFile.DoesNotExist:
|
|
757
|
-
raise ValueError(f"report with ID {
|
|
757
|
+
raise ValueError(f"report with ID {pk} does not exist.")
|
|
758
|
+
|
|
759
|
+
@staticmethod
|
|
760
|
+
def get_report_by_hash(hash: str) -> "RawPdfFile":
|
|
761
|
+
try:
|
|
762
|
+
return RawPdfFile.objects.get(pdf_hash=hash)
|
|
763
|
+
except RawPdfFile.DoesNotExist:
|
|
764
|
+
raise ValueError(f"report with ID {hash} does not exist.")
|
|
@@ -87,7 +87,9 @@ class AbstractExaminationReport(AbstractDocument):
|
|
|
87
87
|
Abstract base class for examination reports.
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
|
-
patient = models.ForeignKey(
|
|
90
|
+
patient = models.ForeignKey(
|
|
91
|
+
"Patient", on_delete=models.DO_NOTHING, blank=True, null=True
|
|
92
|
+
)
|
|
91
93
|
|
|
92
94
|
patient_examination = models.ForeignKey(
|
|
93
95
|
"PatientExamination",
|
|
@@ -101,7 +103,9 @@ class AbstractExaminationReport(AbstractDocument):
|
|
|
101
103
|
blank=True,
|
|
102
104
|
)
|
|
103
105
|
|
|
104
|
-
sensitive_meta = models.ForeignKey(
|
|
106
|
+
sensitive_meta = models.ForeignKey(
|
|
107
|
+
"SensitiveMeta", on_delete=models.SET_NULL, null=True, blank=True
|
|
108
|
+
)
|
|
105
109
|
|
|
106
110
|
if TYPE_CHECKING:
|
|
107
111
|
center: models.ForeignKey["Center|None"]
|
|
@@ -19,15 +19,25 @@ class ReportReaderFlag(models.Model):
|
|
|
19
19
|
if TYPE_CHECKING:
|
|
20
20
|
|
|
21
21
|
@property
|
|
22
|
-
def report_reader_configs_patient_info_line(
|
|
22
|
+
def report_reader_configs_patient_info_line(
|
|
23
|
+
self,
|
|
24
|
+
) -> models.QuerySet["ReportReaderConfig"]: ...
|
|
23
25
|
@property
|
|
24
|
-
def report_reader_configs_endoscope_info_line(
|
|
26
|
+
def report_reader_configs_endoscope_info_line(
|
|
27
|
+
self,
|
|
28
|
+
) -> models.QuerySet["ReportReaderConfig"]: ...
|
|
25
29
|
@property
|
|
26
|
-
def report_reader_configs_examiner_info_line(
|
|
30
|
+
def report_reader_configs_examiner_info_line(
|
|
31
|
+
self,
|
|
32
|
+
) -> models.QuerySet["ReportReaderConfig"]: ...
|
|
27
33
|
@property
|
|
28
|
-
def report_reader_configs_cut_off_below(
|
|
34
|
+
def report_reader_configs_cut_off_below(
|
|
35
|
+
self,
|
|
36
|
+
) -> models.QuerySet["ReportReaderConfig"]: ...
|
|
29
37
|
@property
|
|
30
|
-
def report_reader_configs_cut_off_above(
|
|
38
|
+
def report_reader_configs_cut_off_above(
|
|
39
|
+
self,
|
|
40
|
+
) -> models.QuerySet["ReportReaderConfig"]: ...
|
|
31
41
|
|
|
32
42
|
def natural_key(self):
|
|
33
43
|
return (self.name,)
|