endoreg-db 0.8.8.9__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 +146 -83
- 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/migrations/__init__.py +0 -0
- 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 -3
- 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/ffmpeg_wrapper.py +217 -52
- 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.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
- {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +436 -433
- endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +0 -119
- 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.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -31,7 +31,9 @@ class ModelMetaManager(models.Manager):
|
|
|
31
31
|
Provides methods for retrieving ModelMeta instances using natural keys.
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
|
-
def get_by_natural_key(
|
|
34
|
+
def get_by_natural_key(
|
|
35
|
+
self, name: str, version: str, model_name: str
|
|
36
|
+
) -> "ModelMeta":
|
|
35
37
|
"""
|
|
36
38
|
Retrieves a ModelMeta instance using its natural key.
|
|
37
39
|
|
|
@@ -80,10 +82,16 @@ class ModelMeta(models.Model):
|
|
|
80
82
|
related_name="model_metadata",
|
|
81
83
|
help_text="The set of labels this model version predicts.",
|
|
82
84
|
)
|
|
83
|
-
activation = models.CharField(
|
|
85
|
+
activation = models.CharField(
|
|
86
|
+
max_length=50,
|
|
87
|
+
default="sigmoid",
|
|
88
|
+
help_text="Output activation function (e.g., 'sigmoid', 'softmax', 'none').",
|
|
89
|
+
)
|
|
84
90
|
weights = models.FileField(
|
|
85
91
|
upload_to=WEIGHTS_DIR.name, # Use .name for relative path
|
|
86
|
-
validators=[
|
|
92
|
+
validators=[
|
|
93
|
+
FileExtensionValidator(allowed_extensions=["safetensors", "pth", "pt"])
|
|
94
|
+
],
|
|
87
95
|
null=True,
|
|
88
96
|
blank=True,
|
|
89
97
|
help_text="Path to the model weights file (.safetensors), relative to MEDIA_ROOT.",
|
|
@@ -102,14 +110,24 @@ class ModelMeta(models.Model):
|
|
|
102
110
|
)
|
|
103
111
|
size_x = models.IntegerField(default=716, help_text="Expected input image width.")
|
|
104
112
|
size_y = models.IntegerField(default=716, help_text="Expected input image height.")
|
|
105
|
-
axes = models.CharField(
|
|
113
|
+
axes = models.CharField(
|
|
114
|
+
max_length=10,
|
|
115
|
+
default="2,0,1",
|
|
116
|
+
help_text="Comma-separated target axis order (e.g., '2,0,1' for CHW).",
|
|
117
|
+
)
|
|
106
118
|
|
|
107
119
|
# --- Inference Parameters ---
|
|
108
|
-
batchsize = models.IntegerField(
|
|
109
|
-
|
|
120
|
+
batchsize = models.IntegerField(
|
|
121
|
+
default=16, help_text="Default batch size for inference."
|
|
122
|
+
)
|
|
123
|
+
num_workers = models.IntegerField(
|
|
124
|
+
default=0, help_text="Default number of workers for data loading."
|
|
125
|
+
)
|
|
110
126
|
|
|
111
127
|
# --- Metadata ---
|
|
112
|
-
description = models.TextField(
|
|
128
|
+
description = models.TextField(
|
|
129
|
+
blank=True, null=True, help_text="Optional description."
|
|
130
|
+
)
|
|
113
131
|
date_created = models.DateTimeField(auto_now_add=True)
|
|
114
132
|
|
|
115
133
|
objects = ModelMetaManager()
|
|
@@ -117,7 +135,9 @@ class ModelMeta(models.Model):
|
|
|
117
135
|
# --- Type Hinting for Related Fields ---
|
|
118
136
|
if TYPE_CHECKING:
|
|
119
137
|
labelset: models.ForeignKey["LabelSet"]
|
|
120
|
-
model: models.ForeignKey[
|
|
138
|
+
model: models.ForeignKey[
|
|
139
|
+
"AiModel"
|
|
140
|
+
] # Corrected from ai_model to match field name
|
|
121
141
|
weights = cast(FieldFile, weights)
|
|
122
142
|
|
|
123
143
|
class Meta:
|
|
@@ -175,7 +195,9 @@ class ModelMeta(models.Model):
|
|
|
175
195
|
)
|
|
176
196
|
|
|
177
197
|
@classmethod
|
|
178
|
-
def get_latest_version_number(
|
|
198
|
+
def get_latest_version_number(
|
|
199
|
+
cls: Type["ModelMeta"], meta_name: str, model_name: str
|
|
200
|
+
) -> int:
|
|
179
201
|
"""
|
|
180
202
|
Gets the latest version *number* using external logic.
|
|
181
203
|
"""
|
|
@@ -228,10 +250,16 @@ class ModelMeta(models.Model):
|
|
|
228
250
|
Retrieves a ModelMeta instance by name, model name, and optionally version using external logic.
|
|
229
251
|
"""
|
|
230
252
|
# Delegate to logic function
|
|
231
|
-
return logic.get_model_meta_by_name_version_logic(
|
|
253
|
+
return logic.get_model_meta_by_name_version_logic(
|
|
254
|
+
cls, meta_name, model_name, version
|
|
255
|
+
)
|
|
232
256
|
|
|
233
257
|
@classmethod
|
|
234
|
-
def get_latest(
|
|
258
|
+
def get_latest(
|
|
259
|
+
cls: Type["ModelMeta"], meta_name: str, model_name: str
|
|
260
|
+
) -> "ModelMeta":
|
|
235
261
|
"""Alias for get_by_name_version(meta_name, model_name, version=None) using external logic."""
|
|
236
262
|
# Delegate directly to the specific logic function
|
|
237
|
-
return logic.get_model_meta_by_name_version_logic(
|
|
263
|
+
return logic.get_model_meta_by_name_version_logic(
|
|
264
|
+
cls, meta_name, model_name, version=None
|
|
265
|
+
)
|
|
@@ -25,14 +25,18 @@ def _get_model_meta_class():
|
|
|
25
25
|
return ModelMeta
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def get_latest_version_number_logic(
|
|
28
|
+
def get_latest_version_number_logic(
|
|
29
|
+
cls: Type["ModelMeta"], meta_name: str, model_name: str
|
|
30
|
+
) -> int:
|
|
29
31
|
"""
|
|
30
32
|
Finds the highest numerical version for a given meta_name and model_name.
|
|
31
33
|
Iterates through all versions, attempts to parse them as integers,
|
|
32
34
|
and returns the maximum integer found. If no numeric versions are found,
|
|
33
35
|
returns 0.
|
|
34
36
|
"""
|
|
35
|
-
versions_qs = cls.objects.filter(
|
|
37
|
+
versions_qs = cls.objects.filter(
|
|
38
|
+
name=meta_name, model__name=model_name
|
|
39
|
+
).values_list("version", flat=True)
|
|
36
40
|
|
|
37
41
|
max_v = 0
|
|
38
42
|
found_numeric_version = False
|
|
@@ -88,7 +92,9 @@ def create_from_file_logic(
|
|
|
88
92
|
try:
|
|
89
93
|
label_set = labelset_qs.get()
|
|
90
94
|
except LabelSet.DoesNotExist as exc:
|
|
91
|
-
raise ValueError(
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"LabelSet '{labelset_name}' with version '{labelset_version}' not found."
|
|
97
|
+
) from exc
|
|
92
98
|
except LabelSet.MultipleObjectsReturned:
|
|
93
99
|
# Prefer the highest version when duplicates remain and no explicit version requested
|
|
94
100
|
label_set = labelset_qs.order_by("-version").first()
|
|
@@ -101,17 +107,23 @@ def create_from_file_logic(
|
|
|
101
107
|
|
|
102
108
|
if requested_version:
|
|
103
109
|
target_version = str(requested_version)
|
|
104
|
-
existing = cls.objects.filter(
|
|
110
|
+
existing = cls.objects.filter(
|
|
111
|
+
name=meta_name, model=ai_model, version=target_version
|
|
112
|
+
).first()
|
|
105
113
|
if existing and not bump_if_exists:
|
|
106
114
|
raise ValueError(
|
|
107
115
|
f"ModelMeta '{meta_name}' version '{target_version}' for model '{model_name}' already exists. Use bump_if_exists=True to increment."
|
|
108
116
|
)
|
|
109
117
|
elif existing and bump_if_exists:
|
|
110
118
|
target_version = str(latest_version_num + 1)
|
|
111
|
-
logger.info(
|
|
119
|
+
logger.info(
|
|
120
|
+
f"Bumping version for {meta_name}/{model_name} to {target_version}"
|
|
121
|
+
)
|
|
112
122
|
else:
|
|
113
123
|
target_version = str(latest_version_num + 1)
|
|
114
|
-
logger.info(
|
|
124
|
+
logger.info(
|
|
125
|
+
f"Setting next version for {meta_name}/{model_name} to {target_version}"
|
|
126
|
+
)
|
|
115
127
|
|
|
116
128
|
# --- Prepare Weights File ---
|
|
117
129
|
source_weights_path = Path(weights_file).resolve()
|
|
@@ -121,7 +133,10 @@ def create_from_file_logic(
|
|
|
121
133
|
# Construct destination path within MEDIA_ROOT/WEIGHTS_DIR
|
|
122
134
|
weights_filename = source_weights_path.name
|
|
123
135
|
# Relative path for the FileField upload_to
|
|
124
|
-
relative_dest_path =
|
|
136
|
+
relative_dest_path = (
|
|
137
|
+
Path(WEIGHTS_DIR.relative_to(STORAGE_DIR))
|
|
138
|
+
/ f"{meta_name}_v{target_version}_{weights_filename}"
|
|
139
|
+
)
|
|
125
140
|
# Full path for shutil.copy
|
|
126
141
|
full_dest_path = STORAGE_DIR / relative_dest_path
|
|
127
142
|
|
|
@@ -216,7 +231,9 @@ def _parse_float_sequence(value: Any, *, fallback: Iterable[float]) -> list[floa
|
|
|
216
231
|
try:
|
|
217
232
|
parsed.append(float(token))
|
|
218
233
|
except (TypeError, ValueError):
|
|
219
|
-
logger.warning(
|
|
234
|
+
logger.warning(
|
|
235
|
+
"Failed to parse normalisation value %r; using fallback", token
|
|
236
|
+
)
|
|
220
237
|
return list(fallback)
|
|
221
238
|
|
|
222
239
|
return parsed or list(fallback)
|
|
@@ -237,7 +254,9 @@ def _parse_axes(axes_value: str | Iterable[Any]) -> list[int]:
|
|
|
237
254
|
if isinstance(axes_value, str):
|
|
238
255
|
token_source = axes_value.strip()
|
|
239
256
|
if "," in token_source:
|
|
240
|
-
tokens = [
|
|
257
|
+
tokens = [
|
|
258
|
+
token.strip() for token in token_source.split(",") if token.strip()
|
|
259
|
+
]
|
|
241
260
|
else:
|
|
242
261
|
tokens = [char for char in token_source if char.strip()]
|
|
243
262
|
else:
|
|
@@ -316,14 +335,22 @@ def get_model_meta_by_name_version_logic(
|
|
|
316
335
|
try:
|
|
317
336
|
return cls.objects.get(name=meta_name, model=ai_model, version=version)
|
|
318
337
|
except Exception as exc:
|
|
319
|
-
raise cls.DoesNotExist(
|
|
338
|
+
raise cls.DoesNotExist(
|
|
339
|
+
f"ModelMeta '{meta_name}' version '{version}' for model '{model_name}' not found."
|
|
340
|
+
) from exc
|
|
320
341
|
else:
|
|
321
342
|
# Get latest version
|
|
322
|
-
latest =
|
|
343
|
+
latest = (
|
|
344
|
+
cls.objects.filter(name=meta_name, model=ai_model)
|
|
345
|
+
.order_by("-date_created")
|
|
346
|
+
.first()
|
|
347
|
+
)
|
|
323
348
|
if latest:
|
|
324
349
|
return latest
|
|
325
350
|
else:
|
|
326
|
-
raise cls.DoesNotExist(
|
|
351
|
+
raise cls.DoesNotExist(
|
|
352
|
+
f"No ModelMeta found for '{meta_name}' and model '{model_name}'."
|
|
353
|
+
)
|
|
327
354
|
|
|
328
355
|
|
|
329
356
|
import re
|
|
@@ -341,7 +368,9 @@ def infer_default_model_meta_from_hf(model_id: str) -> dict[str, Any]:
|
|
|
341
368
|
"""
|
|
342
369
|
|
|
343
370
|
if not (info := model_info(model_id)):
|
|
344
|
-
logger.info(
|
|
371
|
+
logger.info(
|
|
372
|
+
f"Could not retrieve model info for {model_id}, using ColoReg segmentation defaults."
|
|
373
|
+
)
|
|
345
374
|
return {
|
|
346
375
|
"name": "wg-lux/colo_segmentation_RegNetX800MF_base",
|
|
347
376
|
"activation": "sigmoid",
|
|
@@ -409,7 +438,9 @@ def setup_default_from_huggingface_logic(
|
|
|
409
438
|
local_dir=WEIGHTS_DIR,
|
|
410
439
|
)
|
|
411
440
|
except Exception as exc: # pragma: no cover - network errors
|
|
412
|
-
raise RuntimeError(
|
|
441
|
+
raise RuntimeError(
|
|
442
|
+
"Failed to download safetensor weights from Hugging Face; ensure the repository provides a .safetensors artifact."
|
|
443
|
+
) from exc
|
|
413
444
|
|
|
414
445
|
ai_model, _ = AiModel.objects.get_or_create(name=meta["name"])
|
|
415
446
|
if not labelset_name:
|
|
@@ -426,12 +457,16 @@ def setup_default_from_huggingface_logic(
|
|
|
426
457
|
labelset_qs = labelset_qs.filter(version=version_value)
|
|
427
458
|
labelset = labelset_qs.order_by("-version").first()
|
|
428
459
|
if not labelset:
|
|
429
|
-
raise ValueError(
|
|
460
|
+
raise ValueError(
|
|
461
|
+
f"LabelSet '{labelset_name}' with version '{labelset_version}' not found."
|
|
462
|
+
)
|
|
430
463
|
|
|
431
464
|
ModelMeta = _get_model_meta_class()
|
|
432
465
|
model_meta = ModelMeta.objects.filter(name=meta["name"], model=ai_model).first()
|
|
433
466
|
if model_meta:
|
|
434
|
-
logger.info(
|
|
467
|
+
logger.info(
|
|
468
|
+
f"ModelMeta {meta['name']} for model {ai_model.name} already exists. Skipping creation."
|
|
469
|
+
)
|
|
435
470
|
return model_meta
|
|
436
471
|
|
|
437
472
|
return create_from_file_logic(
|
|
@@ -2,11 +2,10 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
# Removed hash utils, datetime, random, os, timezone, sha256 imports
|
|
4
4
|
# Removed icecream import (was used in old save logic)
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Dict,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Type, cast
|
|
6
6
|
|
|
7
7
|
from django.db import models
|
|
8
8
|
|
|
9
|
-
from ..administration.person.patient import PatientExternalID
|
|
10
9
|
|
|
11
10
|
# Import models needed for type hints and FKs
|
|
12
11
|
from ..state import SensitiveMetaState # Needed for post-save state check
|
|
@@ -34,8 +33,6 @@ class SensitiveMeta(models.Model):
|
|
|
34
33
|
Stores potentially sensitive information extracted from media.
|
|
35
34
|
Logic for creation, hashing, pseudo-anonymization, and saving is in sensitive_meta_logic.py.
|
|
36
35
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
36
|
|
|
40
37
|
# --- Examination and Patient Info ---
|
|
41
38
|
examination_date = models.DateField(blank=True, null=True)
|
|
@@ -43,44 +40,72 @@ class SensitiveMeta(models.Model):
|
|
|
43
40
|
casenumber = models.CharField(max_length=255, blank=True, null=True)
|
|
44
41
|
file_path = models.CharField(max_length=1024, blank=True, null=True)
|
|
45
42
|
|
|
46
|
-
|
|
47
43
|
# --- Core FKs ---
|
|
48
|
-
pseudo_patient = models.ForeignKey(
|
|
44
|
+
pseudo_patient = models.ForeignKey(
|
|
45
|
+
"Patient",
|
|
46
|
+
on_delete=models.CASCADE,
|
|
47
|
+
blank=True,
|
|
48
|
+
null=True,
|
|
49
|
+
help_text="FK to the pseudo-anonymized Patient record.",
|
|
50
|
+
)
|
|
49
51
|
pseudo_examination = models.ForeignKey(
|
|
50
|
-
"PatientExamination",
|
|
52
|
+
"PatientExamination",
|
|
53
|
+
on_delete=models.CASCADE,
|
|
54
|
+
blank=True,
|
|
55
|
+
null=True,
|
|
56
|
+
help_text="FK to the pseudo-anonymized PatientExamination record.",
|
|
57
|
+
)
|
|
58
|
+
patient_gender = models.ForeignKey(
|
|
59
|
+
"Gender", on_delete=models.CASCADE, blank=True, null=True
|
|
60
|
+
)
|
|
61
|
+
examiners = models.ManyToManyField(
|
|
62
|
+
"Examiner", blank=True, help_text="Pseudo-anonymized examiner(s)"
|
|
63
|
+
)
|
|
64
|
+
center = models.ForeignKey(
|
|
65
|
+
"Center", on_delete=models.CASCADE, blank=True, null=True
|
|
51
66
|
)
|
|
52
|
-
patient_gender = models.ForeignKey("Gender", on_delete=models.CASCADE, blank=True, null=True)
|
|
53
|
-
examiners = models.ManyToManyField("Examiner", blank=True, help_text="Pseudo-anonymized examiner(s)")
|
|
54
|
-
center = models.ForeignKey("Center", on_delete=models.CASCADE, blank=True, null=True)
|
|
55
67
|
|
|
56
68
|
# --- Names and DOB ---
|
|
57
69
|
patient_first_name = models.CharField(max_length=255, blank=True, null=True)
|
|
58
70
|
patient_last_name = models.CharField(max_length=255, blank=True, null=True)
|
|
59
|
-
patient_dob = models.DateTimeField(
|
|
71
|
+
patient_dob = models.DateTimeField(
|
|
72
|
+
blank=True, null=True, help_text="Date of birth (can be auto-generated)."
|
|
73
|
+
)
|
|
60
74
|
|
|
61
|
-
examiner_first_name = models.CharField(
|
|
62
|
-
|
|
75
|
+
examiner_first_name = models.CharField(
|
|
76
|
+
max_length=255, blank=True, null=True, editable=False
|
|
77
|
+
)
|
|
78
|
+
examiner_last_name = models.CharField(
|
|
79
|
+
max_length=255, blank=True, null=True, editable=False
|
|
80
|
+
)
|
|
63
81
|
|
|
64
82
|
# --- Hashes ---
|
|
65
|
-
patient_hash = models.CharField(
|
|
66
|
-
|
|
83
|
+
patient_hash = models.CharField(
|
|
84
|
+
max_length=64, blank=True, null=True, editable=False, db_index=True
|
|
85
|
+
)
|
|
86
|
+
examination_hash = models.CharField(
|
|
87
|
+
max_length=64, blank=True, null=True, editable=False, db_index=True
|
|
88
|
+
)
|
|
67
89
|
|
|
68
90
|
# --- Endoscope Info ---
|
|
69
91
|
endoscope_type = models.CharField(max_length=255, blank=True, null=True)
|
|
70
92
|
endoscope_sn = models.CharField(max_length=255, blank=True, null=True)
|
|
71
93
|
|
|
72
94
|
# --- External patient ID ---
|
|
73
|
-
external_id = models.ForeignKey(
|
|
95
|
+
external_id = models.ForeignKey(
|
|
96
|
+
"PatientExternalID", on_delete=models.CASCADE, blank=True, null=True
|
|
97
|
+
)
|
|
74
98
|
|
|
75
99
|
if TYPE_CHECKING:
|
|
76
100
|
pseudo_patient: models.ForeignKey["Patient|None"]
|
|
77
101
|
|
|
78
102
|
patient_gender: models.ForeignKey["Gender|None"]
|
|
79
103
|
pseudo_examination: models.ForeignKey["PatientExamination|None"]
|
|
80
|
-
state: models.ForeignKey[
|
|
104
|
+
state: models.ForeignKey[
|
|
105
|
+
"SensitiveMetaState|None"
|
|
106
|
+
] # Assuming related_name='state' is defined on SensitiveMetaState.origin
|
|
81
107
|
center: models.ForeignKey["Center|None"]
|
|
82
108
|
|
|
83
|
-
examiners = cast(models.manager.RelatedManager["Examiner"], examiners)
|
|
84
109
|
|
|
85
110
|
@property
|
|
86
111
|
def external_id_origin(self) -> str | None:
|
|
@@ -92,7 +117,7 @@ class SensitiveMeta(models.Model):
|
|
|
92
117
|
# --- Text Fields ---
|
|
93
118
|
text = models.TextField(blank=True, null=True)
|
|
94
119
|
anonymized_text = models.TextField(blank=True, null=True)
|
|
95
|
-
|
|
120
|
+
|
|
96
121
|
# --- Anonymization helper method ---
|
|
97
122
|
create_anonymized_record = logic._create_anonymized_record
|
|
98
123
|
|
|
@@ -142,14 +167,18 @@ class SensitiveMeta(models.Model):
|
|
|
142
167
|
# Keep this method for basic representation, ensure fields are accessed safely
|
|
143
168
|
center_name = self.center.name if self.center else "None"
|
|
144
169
|
gender_str = str(self.patient_gender) if self.patient_gender else "None"
|
|
145
|
-
dob_str =
|
|
170
|
+
dob_str = (
|
|
171
|
+
str(self.patient_dob.date()) if self.patient_dob else "None"
|
|
172
|
+
) # Show only date part
|
|
146
173
|
exam_date_str = str(self.examination_date) if self.examination_date else "None"
|
|
147
174
|
|
|
148
175
|
examiners_str = "[Not saved yet]"
|
|
149
176
|
if self.pk:
|
|
150
177
|
try:
|
|
151
178
|
# Use prefetch_related in queries accessing this for efficiency
|
|
152
|
-
examiners_str =
|
|
179
|
+
examiners_str = (
|
|
180
|
+
", ".join([str(e) for e in self.examiners.all()]) or "[None]"
|
|
181
|
+
)
|
|
153
182
|
except Exception as e:
|
|
154
183
|
examiners_str = f"[Error: {e}]"
|
|
155
184
|
|
|
@@ -173,7 +202,9 @@ class SensitiveMeta(models.Model):
|
|
|
173
202
|
def state_safe(self) -> "SensitiveMetaState":
|
|
174
203
|
state = self.state
|
|
175
204
|
if not state:
|
|
176
|
-
raise SensitiveMetaState.DoesNotExist(
|
|
205
|
+
raise SensitiveMetaState.DoesNotExist(
|
|
206
|
+
"SensitiveMetaState does not exist for this SensitiveMeta instance."
|
|
207
|
+
)
|
|
177
208
|
return state
|
|
178
209
|
|
|
179
210
|
@property
|
|
@@ -217,13 +248,18 @@ class SensitiveMeta(models.Model):
|
|
|
217
248
|
if self.pk:
|
|
218
249
|
state, created = SensitiveMetaState.objects.get_or_create(origin=self)
|
|
219
250
|
if created:
|
|
220
|
-
logger.info(
|
|
251
|
+
logger.info(
|
|
252
|
+
"Created new SensitiveMetaState for SensitiveMeta %s (via get_or_create)",
|
|
253
|
+
self.pk,
|
|
254
|
+
)
|
|
221
255
|
# Link the state back to the instance in memory
|
|
222
256
|
self.state = state
|
|
223
257
|
return state
|
|
224
258
|
else:
|
|
225
259
|
# Cannot create state if the main instance has no PK
|
|
226
|
-
raise ValueError(
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"Cannot get or create state for an unsaved SensitiveMeta instance."
|
|
262
|
+
)
|
|
227
263
|
|
|
228
264
|
def __repr__(self):
|
|
229
265
|
return self.__str__()
|
|
@@ -274,7 +310,11 @@ class SensitiveMeta(models.Model):
|
|
|
274
310
|
SensitiveMetaState.objects.create(origin=self)
|
|
275
311
|
|
|
276
312
|
# 4. Handle ManyToMany linking (examiners) *after* the instance has a PK.
|
|
277
|
-
if
|
|
313
|
+
if (
|
|
314
|
+
examiner_to_link
|
|
315
|
+
and self.pk
|
|
316
|
+
and not self.examiners.filter(pk=examiner_to_link.pk).exists()
|
|
317
|
+
):
|
|
278
318
|
self.examiners.add(examiner_to_link)
|
|
279
319
|
# Adding to M2M handles its own DB interaction, no second super().save() needed.
|
|
280
320
|
|
|
@@ -303,6 +343,3 @@ class SensitiveMeta(models.Model):
|
|
|
303
343
|
This method delegates the update operation to the external logic module responsible for managing name data.
|
|
304
344
|
"""
|
|
305
345
|
logic.update_name_db(first_name, last_name)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
@@ -1017,7 +1017,7 @@ def update_sensitive_meta_from_dict(
|
|
|
1017
1017
|
logger.debug(
|
|
1018
1018
|
"Parsed string patient_dob '%s' during update to aware datetime: %s",
|
|
1019
1019
|
v,
|
|
1020
|
-
|
|
1020
|
+
aware_dob,
|
|
1021
1021
|
)
|
|
1022
1022
|
else:
|
|
1023
1023
|
logger.warning(
|
|
@@ -1025,35 +1025,37 @@ def update_sensitive_meta_from_dict(
|
|
|
1025
1025
|
v,
|
|
1026
1026
|
)
|
|
1027
1027
|
continue
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1028
|
+
elif k == "examination_date":
|
|
1029
|
+
if isinstance(v, str):
|
|
1030
|
+
parsed = parse_any_date(v)
|
|
1031
|
+
if parsed:
|
|
1032
|
+
value_to_set = (
|
|
1033
|
+
parsed # field is DateField, so keep it as date
|
|
1034
|
+
)
|
|
1035
|
+
logger.debug(
|
|
1036
|
+
"Parsed string examination_date '%s' during update to date: %s",
|
|
1037
|
+
v,
|
|
1038
|
+
value_to_set,
|
|
1039
|
+
)
|
|
1040
|
+
else:
|
|
1041
|
+
logger.warning(
|
|
1042
|
+
"Could not parse examination_date string '%s' during update, skipping",
|
|
1043
|
+
v,
|
|
1044
|
+
)
|
|
1045
|
+
continue
|
|
1046
|
+
elif isinstance(v, date):
|
|
1047
|
+
value_to_set = v
|
|
1046
1048
|
|
|
1047
1049
|
# --- End Conversion ---
|
|
1048
1050
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1051
|
+
# Check if patient name is changing
|
|
1052
|
+
if (
|
|
1053
|
+
k in ["patient_first_name", "patient_last_name"]
|
|
1054
|
+
and getattr(instance, k) != value_to_set
|
|
1055
|
+
):
|
|
1056
|
+
patient_name_changed = True
|
|
1055
1057
|
|
|
1056
|
-
|
|
1058
|
+
setattr(instance, k, value_to_set) # Use value_to_set
|
|
1057
1059
|
|
|
1058
1060
|
except Exception as e:
|
|
1059
1061
|
logger.error(
|