diff --git a/atlas/migrations/0038_casecollection_viewer_mode.py b/atlas/migrations/0038_casecollection_viewer_mode.py new file mode 100644 index 00000000..6bb42d5f --- /dev/null +++ b/atlas/migrations/0038_casecollection_viewer_mode.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2024-02-12 12:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0037_alter_seriesimage_image_blake3_hash'), + ] + + operations = [ + migrations.AddField( + model_name='casecollection', + name='viewer_mode', + field=models.CharField(choices=[('BUILT_IN', 'Built in viewer'), ('OHIF', 'OHIF Viewer'), ('BUILT_IN_WITH_OHIF', 'Built in viewer with OHIF Viewer option')], default='BUILT_IN', max_length=100), + ), + ] diff --git a/atlas/models.py b/atlas/models.py index a67bb3d9..85f512a6 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -699,6 +699,26 @@ class CaseCollection(ExamOrCollectionGenericBase): default=COLLECTION_TYPE_CHOICES.REPORT, ) + class VIEWER_MODE_CHOICES(models.TextChoices): + BUILT_IN = ( + "BUILT_IN", + _("Built in viewer"), + ) + OHIF = ( + "OHIF", + _("OHIF Viewer"), + ) + BUILT_IN_WITH_OHIF = ( + "BUILT_IN_WITH_OHIF", + _("Built in viewer with OHIF Viewer option"), + ) + + viewer_mode = models.CharField( + max_length=100, + choices=VIEWER_MODE_CHOICES.choices, + default=VIEWER_MODE_CHOICES.BUILT_IN, + ) + def get_absolute_url(self): return reverse("atlas:collection_detail", kwargs={"pk": self.pk}) @@ -708,6 +728,18 @@ class CaseCollection(ExamOrCollectionGenericBase): def __str__(self) -> str: return self.name + def show_built_in_viewer(self) -> bool: + return self.viewer_mode in ( + self.VIEWER_MODE_CHOICES.BUILT_IN, + self.VIEWER_MODE_CHOICES.BUILT_IN_WITH_OHIF, + ) + + def show_ohif_viewer_link(self) -> bool: + return self.viewer_mode in ( + self.VIEWER_MODE_CHOICES.OHIF, + self.VIEWER_MODE_CHOICES.BUILT_IN_WITH_OHIF, + ) + def review_only(self) -> bool: """Returns True if a users cannot submit responses @@ -719,6 +751,17 @@ class CaseCollection(ExamOrCollectionGenericBase): return False + def in_review_mode(self) -> bool: + return self.review_only() or self.publish_results + + # def can_answer(self) -> bool: + # """Returns true if the user is able to answer questions in a collection + # (independent of current collection state (e.g. published / not published))""" + # if self.collection_type == self.COLLECTION_TYPE_CHOICES.REPORT: + # return True + + # return False + def get_case_by_index( self, case_index: int, case_count=False ) -> Case | Tuple[Case, int]: @@ -892,8 +935,10 @@ class UncategorisedDicom(models.Model): def check_for_duplicates(self, image_hash): duplicate = None - if obj := UncategorisedDicom.objects.filter(image_blake3_hash=image_hash).first(): - #duplicate = obj + if obj := UncategorisedDicom.objects.filter( + image_blake3_hash=image_hash + ).first(): + # duplicate = obj return obj print(image_hash) @@ -912,7 +957,9 @@ class UncategorisedDicom(models.Model): # if self.check_for_duplicates(): # return DuplicateDicom - image_blake3_hash = get_image_dicom_hash(None, dataset=ds, hash_type="blake3", direct_pixel_data=True) + image_blake3_hash = get_image_dicom_hash( + None, dataset=ds, hash_type="blake3", direct_pixel_data=True + ) self.image_blake3_hash = image_blake3_hash diff --git a/atlas/templates/atlas/add_case_to_collection.html b/atlas/templates/atlas/add_case_to_collection.html new file mode 100644 index 00000000..eab95ab0 --- /dev/null +++ b/atlas/templates/atlas/add_case_to_collection.html @@ -0,0 +1,13 @@ + +
+ {% csrf_token %} + + + + +
+
Creating a case in the collection: {{form.collection.name}} + {% endif %}
{% csrf_token %} diff --git a/atlas/templates/atlas/collection_case_view_take.html b/atlas/templates/atlas/collection_case_view_take.html index b8d1789f..62855e0b 100644 --- a/atlas/templates/atlas/collection_case_view_take.html +++ b/atlas/templates/atlas/collection_case_view_take.html @@ -28,29 +28,42 @@ {% endif %} -
-
- - Images - - {% for series in series_list %} - - - - Series {{ forloop.counter }}: - {{series.get_block}} - - - - {% endfor %} -
-
-
-
+ {% if collection.show_ohif_viewer_link %} +
+ View case in OHIF +
+ {% endif %} + + + + {% if collection.show_built_in_viewer %} +
+
+ + Images + + {% for series in series_list %} + + + + Series {{ forloop.counter }}: + {{series.get_block}} + + + + {% endfor %} +
-
+
+
+
+ +
+ {% else %} + {% endif %} + {% if show_discussion and case.discussion %}

diff --git a/atlas/templates/atlas/collection_detail.html b/atlas/templates/atlas/collection_detail.html index 162a20db..922b001b 100644 --- a/atlas/templates/atlas/collection_detail.html +++ b/atlas/templates/atlas/collection_detail.html @@ -21,8 +21,14 @@

This collection will be available to view/take here {% if can_edit %} -

+

+

+

{% endif %} {% include 'exam_overview_js.html' %} {% endblock %} diff --git a/atlas/templates/atlas/collection_take_overview.html b/atlas/templates/atlas/collection_take_overview.html index 6a9cb9e1..e2bee9e9 100644 --- a/atlas/templates/atlas/collection_take_overview.html +++ b/atlas/templates/atlas/collection_take_overview.html @@ -7,12 +7,15 @@

Collection: {{collection.name}} - {% if collection.publish_results or cid_user_exam.completed %} + {% if collection.in_review_mode or cid_user_exam.completed %} REVIEW {% endif %}

- {{answer_count}} out of {{collection_length}} cases answered. Click to go to case. + + {% if not collection.review_only %} + {{answer_count}} out of {{collection_length}} cases answered. Click to go to case. + {% endif %}
    {% for question, answer, self_review in question_answer_tuples %} @@ -40,9 +43,8 @@
+ Start time: {{cid_user_exam.start_time}} - - {% if collection.self_review %}

Completed: {{cid_user_exam.completed}}

diff --git a/atlas/templates/atlas/user_collections.html b/atlas/templates/atlas/user_collections.html index 4e8f9d19..6288266e 100644 --- a/atlas/templates/atlas/user_collections.html +++ b/atlas/templates/atlas/user_collections.html @@ -3,6 +3,8 @@ {% block content %}

Collections

+The following collections are available for you to view / take. +
    {% for collection in collections %} diff --git a/atlas/urls.py b/atlas/urls.py index 63a3e93e..7010921c 100755 --- a/atlas/urls.py +++ b/atlas/urls.py @@ -32,6 +32,7 @@ urlpatterns = [ path("author//", views.author_detail, name="author_detail"), path("author/", views.author_list, name="author_list"), path("case/", views.CaseView.as_view(), name="case_view"), + path("collection//add_case", views.add_case_to_collection, name="add_case_to_collection"), path("collection/", views.CollectionView.as_view(), name="collection_view"), path("collection/user", views.user_collections, name="user_collections"), path("uploads", views.user_uploads, name="user_uploads"), diff --git a/atlas/views.py b/atlas/views.py index d63d8968..f3b7ec22 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -136,6 +136,14 @@ import difflib logger = logging.getLogger(__name__) +def get_cases_available_to_user(user): + """Returns a queryset of cases available to the user""" + return Case.objects.filter(author=user) | Case.objects.filter(open_access=True) + if user.is_superuser: + return Case.objects.all() + else: + return Case.objects.filter(author=user) | Case.objects.filter(open_access=True) + class AuthorOrCheckerRequiredMixin(object): def get_object(self, *args, **kwargs): @@ -370,6 +378,25 @@ def user_collections(request): return render(request, "atlas/user_collections.html", {"collections": collections}) +def add_case_to_collection(request, collection_id): + if not request.htmx: + return Http404 + + + collection = get_object_or_404(CaseCollection, pk=collection_id) + + if request.method == "POST": + case = get_object_or_404(Case, pk=request.POST["case"]) + print(case) + collection.cases.add(case) + return HttpResponse("Case added to collection") + + + #if request.method == "POST": + + cases = get_cases_available_to_user(request.user) + + return render(request, "atlas/add_case_to_collection.html", {"cases": cases, "collection": collection}) @login_required def user_uploads_series(request, series_instance_uid: str):