endoreg-db 0.5.3__py3-none-any.whl → 0.6.1__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 +90 -1
- endoreg_db/case_generator/case_generator.py +159 -0
- endoreg_db/case_generator/lab_sample_factory.py +33 -0
- endoreg_db/case_generator/utils.py +30 -0
- endoreg_db/data/__init__.py +50 -4
- endoreg_db/data/ai_model/data.yaml +7 -0
- endoreg_db/data/{label → ai_model_label}/label/data.yaml +27 -1
- endoreg_db/data/ai_model_label/label-set/data.yaml +21 -0
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +5 -0
- endoreg_db/data/ai_model_video_segmentation_label/base_segmentation.yaml +176 -0
- endoreg_db/data/ai_model_video_segmentation_labelset/data.yaml +20 -0
- endoreg_db/data/center/data.yaml +35 -5
- endoreg_db/data/contraindication/bleeding.yaml +11 -0
- endoreg_db/data/distribution/numeric/data.yaml +14 -0
- endoreg_db/data/endoscope/data.yaml +93 -0
- endoreg_db/data/examination_indication/endoscopy.yaml +8 -0
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +8 -0
- endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +101 -0
- endoreg_db/data/finding/data.yaml +141 -0
- endoreg_db/data/finding_intervention/endoscopy.yaml +138 -0
- endoreg_db/data/finding_intervention_type/endoscopy.yaml +15 -0
- endoreg_db/data/finding_location_classification/colonoscopy.yaml +46 -0
- endoreg_db/data/finding_location_classification_choice/colonoscopy.yaml +240 -0
- endoreg_db/data/finding_morphology_classification/colonoscopy.yaml +48 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_circularity_default.yaml +34 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_nice.yaml +20 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_paris.yaml +65 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_planarity_default.yaml +56 -0
- endoreg_db/data/finding_morphology_classification_choice/colon_lesion_surface_intact_default.yaml +39 -0
- endoreg_db/data/finding_morphology_classification_choice/colonoscopy_size.yaml +57 -0
- endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +79 -0
- endoreg_db/data/finding_type/data.yaml +30 -0
- endoreg_db/data/gender/data.yaml +17 -0
- endoreg_db/data/lab_value/cardiac_enzymes.yaml +7 -1
- endoreg_db/data/lab_value/coagulation.yaml +6 -1
- endoreg_db/data/lab_value/electrolytes.yaml +39 -1
- endoreg_db/data/lab_value/gastrointestinal_function.yaml +12 -0
- endoreg_db/data/lab_value/hematology.yaml +17 -2
- endoreg_db/data/lab_value/hormones.yaml +6 -0
- endoreg_db/data/lab_value/lipids.yaml +12 -3
- endoreg_db/data/lab_value/misc.yaml +5 -2
- endoreg_db/data/lab_value/renal_function.yaml +2 -1
- endoreg_db/data/lx_client_tag/base.yaml +54 -0
- endoreg_db/data/lx_client_type/base.yaml +30 -0
- endoreg_db/data/lx_permission/base.yaml +24 -0
- endoreg_db/data/lx_permission/endoreg.yaml +52 -0
- endoreg_db/data/medication_indication/anticoagulation.yaml +44 -49
- endoreg_db/data/names_first/first_names.yaml +51 -0
- endoreg_db/data/names_last/last_names.yaml +51 -0
- endoreg_db/data/network_device/data.yaml +30 -0
- endoreg_db/data/organ/data.yaml +29 -0
- endoreg_db/data/pdf_type/data.yaml +2 -1
- endoreg_db/data/report_reader_flag/ukw-examination-generic.yaml +4 -0
- endoreg_db/forms/__init__.py +3 -1
- endoreg_db/forms/examination_form.py +11 -0
- endoreg_db/forms/patient_finding_intervention_form.py +19 -0
- endoreg_db/forms/patient_form.py +26 -0
- endoreg_db/management/commands/__init__.py +0 -0
- endoreg_db/management/commands/load_ai_model_data.py +57 -23
- endoreg_db/management/commands/load_ai_model_label_data.py +59 -0
- endoreg_db/management/commands/load_base_db_data.py +160 -118
- endoreg_db/management/commands/{load_endoscope_type_data.py → load_contraindication_data.py} +3 -7
- endoreg_db/management/commands/load_disease_data.py +29 -7
- endoreg_db/management/commands/load_endoscope_data.py +68 -0
- endoreg_db/management/commands/load_examination_indication_data.py +65 -0
- endoreg_db/management/commands/load_finding_data.py +171 -0
- endoreg_db/management/commands/load_lab_value_data.py +3 -3
- endoreg_db/management/commands/load_lx_data.py +64 -0
- endoreg_db/management/commands/load_medication_data.py +83 -21
- endoreg_db/management/commands/load_name_data.py +37 -0
- endoreg_db/management/commands/{load_endoscopy_processor_data.py → load_organ_data.py} +7 -9
- endoreg_db/migrations/0001_initial.py +1206 -728
- endoreg_db/migrations/0002_alter_frame_image_alter_rawframe_image.py +23 -0
- endoreg_db/migrations/0003_alter_frame_image_alter_rawframe_image.py +23 -0
- endoreg_db/migrations/0004_alter_rawvideofile_file_alter_video_file.py +25 -0
- endoreg_db/migrations/0005_rawvideofile_frame_count_and_more.py +33 -0
- endoreg_db/migrations/0006_frame_extracted_rawframe_extracted.py +23 -0
- endoreg_db/migrations/0007_rename_pseudo_patient_video_patient_and_more.py +24 -0
- endoreg_db/migrations/0008_remove_reportfile_patient_examination_and_more.py +48 -0
- endoreg_db/models/__init__.py +331 -28
- endoreg_db/models/ai_model/__init__.py +1 -0
- endoreg_db/models/ai_model/ai_model.py +103 -0
- endoreg_db/models/ai_model/lightning/__init__.py +3 -0
- endoreg_db/models/ai_model/lightning/inference_dataset.py +53 -0
- endoreg_db/models/ai_model/lightning/multilabel_classification_net.py +155 -0
- endoreg_db/models/ai_model/lightning/postprocess.py +53 -0
- endoreg_db/models/ai_model/lightning/predict.py +172 -0
- endoreg_db/models/ai_model/lightning/prediction_visualizer.py +55 -0
- endoreg_db/models/ai_model/lightning/preprocess.py +68 -0
- endoreg_db/models/ai_model/lightning/run_visualizer.py +21 -0
- endoreg_db/models/ai_model/model_meta.py +232 -6
- endoreg_db/models/ai_model/model_type.py +13 -3
- endoreg_db/models/annotation/__init__.py +31 -2
- endoreg_db/models/annotation/anonymized_image_annotation.py +73 -18
- endoreg_db/models/annotation/binary_classification_annotation_task.py +94 -57
- endoreg_db/models/annotation/image_classification.py +73 -14
- endoreg_db/models/annotation/video_segmentation_annotation.py +52 -0
- endoreg_db/models/annotation/video_segmentation_labelset.py +20 -0
- endoreg_db/models/case/__init__.py +1 -0
- endoreg_db/models/{persons/patient/case → case}/case.py +4 -0
- endoreg_db/models/case_template/__init__.py +10 -1
- endoreg_db/models/case_template/case_template.py +57 -13
- endoreg_db/models/case_template/case_template_rule.py +5 -5
- endoreg_db/models/case_template/case_template_rule_value.py +19 -4
- endoreg_db/models/center/__init__.py +7 -0
- endoreg_db/models/center/center.py +31 -5
- endoreg_db/models/center/center_product.py +0 -1
- endoreg_db/models/center/center_resource.py +16 -2
- endoreg_db/models/center/center_waste.py +6 -1
- endoreg_db/models/contraindication/__init__.py +21 -0
- endoreg_db/models/data_file/__init__.py +38 -5
- endoreg_db/models/data_file/base_classes/__init__.py +6 -1
- endoreg_db/models/data_file/base_classes/abstract_frame.py +64 -15
- endoreg_db/models/data_file/base_classes/abstract_pdf.py +136 -0
- endoreg_db/models/data_file/base_classes/abstract_video.py +744 -138
- endoreg_db/models/data_file/base_classes/frame_helpers.py +17 -0
- endoreg_db/models/data_file/base_classes/prepare_bulk_frames.py +19 -0
- endoreg_db/models/data_file/base_classes/utils.py +80 -0
- endoreg_db/models/data_file/frame.py +22 -38
- endoreg_db/models/data_file/import_classes/__init__.py +4 -18
- endoreg_db/models/data_file/import_classes/raw_pdf.py +162 -90
- endoreg_db/models/data_file/import_classes/raw_video.py +239 -294
- endoreg_db/models/data_file/metadata/__init__.py +10 -0
- endoreg_db/models/data_file/metadata/pdf_meta.py +4 -0
- endoreg_db/models/data_file/metadata/sensitive_meta.py +265 -6
- endoreg_db/models/data_file/metadata/video_meta.py +116 -50
- endoreg_db/models/data_file/report_file.py +30 -63
- endoreg_db/models/data_file/video/__init__.py +6 -2
- endoreg_db/models/data_file/video/video.py +187 -16
- endoreg_db/models/data_file/video_segment.py +162 -55
- endoreg_db/models/disease.py +25 -2
- endoreg_db/models/emission/__init__.py +5 -1
- endoreg_db/models/emission/emission_factor.py +71 -6
- endoreg_db/models/event.py +51 -0
- endoreg_db/models/examination/__init__.py +6 -1
- endoreg_db/models/examination/examination.py +53 -12
- endoreg_db/models/examination/examination_indication.py +170 -0
- endoreg_db/models/examination/examination_time.py +31 -5
- endoreg_db/models/examination/examination_time_type.py +28 -4
- endoreg_db/models/examination/examination_type.py +28 -6
- endoreg_db/models/finding/__init__.py +11 -0
- endoreg_db/models/finding/finding.py +75 -0
- endoreg_db/models/finding/finding_intervention.py +60 -0
- endoreg_db/models/finding/finding_location_classification.py +94 -0
- endoreg_db/models/finding/finding_morphology_classification.py +89 -0
- endoreg_db/models/finding/finding_type.py +22 -0
- endoreg_db/models/hardware/endoscope.py +16 -0
- endoreg_db/models/hardware/endoscopy_processor.py +31 -19
- endoreg_db/models/label/label.py +35 -7
- endoreg_db/models/laboratory/lab_value.py +12 -3
- endoreg_db/models/logging/__init__.py +8 -1
- endoreg_db/models/lx/__init__.py +4 -0
- endoreg_db/models/lx/client.py +57 -0
- endoreg_db/models/lx/identity.py +34 -0
- endoreg_db/models/lx/permission.py +18 -0
- endoreg_db/models/lx/user.py +16 -0
- endoreg_db/models/medication/__init__.py +19 -1
- endoreg_db/models/medication/medication.py +7 -122
- endoreg_db/models/medication/medication_indication.py +50 -0
- endoreg_db/models/medication/medication_indication_type.py +34 -0
- endoreg_db/models/medication/medication_intake_time.py +26 -0
- endoreg_db/models/medication/medication_schedule.py +37 -0
- endoreg_db/models/network/__init__.py +7 -1
- endoreg_db/models/network/network_device.py +13 -8
- endoreg_db/models/organ/__init__.py +38 -0
- endoreg_db/models/other/__init__.py +19 -1
- endoreg_db/models/other/distribution/__init__.py +44 -0
- endoreg_db/models/other/distribution/base_value_distribution.py +20 -0
- endoreg_db/models/other/distribution/date_value_distribution.py +91 -0
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +32 -0
- endoreg_db/models/other/distribution/numeric_value_distribution.py +97 -0
- endoreg_db/models/other/distribution/single_categorical_value_distribution.py +22 -0
- endoreg_db/models/other/distribution.py +1 -211
- endoreg_db/models/other/material.py +4 -0
- endoreg_db/models/other/transport_route.py +2 -1
- endoreg_db/models/patient/__init__.py +24 -0
- endoreg_db/models/patient/patient_examination.py +182 -0
- endoreg_db/models/patient/patient_finding.py +143 -0
- endoreg_db/models/patient/patient_finding_intervention.py +26 -0
- endoreg_db/models/patient/patient_finding_location.py +120 -0
- endoreg_db/models/patient/patient_finding_morphology.py +166 -0
- endoreg_db/models/persons/__init__.py +29 -2
- endoreg_db/models/persons/examiner/examiner.py +48 -4
- endoreg_db/models/persons/patient/__init__.py +1 -1
- endoreg_db/models/persons/patient/patient.py +227 -54
- endoreg_db/models/persons/patient/patient_disease.py +6 -0
- endoreg_db/models/persons/patient/patient_event.py +31 -1
- endoreg_db/models/persons/patient/patient_examination_indication.py +32 -0
- endoreg_db/models/persons/patient/patient_lab_sample.py +4 -2
- endoreg_db/models/persons/patient/patient_lab_value.py +37 -16
- endoreg_db/models/persons/patient/patient_medication.py +27 -12
- endoreg_db/models/persons/patient/patient_medication_schedule.py +62 -2
- endoreg_db/models/prediction/__init__.py +7 -1
- endoreg_db/models/prediction/image_classification.py +20 -6
- endoreg_db/models/prediction/video_prediction_meta.py +151 -89
- endoreg_db/models/product/__init__.py +10 -1
- endoreg_db/models/product/product.py +15 -2
- endoreg_db/models/product/product_group.py +8 -0
- endoreg_db/models/product/product_material.py +4 -0
- endoreg_db/models/product/product_weight.py +12 -0
- endoreg_db/models/product/reference_product.py +19 -3
- endoreg_db/models/quiz/__init__.py +8 -1
- endoreg_db/models/report_reader/__init__.py +6 -1
- endoreg_db/serializers/__init__.py +1 -1
- endoreg_db/serializers/annotation.py +2 -5
- endoreg_db/serializers/frame.py +1 -5
- endoreg_db/serializers/patient.py +26 -3
- endoreg_db/serializers/prediction.py +2 -7
- endoreg_db/serializers/raw_video_meta_validation.py +13 -0
- endoreg_db/serializers/video.py +6 -13
- endoreg_db/serializers/video_segmentation.py +492 -0
- endoreg_db/templates/admin/patient_finding_intervention.html +253 -0
- endoreg_db/templates/admin/start_examination.html +12 -0
- endoreg_db/templates/timeline.html +176 -0
- endoreg_db/urls.py +173 -0
- endoreg_db/utils/__init__.py +36 -1
- endoreg_db/utils/dataloader.py +45 -19
- endoreg_db/utils/dates.py +39 -0
- endoreg_db/utils/hashs.py +122 -4
- endoreg_db/utils/names.py +74 -0
- endoreg_db/utils/parse_and_generate_yaml.py +46 -0
- endoreg_db/utils/pydantic_models/__init__.py +6 -0
- endoreg_db/utils/pydantic_models/db_config.py +57 -0
- endoreg_db/utils/validate_endo_roi.py +19 -0
- endoreg_db/utils/validate_subcategory_dict.py +91 -0
- endoreg_db/utils/video/__init__.py +13 -0
- endoreg_db/utils/video/extract_frames.py +121 -0
- endoreg_db/utils/video/transcode_videofile.py +111 -0
- endoreg_db/views/__init__.py +2 -0
- endoreg_db/views/csrf.py +7 -0
- endoreg_db/views/patient_views.py +90 -0
- endoreg_db/views/raw_video_meta_validation_views.py +38 -0
- endoreg_db/views/report_views.py +96 -0
- endoreg_db/views/video_segmentation_views.py +149 -0
- endoreg_db/views/views_for_timeline.py +46 -0
- endoreg_db/views.py +0 -3
- endoreg_db-0.6.1.dist-info/METADATA +151 -0
- endoreg_db-0.6.1.dist-info/RECORD +420 -0
- {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/WHEEL +1 -1
- endoreg_db/data/active_model/data.yaml +0 -3
- endoreg_db/data/label/label-set/data.yaml +0 -18
- endoreg_db/management/commands/delete_legacy_images.py +0 -19
- endoreg_db/management/commands/delete_legacy_videos.py +0 -17
- endoreg_db/management/commands/extract_legacy_video_frames.py +0 -18
- endoreg_db/management/commands/import_legacy_images.py +0 -94
- endoreg_db/management/commands/import_legacy_videos.py +0 -76
- endoreg_db/management/commands/load_label_data.py +0 -67
- endoreg_db/migrations/0002_anonymizedimagelabel_anonymousimageannotation_and_more.py +0 -55
- endoreg_db/migrations/0003_anonymousimageannotation_original_image_url_and_more.py +0 -39
- endoreg_db/migrations/0004_alter_rawpdffile_file.py +0 -20
- endoreg_db/migrations/0005_uploadedfile_alter_rawpdffile_file_anonymizedfile.py +0 -40
- endoreg_db/migrations/0006_alter_rawpdffile_file.py +0 -20
- endoreg_db/migrations/0007_networkdevicelogentry_datetime_and_more.py +0 -43
- endoreg_db/migrations/0008_networkdevicelogentry_aglnet_ip_and_more.py +0 -28
- endoreg_db/migrations/0009_alter_networkdevicelogentry_vpn_service_status.py +0 -18
- endoreg_db/migrations/0010_remove_networkdevicelogentry_hostname.py +0 -17
- endoreg_db/models/legacy_data/__init__.py +0 -3
- endoreg_db/models/legacy_data/image.py +0 -34
- endoreg_db/models/patient_examination/__init__.py +0 -35
- endoreg_db/utils/video_metadata.py +0 -87
- endoreg_db-0.5.3.dist-info/METADATA +0 -28
- endoreg_db-0.5.3.dist-info/RECORD +0 -319
- /endoreg_db/{models/persons/patient/case → case_generator}/__init__.py +0 -0
- /endoreg_db/data/{label → ai_model_label}/label-type/data.yaml +0 -0
- /endoreg_db/data/{model_type → ai_model_type}/data.yaml +0 -0
- /endoreg_db/{data/distribution/numeric/.init → management/__init__.py} +0 -0
- /endoreg_db/management/commands/{load_report_reader_flag.py → load_report_reader_flag_data.py} +0 -0
- {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,46 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from
|
|
4
|
+
from django.db import models
|
|
5
|
+
from typing import TYPE_CHECKING, List, Tuple
|
|
6
|
+
|
|
7
|
+
from icecream import ic
|
|
8
|
+
from tqdm import tqdm
|
|
9
|
+
import cv2
|
|
10
|
+
from django.core.validators import FileExtensionValidator
|
|
11
|
+
from django.core.files.storage import FileSystemStorage
|
|
12
|
+
|
|
13
|
+
from endoreg_db.utils.validate_endo_roi import validate_endo_roi
|
|
14
|
+
from ..base_classes.utils import (
|
|
15
|
+
anonymize_frame,
|
|
16
|
+
RAW_VIDEO_DIR_NAME,
|
|
17
|
+
VIDEO_DIR,
|
|
18
|
+
STORAGE_LOCATION,
|
|
19
|
+
)
|
|
20
|
+
from ..base_classes.abstract_video import AbstractVideoFile
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
# import Queryset
|
|
24
|
+
from django.db.models import QuerySet
|
|
25
|
+
from endoreg_db.models import (
|
|
26
|
+
SensitiveMeta,
|
|
27
|
+
LabelVideoSegment,
|
|
28
|
+
)
|
|
4
29
|
|
|
5
|
-
from endoreg_db.utils.hashs import get_video_hash
|
|
6
|
-
from endoreg_db.utils.file_operations import get_uuid_filename
|
|
7
|
-
from endoreg_db.utils.ocr import extract_text_from_rois
|
|
8
30
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
31
|
+
# pylint: disable=attribute-defined-outside-init,no-member
|
|
32
|
+
class RawVideoFile(AbstractVideoFile):
|
|
33
|
+
""" """
|
|
12
34
|
|
|
13
|
-
|
|
35
|
+
file = models.FileField(
|
|
36
|
+
upload_to=RAW_VIDEO_DIR_NAME,
|
|
37
|
+
validators=[FileExtensionValidator(allowed_extensions=["mp4"])], # FIXME
|
|
38
|
+
storage=FileSystemStorage(location=STORAGE_LOCATION.resolve().as_posix()),
|
|
39
|
+
)
|
|
14
40
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
sensitive_meta = models.OneToOneField(
|
|
20
|
-
"SensitiveMeta", on_delete=models.CASCADE, blank=True, null=True
|
|
21
|
-
)
|
|
41
|
+
patient = models.ForeignKey(
|
|
42
|
+
"Patient", on_delete=models.SET_NULL, blank=True, null=True
|
|
43
|
+
)
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
sensitive_meta = models.ForeignKey(
|
|
46
|
+
"SensitiveMeta",
|
|
47
|
+
on_delete=models.SET_NULL,
|
|
48
|
+
related_name="raw_videos",
|
|
49
|
+
null=True,
|
|
50
|
+
blank=True,
|
|
26
51
|
)
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
|
|
53
|
+
video = models.ForeignKey(
|
|
54
|
+
"Video",
|
|
55
|
+
on_delete=models.SET_NULL,
|
|
56
|
+
related_name="raw_videos",
|
|
57
|
+
null=True,
|
|
58
|
+
blank=True,
|
|
29
59
|
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# Video
|
|
39
|
-
## Prediction
|
|
40
|
-
state_initial_prediction_required = models.BooleanField(default=True)
|
|
41
|
-
state_initial_prediction_completed = models.BooleanField(default=False)
|
|
42
|
-
state_initial_prediction_import_required = models.BooleanField(default=True)
|
|
43
|
-
state_initial_prediction_import_completed = models.BooleanField(default=False)
|
|
60
|
+
|
|
61
|
+
if TYPE_CHECKING:
|
|
62
|
+
sensitive_meta: "SensitiveMeta"
|
|
63
|
+
label_video_segments: "QuerySet[LabelVideoSegment]"
|
|
64
|
+
|
|
65
|
+
# Crop Frames
|
|
66
|
+
state_anonymized_frames_generated = models.BooleanField(default=False)
|
|
67
|
+
|
|
44
68
|
## OCR
|
|
45
69
|
state_ocr_required = models.BooleanField(default=True)
|
|
46
70
|
state_ocr_completed = models.BooleanField(default=False)
|
|
@@ -50,294 +74,215 @@ class RawVideoFile(models.Model):
|
|
|
50
74
|
|
|
51
75
|
state_sensitive_data_retrieved = models.BooleanField(default=False)
|
|
52
76
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
print(f"Creating RawVideoFile from {file_path}")
|
|
83
|
-
original_file_name = file_path.name
|
|
84
|
-
# Rename and and move
|
|
85
|
-
|
|
86
|
-
new_file_name, uuid = get_uuid_filename(file_path)
|
|
87
|
-
framedir: Path = frame_dir_parent / str(uuid)
|
|
88
|
-
|
|
89
|
-
if not framedir.exists():
|
|
90
|
-
framedir.mkdir(parents=True, exist_ok=True)
|
|
91
|
-
|
|
92
|
-
if not video_dir.exists():
|
|
93
|
-
video_dir.mkdir(parents=True, exist_ok=True)
|
|
94
|
-
|
|
95
|
-
video_hash = get_video_hash(file_path)
|
|
96
|
-
|
|
97
|
-
center = Center.objects.get(name=center_name)
|
|
98
|
-
assert center is not None, "Center must exist"
|
|
99
|
-
|
|
100
|
-
processor = EndoscopyProcessor.objects.get(name=processor_name)
|
|
101
|
-
assert processor is not None, "Processor must exist"
|
|
102
|
-
|
|
103
|
-
new_filepath = video_dir / new_file_name
|
|
104
|
-
|
|
105
|
-
print(f"Moving {file_path} to {new_filepath}")
|
|
106
|
-
shutil.move(file_path.resolve().as_posix(), new_filepath.resolve().as_posix())
|
|
107
|
-
print(f"Moved to {new_filepath}")
|
|
108
|
-
|
|
109
|
-
# Make sure file was transferred correctly and hash is correct
|
|
110
|
-
if not new_filepath.exists():
|
|
111
|
-
print(f"File {file_path} was not transferred correctly to {new_filepath}")
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
new_hash = get_video_hash(new_filepath)
|
|
115
|
-
if new_hash != video_hash:
|
|
116
|
-
print(f"Hash of file {file_path} is not correct")
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
|
-
# make sure that no other file with the same hash exists
|
|
120
|
-
if cls.objects.filter(video_hash=video_hash).exists():
|
|
121
|
-
# log and print warnint
|
|
122
|
-
print(f"File with hash {video_hash} already exists")
|
|
123
|
-
return None
|
|
124
|
-
|
|
125
|
-
else:
|
|
126
|
-
print(center)
|
|
127
|
-
# Create a new instance of RawVideoFile
|
|
128
|
-
raw_video_file = cls(
|
|
129
|
-
uuid=uuid,
|
|
130
|
-
file=new_filepath.resolve().as_posix(),
|
|
131
|
-
center=center,
|
|
132
|
-
processor=processor,
|
|
133
|
-
original_file_name=original_file_name,
|
|
134
|
-
video_hash=video_hash,
|
|
135
|
-
frame_dir=framedir.as_posix(),
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# Save the instance to the database
|
|
139
|
-
raw_video_file.save()
|
|
77
|
+
# Censor Outside
|
|
78
|
+
state_censor_outside_required = models.BooleanField(default=True)
|
|
79
|
+
state_censor_outside_completed = models.BooleanField(default=False)
|
|
80
|
+
state_make_anonymized_video_required = models.BooleanField(default=True)
|
|
81
|
+
state_make_anonymized_video_completed = models.BooleanField(default=False)
|
|
82
|
+
|
|
83
|
+
def get_anonymized_video_path(self):
|
|
84
|
+
video_dir = VIDEO_DIR
|
|
85
|
+
video_suffix = Path(self.file.path).suffix
|
|
86
|
+
video_name = f"{self.uuid}{video_suffix}"
|
|
87
|
+
anonymized_video_name = f"TMP_anonymized_{video_name}"
|
|
88
|
+
anonymized_video_path = video_dir / anonymized_video_name
|
|
89
|
+
|
|
90
|
+
return anonymized_video_path
|
|
91
|
+
|
|
92
|
+
def censor_outside_frames(self):
|
|
93
|
+
assert self.state_frames_extracted, "Frames not extracted"
|
|
94
|
+
assert self.state_initial_prediction_completed, (
|
|
95
|
+
"Initial prediction not completed"
|
|
96
|
+
)
|
|
97
|
+
assert self.state_sensitive_data_retrieved, "Sensitive data not retrieved"
|
|
98
|
+
|
|
99
|
+
ic(
|
|
100
|
+
"WARNING: Outside validation is not yet implemented and automatically set to true in this function"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
self.state_outside_validated = True
|
|
104
|
+
self.save()
|
|
140
105
|
|
|
141
|
-
|
|
106
|
+
assert self.state_outside_validated, "Outside validation not completed"
|
|
142
107
|
|
|
143
|
-
|
|
144
|
-
return self.file.name
|
|
108
|
+
outside_frame_paths = self.get_outside_frame_paths()
|
|
145
109
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return endo_roi
|
|
110
|
+
if not outside_frame_paths:
|
|
111
|
+
ic("No outside frames found")
|
|
149
112
|
|
|
150
|
-
# video meta should be created when video file is created
|
|
151
|
-
def save(self, *args, **kwargs):
|
|
152
|
-
if self.video_meta is None:
|
|
153
|
-
center = self.center
|
|
154
|
-
processor = self.processor
|
|
155
|
-
self.video_meta = VideoMeta.objects.create(
|
|
156
|
-
center=center, processor=processor
|
|
157
|
-
)
|
|
158
|
-
self.video_meta.initialize_ffmpeg_meta(self.file.path)
|
|
159
|
-
super(RawVideoFile, self).save(*args, **kwargs)
|
|
160
|
-
|
|
161
|
-
def extract_frames(
|
|
162
|
-
self,
|
|
163
|
-
quality: int = 2,
|
|
164
|
-
frame_dir: Path = None,
|
|
165
|
-
overwrite: bool = False,
|
|
166
|
-
ext="jpg",
|
|
167
|
-
):
|
|
168
|
-
"""
|
|
169
|
-
Extract frames from the video file and save them to the frame_dir.
|
|
170
|
-
For this, ffmpeg must be available in in the current environment.
|
|
171
|
-
"""
|
|
172
|
-
if frame_dir is None:
|
|
173
|
-
frame_dir = Path(self.frame_dir)
|
|
174
113
|
else:
|
|
175
|
-
|
|
114
|
+
ic(f"Found {len(outside_frame_paths)} outside frames")
|
|
115
|
+
# use cv2 to replace all outside frames with completely black frames
|
|
176
116
|
|
|
177
|
-
|
|
178
|
-
|
|
117
|
+
for frame_path in tqdm(outside_frame_paths):
|
|
118
|
+
frame = cv2.imread(frame_path.as_posix())
|
|
119
|
+
frame.fill(0)
|
|
120
|
+
cv2.imwrite(frame_path.as_posix(), frame)
|
|
179
121
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
122
|
+
self.state_censor_outside_required = False
|
|
123
|
+
self.state_censor_outside_completed = True
|
|
124
|
+
self.save()
|
|
183
125
|
|
|
184
|
-
|
|
126
|
+
def get_anonymized_frame_dir(self):
|
|
127
|
+
anonymized_frame_dir = Path(self.frame_dir).parent / f"tmp_{self.uuid}"
|
|
128
|
+
return anonymized_frame_dir
|
|
185
129
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"ffmpeg",
|
|
189
|
-
"-i",
|
|
190
|
-
video_path, #
|
|
191
|
-
"-q:v",
|
|
192
|
-
str(quality),
|
|
193
|
-
os.path.join(frame_path_string, f"frame_%07d.{ext}"),
|
|
194
|
-
]
|
|
130
|
+
def make_temporary_anonymized_frames(self) -> Tuple[Path, List[Path]]:
|
|
131
|
+
anonymized_frame_dir = self.get_anonymized_frame_dir()
|
|
195
132
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
raise EnvironmentError(
|
|
199
|
-
"FFmpeg could not be found. Ensure it is installed and in your PATH."
|
|
200
|
-
)
|
|
133
|
+
assert self.state_frames_extracted, "Frames not extracted"
|
|
134
|
+
assert self.processor, "Processor not set"
|
|
201
135
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
raise Exception(f"Error extracting frames: {result.stderr}")
|
|
136
|
+
anonymized_frame_dir.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
endo_roi = self.get_endo_roi()
|
|
138
|
+
assert validate_endo_roi(endo_roi), "Endoscope ROI is not valid"
|
|
139
|
+
generated_frame_paths = []
|
|
207
140
|
|
|
208
|
-
|
|
141
|
+
all_frames = self.frames.all()
|
|
142
|
+
outside_frames = self.get_outside_frames() #
|
|
143
|
+
outside_frame_numbers = [frame.frame_number for frame in outside_frames]
|
|
209
144
|
|
|
210
|
-
|
|
145
|
+
# anonymize frames: copy endo-roi content while making other pixels black. (frames are Path objects to jpgs or pngs)
|
|
146
|
+
for frame in tqdm(all_frames):
|
|
147
|
+
frame_path = Path(frame.image.path)
|
|
148
|
+
frame_name = frame_path.name
|
|
149
|
+
frame_number = frame.frame_number
|
|
211
150
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
frame_dir = Path(self.frame_dir)
|
|
217
|
-
if frame_dir.exists():
|
|
218
|
-
shutil.rmtree(frame_dir)
|
|
219
|
-
self.state_frames_extracted = False
|
|
220
|
-
self.save()
|
|
221
|
-
return f"Frames deleted from {frame_dir}"
|
|
222
|
-
else:
|
|
223
|
-
return f"No frames to delete for {self.file.name}"
|
|
151
|
+
if frame_number in outside_frame_numbers:
|
|
152
|
+
all_black = True
|
|
153
|
+
else:
|
|
154
|
+
all_black = False
|
|
224
155
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# Adjust index
|
|
231
|
-
n = n + 1
|
|
156
|
+
target_frame_path = anonymized_frame_dir / frame_name
|
|
157
|
+
anonymize_frame(
|
|
158
|
+
frame_path, target_frame_path, endo_roi, all_black=all_black
|
|
159
|
+
)
|
|
160
|
+
generated_frame_paths.append(target_frame_path)
|
|
232
161
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
def get_frame_paths(self):
|
|
237
|
-
if not self.state_frames_extracted:
|
|
238
|
-
return None
|
|
239
|
-
frame_dir = Path(self.frame_dir)
|
|
240
|
-
paths = [p for p in frame_dir.glob('*')]
|
|
241
|
-
indices = [int(p.stem.split("_")[1]) for p in paths]
|
|
242
|
-
path_index_tuples = list(zip(paths, indices))
|
|
243
|
-
# sort ascending by index
|
|
244
|
-
path_index_tuples.sort(key=lambda x: x[1])
|
|
245
|
-
paths, indices = zip(*path_index_tuples)
|
|
246
|
-
|
|
247
|
-
return paths
|
|
248
|
-
|
|
249
|
-
def get_prediction_dir(self):
|
|
250
|
-
return Path(self.prediction_dir)
|
|
251
|
-
|
|
252
|
-
def get_predictions_path(self, suffix = ".json"):
|
|
253
|
-
pred_dir = self.get_prediction_dir()
|
|
254
|
-
return pred_dir.joinpath("predictions").with_suffix(suffix)
|
|
255
|
-
|
|
256
|
-
def get_smooth_predictions_path(self, suffix = ".json"):
|
|
257
|
-
pred_dir = self.get_prediction_dir()
|
|
258
|
-
return pred_dir.joinpath("smooth_predictions").with_suffix(suffix)
|
|
259
|
-
|
|
260
|
-
def get_binary_predictions_path(self, suffix = ".json"):
|
|
261
|
-
pred_dir = self.get_prediction_dir()
|
|
262
|
-
return pred_dir.joinpath("binary_predictions").with_suffix(suffix)
|
|
263
|
-
|
|
264
|
-
def get_raw_sequences_path(self, suffix = ".json"):
|
|
265
|
-
pred_dir = self.get_prediction_dir()
|
|
266
|
-
return pred_dir.joinpath("raw_sequences").with_suffix(suffix)
|
|
267
|
-
|
|
268
|
-
def get_filtered_sequences_path(self, suffix=".json"):
|
|
269
|
-
pred_dir = self.get_prediction_dir()
|
|
270
|
-
return pred_dir.joinpath("filtered_sequences").with_suffix(suffix)
|
|
271
|
-
|
|
272
|
-
def extract_text_information(self, frame_fraction: float = 0.001):
|
|
162
|
+
return anonymized_frame_dir, generated_frame_paths
|
|
163
|
+
|
|
164
|
+
def make_anonymized_video(self):
|
|
273
165
|
"""
|
|
274
|
-
|
|
275
|
-
Makes sure that frames are extracted and then processes the frames.
|
|
276
|
-
gets all frames from frame_dir and selects a fraction of them to process (at least 1)
|
|
166
|
+
Make an anonymized video from the anonymized frames.
|
|
277
167
|
"""
|
|
278
|
-
if not self.state_frames_extracted:
|
|
279
|
-
print(f"Frames not extracted for {self.file.name}")
|
|
280
|
-
return None
|
|
281
168
|
|
|
282
|
-
|
|
169
|
+
assert self.state_initial_prediction_completed, (
|
|
170
|
+
"Initial prediction not completed"
|
|
171
|
+
)
|
|
172
|
+
assert self.state_sensitive_data_retrieved, "Sensitive data not retrieved"
|
|
283
173
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
# Select evenly spaced frames
|
|
290
|
-
frames = frames[:: n_frames // n_frames_to_process]
|
|
174
|
+
ic(
|
|
175
|
+
"WARNING: Outside validation is not yet implemented and automatically set to true in this function"
|
|
176
|
+
)
|
|
177
|
+
self.state_outside_validated = True
|
|
178
|
+
self.save()
|
|
291
179
|
|
|
292
|
-
|
|
293
|
-
# defaultdict of lists.
|
|
294
|
-
# Then, extract the most frequent value from each list
|
|
295
|
-
# Finally, return the dictionary of most frequent values
|
|
180
|
+
assert self.state_outside_validated, "Outside validation not completed"
|
|
296
181
|
|
|
297
|
-
|
|
298
|
-
|
|
182
|
+
_anonymized_frame_dir, generated_frame_paths = (
|
|
183
|
+
self.make_temporary_anonymized_frames()
|
|
184
|
+
)
|
|
299
185
|
|
|
300
|
-
|
|
301
|
-
#
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
for roi, text in extracted_texts.items():
|
|
305
|
-
rois_texts[roi].append(text)
|
|
186
|
+
anonymized_video_path = self.get_anonymized_video_path()
|
|
187
|
+
# if anonymized video already exists, delete it
|
|
188
|
+
if anonymized_video_path.exists():
|
|
189
|
+
anonymized_video_path.unlink()
|
|
306
190
|
|
|
307
|
-
#
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
191
|
+
# Use ffmpeg and the frame paths to create a video
|
|
192
|
+
fps = self.get_fps()
|
|
193
|
+
height, width = cv2.imread(generated_frame_paths[0].as_posix()).shape[:2]
|
|
194
|
+
ic("Assembling anonymized video")
|
|
195
|
+
ic(f"Frame width: {width}, height: {height}")
|
|
196
|
+
ic(f"FPS: {fps}")
|
|
311
197
|
|
|
312
|
-
|
|
198
|
+
command = [
|
|
199
|
+
"ffmpeg",
|
|
200
|
+
"-y",
|
|
201
|
+
"-pattern_type",
|
|
202
|
+
"glob",
|
|
203
|
+
"-f",
|
|
204
|
+
"image2",
|
|
205
|
+
"-framerate",
|
|
206
|
+
str(fps),
|
|
207
|
+
"-i",
|
|
208
|
+
f"{generated_frame_paths[0].parent.as_posix()}/frame_[0-9]*.jpg",
|
|
209
|
+
"-c:v",
|
|
210
|
+
"libx264",
|
|
211
|
+
"-pix_fmt",
|
|
212
|
+
"yuv420p",
|
|
213
|
+
"-vf",
|
|
214
|
+
f"scale={width}:{height}",
|
|
215
|
+
anonymized_video_path.as_posix(),
|
|
216
|
+
]
|
|
313
217
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
texts = self.extract_text_information(ocr_frame_fraction)
|
|
218
|
+
subprocess.run(command, check=True)
|
|
219
|
+
ic(f"Anonymized video saved at {anonymized_video_path}")
|
|
317
220
|
|
|
318
|
-
self.
|
|
319
|
-
self.
|
|
221
|
+
self.state_make_anonymized_video_required = False
|
|
222
|
+
self.state_make_anonymized_video_completed = True
|
|
320
223
|
self.save()
|
|
321
224
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def update_video_meta(self):
|
|
325
|
-
video_meta = self.video_meta
|
|
326
|
-
video_path = Path(self.file.path)
|
|
327
|
-
|
|
328
|
-
if video_meta is None:
|
|
329
|
-
video_meta = VideoMeta.create_from_video(video_path)
|
|
330
|
-
self.video_meta = video_meta
|
|
331
|
-
self.save()
|
|
225
|
+
return anonymized_video_path, generated_frame_paths
|
|
332
226
|
|
|
227
|
+
def delete_frames_anonymized(self):
|
|
228
|
+
"""
|
|
229
|
+
Delete anonymized frames extracted from the video file.
|
|
230
|
+
"""
|
|
231
|
+
frame_dir = Path(self.frame_dir)
|
|
232
|
+
anonymized_frame_dir = frame_dir.parent / f"anonymized_{self.uuid}"
|
|
233
|
+
if anonymized_frame_dir.exists():
|
|
234
|
+
shutil.rmtree(anonymized_frame_dir)
|
|
235
|
+
return f"Anonymized frames deleted from {anonymized_frame_dir}"
|
|
333
236
|
else:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
237
|
+
return f"No anonymized frames to delete for {self.file.name}"
|
|
238
|
+
|
|
239
|
+
def get_or_create_video(self):
|
|
240
|
+
from endoreg_db.models import Video, Patient, PatientExamination
|
|
241
|
+
|
|
242
|
+
video = self.video
|
|
243
|
+
expected_path = self.get_anonymized_video_path()
|
|
244
|
+
if not video:
|
|
245
|
+
video_hash = self.video_hash
|
|
246
|
+
if Video.objects.filter(video_hash=video_hash).exists():
|
|
247
|
+
video = Video.objects.filter(video_hash=video_hash).first()
|
|
248
|
+
|
|
249
|
+
else:
|
|
250
|
+
if not expected_path.exists():
|
|
251
|
+
ic(
|
|
252
|
+
f"No anonymized video found at {expected_path}, Creating new one"
|
|
253
|
+
)
|
|
254
|
+
video_path, frame_paths = self.make_anonymized_video()
|
|
255
|
+
|
|
256
|
+
else:
|
|
257
|
+
ic(f"Anonymized video found at {expected_path}")
|
|
258
|
+
video_path = expected_path
|
|
259
|
+
frame_dir = self.get_anonymized_frame_dir()
|
|
260
|
+
ic(f"Frame dir: {frame_dir}")
|
|
261
|
+
frame_paths = list(frame_dir.glob("*.jpg"))
|
|
262
|
+
ic(f"Found {len(frame_paths)} frames")
|
|
263
|
+
|
|
264
|
+
video_object = Video.create_from_file(
|
|
265
|
+
video_path,
|
|
266
|
+
self.center,
|
|
267
|
+
self.processor,
|
|
268
|
+
video_dir=VIDEO_DIR,
|
|
269
|
+
frame_paths=frame_paths,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
ex: PatientExamination = self.sensitive_meta.pseudo_examination
|
|
273
|
+
pat: Patient = self.sensitive_meta.pseudo_patient
|
|
274
|
+
video_object.examination = ex
|
|
275
|
+
video_object.patient = pat
|
|
276
|
+
|
|
277
|
+
self.video = video_object
|
|
278
|
+
self.save()
|
|
279
|
+
video_object.sync_from_raw_video()
|
|
280
|
+
|
|
281
|
+
ic(f"Video object created: {video_object}")
|
|
282
|
+
return video_object
|
|
283
|
+
|
|
284
|
+
self.video = video
|
|
285
|
+
self.save()
|
|
342
286
|
|
|
343
|
-
|
|
287
|
+
# self.vi
|
|
288
|
+
return video
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
from .sensitive_meta import SensitiveMeta
|
|
2
2
|
from .pdf_meta import PdfMeta, PdfType
|
|
3
3
|
from .video_meta import VideoMeta, FFMpegMeta, VideoImportMeta
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"SensitiveMeta",
|
|
8
|
+
"PdfMeta",
|
|
9
|
+
"PdfType",
|
|
10
|
+
"VideoMeta",
|
|
11
|
+
"FFMpegMeta",
|
|
12
|
+
"VideoImportMeta",
|
|
13
|
+
]
|
|
@@ -52,6 +52,10 @@ class PdfType(models.Model):
|
|
|
52
52
|
|
|
53
53
|
return summary
|
|
54
54
|
|
|
55
|
+
@classmethod
|
|
56
|
+
def default_pdf_type(cls):
|
|
57
|
+
return PdfType.objects.get(name="ukw-endoscopy-examination-report-generic")
|
|
58
|
+
|
|
55
59
|
class PdfMeta(models.Model):
|
|
56
60
|
pdf_type = models.ForeignKey(PdfType, on_delete=models.CASCADE)
|
|
57
61
|
date = models.DateField()
|