Skip to content

Commit

Permalink
Merge pull request #598 from jbernal0019/master
Browse files Browse the repository at this point in the history
Implement PACS retrieve API endpoint
  • Loading branch information
jbernal0019 authored Dec 6, 2024
2 parents 975192b + e96ce16 commit 05cd33b
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 27 deletions.
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

0 comments on commit 05cd33b

Please sign in to comment.