endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.9__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/authz/auth.py +74 -0
- endoreg_db/authz/backends.py +168 -0
- endoreg_db/authz/management/commands/list_routes.py +18 -0
- endoreg_db/authz/middleware.py +83 -0
- endoreg_db/authz/permissions.py +127 -0
- endoreg_db/authz/policy.py +218 -0
- endoreg_db/authz/views_auth.py +66 -0
- endoreg_db/config/env.py +13 -8
- endoreg_db/data/__init__.py +2 -11
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +3 -3
- endoreg_db/data/event_classification/data.yaml +4 -0
- endoreg_db/data/event_classification_choice/data.yaml +9 -0
- endoreg_db/data/examination/examinations/data.yaml +114 -14
- endoreg_db/data/examination/time-type/data.yaml +0 -3
- endoreg_db/data/examination_indication/endoscopy.yaml +108 -173
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +0 -70
- endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +33 -37
- endoreg_db/data/finding/00_generic.yaml +35 -0
- endoreg_db/data/finding/00_generic_complication.yaml +9 -0
- endoreg_db/data/finding/01_gastroscopy_baseline.yaml +88 -0
- endoreg_db/data/finding/01_gastroscopy_observation.yaml +113 -0
- endoreg_db/data/finding/02_colonoscopy_baseline.yaml +53 -0
- endoreg_db/data/finding/02_colonoscopy_hidden.yaml +119 -0
- endoreg_db/data/finding/02_colonoscopy_observation.yaml +152 -0
- endoreg_db/data/finding_classification/00_generic.yaml +44 -0
- endoreg_db/data/finding_classification/00_generic_histology.yaml +28 -0
- endoreg_db/data/finding_classification/00_generic_lesion.yaml +52 -0
- endoreg_db/data/finding_classification/02_colonoscopy_baseline.yaml +83 -0
- endoreg_db/data/finding_classification/02_colonoscopy_histology.yaml +13 -0
- endoreg_db/data/finding_classification/02_colonoscopy_other.yaml +12 -0
- endoreg_db/data/finding_classification/02_colonoscopy_polyp.yaml +101 -0
- endoreg_db/data/finding_classification_choice/{yes_no_na.yaml → 00_generic.yaml} +5 -1
- endoreg_db/data/finding_classification_choice/{examination_setting_generic_types.yaml → 00_generic_baseline.yaml} +10 -2
- endoreg_db/data/finding_classification_choice/{complication_generic_types.yaml → 00_generic_complication.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology.yaml → 00_generic_histology.yaml} +1 -4
- endoreg_db/data/finding_classification_choice/00_generic_lesion.yaml +158 -0
- endoreg_db/data/finding_classification_choice/{bowel_preparation.yaml → 02_colonoscopy_bowel_preparation.yaml} +1 -30
- endoreg_db/data/finding_classification_choice/{colonoscopy_not_complete_reason.yaml → 02_colonoscopy_generic.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology_polyp.yaml → 02_colonoscopy_histology.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{colonoscopy_location.yaml → 02_colonoscopy_location.yaml} +23 -4
- endoreg_db/data/finding_classification_choice/02_colonoscopy_other.yaml +34 -0
- endoreg_db/data/finding_classification_choice/02_colonoscopy_polyp_advanced_imaging.yaml +76 -0
- endoreg_db/data/finding_classification_choice/{colon_lesion_paris.yaml → 02_colonoscopy_polyp_morphology.yaml} +26 -8
- endoreg_db/data/finding_classification_choice/02_colonoscopy_size.yaml +27 -0
- endoreg_db/data/finding_classification_type/{colonoscopy_basic.yaml → 00_generic.yaml} +18 -13
- endoreg_db/data/finding_classification_type/02_colonoscopy.yaml +9 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy.yaml +59 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_ablation.yaml +44 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_bleeding.yaml +55 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_resection.yaml +85 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_stenosis.yaml +17 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_stent.yaml +9 -0
- endoreg_db/data/finding_intervention/01_gastroscopy.yaml +19 -0
- endoreg_db/data/finding_intervention/04_eus.yaml +39 -0
- endoreg_db/data/finding_intervention/05_ercp.yaml +3 -0
- endoreg_db/data/finding_type/data.yaml +8 -12
- endoreg_db/data/requirement/01_patient_data.yaml +93 -0
- endoreg_db/data/requirement/old/colon_polyp_intervention.yaml +49 -0
- endoreg_db/data/requirement/old/coloreg_colon_polyp.yaml +49 -0
- endoreg_db/data/requirement_operator/new_operators.yaml +36 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +29 -12
- endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
- endoreg_db/data/requirement_set/{endoscopy_bleeding_risk.yaml → 02_endoscopy_bleeding_risk.yaml} +0 -6
- endoreg_db/data/requirement_set/90_coloreg.yaml +190 -0
- endoreg_db/data/requirement_set/_old_ +109 -0
- endoreg_db/data/requirement_set_type/data.yaml +21 -0
- endoreg_db/data/setup_config.yaml +4 -4
- endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
- endoreg_db/exceptions.py +4 -2
- endoreg_db/forms/examination_form.py +1 -1
- endoreg_db/helpers/data_loader.py +125 -53
- endoreg_db/helpers/default_objects.py +116 -81
- endoreg_db/import_files/__init__.py +27 -0
- endoreg_db/import_files/context/__init__.py +7 -0
- endoreg_db/import_files/context/default_sensitive_meta.py +81 -0
- endoreg_db/import_files/context/ensure_center.py +17 -0
- endoreg_db/import_files/context/file_lock.py +66 -0
- endoreg_db/import_files/context/import_context.py +43 -0
- endoreg_db/import_files/context/validate_directories.py +56 -0
- endoreg_db/import_files/file_storage/__init__.py +15 -0
- endoreg_db/import_files/file_storage/create_report_file.py +76 -0
- endoreg_db/import_files/file_storage/create_video_file.py +75 -0
- endoreg_db/import_files/file_storage/sensitive_meta_storage.py +39 -0
- endoreg_db/import_files/file_storage/state_management.py +400 -0
- endoreg_db/import_files/file_storage/storage.py +36 -0
- endoreg_db/import_files/import_service.md +26 -0
- endoreg_db/import_files/processing/__init__.py +11 -0
- endoreg_db/import_files/processing/report_processing/report_anonymization.py +94 -0
- endoreg_db/import_files/processing/sensitive_meta_adapter.py +51 -0
- endoreg_db/import_files/processing/video_processing/video_anonymization.py +107 -0
- endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +119 -0
- endoreg_db/import_files/pseudonymization/fake.py +52 -0
- endoreg_db/import_files/pseudonymization/k_anonymity.py +182 -0
- endoreg_db/import_files/pseudonymization/k_pseudonymity.py +128 -0
- endoreg_db/import_files/report_import_service.py +141 -0
- endoreg_db/import_files/video_import_service.py +150 -0
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
- endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
- endoreg_db/management/commands/import_report.py +130 -65
- endoreg_db/management/commands/import_video.py +9 -10
- endoreg_db/management/commands/import_video_with_classification.py +2 -2
- endoreg_db/management/commands/list_routes.py +18 -0
- endoreg_db/management/commands/load_ai_model_data.py +5 -5
- endoreg_db/management/commands/load_ai_model_label_data.py +9 -7
- endoreg_db/management/commands/load_base_db_data.py +5 -134
- endoreg_db/management/commands/load_center_data.py +12 -12
- endoreg_db/management/commands/load_contraindication_data.py +14 -16
- endoreg_db/management/commands/load_disease_classification_choices_data.py +15 -18
- endoreg_db/management/commands/load_disease_classification_data.py +15 -18
- endoreg_db/management/commands/load_disease_data.py +25 -28
- endoreg_db/management/commands/load_endoscope_data.py +20 -27
- endoreg_db/management/commands/load_event_data.py +14 -16
- endoreg_db/management/commands/load_examination_data.py +31 -44
- endoreg_db/management/commands/load_examination_indication_data.py +20 -21
- endoreg_db/management/commands/load_finding_data.py +52 -80
- endoreg_db/management/commands/load_information_source.py +21 -23
- endoreg_db/management/commands/load_lab_value_data.py +17 -26
- endoreg_db/management/commands/load_medication_data.py +13 -12
- endoreg_db/management/commands/load_organ_data.py +15 -19
- endoreg_db/management/commands/load_pdf_type_data.py +19 -18
- endoreg_db/management/commands/load_profession_data.py +14 -17
- endoreg_db/management/commands/load_qualification_data.py +20 -23
- endoreg_db/management/commands/load_report_reader_flag_data.py +17 -19
- endoreg_db/management/commands/load_requirement_data.py +62 -39
- endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
- endoreg_db/management/commands/load_risk_data.py +7 -6
- endoreg_db/management/commands/load_shift_data.py +20 -23
- endoreg_db/management/commands/load_tag_data.py +8 -11
- endoreg_db/management/commands/load_unit_data.py +17 -19
- endoreg_db/management/commands/setup_endoreg_db.py +3 -3
- endoreg_db/management/commands/start_filewatcher.py +46 -37
- endoreg_db/management/commands/storage_management.py +271 -203
- endoreg_db/management/commands/validate_video_files.py +1 -5
- endoreg_db/migrations/0001_initial.py +297 -250
- endoreg_db/models/__init__.py +78 -123
- endoreg_db/models/administration/__init__.py +21 -42
- endoreg_db/models/administration/ai/active_model.py +2 -2
- endoreg_db/models/administration/ai/ai_model.py +7 -6
- endoreg_db/models/administration/case/__init__.py +1 -15
- endoreg_db/models/administration/case/case.py +3 -3
- endoreg_db/models/administration/case/case_template/__init__.py +2 -14
- endoreg_db/models/administration/case/case_template/case_template.py +2 -124
- endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
- endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
- endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
- endoreg_db/models/administration/center/center.py +33 -19
- endoreg_db/models/administration/center/center_product.py +12 -9
- endoreg_db/models/administration/center/center_resource.py +25 -19
- endoreg_db/models/administration/center/center_shift.py +21 -17
- endoreg_db/models/administration/center/center_waste.py +16 -8
- endoreg_db/models/administration/person/__init__.py +2 -0
- endoreg_db/models/administration/person/employee/employee.py +10 -5
- endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
- endoreg_db/models/administration/person/employee/employee_type.py +12 -6
- endoreg_db/models/administration/person/examiner/examiner.py +13 -11
- endoreg_db/models/administration/person/patient/__init__.py +2 -0
- endoreg_db/models/administration/person/patient/patient.py +129 -100
- endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
- endoreg_db/models/administration/person/person.py +4 -0
- endoreg_db/models/administration/person/profession/__init__.py +8 -4
- endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
- endoreg_db/models/administration/product/product.py +20 -15
- endoreg_db/models/administration/product/product_material.py +17 -18
- endoreg_db/models/administration/product/product_weight.py +12 -8
- endoreg_db/models/administration/product/reference_product.py +23 -55
- endoreg_db/models/administration/qualification/qualification.py +7 -3
- endoreg_db/models/administration/qualification/qualification_type.py +7 -3
- endoreg_db/models/administration/shift/scheduled_days.py +8 -5
- endoreg_db/models/administration/shift/shift.py +16 -12
- endoreg_db/models/administration/shift/shift_type.py +23 -31
- endoreg_db/models/label/__init__.py +8 -9
- endoreg_db/models/label/annotation/image_classification.py +10 -9
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +23 -28
- endoreg_db/models/label/label.py +15 -15
- endoreg_db/models/label/label_set.py +19 -6
- endoreg_db/models/label/label_type.py +1 -1
- endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
- endoreg_db/models/label/label_video_segment/label_video_segment.py +98 -102
- endoreg_db/models/label/video_segmentation_label.py +4 -0
- endoreg_db/models/label/video_segmentation_labelset.py +4 -3
- endoreg_db/models/media/frame/frame.py +22 -22
- endoreg_db/models/media/pdf/raw_pdf.py +194 -194
- endoreg_db/models/media/pdf/report_file.py +25 -29
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +55 -47
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
- endoreg_db/models/media/processing_history/__init__.py +5 -0
- endoreg_db/models/media/processing_history/processing_history.py +96 -0
- endoreg_db/models/media/video/__init__.py +1 -0
- endoreg_db/models/media/video/create_from_file.py +139 -77
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +174 -112
- endoreg_db/models/media/video/video_file_ai.py +288 -74
- endoreg_db/models/media/video/video_file_anonymize.py +38 -38
- endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
- endoreg_db/models/media/video/video_file_io.py +113 -61
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
- endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
- endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
- endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
- endoreg_db/models/media/video/video_file_segments.py +24 -17
- endoreg_db/models/media/video/video_metadata.py +19 -35
- endoreg_db/models/media/video/video_processing.py +96 -95
- endoreg_db/models/medical/contraindication/README.md +1 -0
- endoreg_db/models/medical/contraindication/__init__.py +13 -3
- endoreg_db/models/medical/disease.py +22 -16
- endoreg_db/models/medical/event.py +31 -18
- endoreg_db/models/medical/examination/__init__.py +13 -6
- endoreg_db/models/medical/examination/examination.py +39 -20
- endoreg_db/models/medical/examination/examination_indication.py +30 -95
- endoreg_db/models/medical/examination/examination_time.py +23 -8
- endoreg_db/models/medical/examination/examination_time_type.py +9 -6
- endoreg_db/models/medical/examination/examination_type.py +3 -4
- endoreg_db/models/medical/finding/finding.py +32 -40
- endoreg_db/models/medical/finding/finding_classification.py +42 -72
- endoreg_db/models/medical/finding/finding_intervention.py +25 -22
- endoreg_db/models/medical/finding/finding_type.py +13 -12
- endoreg_db/models/medical/hardware/endoscope.py +26 -26
- endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
- endoreg_db/models/medical/laboratory/lab_value.py +62 -91
- endoreg_db/models/medical/medication/medication.py +22 -10
- endoreg_db/models/medical/medication/medication_indication.py +29 -3
- endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
- endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
- endoreg_db/models/medical/medication/medication_schedule.py +27 -16
- endoreg_db/models/medical/organ/__init__.py +15 -12
- endoreg_db/models/medical/patient/medication_examples.py +6 -6
- endoreg_db/models/medical/patient/patient_disease.py +20 -23
- endoreg_db/models/medical/patient/patient_event.py +19 -22
- endoreg_db/models/medical/patient/patient_examination.py +48 -54
- endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
- endoreg_db/models/medical/patient/patient_finding.py +122 -139
- endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
- endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
- endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
- endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
- endoreg_db/models/medical/patient/patient_medication.py +27 -38
- endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
- endoreg_db/models/medical/risk/risk.py +7 -6
- endoreg_db/models/medical/risk/risk_type.py +8 -5
- endoreg_db/models/metadata/model_meta.py +60 -29
- endoreg_db/models/metadata/model_meta_logic.py +125 -18
- endoreg_db/models/metadata/pdf_meta.py +31 -24
- endoreg_db/models/metadata/sensitive_meta.py +105 -85
- endoreg_db/models/metadata/sensitive_meta_logic.py +198 -103
- endoreg_db/models/metadata/video_meta.py +51 -31
- endoreg_db/models/metadata/video_prediction_logic.py +16 -23
- endoreg_db/models/metadata/video_prediction_meta.py +29 -33
- endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
- endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
- endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
- endoreg_db/models/other/emission/emission_factor.py +18 -8
- endoreg_db/models/other/gender.py +10 -5
- endoreg_db/models/other/information_source.py +50 -29
- endoreg_db/models/other/material.py +9 -5
- endoreg_db/models/other/resource.py +6 -4
- endoreg_db/models/other/tag.py +10 -5
- endoreg_db/models/other/transport_route.py +13 -8
- endoreg_db/models/other/unit.py +10 -6
- endoreg_db/models/other/waste.py +6 -5
- endoreg_db/models/report/report.py +6 -0
- endoreg_db/models/requirement/requirement.py +329 -361
- endoreg_db/models/requirement/requirement_error.py +85 -0
- endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
- endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
- endoreg_db/models/requirement/requirement_operator.py +103 -112
- endoreg_db/models/requirement/requirement_set.py +74 -57
- endoreg_db/models/state/__init__.py +4 -4
- endoreg_db/models/state/abstract.py +2 -2
- endoreg_db/models/state/anonymization.py +12 -0
- endoreg_db/models/state/audit_ledger.py +49 -51
- endoreg_db/models/state/label_video_segment.py +9 -0
- endoreg_db/models/state/raw_pdf.py +101 -68
- endoreg_db/models/state/sensitive_meta.py +6 -2
- endoreg_db/models/state/video.py +110 -90
- endoreg_db/models/upload_job.py +35 -34
- endoreg_db/models/utils.py +28 -25
- endoreg_db/queries/__init__.py +3 -1
- endoreg_db/root_urls.py +21 -2
- endoreg_db/schemas/examination_evaluation.py +1 -1
- endoreg_db/serializers/__init__.py +2 -10
- endoreg_db/serializers/anonymization.py +18 -10
- endoreg_db/serializers/label_video_segment/label_video_segment.py +2 -29
- endoreg_db/serializers/meta/__init__.py +1 -6
- endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
- endoreg_db/serializers/misc/file_overview.py +11 -99
- endoreg_db/serializers/misc/sensitive_patient_data.py +50 -26
- endoreg_db/serializers/patient_examination/patient_examination.py +3 -3
- endoreg_db/serializers/pdf/anony_text_validation.py +39 -23
- endoreg_db/serializers/requirements/requirement_sets.py +92 -22
- endoreg_db/serializers/video/segmentation.py +2 -1
- endoreg_db/serializers/video/video_file_list.py +65 -34
- endoreg_db/serializers/video/video_processing_history.py +20 -5
- endoreg_db/services/__old/pdf_import.py +1487 -0
- endoreg_db/services/__old/video_import.py +1306 -0
- endoreg_db/services/anonymization.py +128 -89
- endoreg_db/services/lookup_service.py +65 -52
- endoreg_db/services/lookup_store.py +2 -2
- endoreg_db/services/pdf_import.py +0 -1382
- endoreg_db/services/report_import.py +10 -0
- endoreg_db/services/video_import.py +6 -1255
- endoreg_db/tasks/upload_tasks.py +79 -70
- endoreg_db/tasks/video_ingest.py +8 -4
- endoreg_db/urls/__init__.py +5 -32
- endoreg_db/urls/ai.py +32 -0
- endoreg_db/urls/media.py +121 -83
- endoreg_db/urls/root_urls.py +29 -0
- endoreg_db/utils/__init__.py +15 -5
- endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
- endoreg_db/utils/case_generator/__init__.py +3 -0
- endoreg_db/utils/dataloader.py +142 -40
- endoreg_db/utils/defaults/set_default_center.py +32 -0
- endoreg_db/utils/names.py +22 -16
- endoreg_db/utils/paths.py +110 -46
- endoreg_db/utils/permissions.py +2 -1
- endoreg_db/utils/pipelines/Readme.md +1 -1
- endoreg_db/utils/pipelines/process_video_dir.py +1 -1
- endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +655 -0
- endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +97 -0
- endoreg_db/utils/setup_config.py +8 -5
- endoreg_db/utils/storage.py +115 -0
- endoreg_db/utils/validate_endo_roi.py +8 -2
- endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
- endoreg_db/views/__init__.py +85 -183
- endoreg_db/views/ai/__init__.py +8 -0
- endoreg_db/views/ai/label.py +155 -0
- endoreg_db/views/anonymization/media_management.py +202 -166
- endoreg_db/views/anonymization/overview.py +99 -67
- endoreg_db/views/anonymization/validate.py +182 -44
- endoreg_db/views/media/__init__.py +7 -20
- endoreg_db/views/media/pdf_media.py +197 -174
- endoreg_db/views/media/sensitive_metadata.py +193 -138
- endoreg_db/views/media/video_media.py +89 -82
- endoreg_db/views/meta/__init__.py +0 -8
- endoreg_db/views/misc/__init__.py +1 -7
- endoreg_db/views/misc/upload_views.py +94 -93
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/report/__init__.py +5 -7
- endoreg_db/views/{pdf → report}/reimport.py +22 -22
- endoreg_db/views/{pdf/pdf_stream.py → report/report_stream.py} +46 -39
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +17 -3
- endoreg_db/views/requirement/lookup_store.py +22 -90
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +23 -24
- endoreg_db/views/video/correction.py +201 -172
- endoreg_db/views/video/reimport.py +1 -1
- endoreg_db/views/{media/video_segments.py → video/segments_crud.py} +77 -40
- endoreg_db/views/video/{video_meta.py → video_meta_stats.py} +2 -2
- endoreg_db/views/video/video_stream.py +7 -8
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/RECORD +391 -413
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/WHEEL +1 -1
- endoreg_db/data/finding/anatomy_colon.yaml +0 -128
- endoreg_db/data/finding/colonoscopy.yaml +0 -40
- endoreg_db/data/finding/colonoscopy_bowel_prep.yaml +0 -56
- endoreg_db/data/finding/complication.yaml +0 -16
- endoreg_db/data/finding/data.yaml +0 -105
- endoreg_db/data/finding/examination_setting.yaml +0 -16
- endoreg_db/data/finding/medication_related.yaml +0 -18
- endoreg_db/data/finding/outcome.yaml +0 -12
- endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +0 -95
- endoreg_db/data/finding_classification/colonoscopy_jnet.yaml +0 -22
- endoreg_db/data/finding_classification/colonoscopy_kudo.yaml +0 -25
- endoreg_db/data/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +0 -68
- endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +0 -80
- endoreg_db/data/finding_classification/colonoscopy_lst.yaml +0 -21
- endoreg_db/data/finding_classification/colonoscopy_nice.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_paris.yaml +0 -26
- endoreg_db/data/finding_classification/colonoscopy_sano.yaml +0 -22
- endoreg_db/data/finding_classification/colonoscopy_summary.yaml +0 -53
- endoreg_db/data/finding_classification/complication_generic.yaml +0 -25
- endoreg_db/data/finding_classification/examination_setting_generic.yaml +0 -40
- endoreg_db/data/finding_classification/histology_colo.yaml +0 -51
- endoreg_db/data/finding_classification/intervention_required.yaml +0 -26
- endoreg_db/data/finding_classification/medication_related.yaml +0 -23
- endoreg_db/data/finding_classification/visualized.yaml +0 -33
- endoreg_db/data/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
- endoreg_db/data/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
- endoreg_db/data/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
- endoreg_db/data/finding_classification_choice/colon_lesion_lst.yaml +0 -15
- endoreg_db/data/finding_classification_choice/colon_lesion_nice.yaml +0 -17
- endoreg_db/data/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
- endoreg_db/data/finding_classification_choice/colon_lesion_sano.yaml +0 -14
- endoreg_db/data/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
- endoreg_db/data/finding_classification_choice/colonoscopy_size.yaml +0 -82
- endoreg_db/data/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
- endoreg_db/data/finding_classification_choice/outcome.yaml +0 -19
- endoreg_db/data/finding_intervention/endoscopy.yaml +0 -43
- endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
- endoreg_db/data/finding_intervention/endoscopy_egd.yaml +0 -128
- endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +0 -32
- endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +0 -9
- endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +0 -36
- endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +0 -79
- endoreg_db/data/requirement/age.yaml +0 -26
- endoreg_db/data/requirement/gender.yaml +0 -25
- endoreg_db/management/commands/init_default_ai_model.py +0 -112
- endoreg_db/management/commands/reset_celery_schedule.py +0 -9
- endoreg_db/management/commands/validate_video.py +0 -204
- endoreg_db/migrations/0002_add_video_correction_models.py +0 -52
- endoreg_db/migrations/0003_add_center_display_name.py +0 -30
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/rule/__init__.py +0 -13
- endoreg_db/models/rule/rule.py +0 -27
- endoreg_db/models/rule/rule_applicator.py +0 -224
- endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
- endoreg_db/models/rule/rule_type.py +0 -20
- endoreg_db/models/rule/ruleset.py +0 -17
- endoreg_db/renames.yml +0 -8
- endoreg_db/serializers/_old/raw_pdf_meta_validation.py +0 -223
- endoreg_db/serializers/_old/raw_video_meta_validation.py +0 -179
- endoreg_db/serializers/_old/video.py +0 -71
- endoreg_db/serializers/meta/pdf_file_meta_extraction.py +0 -115
- endoreg_db/serializers/meta/report_meta.py +0 -53
- endoreg_db/serializers/report/__init__.py +0 -9
- endoreg_db/serializers/report/mixins.py +0 -45
- endoreg_db/serializers/report/report.py +0 -105
- endoreg_db/serializers/report/report_list.py +0 -22
- endoreg_db/serializers/report/secure_file_url.py +0 -26
- endoreg_db/serializers/video/video_metadata.py +0 -105
- endoreg_db/services/requirements_object.py +0 -147
- endoreg_db/services/storage_aware_video_processor.py +0 -344
- endoreg_db/urls/files.py +0 -6
- endoreg_db/urls/label_video_segment_validate.py +0 -33
- endoreg_db/urls/label_video_segments.py +0 -46
- endoreg_db/urls/report.py +0 -48
- endoreg_db/urls/video.py +0 -61
- endoreg_db/utils/case_generator/case_generator.py +0 -159
- endoreg_db/utils/case_generator/utils.py +0 -30
- endoreg_db/utils/requirement_operator_logic/model_evaluators.py +0 -368
- endoreg_db/views/label/__init__.py +0 -5
- endoreg_db/views/label/label.py +0 -15
- endoreg_db/views/label_video_segment/__init__.py +0 -16
- endoreg_db/views/label_video_segment/create_lvs_from_annotation.py +0 -44
- endoreg_db/views/label_video_segment/get_lvs_by_name_and_video.py +0 -50
- endoreg_db/views/label_video_segment/label_video_segment.py +0 -77
- endoreg_db/views/label_video_segment/label_video_segment_by_label.py +0 -174
- endoreg_db/views/label_video_segment/label_video_segment_detail.py +0 -73
- endoreg_db/views/label_video_segment/update_lvs_from_annotation.py +0 -46
- endoreg_db/views/label_video_segment/validate.py +0 -226
- endoreg_db/views/media/segments.py +0 -71
- endoreg_db/views/meta/available_files_list.py +0 -146
- endoreg_db/views/meta/report_meta.py +0 -53
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -148
- endoreg_db/views/misc/secure_file_serving_view.py +0 -80
- endoreg_db/views/misc/secure_file_url_view.py +0 -84
- endoreg_db/views/misc/secure_url_validate.py +0 -79
- endoreg_db/views/patient_examination/DEPRECATED_video_backup.py +0 -164
- endoreg_db/views/patient_finding_location/__init__.py +0 -5
- endoreg_db/views/patient_finding_location/pfl_create.py +0 -70
- endoreg_db/views/patient_finding_morphology/__init__.py +0 -5
- endoreg_db/views/patient_finding_morphology/pfm_create.py +0 -70
- endoreg_db/views/pdf/__init__.py +0 -8
- endoreg_db/views/report/report_list.py +0 -112
- endoreg_db/views/report/report_with_secure_url.py +0 -28
- endoreg_db/views/report/start_examination.py +0 -7
- endoreg_db/views/video/segmentation.py +0 -274
- endoreg_db/views/video/task_status.py +0 -49
- endoreg_db/views/video/timeline.py +0 -46
- endoreg_db/views/video/video_analyze.py +0 -52
- endoreg_db/views.py +0 -0
- /endoreg_db/data/requirement/{colonoscopy_baseline_austria.yaml → old/colonoscopy_baseline_austria.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_cardiovascular.yaml → old/disease_cardiovascular.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_classification_choice_cardiovascular.yaml → old/disease_classification_choice_cardiovascular.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_hepatology.yaml → old/disease_hepatology.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_misc.yaml → old/disease_misc.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_renal.yaml → old/disease_renal.yaml} +0 -0
- /endoreg_db/data/requirement/{endoscopy_bleeding_risk.yaml → old/endoscopy_bleeding_risk.yaml} +0 -0
- /endoreg_db/data/requirement/{event_cardiology.yaml → old/event_cardiology.yaml} +0 -0
- /endoreg_db/data/requirement/{event_requirements.yaml → old/event_requirements.yaml} +0 -0
- /endoreg_db/data/requirement/{finding_colon_polyp.yaml → old/finding_colon_polyp.yaml} +0 -0
- /endoreg_db/{migrations/__init__.py → data/requirement/old/gender.yaml} +0 -0
- /endoreg_db/data/requirement/{lab_value.yaml → old/lab_value.yaml} +0 -0
- /endoreg_db/data/requirement/{medication.yaml → old/medication.yaml} +0 -0
- /endoreg_db/data/requirement_operator/{age.yaml → _old/age.yaml} +0 -0
- /endoreg_db/data/requirement_operator/{lab_operators.yaml → _old/lab_operators.yaml} +0 -0
- /endoreg_db/data/requirement_operator/{model_operators.yaml → _old/model_operators.yaml} +0 -0
- /endoreg_db/{models/media/video/refactor_plan.md → import_files/pseudonymization/__init__.py} +0 -0
- /endoreg_db/{models/media/video/video_file_frames.py → import_files/pseudonymization/pseudonymize.py} +0 -0
- /endoreg_db/models/{metadata/frame_ocr_result.py → report/__init__.py} +0 -0
- /endoreg_db/{urls/sensitive_meta.py → models/report/images.py} +0 -0
- /endoreg_db/utils/requirement_operator_logic/{lab_value_operators.py → _old/lab_value_operators.py} +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/licenses/LICENSE +0 -0
endoreg_db/models/label/label.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
from django.db import models
|
|
2
1
|
from typing import TYPE_CHECKING
|
|
3
2
|
|
|
3
|
+
from django.db import models
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
-
from .label_type import LabelType
|
|
7
6
|
from .label_set import LabelSet
|
|
7
|
+
from .label_type import LabelType
|
|
8
|
+
|
|
9
|
+
|
|
8
10
|
class LabelManager(models.Manager):
|
|
9
11
|
"""Manager class for handling Label model operations."""
|
|
10
12
|
|
|
@@ -25,16 +27,16 @@ class Label(models.Model):
|
|
|
25
27
|
"""
|
|
26
28
|
|
|
27
29
|
name = models.CharField(max_length=255)
|
|
28
|
-
label_type = models.ForeignKey(
|
|
29
|
-
"LabelType", on_delete=models.CASCADE, related_name="labels", blank=True, null=True
|
|
30
|
-
)
|
|
30
|
+
label_type = models.ForeignKey("LabelType", on_delete=models.CASCADE, related_name="labels", blank=True, null=True)
|
|
31
31
|
description = models.TextField(blank=True, null=True)
|
|
32
32
|
|
|
33
33
|
objects = LabelManager()
|
|
34
34
|
|
|
35
35
|
if TYPE_CHECKING:
|
|
36
|
-
label_type: "LabelType"
|
|
37
|
-
|
|
36
|
+
label_type: models.ForeignKey["LabelType|None"]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def label_sets(self) -> models.QuerySet["LabelSet"]: ...
|
|
38
40
|
|
|
39
41
|
def natural_key(self):
|
|
40
42
|
"""Return the natural key of this label"""
|
|
@@ -42,7 +44,7 @@ class Label(models.Model):
|
|
|
42
44
|
|
|
43
45
|
def __str__(self):
|
|
44
46
|
return str(self.name)
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
@classmethod
|
|
47
49
|
def get_outside_label(cls):
|
|
48
50
|
"""
|
|
@@ -57,7 +59,7 @@ class Label(models.Model):
|
|
|
57
59
|
def get_low_quality_label(cls):
|
|
58
60
|
"""
|
|
59
61
|
Retrieve the label instance with the name 'low_quality'.
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
Raises:
|
|
62
64
|
ValueError: If a label with the name 'low_quality' does not exist.
|
|
63
65
|
"""
|
|
@@ -65,19 +67,17 @@ class Label(models.Model):
|
|
|
65
67
|
return cls.objects.get(name="low_quality")
|
|
66
68
|
except Exception as exc:
|
|
67
69
|
raise ValueError("'low_quality' label does not exist in the database") from exc
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
@classmethod
|
|
70
|
-
def get_or_create_from_name(cls, name:str):
|
|
72
|
+
def get_or_create_from_name(cls, name: str):
|
|
71
73
|
"""
|
|
72
74
|
Retrieve or create a Label instance with the specified name.
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
Parameters:
|
|
75
77
|
name (str): The name of the label to retrieve or create.
|
|
76
|
-
|
|
78
|
+
|
|
77
79
|
Returns:
|
|
78
80
|
tuple: A tuple containing the Label instance and a boolean indicating whether the instance was created (True) or retrieved (False).
|
|
79
81
|
"""
|
|
80
82
|
label, _created = cls.objects.get_or_create(name=name)
|
|
81
83
|
return label, _created
|
|
82
|
-
|
|
83
|
-
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
from django.db import models
|
|
2
1
|
from typing import TYPE_CHECKING
|
|
3
2
|
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
|
|
4
6
|
class LabelSetManager(models.Manager):
|
|
5
7
|
"""
|
|
6
8
|
Manager class for handling LabelSet model operations.
|
|
@@ -10,9 +12,17 @@ class LabelSetManager(models.Manager):
|
|
|
10
12
|
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
|
-
def get_by_natural_key(self, name):
|
|
14
|
-
"""Retrieves a LabelSet instance by its natural key (name)."""
|
|
15
|
-
|
|
15
|
+
def get_by_natural_key(self, name, version=None):
|
|
16
|
+
"""Retrieves a LabelSet instance by its natural key (name[, version])."""
|
|
17
|
+
|
|
18
|
+
queryset = self.filter(name=name)
|
|
19
|
+
if version not in (None, "", -1):
|
|
20
|
+
queryset = queryset.filter(version=version)
|
|
21
|
+
|
|
22
|
+
labelset = queryset.order_by("-version").first()
|
|
23
|
+
if not labelset:
|
|
24
|
+
raise self.model.DoesNotExist(f"LabelSet with name='{name}' and version='{version}' not found")
|
|
25
|
+
return labelset
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
class LabelSet(models.Model):
|
|
@@ -33,12 +43,15 @@ class LabelSet(models.Model):
|
|
|
33
43
|
objects = LabelSetManager()
|
|
34
44
|
|
|
35
45
|
if TYPE_CHECKING:
|
|
46
|
+
from typing import cast
|
|
47
|
+
|
|
36
48
|
from .label import Label
|
|
37
|
-
|
|
49
|
+
|
|
50
|
+
labels = cast(models.manager.RelatedManager["Label"], labels)
|
|
38
51
|
|
|
39
52
|
def natural_key(self):
|
|
40
53
|
"""Return the natural key of this label set"""
|
|
41
|
-
return (self.name,)
|
|
54
|
+
return (self.name, self.version)
|
|
42
55
|
|
|
43
56
|
def __str__(self) -> str:
|
|
44
57
|
return str(self.name)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Optional
|
|
2
2
|
|
|
3
3
|
if TYPE_CHECKING:
|
|
4
|
-
from endoreg_db.models import Label,
|
|
4
|
+
from endoreg_db.models import Label, VideoFile, VideoPredictionMeta
|
|
5
|
+
|
|
6
|
+
__all__ = ["_create_from_video"]
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def _create_from_video(
|
|
@@ -21,15 +23,10 @@ def _create_from_video(
|
|
|
21
23
|
raise ValueError("Source must be a VideoFile instance.")
|
|
22
24
|
|
|
23
25
|
if start_frame_number < 0 or end_frame_number < 0:
|
|
24
|
-
raise ValueError(
|
|
25
|
-
f"Frame numbers must be non-negative: start={start_frame_number}, end={end_frame_number}"
|
|
26
|
-
)
|
|
26
|
+
raise ValueError(f"Frame numbers must be non-negative: start={start_frame_number}, end={end_frame_number}")
|
|
27
27
|
|
|
28
28
|
if start_frame_number > end_frame_number:
|
|
29
|
-
raise ValueError(
|
|
30
|
-
f"Start frame number ({start_frame_number}) must be less than or equal to "
|
|
31
|
-
f"end frame number ({end_frame_number})"
|
|
32
|
-
)
|
|
29
|
+
raise ValueError(f"Start frame number ({start_frame_number}) must be less than or equal to end frame number ({end_frame_number})")
|
|
33
30
|
|
|
34
31
|
segment = cls(
|
|
35
32
|
start_frame_number=start_frame_number,
|
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
from django.db import models
|
|
2
|
-
from django.db.models import Q, CheckConstraint, F
|
|
3
|
-
from typing import TYPE_CHECKING, Union, Optional, Tuple
|
|
4
|
-
from tqdm import tqdm
|
|
5
1
|
import logging
|
|
2
|
+
from typing import TYPE_CHECKING, Optional, Tuple, Union, cast
|
|
3
|
+
|
|
6
4
|
from django.core.exceptions import ObjectDoesNotExist
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.db.models import CheckConstraint, F, Q
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
7
9
|
from ._create_from_video import _create_from_video
|
|
8
10
|
|
|
9
11
|
logger = logging.getLogger(__name__)
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
12
14
|
from endoreg_db.models import (
|
|
13
|
-
LabelVideoSegmentState,
|
|
14
|
-
VideoFile,
|
|
15
15
|
Frame,
|
|
16
|
-
|
|
16
|
+
ImageClassificationAnnotation,
|
|
17
17
|
InformationSource,
|
|
18
|
+
Label,
|
|
19
|
+
LabelVideoSegmentState,
|
|
18
20
|
ModelMeta,
|
|
19
|
-
VideoPredictionMeta,
|
|
20
21
|
PatientFinding,
|
|
21
|
-
|
|
22
|
+
VideoFile,
|
|
23
|
+
VideoPredictionMeta,
|
|
22
24
|
)
|
|
23
|
-
|
|
25
|
+
|
|
26
|
+
|
|
24
27
|
class LabelVideoSegment(models.Model):
|
|
25
28
|
"""
|
|
26
29
|
Represents a labeled segment within a video, defined by start and end frame numbers.
|
|
@@ -28,11 +31,10 @@ class LabelVideoSegment(models.Model):
|
|
|
28
31
|
A segment must be associated with exactly one `VideoFile`.
|
|
29
32
|
If it originates from a prediction, it links to a single `VideoPredictionMeta`.
|
|
30
33
|
"""
|
|
34
|
+
|
|
31
35
|
start_frame_number = models.IntegerField()
|
|
32
36
|
end_frame_number = models.IntegerField()
|
|
33
|
-
source = models.ForeignKey(
|
|
34
|
-
"InformationSource", on_delete=models.SET_NULL, null=True
|
|
35
|
-
)
|
|
37
|
+
source = models.ForeignKey("InformationSource", on_delete=models.SET_NULL, null=True)
|
|
36
38
|
label = models.ForeignKey("Label", on_delete=models.SET_NULL, null=True, blank=True)
|
|
37
39
|
|
|
38
40
|
# Single ForeignKey to the unified VideoFile model
|
|
@@ -61,31 +63,29 @@ class LabelVideoSegment(models.Model):
|
|
|
61
63
|
)
|
|
62
64
|
|
|
63
65
|
if TYPE_CHECKING:
|
|
64
|
-
video_file: "VideoFile"
|
|
65
|
-
label:
|
|
66
|
-
source:
|
|
67
|
-
prediction_meta:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
video_file: models.ForeignKey["VideoFile"]
|
|
67
|
+
label: models.ForeignKey["Label|None"]
|
|
68
|
+
source: models.ForeignKey["InformationSource|None"]
|
|
69
|
+
prediction_meta: models.ForeignKey["VideoPredictionMeta|None"]
|
|
70
|
+
|
|
71
|
+
patient_findings = cast(models.manager.RelatedManager["PatientFinding"], patient_findings)
|
|
72
|
+
model_meta: models.ForeignKey["ModelMeta|None"]
|
|
73
|
+
state: models.OneToOneField["LabelVideoSegmentState"]
|
|
71
74
|
|
|
72
75
|
class Meta:
|
|
73
76
|
constraints = [
|
|
74
|
-
CheckConstraint(
|
|
75
|
-
condition=Q(start_frame_number__lt=F("end_frame_number")),
|
|
76
|
-
name="segment_start_lt_end"
|
|
77
|
-
),
|
|
77
|
+
CheckConstraint(condition=Q(start_frame_number__lt=F("end_frame_number")), name="segment_start_lt_end"),
|
|
78
78
|
]
|
|
79
79
|
indexes = [
|
|
80
|
-
models.Index(fields=[
|
|
81
|
-
models.Index(fields=[
|
|
80
|
+
models.Index(fields=["video_file", "label", "start_frame_number"]),
|
|
81
|
+
models.Index(fields=["prediction_meta", "label"]),
|
|
82
82
|
]
|
|
83
83
|
|
|
84
84
|
@property
|
|
85
85
|
def start_time(self) -> float:
|
|
86
86
|
"""
|
|
87
87
|
Return the segment's start time in seconds, calculated from the start frame number and video FPS.
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
Returns:
|
|
90
90
|
float: Start time in seconds. Returns 0.0 if FPS is unavailable or zero.
|
|
91
91
|
"""
|
|
@@ -93,12 +93,12 @@ class LabelVideoSegment(models.Model):
|
|
|
93
93
|
if fps == 0.0:
|
|
94
94
|
return 0.0
|
|
95
95
|
return self.start_frame_number / fps
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
@property
|
|
98
98
|
def end_time(self) -> float:
|
|
99
99
|
"""
|
|
100
100
|
Return the segment's end time in seconds, calculated from the end frame number and video FPS.
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
Returns:
|
|
103
103
|
float: End time in seconds, or 0.0 if FPS is unavailable.
|
|
104
104
|
"""
|
|
@@ -131,6 +131,28 @@ class LabelVideoSegment(models.Model):
|
|
|
131
131
|
# Should not happen if self.state exists and has the is_validated attribute.
|
|
132
132
|
logger.error("AttributeError accessing 'state.is_validated' for LabelVideoSegment %s.", self.pk)
|
|
133
133
|
return False
|
|
134
|
+
|
|
135
|
+
def mark_validated(
|
|
136
|
+
self,
|
|
137
|
+
is_validated: bool = True,
|
|
138
|
+
information_source_name: str = "frontend",
|
|
139
|
+
) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Domain helper: update validation state (and optionally information source).
|
|
142
|
+
"""
|
|
143
|
+
from endoreg_db.models import InformationSource # avoid import cycle
|
|
144
|
+
|
|
145
|
+
# ensure state exists
|
|
146
|
+
state, _ = self.get_or_create_state()
|
|
147
|
+
state.is_validated = is_validated
|
|
148
|
+
state.save()
|
|
149
|
+
|
|
150
|
+
# update information source
|
|
151
|
+
info_source, _ = InformationSource.objects.get_or_create(
|
|
152
|
+
name=information_source_name
|
|
153
|
+
)
|
|
154
|
+
self.source = info_source
|
|
155
|
+
self.save(update_fields=["source"])
|
|
134
156
|
|
|
135
157
|
def extract_segment_frame_files(self, overwrite: bool = False, **kwargs) -> bool:
|
|
136
158
|
"""
|
|
@@ -138,55 +160,43 @@ class LabelVideoSegment(models.Model):
|
|
|
138
160
|
Passes additional keyword arguments to extract_frames.
|
|
139
161
|
"""
|
|
140
162
|
from endoreg_db.models import VideoFile
|
|
163
|
+
|
|
141
164
|
if not isinstance(self.video_file, VideoFile):
|
|
142
165
|
raise ValueError("Cannot extract frame files: No associated VideoFile.")
|
|
143
|
-
return self.video_file.extract_specific_frame_range(
|
|
144
|
-
|
|
145
|
-
end_frame=self.end_frame_number,
|
|
146
|
-
overwrite=overwrite,
|
|
147
|
-
**kwargs
|
|
148
|
-
)
|
|
149
|
-
|
|
166
|
+
return self.video_file.extract_specific_frame_range(start_frame=self.start_frame_number, end_frame=self.end_frame_number, overwrite=overwrite, **kwargs)
|
|
167
|
+
|
|
150
168
|
def delete_frame_files(self) -> None:
|
|
151
169
|
"""
|
|
152
170
|
Delete the frame files corresponding to this segment's frame range from the associated video file.
|
|
153
|
-
|
|
171
|
+
|
|
154
172
|
Raises:
|
|
155
173
|
ValueError: If there is no associated VideoFile.
|
|
156
174
|
"""
|
|
157
175
|
from endoreg_db.models import VideoFile
|
|
176
|
+
|
|
158
177
|
if not isinstance(self.video_file, VideoFile):
|
|
159
178
|
raise ValueError("Cannot delete frame files: No associated VideoFile.")
|
|
160
|
-
self.video_file.delete_specific_frame_range(
|
|
161
|
-
|
|
162
|
-
end_frame=self.end_frame_number
|
|
163
|
-
)
|
|
179
|
+
self.video_file.delete_specific_frame_range(start_frame=self.start_frame_number, end_frame=self.end_frame_number)
|
|
180
|
+
|
|
164
181
|
@classmethod
|
|
165
182
|
def safe_create(cls, video_file, label, start_frame_number, end_frame_number, **kwargs):
|
|
166
183
|
"""
|
|
167
184
|
Create a new LabelVideoSegment instance after validating the frame range.
|
|
168
|
-
|
|
185
|
+
|
|
169
186
|
Validates that the provided start and end frame numbers are appropriate for the given video file before creating the segment. Raises a ValueError if validation fails.
|
|
170
|
-
|
|
187
|
+
|
|
171
188
|
Returns:
|
|
172
|
-
|
|
189
|
+
LabelVideoSegment: The newly created segment instance.
|
|
173
190
|
"""
|
|
174
191
|
cls.validate_frame_range(start_frame_number, end_frame_number, video_file=video_file)
|
|
175
|
-
return cls.objects.create(
|
|
176
|
-
|
|
177
|
-
label=label,
|
|
178
|
-
start_frame_number=start_frame_number,
|
|
179
|
-
end_frame_number=end_frame_number,
|
|
180
|
-
**kwargs
|
|
181
|
-
)
|
|
182
|
-
|
|
192
|
+
return cls.objects.create(video_file=video_file, label=label, start_frame_number=start_frame_number, end_frame_number=end_frame_number, **kwargs)
|
|
193
|
+
|
|
183
194
|
def save(self, *args, **kwargs):
|
|
184
195
|
"""
|
|
185
196
|
Saves the LabelVideoSegment instance and ensures its associated state object exists.
|
|
186
|
-
|
|
197
|
+
|
|
187
198
|
Overrides the default save behavior to guarantee that a related LabelVideoSegmentState is created or retrieved after saving.
|
|
188
199
|
"""
|
|
189
|
-
from endoreg_db.models import LabelVideoSegmentState
|
|
190
200
|
# Call the original save method first
|
|
191
201
|
super().save(*args, **kwargs)
|
|
192
202
|
|
|
@@ -206,6 +216,7 @@ class LabelVideoSegment(models.Model):
|
|
|
206
216
|
if it was created.
|
|
207
217
|
"""
|
|
208
218
|
from endoreg_db.models import LabelVideoSegmentState
|
|
219
|
+
|
|
209
220
|
state, created = LabelVideoSegmentState.objects.get_or_create(origin=self)
|
|
210
221
|
return state, created
|
|
211
222
|
|
|
@@ -221,14 +232,7 @@ class LabelVideoSegment(models.Model):
|
|
|
221
232
|
"""
|
|
222
233
|
Create a LabelVideoSegment instance from a VideoFile.
|
|
223
234
|
"""
|
|
224
|
-
return _create_from_video(
|
|
225
|
-
cls,
|
|
226
|
-
source,
|
|
227
|
-
prediction_meta,
|
|
228
|
-
label,
|
|
229
|
-
start_frame_number,
|
|
230
|
-
end_frame_number
|
|
231
|
-
)
|
|
235
|
+
return _create_from_video(cls, source, prediction_meta, label, start_frame_number, end_frame_number)
|
|
232
236
|
|
|
233
237
|
def get_video(self) -> "VideoFile":
|
|
234
238
|
"""Returns the associated VideoFile instance."""
|
|
@@ -249,10 +253,7 @@ class LabelVideoSegment(models.Model):
|
|
|
249
253
|
active_path = video_obj.active_file_path
|
|
250
254
|
video_identifier = active_path.name if active_path else f"UUID {video_obj.uuid}"
|
|
251
255
|
|
|
252
|
-
str_repr =
|
|
253
|
-
f"{video_identifier} Label - {label_name} - "
|
|
254
|
-
f"{self.start_frame_number} - {self.end_frame_number}"
|
|
255
|
-
)
|
|
256
|
+
str_repr = f"{video_identifier} Label - {label_name} - {self.start_frame_number} - {self.end_frame_number}"
|
|
256
257
|
except ObjectDoesNotExist: # More specific exception
|
|
257
258
|
str_repr = f"Segment {self.pk} (Error: Associated VideoFile missing)"
|
|
258
259
|
except ValueError as e: # Catch specific error from get_video
|
|
@@ -266,7 +267,7 @@ class LabelVideoSegment(models.Model):
|
|
|
266
267
|
def get_model_meta(self) -> Optional["ModelMeta"]:
|
|
267
268
|
"""
|
|
268
269
|
Retrieve the associated ModelMeta object from the segment's prediction metadata, if available.
|
|
269
|
-
|
|
270
|
+
|
|
270
271
|
Returns:
|
|
271
272
|
ModelMeta or None: The related ModelMeta instance, or None if no prediction metadata is set.
|
|
272
273
|
"""
|
|
@@ -278,26 +279,24 @@ class LabelVideoSegment(models.Model):
|
|
|
278
279
|
def frames(self) -> Union[models.QuerySet["Frame"], list]:
|
|
279
280
|
"""
|
|
280
281
|
Return all frames within the segment's frame range.
|
|
281
|
-
|
|
282
|
+
|
|
282
283
|
Returns:
|
|
283
284
|
QuerySet[Frame] or list: Frames from the associated video file that fall within the segment's start and end frame numbers. Returns an empty list if the video file is unavailable.
|
|
284
285
|
"""
|
|
285
286
|
return self.get_frames()
|
|
286
287
|
|
|
287
|
-
def get_frames(self) ->
|
|
288
|
+
def get_frames(self) -> models.QuerySet["Frame"]:
|
|
288
289
|
"""
|
|
289
290
|
Retrieve all frames within the segment's frame range from the associated video.
|
|
290
|
-
|
|
291
|
+
|
|
291
292
|
Returns:
|
|
292
293
|
QuerySet[Frame]: Frames with frame numbers in [start_frame_number, end_frame_number) ordered by frame number, or an empty queryset if unavailable.
|
|
293
294
|
"""
|
|
294
295
|
from endoreg_db.models.media.frame import Frame
|
|
296
|
+
|
|
295
297
|
try:
|
|
296
298
|
video_obj = self.get_video()
|
|
297
|
-
return video_obj.frames.filter(
|
|
298
|
-
frame_number__gte=self.start_frame_number,
|
|
299
|
-
frame_number__lt=self.end_frame_number
|
|
300
|
-
).order_by('frame_number')
|
|
299
|
+
return video_obj.frames.filter(frame_number__gte=self.start_frame_number, frame_number__lt=self.end_frame_number).order_by("frame_number")
|
|
301
300
|
except ValueError:
|
|
302
301
|
logger.error("Cannot get frames for segment %s: No associated VideoFile.", self.pk)
|
|
303
302
|
return Frame.objects.none()
|
|
@@ -309,7 +308,7 @@ class LabelVideoSegment(models.Model):
|
|
|
309
308
|
def all_frame_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
|
|
310
309
|
"""
|
|
311
310
|
Return all image classification annotations for frames within this segment that match the segment's label.
|
|
312
|
-
|
|
311
|
+
|
|
313
312
|
Returns:
|
|
314
313
|
QuerySet: ImageClassificationAnnotation objects for frames in the segment with the segment's label. Returns an empty queryset if the segment is not associated with a video.
|
|
315
314
|
"""
|
|
@@ -321,7 +320,7 @@ class LabelVideoSegment(models.Model):
|
|
|
321
320
|
frame__video=video_obj, # Changed frame__video_file to frame__video
|
|
322
321
|
frame__frame_number__gte=self.start_frame_number,
|
|
323
322
|
frame__frame_number__lt=self.end_frame_number,
|
|
324
|
-
label=self.label
|
|
323
|
+
label=self.label,
|
|
325
324
|
)
|
|
326
325
|
except ValueError:
|
|
327
326
|
logger.error("Cannot get annotations for segment %s: No associated VideoFile.", self.pk)
|
|
@@ -331,7 +330,7 @@ class LabelVideoSegment(models.Model):
|
|
|
331
330
|
def frame_predictions(self) -> models.QuerySet["ImageClassificationAnnotation"]:
|
|
332
331
|
"""
|
|
333
332
|
Return prediction annotations for frames within this segment and matching the segment's label.
|
|
334
|
-
|
|
333
|
+
|
|
335
334
|
Returns:
|
|
336
335
|
QuerySet: ImageClassificationAnnotation objects for frames in the segment, filtered by label and information source type "prediction".
|
|
337
336
|
"""
|
|
@@ -344,17 +343,17 @@ class LabelVideoSegment(models.Model):
|
|
|
344
343
|
frame__frame_number__gte=self.start_frame_number,
|
|
345
344
|
frame__frame_number__lt=self.end_frame_number,
|
|
346
345
|
label=self.label,
|
|
347
|
-
information_source__information_source_types__name="prediction"
|
|
346
|
+
information_source__information_source_types__name="prediction",
|
|
348
347
|
)
|
|
349
348
|
except ValueError:
|
|
350
349
|
logger.error("Cannot get predictions for segment %s: No associated VideoFile.", self.pk)
|
|
351
350
|
return ImageClassificationAnnotation.objects.none()
|
|
352
|
-
|
|
351
|
+
|
|
353
352
|
@property
|
|
354
353
|
def manual_frame_annotations(self) -> models.QuerySet["ImageClassificationAnnotation"]:
|
|
355
354
|
"""
|
|
356
355
|
Return manual image classification annotations for frames within this segment and matching the segment's label.
|
|
357
|
-
|
|
356
|
+
|
|
358
357
|
Returns:
|
|
359
358
|
QuerySet: Manual `ImageClassificationAnnotation` objects for the segment's frames and label. Returns an empty queryset if the segment is not associated with a video.
|
|
360
359
|
"""
|
|
@@ -367,7 +366,7 @@ class LabelVideoSegment(models.Model):
|
|
|
367
366
|
frame__frame_number__gte=self.start_frame_number,
|
|
368
367
|
frame__frame_number__lt=self.end_frame_number,
|
|
369
368
|
label=self.label,
|
|
370
|
-
information_source__information_source_types__name="manual_annotation"
|
|
369
|
+
information_source__information_source_types__name="manual_annotation",
|
|
371
370
|
)
|
|
372
371
|
except ValueError:
|
|
373
372
|
logger.error("Cannot get manual annotations for segment %s: No associated VideoFile.", self.pk)
|
|
@@ -376,7 +375,7 @@ class LabelVideoSegment(models.Model):
|
|
|
376
375
|
def get_segment_len_in_s(self) -> float:
|
|
377
376
|
"""
|
|
378
377
|
Return the duration of the video segment in seconds, based on frame numbers and video FPS.
|
|
379
|
-
|
|
378
|
+
|
|
380
379
|
Returns:
|
|
381
380
|
float: Segment duration in seconds, or 0.0 if FPS is invalid or video is unavailable.
|
|
382
381
|
"""
|
|
@@ -406,10 +405,9 @@ class LabelVideoSegment(models.Model):
|
|
|
406
405
|
logger.warning("Segment %s has no label. Cannot find frames without annotation.", self.pk)
|
|
407
406
|
return []
|
|
408
407
|
|
|
409
|
-
annotated_frame_ids = ImageClassificationAnnotation.objects.filter(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
).values_list('frame_id', flat=True)
|
|
408
|
+
annotated_frame_ids = ImageClassificationAnnotation.objects.filter(frame__in=frames_qs.values_list("id", flat=True), label=self.label).values_list(
|
|
409
|
+
"frame_id", flat=True
|
|
410
|
+
)
|
|
413
411
|
|
|
414
412
|
frames_without_annotation = list(frames_qs.exclude(id__in=annotated_frame_ids)[:n_frames])
|
|
415
413
|
return frames_without_annotation
|
|
@@ -417,11 +415,11 @@ class LabelVideoSegment(models.Model):
|
|
|
417
415
|
def generate_annotations(self):
|
|
418
416
|
"""
|
|
419
417
|
Creates image classification annotations for all frames in the segment if the segment is linked to a prediction, avoiding duplicates.
|
|
420
|
-
|
|
418
|
+
|
|
421
419
|
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.
|
|
422
420
|
"""
|
|
423
421
|
if not self.prediction_meta:
|
|
424
|
-
logger.info("Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.", self.
|
|
422
|
+
logger.info("Skipping annotation generation for segment %s: Requires linked VideoPredictionMeta.", self.pk)
|
|
425
423
|
return
|
|
426
424
|
|
|
427
425
|
from endoreg_db.models import ImageClassificationAnnotation, InformationSource
|
|
@@ -434,27 +432,27 @@ class LabelVideoSegment(models.Model):
|
|
|
434
432
|
label = self.label
|
|
435
433
|
|
|
436
434
|
if not model_meta or not label:
|
|
437
|
-
logger.warning("Missing model_meta or label for segment %s. Skipping annotation generation.", self.
|
|
435
|
+
logger.warning("Missing model_meta or label for segment %s. Skipping annotation generation.", self.pk)
|
|
438
436
|
return
|
|
439
437
|
|
|
440
|
-
frames_queryset = self.get_frames().only(
|
|
438
|
+
frames_queryset = self.get_frames().only("id")
|
|
441
439
|
if not isinstance(frames_queryset, models.QuerySet):
|
|
442
|
-
logger.error("Could not get frame queryset for segment %s. Skipping.", self.
|
|
440
|
+
logger.error("Could not get frame queryset for segment %s. Skipping.", self.pk)
|
|
443
441
|
return
|
|
444
442
|
|
|
445
443
|
existing_annotation_frame_ids = set(
|
|
446
444
|
ImageClassificationAnnotation.objects.filter(
|
|
447
|
-
frame_id__in=frames_queryset.values(
|
|
445
|
+
frame_id__in=frames_queryset.values("id"),
|
|
448
446
|
label=label,
|
|
449
447
|
model_meta=model_meta,
|
|
450
448
|
information_source=information_source,
|
|
451
|
-
).values_list(
|
|
449
|
+
).values_list("frame_id", flat=True)
|
|
452
450
|
)
|
|
453
451
|
|
|
454
452
|
annotations_to_create = []
|
|
455
453
|
frames_to_annotate = frames_queryset.exclude(id__in=existing_annotation_frame_ids)
|
|
456
454
|
|
|
457
|
-
for frame in tqdm(frames_to_annotate.iterator(), total=frames_to_annotate.count(), desc=f"Preparing annotations for segment {self.
|
|
455
|
+
for frame in tqdm(frames_to_annotate.iterator(), total=frames_to_annotate.count(), desc=f"Preparing annotations for segment {self.pk} ({label.name})"):
|
|
458
456
|
annotations_to_create.append(
|
|
459
457
|
ImageClassificationAnnotation(
|
|
460
458
|
frame=frame,
|
|
@@ -466,16 +464,16 @@ class LabelVideoSegment(models.Model):
|
|
|
466
464
|
)
|
|
467
465
|
|
|
468
466
|
if annotations_to_create:
|
|
469
|
-
logger.info("Bulk creating %d annotations for segment %s...", len(annotations_to_create), self.
|
|
467
|
+
logger.info("Bulk creating %d annotations for segment %s...", len(annotations_to_create), self.pk)
|
|
470
468
|
ImageClassificationAnnotation.objects.bulk_create(annotations_to_create, ignore_conflicts=True)
|
|
471
469
|
logger.info("Bulk creation complete.")
|
|
472
470
|
else:
|
|
473
|
-
logger.info("No new annotations needed for segment %s.", self.
|
|
471
|
+
logger.info("No new annotations needed for segment %s.", self.pk)
|
|
474
472
|
|
|
475
473
|
def _get_fps_safe(self):
|
|
476
474
|
"""
|
|
477
475
|
Safely retrieves the frames per second (FPS) value from the associated video.
|
|
478
|
-
|
|
476
|
+
|
|
479
477
|
Returns:
|
|
480
478
|
float: The FPS of the associated video, or 0.0 if unavailable or invalid.
|
|
481
479
|
"""
|
|
@@ -488,12 +486,12 @@ class LabelVideoSegment(models.Model):
|
|
|
488
486
|
def validate_frame_range(start_frame_number: int, end_frame_number: int, video_file=None):
|
|
489
487
|
"""
|
|
490
488
|
Validate that the provided frame numbers define a valid segment range, optionally checking against a video's frame count.
|
|
491
|
-
|
|
489
|
+
|
|
492
490
|
Parameters:
|
|
493
491
|
start_frame_number (int): The starting frame number of the segment.
|
|
494
492
|
end_frame_number (int): The ending frame number of the segment.
|
|
495
493
|
video_file: Optional video file object to validate frame numbers against its frame count.
|
|
496
|
-
|
|
494
|
+
|
|
497
495
|
Raises:
|
|
498
496
|
ValueError: If frame numbers are not integers, are negative, are out of order, or exceed the video's frame count.
|
|
499
497
|
"""
|
|
@@ -504,8 +502,6 @@ class LabelVideoSegment(models.Model):
|
|
|
504
502
|
if end_frame_number < start_frame_number:
|
|
505
503
|
raise ValueError("end_frame_number must be equal or greater than start_frame_number.")
|
|
506
504
|
if video_file is not None:
|
|
507
|
-
frame_count = getattr(video_file,
|
|
505
|
+
frame_count = getattr(video_file, "frame_count", None)
|
|
508
506
|
if frame_count is not None and end_frame_number > frame_count:
|
|
509
507
|
raise ValueError(f"end_frame_number ({end_frame_number}) exceeds video frame count ({frame_count}).")
|
|
510
|
-
|
|
511
|
-
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
from django.db import models
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
class VideoSegmentationLabelManager(models.Manager):
|
|
4
5
|
"""
|
|
5
6
|
Manager for VideoSegmentationLabel with custom query methods.
|
|
6
7
|
"""
|
|
8
|
+
|
|
7
9
|
def get_by_natural_key(self, name: str) -> "VideoSegmentationLabel":
|
|
8
10
|
return self.get(name=name)
|
|
9
11
|
|
|
12
|
+
|
|
10
13
|
class VideoSegmentationLabel(models.Model):
|
|
11
14
|
"""
|
|
12
15
|
Represents a label for video segmentation annotations.
|
|
@@ -17,6 +20,7 @@ class VideoSegmentationLabel(models.Model):
|
|
|
17
20
|
color (str): The color associated with the label.
|
|
18
21
|
order_priority (int): The priority for ordering labels.
|
|
19
22
|
"""
|
|
23
|
+
|
|
20
24
|
objects = VideoSegmentationLabelManager()
|
|
21
25
|
|
|
22
26
|
name = models.CharField(max_length=255)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import TYPE_CHECKING, cast
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from django.db import models
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from endoreg_db.models import VideoSegmentationLabel
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
class VideoSegmentationLabelSetManager(models.Manager):
|
|
9
10
|
def get_by_natural_key(self, name):
|
|
10
11
|
return self.get(name=name)
|
|
@@ -18,7 +19,7 @@ class VideoSegmentationLabelSet(models.Model):
|
|
|
18
19
|
objects = VideoSegmentationLabelSetManager()
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
|
-
labels
|
|
22
|
+
labels = cast(models.manager.RelatedManager["VideoSegmentationLabel"], labels)
|
|
22
23
|
|
|
23
24
|
def natural_key(self):
|
|
24
25
|
return (self.name,)
|