start integrating cimar

This commit is contained in:
Ross
2025-02-24 11:53:36 +00:00
parent 249bc656c8
commit a0960c9b7c
21 changed files with 826 additions and 8 deletions
+17
View File
@@ -67,6 +67,9 @@ from atlas.helpers import get_cases_available_to_user
from typing import Any
from helpers.cimar import CimarAPI, NotFoundError
from rad.settings import CIMAR_USERNAME, CIMAR_PASSWORD
class CaseSelect(Select):
template_name = "atlas/case_select_widget.html"
@@ -363,6 +366,7 @@ class CaseForm(ModelForm):
"open_access",
"previous_case",
"diagnostic_certainty",
"cimar_uuid"
),
)
@@ -396,6 +400,7 @@ class CaseForm(ModelForm):
"open_access",
"previous_case",
"diagnostic_certainty",
"cimar_uuid"
]
# fields = ['question', 'findings', 'subspecialty', 'references']
widgets = {
@@ -418,6 +423,18 @@ class CaseForm(ModelForm):
"previous_case": CaseSelect(),
}
def clean_cimar_uuid(self):
cimar_uuid = self.cleaned_data["cimar_uuid"]
if cimar_uuid:
with CimarAPI(username=CIMAR_USERNAME, password=CIMAR_PASSWORD) as api:
try:
study = api.get_study_by_uuid(cimar_uuid)
except NotFoundError:
raise ValidationError("Study not found in CIMAR")
return cimar_uuid
def save(self, commit=True):
# Get the unsaved Case instance
instance = ModelForm.save(self, False)
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2025-02-10 13:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0066_case_total_images_series_size'),
]
operations = [
migrations.AddField(
model_name='case',
name='cimar_uuid',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
+14
View File
@@ -46,6 +46,7 @@ from generic.models import (
CidUser,
CidUserExam,
CidUserGroup,
CimarCase,
ExamOrCollectionGenericBase,
ExamUserStatus,
Examination,
@@ -392,6 +393,8 @@ class Case(models.Model, AuthorMixin, QuestionMixin):
total_images_series_size = models.BigIntegerField(null=True, blank=True)
cimar_uuid = models.CharField(max_length=255, null=True, blank=True)
def get_app_name(self):
return "atlas"
@@ -404,6 +407,14 @@ class Case(models.Model, AuthorMixin, QuestionMixin):
# return f"{self.pk}: {self.title}"
return format_html("<a href='{}'>{}</a>", self.get_absolute_url(), str(self))
def get_cimar_case(self):
return get_object_or_404(CimarCase, uuid=self.cimar_uuid)
def get_cimar_case_details(self):
cimar_case = self.get_cimar_case()
return cimar_case.study_details
# def get_base_template(self):
# def get_edit_template_links(self):
@@ -427,6 +438,7 @@ class Case(models.Model, AuthorMixin, QuestionMixin):
def get_case_dicom_json(self, case_title_as_patient_name=True, priors=None):
series_json = []
series: Series
for series in self.series.all():
series_json.append(series.get_series_dicom_json())
@@ -509,6 +521,8 @@ class Case(models.Model, AuthorMixin, QuestionMixin):
def extract_image_dicom_json_from_ds(ds, url, image_index):
to_keep = [
"Columns",
+59 -1
View File
@@ -19,7 +19,65 @@
</div>
</div>
{% endif %}
<div><a href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">View case in OHIF</a> <a target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">(new tab)</a>
<div>
{% if case.cimar_uuid %}
<details><summary>Details</summary>
<br/>
Case uuid: {{case.cimar_uuid}}
<br/>
{{case.get_cimar_case.viewer_link}}?{{cimar_sid}}
<button
hx-get='{% url "cimar_study_viewer_embed" case.cimar_uuid %}'
hx-target="#modals-here"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#modals-here"
class="btn primary">Open Modal</button>
<div id="modals-here"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
<a href='{% url "cimar_study_viewer" case.cimar_uuid %}'>Viewer</a>
<details>
<summary>Study details</summary>
{{ case.get_cimar_case.get_pretty_study_details }}
</details>
<details>
<summary>Study schema</summary>
{{ case.get_cimar_case.get_pretty_study_schema }}
</details>
<details>
<summary>Study series</summary>
<button
id='load-series'
hx-get='{% url "cimar_study_series_block" case.cimar_uuid %}'
hx-trigger='load'
hx-swap='outerHTML'>
Load Series
</button>
</details>
<a href='{% url "generic:cimar_case_refresh" case.cimar_uuid %}'>Refresh</a>
</details>
{% else %}
<a href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">View case in OHIF</a> <a target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">(new tab)</a>
{% endif %}
<div class="date">
{{ case.created_date|date:"d/m/Y" }}
+1
View File
@@ -378,6 +378,7 @@ urlpatterns = [
# path("all_questions/", views.all_questions, name="all_questions"),
path("case/<int:pk>/scrap", views.atlas_scrap, name="case_scrap"),
path("case/<int:pk>/delete", views.CaseDelete.as_view(), name="case_delete"),
path("case/<int:case_id>/push_to_cimar", views.push_case_to_cimar, name="push_case_to_cimar"),
path("case/create/", views.AtlasCreate.as_view(), name="case_create"),
path(
"case/collection/<int:pk>/create",
+37 -2
View File
@@ -22,6 +22,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
from django.views.generic import View
from django.views.generic.detail import DetailView
import pydicom
from pydicom.uid import generate_uid
from generic.mixins import SuperuserRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
@@ -33,8 +34,9 @@ from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from generic.models import CidUser, CidUserExam
from generic.models import CidUser, CidUserExam, CimarCase
from atlas.helpers import get_cases_available_to_user
from rad.settings import REMOTE_URL, CIMAR_USERNAME, CIMAR_PASSWORD
from .forms import (
AddCollectionToCaseForm,
@@ -159,6 +161,8 @@ import reversion
import os
import difflib
from helpers.cimar import CimarAPI
logger = logging.getLogger(__name__)
@@ -195,7 +199,7 @@ def case_detail(request, pk):
# raise PermissionDenied
# logging.debug(atlas.subspecialty.first().name.all())
return render(request, "atlas/case_detail.html", {"case": case})
return render(request, "atlas/case_detail.html", {"case": case, "cimar_sid":request.user.userprofile.cimar_sid})
@login_required
@user_is_author_or_atlas_series_checker_or_atlas_marker_or_open_access
@@ -955,6 +959,9 @@ class AtlasCreateBase(RevisionMixin, LoginRequiredMixin):
form.instance.author.add(self.request.user.id)
# Check Cimar Case
#CimarAPI().get_study_by_uuid(self.object.cimar_uuid)
context = self.get_context_data(form=form)
series_formset = context["series_formset"]
casedifferential_formset = context["casedifferential_formset"]
@@ -2390,6 +2397,7 @@ def collection_case_view(request, pk, case_number):
"previous": previous,
"next": next,
"answer": answer,
"cimar_sid":request.user.userprofile.cimar_sid
},
)
@@ -2419,6 +2427,7 @@ def collection_case_dicom_json(request, exam_id, case_id):
return JsonResponse(case_detail.case.get_case_dicom_json(priors=priors))
def case_dicom_json(request, pk):
case = get_object_or_404(Case, pk=pk)
@@ -2971,3 +2980,29 @@ def collection_reset_answers(request, exam_id: int):
else:
raise Http404("Invalid request")
def push_case_to_cimar(request, case_id):
case = get_object_or_404(Case, pk=case_id)
api = CimarAPI()
api.login(username=CIMAR_USERNAME, password=CIMAR_PASSWORD)
# We use the same study_uid for all images in a case
study_uid = generate_uid()
for series in case.get_series():
for image in series.images.all():
data = api.upload_dicom(image.image.path, study_uid=study_uid)
cimar_uuid = api.get_study_by_study_uid(data["study_uid"])["uuid"]
case.cimar_uuid = cimar_uuid
case.save()
cimar_case, created = CimarCase.objects.get_or_create(uuid=cimar_uuid)
cimar_case.refresh_study()
return HttpResponse("Success")
@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2025-02-10 12:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0020_remove_examuserstatus_share_with_supervisor_and_more'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='cimar_sid',
field=models.CharField(blank=True, max_length=100),
),
]
+22
View File
@@ -0,0 +1,22 @@
# Generated by Django 5.1.4 on 2025-02-10 13:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0021_userprofile_cimar_sid'),
]
operations = [
migrations.CreateModel(
name='CimarCase',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.CharField(max_length=255, unique=True)),
('viewer_link', models.URLField(blank=True)),
('study_details', models.JSONField(blank=True)),
],
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.4 on 2025-02-17 10:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0022_cimarcase'),
]
operations = [
migrations.AddField(
model_name='cimarcase',
name='valid_uuid',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='cimarcase',
name='study_details',
field=models.JSONField(blank=True, default=dict),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.4 on 2025-02-17 11:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('generic', '0023_cimarcase_valid_uuid_alter_cimarcase_study_details'),
]
operations = [
migrations.AddField(
model_name='cimarcase',
name='series_data',
field=models.JSONField(blank=True, default=dict),
),
migrations.AddField(
model_name='cimarcase',
name='study_schema',
field=models.JSONField(blank=True, default=dict),
),
]
+118 -1
View File
@@ -7,6 +7,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import models
from django.forms import ValidationError
from django.http import Http404, HttpRequest
from django.shortcuts import render
from django.utils import timezone
from django.core.validators import MaxValueValidator, MinValueValidator
from smtplib import SMTPException
@@ -29,7 +30,8 @@ from django.db.models.signals import post_save
from django.dispatch import receiver
from generic.mixins import AuthorMixin, QuestionMixin
from helpers.images import get_image_hash, pretty_print_dicom
from rad.settings import REMOTE_URL
from helpers.cimar import CimarAPI, STORAGE_API_URL
from rad.settings import REMOTE_URL, CIMAR_USERNAME, CIMAR_PASSWORD
from django.utils.html import format_html
from easy_thumbnails.files import get_thumbnailer
@@ -45,6 +47,34 @@ from django.forms.utils import from_current_timezone, to_current_timezone
from loguru import logger
from pygments import highlight
from pygments.lexers import JsonLexer
from pygments.formatters import HtmlFormatter
from django.contrib import admin
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string
def get_pretty_json(data):
"""Function to display pretty version of our data"""
if not data:
return "No data"
# Convert the data to sorted, indented JSON
response = json.dumps(data, sort_keys=True, indent=2)
# Get the Pygments formatter
formatter = HtmlFormatter(style='colorful')
# Highlight the data
response = highlight(response, JsonLexer(), formatter)
# Get the stylesheet
style = "<style>" + formatter.get_style_defs() + "</style><br>"
# Safe the output
return mark_safe(style + response)
def findMiddle(input_list):
"""Returns the middle element of a list"""
@@ -1690,6 +1720,8 @@ class UserProfile(models.Model):
username = property(getusername)
cimar_sid = models.CharField(max_length=100, blank=True)
def get_exams(self):
available_exams = defaultdict(list)
@@ -1827,3 +1859,88 @@ class ExamCollection(models.Model, AuthorMixin):
# blank=True,
# help_text="Author/Manager(s) of the exam collection",
# )
class CimarCase(models.Model):
uuid = models.CharField(max_length=255, unique=True)
viewer_link = models.URLField(blank=True)
study_details = models.JSONField(blank=True, default=dict)
study_schema = models.JSONField(blank=True, default=dict)
series_data = models.JSONField(blank=True, default=dict)
valid_uuid = models.BooleanField(default=False)
def load_series_data(self, api=None):
if api is None:
api = CimarAPI()
api.login(username=CIMAR_USERNAME, password=CIMAR_PASSWORD)
def refresh_study(self):
api = CimarAPI()
api.login(username=CIMAR_USERNAME, password=CIMAR_PASSWORD)
self.study_details = api.get_study_by_uuid(self.uuid)
self.study_schema = api.get_study_schema_by_uuid(self.uuid)
self.viewer_link = api.get_study_viewer_url_by_uuid(self.uuid)
series_data = []
for series in self.study_schema["series"]:
series_data.append({
"series_id" : series["id"],
"series_description" : series["description"],
"image_count": len(series["images"]),
"thumbnail": self.get_series_image_thumbnail_link(series)
}
)
self.series_data = {}
self.series_data["series"] = series_data
self.valid_uuid = True
self.save()
def __str__(self):
return f"{self.uuid} ({self.valid_uuid})"
def get_user_viewer_link(self, user):
return self.viewer_link + f"?sid={user.userprofile.cimar_sid}"
def get_pretty_study_details(self):
"""Function to display pretty version of our data"""
return get_pretty_json(self.study_details)
def get_pretty_study_schema(self):
"""Function to display pretty version of our data"""
return get_pretty_json(self.study_schema)
def get_pretty_study_series(self):
"""Function to display pretty version of our data"""
return get_pretty_json(self.series_data)
#def get_series_details_block(self, cimar_sid=None):
# if "series" in self.series_data:
# return render_to_string("cimar_series.html", {"series_data": self.series_data["series"], cimar_sid: cimar_sid})
#
# return "No series data available"
# return render_to_string("cimar_series.html", {"series_data": self.series_data["series"]})
def get_series_image_thumbnail_link(self,series):
middle_point = len(series["images"]) // 2
image = series["images"][middle_point]
return STORAGE_API_URL + f"/study/{self.study_details["storage_namespace"]}/{self.study_details["study_uid"]}/image/{image["id"]}/version/{image["version"]}/frame/0/thumbnail"
+7
View File
@@ -243,6 +243,13 @@ urlpatterns = [
views.toggle_share_with_supervisor,
name="toggle_share_with_supervisor",
),
path(
"cimar_case/<str:uuid>/refresh",
views.cimar_case_refresh,
name="cimar_case_refresh"
),
]
+14
View File
@@ -84,6 +84,7 @@ from .models import (
CidUser,
CidUserExam,
CidUserGroup,
CimarCase,
ExamBase,
ExamCollection,
ExamUserStatus,
@@ -4085,3 +4086,16 @@ def exam_collection_add_marker(request, collection_id, exam_type):
collection.add_marker_to_exams(users, exam_types=(exam_type,))
return HttpResponse("Marker added to exams")
def cimar_case_refresh(request, uuid):
try:
CimarCase.objects.get(uuid=uuid).refresh_study()
except CimarCase.DoesNotExist:
CimarCase(uuid=uuid).save()
return HttpResponse("Case refreshed")
+284
View File
@@ -0,0 +1,284 @@
import json
import requests
from PIL import Image
import io
#def fetch_and_print_articles(api_url):
# response = requests.get(api_url)
#
# if response.status_code == 200:
# articles = response.json().get('articles', [])
#
# for index, article in enumerate(articles[:3], start=1):
# print(f"Article {index}:\n{json.dumps(article, sort_keys=True, indent=4)}\n")
# else:
# print(f"Error: {response.status_code}")
#API_KEY = '3805f6bbabcb42b3a0c08a489baf603d'
#api_endpoint = f"https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey={API_KEY}"
API_URL = "https://cloud.cimar.co.uk/api/v3"
STORAGE_API_URL = 'https://storelpu.cimar.co.uk' + "/api/v3/storage"
class CimarAPI:
def __init__(self, sid=None, username=None, password=None):
self.sid = sid
self.context = False
self.username = username
self.password = password
# Default namespace to save studies to
self.namespace = "eff0ca95-6ce6-443c-80dd-1136c8a58e4d"
def get_storage(self, endpoint ):
pass
def post(self, endpoint, data=None, add_sid=True):
if data is None:
data = {}
if add_sid:
if self.sid is None:
raise NoSession("No session ID")
data["sid"] = self.sid
print(f"{data=}")
r = requests.post(API_URL + endpoint, data=data)
if r.status_code == 412:
match r.json():
case {"status": "ERROR", "error_type": "NOT_FOUND"}:
raise NotFoundError("Study not found")
case {"status": "ERROR", "error_type": error}:
raise APIError(error)
if r.status_code == 401:
raise InvalidCredentials("Invalid credentials")
print(r.status_code)
print(r.content)
return r.json()
def login(self, username=None, password=None):
if username is None:
username = self.username
if password is None:
password = self.password
data={"login": username, "password": password}
r = requests.post(API_URL + "/session/login", data=data)
if r.status_code == 412:
raise InvalidCredentials()
self.sid = r.json()["sid"]
return self.sid
def check_login_status(self):
try:
data = self.post("/session/user")
except InvalidCredentials:
return False
return data["status"] == "OK"
def logout(self):
data = self.post("/session/logout")
def __enter__(self):
if self.sid is None:
self.login()
self.context = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.context:
self.logout()
self.sid = None
def list_studies(self):
return self.post("/study/list")
def get_study_image_thumbnail(self, study_json):
schema = self.get_study_schema(study_json)
image = schema["series"][0]["images"][0]
r = requests.get(STORAGE_API_URL + f"/study/{study_json["storage_namespace"]}/{study_json["study_uid"]}/image/{image["id"]}/version/{image["version"]}/frame/0/thumbnail", params={"sid": self.sid})
print(r.status_code)
img = Image.open(io.BytesIO(r.content))
print(r.json())
# def get_series_image_thumbnail_link(self, study_json, series):
#
# middle_point = len(series["images"]) // 2
#
# image = series["images"][middle_point]
#
# return requests.Request("GET",STORAGE_API_URL + f"/study/{study_json["storage_namespace"]}/{study_json["study_uid"]}/image/{image["id"]}/version/{image["version"]}/frame/0/thumbnail").prepare()
#
def get_diagnostic_image(self, study_json, image_id, version, frame):
r = requests.get(STORAGE_API_URL + f"/study/{study_json["storage_namespace"]}/{study_json["study_uid"]}/image/{image_id}/version/{version}/frame/{frame}/diagnostic", params={"sid": self.sid})
print(r.status_code)
img = Image.open(io.BytesIO(r.content))
print(r.json())
def get_study_by_uuid(self, uuid):
return self.post("/study/get", data={"uuid": uuid})
def get_study_viewer_url_by_uuid(self, uuid):
return self.get_study_by_uuid(uuid)["viewer_link"]
def get_study_schema_by_uuid(self, uuid):
study_json = self.get_study_by_uuid(uuid)
return self.get_study_schema(study_json)
def get_study_by_study_uid(self, study_uid):
return self.post("/study/get", data={"study_uid": study_uid, "storage_namespace": self.namespace, "phi_namespace": self.namespace})
def get_study_schema(self, study_json):
r = requests.get(STORAGE_API_URL + f"/study/{study_json["storage_namespace"]}/{study_json["study_uid"]}/schema", params={"sid": self.sid})
return r.json()
def get_series_data(self, cimar_study_json):
series_json = []
for series in cimar_study_json["series"]:
instances = []
for image in series["images"]:
p= requests.Request("GET", STORAGE_API_URL + f"/study/{cimar_study_json["namespace"]}/{cimar_study_json["study_uid"]}/image/{image["id"]}/version/{image["version"]}/frame/0/diagnostic", params={"sid": self.sid}).prepare()
instances.append({"metadata": {}, "url": f"{p.url}"})
new_json = {
"SeriesInstanceUID": series["series_uid"],
"SeriesDescription": series["description"],
"Modality": series["modality"],
"instances": instances
}
series_json.append(new_json)
return series_json
def get_study_dicom_json(self, cimar_study_json):
series_json = []
for series in cimar_study_json["series"]:
instances = []
for image in series["images"]:
p= requests.Request("GET", STORAGE_API_URL + f"/study/{cimar_study_json["namespace"]}/{cimar_study_json["study_uid"]}/image/{image["id"]}/version/{image["version"]}/frame/0/diagnostic", params={"sid": self.sid}).prepare()
instances.append({"metadata": {}, "url": f"dicomweb:{p.url}"})
new_json = {
"SeriesInstanceUID": series["series_uid"],
"SeriesDescription": series["description"],
"Modality": series["modality"],
"instances": instances
}
series_json.append(new_json)
studies_json = {
"studies": [
{
"StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178",
#"StudyDate": "20000101",
"StudyTime": "",
"PatientName": "Patient^Name",
"PatientID": "LIDC-IDRI-0001",
"AccessionNumber": "",
"PatientAge": "",
"PatientSex": "",
"series": series_json,
}
]
}
return studies_json
def get_session_user(self):
return self.post("/session/user")
def upload_dicom(self, file, study_uid=""):
if study_uid:
study_uid = f"&study_uid={study_uid}"
#data = {}
#data["sid"] = self.sid
#data["study_uid"] = "1.2.840.113773.2.10070.7173.20210208144916.37"
#data["image_uid"]= "1.2.840.113773.4.10070.7173.20210208144916.39"
files = open(file, "rb")
#headers = {'content-type': 'application/dicom'}
r = requests.post(STORAGE_API_URL + f"/namespace/{self.namespace}/image?sid={self.sid}{study_uid}", data=files)#, headers=headers)
print(r)
return r.json()
#CimarAdminApi = CimarAPI().login("")
if __name__ == "__main__":
with CimarAPI(username="ross.kruger@nhs.net", password="") as api:
print("***")
#data = api.get_session_user()
#data = api.upload_dicom()
data = api.get_study_by_study_uid("2.1159655940009962407176401939062805636618")
print(data)
pass
#print(api.sid)
##studies = api.list_studies()
##test_study = studies["studies"][0]
##schema = api.get_study_schema(test_study)
#schema = api.get_study_schema_by_uuid("c9417b53-87aa-4c40-aca0-3fa34c225536")
#api.get_study_dicom_json(schema)
#print(schema)
##img = api.get_study_image_thumbnail(test_study)
class NoSession(Exception):
pass
class InvalidCredentials(Exception):
pass
class NotFoundError(Exception):
pass
class APIError(Exception):
pass
+3
View File
@@ -315,6 +315,9 @@ STATICFILES_DIRS = (
os.path.join(BASE_DIR, "rad", "static"), # That's it!!
)
CIMAR_USERNAME = ""
CIMAR_PASSWORD = ""
try:
from .settings_local import *
except ImportError as e:
+22
View File
@@ -158,6 +158,28 @@ urlpatterns = [
path("ac/", autocomplete_urls),
path('psutil/', psutil_urlpatterns(), name='psutil'),
path("cimar/", views.cimar, name="cimar"),
path(
"cimar/study/<str:uuid>/dicom_json",
views.cimar_study_dicom_json,
name="cimar_study_dicom_json",
),
path(
"cimar/study/<str:uuid>/viewer",
views.cimar_study_viewer,
name="cimar_study_viewer",
),
path(
"cimar/study/<str:uuid>/viewer/embed",
views.cimar_study_viewer_embed,
name="cimar_study_viewer_embed",
),
path(
"cimar/study/<str:uuid>/series_block",
views.cimar_study_series_block,
name="cimar_study_series_block",
),
path("cimar/login/", views.cimar_login, name="cimar_login"),
]
+64 -1
View File
@@ -48,6 +48,7 @@ from anatomy.models import AnatomyQuestion
from anatomy.models import Answer as AnatomyAnswer
from rad.filters import UserListFilter
from helpers.cimar import CimarAPI, InvalidCredentials, NoSession
from rapids.models import UserAnswer as RapidsUserAnswer
from rapids.models import Exam as RapidsExam
from rapids.models import Rapid
@@ -65,7 +66,7 @@ from rapids.views import GenericExamViews as RapidsExamsViews
from longs.views import GenericExamViews as LongsExamViews
from generic.forms import QuestionNoteForm
from generic.models import CidUser, ExamCollection, QuestionNote, Supervisor, UserGrades, UserProfile
from generic.models import CidUser, CimarCase, ExamCollection, QuestionNote, Supervisor, UserGrades, UserProfile
from django_filters.views import FilterView
@@ -919,3 +920,65 @@ def request_cid_details(request):
return HttpResponse(
f"Candidate details will be sent to the requested email (if it is found in the system). If you have not received an email after a few minutes you may need to contact <a href='mailto:{settings.CONTACT_EMAIL}'>{settings.CONTACT_EMAIL}</a>"
)
def cimar_study_dicom_json(request, uuid="c9417b53-87aa-4c40-aca0-3fa34c225536"):
with CimarAPI(request.user.userprofile.cimar_sid) as api:
print(api.sid)
#studies = api.list_studies()
#test_study = studies["studies"][0]
#schema = api.get_study_schema(test_study)
schema = api.get_study_schema_by_uuid("c9417b53-87aa-4c40-aca0-3fa34c225536")
return JsonResponse(api.get_study_dicom_json(schema))
def cimar_study_viewer(request, uuid="c9417b53-87aa-4c40-aca0-3fa34c225536"):
case = get_object_or_404(CimarCase, uuid=uuid)
viewer_link = case.viewer_link + f"?sid={request.user.userprofile.cimar_sid}"
return render(request, "cimar_embed.html", {"viewer_link": viewer_link})
def cimar_study_viewer_embed(request, uuid="c9417b53-87aa-4c40-aca0-3fa34c225536"):
case = get_object_or_404(CimarCase, uuid=uuid)
viewer_link = case.viewer_link + f"?sid={request.user.userprofile.cimar_sid}"
return render(request, "cimar_embed.html#embed", {"viewer_link": viewer_link})
def cimar_study_series_block(request, uuid):
case = get_object_or_404(CimarCase, uuid=uuid)
return render(request, "cimar_series.html", {"series_data": case.series_data["series"], "cimar_sid": request.user.userprofile.cimar_sid})
def cimar_login(request):
if request.htmx:
username = request.POST.get("username")
password = request.POST.get("password")
if not username and not password:
return HttpResponse("Please fill in login details")
try:
sid = CimarAPI().login(username, password)
request.user.userprofile.cimar_sid = sid
request.user.userprofile.save()
except InvalidCredentials:
return HttpResponse("Invalid login details")
return HttpResponse("Logged in")
else:
try:
login_status = CimarAPI(request.user.userprofile.cimar_sid).check_login_status()
except NoSession:
login_status = False
return render(request, "cimar_login.html", {"login_status": login_status, "cimar_sid": request.user.userprofile.cimar_sid})
+2
View File
@@ -54,3 +54,5 @@ psutil
django-psutil-dash
django-template-partials
easy_thumbnails
requests
pygments
+39
View File
@@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% load partials %}
{% partialdef viewer %}
<div style="height: 100vh; width: 95%, mangin: 0; padding: 0; overflow: hidden;">
<iframe class="embedded-viewer" src="{{viewer_link}}" width="100%" height="95%" style="border:none;"></iframe>
</div>
<style>
.embedded-viewer {
border: none;
pointer-events: auto;
margin: 0; height: 100%; overflow: hidden
}
</style>
{% endpartialdef %}
{% partialdef embed %}
Close
{% partial viewer %}
{% endpartialdef %}
{% block content %}
{% partial viewer %}
{% endblock content %}
{% block css %}
{% endblock css %}
+23
View File
@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block content %}
<div class="anatomy">
<h2>CIMAR login</h2>
Login status: {{ login_status }} ({{cimar_sid}})<br/>
Logging in here allows your account to be linked with CIMAR. You may periodically need to re-login to keep the link active.
<form method="POST" class="post-form">
Username: <input type="text" id="username" name="username" value=""/> <br/>
Password: <input type="password" id="password" name="password" value=""/> <br/>
<button hx-post='{% url "cimar_login" %}'
hx-target="#results">Login</button>
</form>
<div id="results"></div>
</div>
{% endblock %}
+15
View File
@@ -0,0 +1,15 @@
<h3>Series</h3>
<div class="pre-whitespace multi-image-block"><b>Series:</b>
{% for series in series_data %}
<span class="series-block">
<span>
<span class="series-block-series-number">Series {{ forloop.counter }}:</span><br>
<span>
<span class='series-block-examination'>{{series|get_item:"series_description"}}</span><br/>
<span class='series-block-thumbnail'><img src='{{series|get_item:"thumbnail"}}?{{cimar_sid}}' /></span><br/>
<span class='series-block-image-number'>Images: <span class='series-block-image-number-count'>{{series|get_item:"image_count"}}</span></span>
</span>
</span>
</span>
{% endfor %}
</div>