From 7b21b1ad6fdeef0d13fb565c2e8edad448b08ea5 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 18 Dec 2023 11:54:00 +0000 Subject: [PATCH] add the ablitiy to truncate series on the site --- atlas/api.py | 20 +++ ...032_series_modified_seriesimage_removed.py | 23 +++ .../0033_alter_seriesimage_image.py | 19 +++ atlas/models.py | 18 +-- atlas/templates/atlas/series_viewer.html | 136 ++++++++++++------ generic/models.py | 93 +++++++++--- ...series_modified_longseriesimage_removed.py | 23 +++ 7 files changed, 255 insertions(+), 77 deletions(-) create mode 100644 atlas/migrations/0032_series_modified_seriesimage_removed.py create mode 100644 atlas/migrations/0033_alter_seriesimage_image.py create mode 100644 longs/migrations/0011_longseries_modified_longseriesimage_removed.py diff --git a/atlas/api.py b/atlas/api.py index 63eb9848..d5e0989a 100644 --- a/atlas/api.py +++ b/atlas/api.py @@ -180,6 +180,26 @@ def series_remove_duplicate_images(request, series_id: int): return len(dupes) +@router.get("/series_truncate/{series_id}/{start}/{end}/", auth=django_auth) +def series_truncate(request, series_id: int, start: int, end:int): + print(start, end) + series = get_object_or_404(Series, pk=series_id) + + if not series.check_user_can_edit(request.user): + return {"status": "permission denied"} + + images_removed = [] + for n, image in enumerate(series.get_images()): + if n >= start and n <=end: + continue + else: + print(n, image) + image.removed=True + image.image = None + image.save() + images_removed.append(image.pk) + return {"status": "success", "images_removed": images_removed} + @router.get("/get_cases_user", auth=django_auth, response=List[CaseSchema]) def get_cases_user(request): diff --git a/atlas/migrations/0032_series_modified_seriesimage_removed.py b/atlas/migrations/0032_series_modified_seriesimage_removed.py new file mode 100644 index 00000000..9fa78760 --- /dev/null +++ b/atlas/migrations/0032_series_modified_seriesimage_removed.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.4 on 2023-12-18 10:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0031_seriesimage_replaced'), + ] + + operations = [ + migrations.AddField( + model_name='series', + name='modified', + field=models.CharField(choices=[('NO', 'Not modified'), ('TR', 'Truncated'), ('RE', 'Resliced/resampled')], default='NO', max_length=2), + ), + migrations.AddField( + model_name='seriesimage', + name='removed', + field=models.BooleanField(default=False, help_text='Set to true if the file this object refers to has been removed (either from series truncation or merging)'), + ), + ] diff --git a/atlas/migrations/0033_alter_seriesimage_image.py b/atlas/migrations/0033_alter_seriesimage_image.py new file mode 100644 index 00000000..454753cf --- /dev/null +++ b/atlas/migrations/0033_alter_seriesimage_image.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.4 on 2023-12-18 11:47 + +import atlas.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0032_series_modified_seriesimage_removed'), + ] + + operations = [ + migrations.AlterField( + model_name='seriesimage', + name='image', + field=models.FileField(blank=True, null=True, upload_to=atlas.models.image_directory_path), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index 0582f37f..aa4b54c5 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -466,7 +466,7 @@ def extract_image_dicom_json_from_ds(ds, url, image_index): class SeriesImage(SeriesImageBase): - image = models.FileField(upload_to=image_directory_path) + image = models.FileField(upload_to=image_directory_path, null=True) series = models.ForeignKey( "Series", related_name="images", on_delete=models.CASCADE, null=True ) @@ -479,11 +479,6 @@ class SeriesImage(SeriesImageBase): help_text="Reference to the object that has replaced this one.", ) - removed = models.BooleanField( - default=False, - help_text="Set to true if the file this object refers to has been removed (either from series truncation or merging)", - ) - def get_dicom_data(self): try: with pydicom.dcmread(self.image) as d: @@ -563,15 +558,6 @@ class Series(SeriesBase): related_name="series", ) - - class SeriesModifiedChocies(models.TextChoices): - NO = "NO", "Not modified", - TR = "TR", "Truncated", - RE = "RE", "Resliced/resampled", - - - modified = models.CharField(max_length=2, default=SeriesModifiedChocies.NO, choices=SeriesModifiedChocies.choices) - series_instance_uid = models.CharField(max_length=255, blank=True, null=True) # findings = models.TextField(null=True, blank=True, help_text="Findings on the series / stack") @@ -603,7 +589,7 @@ class Series(SeriesBase): # TODO: clean up (this is rather convoluted....) image: SeriesImage - for series_n, image in enumerate(self.images.all()): + for series_n, image in enumerate(self.images.filter(removed=False)): ds = image.get_dicom_data() if series_n == 0: for tag in to_keep: diff --git a/atlas/templates/atlas/series_viewer.html b/atlas/templates/atlas/series_viewer.html index 0df66b80..f1273b42 100755 --- a/atlas/templates/atlas/series_viewer.html +++ b/atlas/templates/atlas/series_viewer.html @@ -10,47 +10,64 @@ This series is not associated with any cases. {% endif %} -
-
- -{% if editing_finding < 1 %} - -{% endif %} - -
-
-
- {% csrf_token %} - {{series_finding_form.management_form}} -
-
Description: {{ series_finding_form.description }}
-
Findings: {{ series_finding_form.findings }}
-
Structures: {{ series_finding_form.structures }}
-
- - -
+{% with image_url_array_and_count=series.get_image_url_array_and_count %} +
-
-
- Findings - - {% for finding in series.findings.all %} -
- - - Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}
- Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}
- Description: {{finding.description}}
-
- Edit - Delete + {% if editing_finding < 1 %} + + {% endif %} + +
+
+
+ {% csrf_token %} + {{series_finding_form.management_form}} +
+
Description: {{ series_finding_form.description }}
+
Findings: {{ series_finding_form.findings }}
+
Structures: {{ series_finding_form.structures }}
+
+ + +
+
- {% endfor %} -
+
+ Findings + + + + + + {% for finding in series.findings.all %} +
+ + + Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}
+ Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}
+ Description: {{finding.description}}
+
+ Edit + Delete +
+ + {% endfor %} +
+
+ Truncate series +

+ This will limit the series to the selected bounds (the rest of the images will be deleted). This is useful when you have a large volume of which only a small area is relevant to the case. NOTE: once deleted the images cannot be recovered on the site (they would have to be reuploaded if required). +

+ Start
+ End
+ +
+ +
+{% endwith %}
Author: {{ series.get_author_display }}
@@ -72,20 +89,20 @@ Image details - {% for image in series.images.all %} - [{{ image.id }}] {{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} + {% for image in series.get_images %} + [{{ image.id }}] {{image.image.url}}, pos: {{image.position}}, {{image.upload_filename}} - {% if image.image %} - [{{image.image.size|filesizeformat}}] - {% endif %} + {% if image.image %} + [{{image.image.size|filesizeformat}}] + {% endif %} - {{image.image_md5_hash}} ({{image.is_dicom}}) + {{image.image_md5_hash}} ({{image.is_dicom}}) -
+
{% comment %} {{image.get_dicom_info|safe}}
{% endcomment %} - {% endfor %} + {% endfor %}

Total image size: {{series.get_total_image_size|filesizeformat}}

@@ -95,6 +112,35 @@