Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PACS retrieve API endpoint #598

Merged
merged 1 commit into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions chris_backend/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,16 @@
pacsfile_views.PACSQueryDetail.as_view(),
name='pacsquery-detail'),

path('v1/pacs/queries/<int:pk>/retrieves/',
pacsfile_views.PACSRetrieveList.as_view(), name='pacsretrieve-list'),

path('v1/pacs/queries/<int:pk>/retrieves/search/',
pacsfile_views.PACSRetrieveListQuerySearch.as_view(),
name='pacsretrieve-list-query-search'),

path('v1/pacs/queries/retrieves/<int:pk>/',
pacsfile_views.PACSRetrieveDetail.as_view(), name='pacsretrieve-detail'),

path('v1/pacs/<int:pk>/series/',
pacsfile_views.PACSSpecificSeriesList.as_view(),
name='pacs-specific-series-list'),
Expand Down
29 changes: 29 additions & 0 deletions chris_backend/pacsfiles/migrations/0004_pacsretrieve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.5 on 2024-11-27 00:01

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('pacsfiles', '0003_pacsquery'),
]

operations = [
migrations.CreateModel(
name='PACSRetrieve',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('creation_date', models.DateTimeField(auto_now_add=True)),
('result', models.TextField(blank=True)),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('pacs_query', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='retrieve_list', to='pacsfiles.pacsquery')),
],
options={
'ordering': ('pacs_query', '-creation_date'),
},
),
]
33 changes: 31 additions & 2 deletions chris_backend/pacsfiles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class PACSQueryFilter(FilterSet):
title = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
description = django_filters.CharFilter(field_name='description',
lookup_expr='icontains')
pacs_id = django_filters.CharFilter(field_name='pacs_id', lookup_expr='exact')
pacs_identifier = django_filters.CharFilter(field_name='pacs__identifier',
lookup_expr='exact')
owner_username = django_filters.CharFilter(field_name='owner__username',
Expand All @@ -77,7 +78,34 @@ class PACSQueryFilter(FilterSet):
class Meta:
model = PACSQuery
fields = ['id', 'min_creation_date', 'max_creation_date', 'title_exact',
'title', 'description', 'pacs_identifier', 'owner_username']
'title', 'description', 'pacs_id', 'pacs_identifier', 'owner_username']


class PACSRetrieve(models.Model):
creation_date = models.DateTimeField(auto_now_add=True)
result = models.TextField(blank=True)
pacs_query = models.ForeignKey(PACSQuery, on_delete=models.CASCADE,
related_name='retrieve_list')
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)

class Meta:
ordering = ('pacs_query', '-creation_date',)

def __str__(self):
return self.pacs_query.query


class PACSRetrieveFilter(FilterSet):
min_creation_date = django_filters.IsoDateTimeFilter(field_name='creation_date',
lookup_expr='gte')
max_creation_date = django_filters.IsoDateTimeFilter(field_name='creation_date',
lookup_expr='lte')
owner_username = django_filters.CharFilter(field_name='owner__username',
lookup_expr='exact')

class Meta:
model = PACSRetrieve
fields = ['id', 'min_creation_date', 'max_creation_date', 'owner_username']


class PACSSeries(models.Model):
Expand Down Expand Up @@ -133,6 +161,7 @@ class PACSSeriesFilter(FilterSet):
lookup_expr='gte')
max_PatientAge = django_filters.NumberFilter(field_name='PatientAge',
lookup_expr='lte')
pacs_id = django_filters.CharFilter(field_name='pacs_id', lookup_expr='exact')
pacs_identifier = django_filters.CharFilter(field_name='pacs__identifier',
lookup_expr='exact')

Expand All @@ -142,7 +171,7 @@ class Meta:
'PatientName', 'PatientSex', 'PatientAge', 'min_PatientAge',
'max_PatientAge', 'PatientBirthDate', 'StudyDate', 'AccessionNumber',
'ProtocolName', 'StudyInstanceUID', 'StudyDescription',
'SeriesInstanceUID', 'SeriesDescription', 'pacs_identifier']
'SeriesInstanceUID', 'SeriesDescription', 'pacs_id', 'pacs_identifier']


class PACSFile(ChrisFile):
Expand Down
47 changes: 42 additions & 5 deletions chris_backend/pacsfiles/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from core.serializers import ChrisFileSerializer
from core.utils import json_zip2str

from .models import PACS, PACSQuery, PACSSeries, PACSFile
from .models import PACS, PACSQuery, PACSRetrieve, PACSSeries, PACSFile
from .services import PfdcmClient


Expand All @@ -25,7 +25,8 @@ class PACSSerializer(serializers.HyperlinkedModelSerializer):
folder = serializers.HyperlinkedRelatedField(view_name='chrisfolder-detail',
read_only=True)
query_list = serializers.HyperlinkedIdentityField(view_name='pacsquery-list')
series_list = serializers.HyperlinkedIdentityField(view_name='pacs-specific-series-list')
series_list = serializers.HyperlinkedIdentityField(
view_name='pacs-specific-series-list')

class Meta:
model = PACS
Expand All @@ -35,19 +36,21 @@ class Meta:

class PACSQuerySerializer(serializers.HyperlinkedModelSerializer):
query = serializers.JSONField(binary=True, required=False)
result = serializers.ReadOnlyField()
pacs_identifier = serializers.ReadOnlyField(source='pacs.identifier')
owner_username = serializers.ReadOnlyField(source='owner.username')
result = serializers.ReadOnlyField()
retrieve_list = serializers.HyperlinkedIdentityField(view_name='pacsretrieve-list')

class Meta:
model = PACSQuery
fields = ('url', 'id', 'creation_date', 'title', 'query', 'description',
'result', 'pacs_identifier', 'owner_username')
'pacs_identifier', 'owner_username', 'result', 'retrieve_list')

def create(self, validated_data):
"""
Overriden to rise a serializer error when attempting to create a PACSQuery
object that results in a DB conflict. Then a query is made to the PFDCM service.
object that results in a DB conflict. Then a PACS query operation is requested
to the PFDCM service.
"""
title = validated_data['title']
query = validated_data['query']
Expand Down Expand Up @@ -96,6 +99,40 @@ def validate(self, data):
return data


class PACSRetrieveSerializer(serializers.HyperlinkedModelSerializer):
pacs_query_id = serializers.ReadOnlyField(source='pacs_query.id')
pacs_query_title = serializers.ReadOnlyField(source='pacs_query.title')
query = serializers.JSONField(binary=True, read_only=True, source='pacs_query.query')
pacs_identifier = serializers.ReadOnlyField(source='pacs_query.pacs.identifier')
owner_username = serializers.ReadOnlyField(source='owner.username')
result = serializers.ReadOnlyField()
pacs_query = serializers.HyperlinkedRelatedField(view_name='pacsquery-detail',
read_only=True)

class Meta:
model = PACSRetrieve
fields = ('url', 'id', 'creation_date', 'pacs_query_id', 'pacs_query_title',
'query', 'pacs_identifier', 'owner_username', 'result', 'pacs_query')

def create(self, validated_data):
"""
Overriden to request a PACS retrieve operation to the PFDCM service.
"""
pacs_query = validated_data['pacs_query']
query = pacs_query.query
pacs_name = pacs_query.pacs.identifier

pacs_retrieve = super(PACSRetrieveSerializer, self).create(validated_data)

pfdcm_cl = PfdcmClient()
result = pfdcm_cl.retrieve(pacs_name, query)

if result:
pacs_retrieve.result = json_zip2str(result)
pacs_retrieve.save()
return pacs_retrieve


class PACSSeriesSerializer(serializers.HyperlinkedModelSerializer):
path = serializers.CharField(max_length=1024, write_only=True)
folder_path = serializers.ReadOnlyField(source='folder.path')
Expand Down
26 changes: 25 additions & 1 deletion chris_backend/pacsfiles/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from core.models import ChrisFolder
from core.utils import json_zip2str
from pacsfiles.models import PACS, PACSQuery
from pacsfiles.serializers import PACSQuerySerializer, PACSSeriesSerializer
from pacsfiles.serializers import (PACSQuerySerializer, PACSRetrieveSerializer,
PACSSeriesSerializer)


CHRIS_SUPERUSER_PASSWORD = settings.CHRIS_SUPERUSER_PASSWORD
Expand Down Expand Up @@ -113,6 +114,29 @@ def test_update_failure_pacs_user_title_combination_already_exists(self):
pacs_query_serializer.update(pacs_query, data)


class PACSRetrieveSerializerTests(SerializerTests):

def test_create_success(self):
"""
Test whether overriden 'create' method successfully creates a new PACS retrieve.
"""
user = User.objects.get(username=self.username)
pacs = PACS.objects.get(identifier=self.pacs_name)
query = {'SeriesInstanceUID': '2.3.15.2.1057'}

pacs_query, _ = PACSQuery.objects.get_or_create(title='query2', query=query,
owner=user, pacs=pacs)
data = {'pacs_query': pacs_query, 'owner': user}

with mock.patch('pacsfiles.serializers.PfdcmClient.retrieve') as pfdcm_retrieve_mock:
result = {'mock': 'mock'}
pfdcm_retrieve_mock.return_value = result
pacs_retrieve_serializer = PACSRetrieveSerializer(data=data)
pacs_retrieve = pacs_retrieve_serializer.create(data)
pfdcm_retrieve_mock.assert_called_with(self.pacs_name, query)
self.assertEqual(pacs_retrieve.result, json_zip2str(result))


class PACSSeriesSerializerTests(SerializerTests):

def test_validate_ndicom_failure_not_positive(self):
Expand Down
Loading
Loading