Skip to content

Commit eeea3d2

Browse files
committed
Modalities.get_async() + move_async()
1 parent 9443400 commit eeea3d2

File tree

8 files changed

+99
-20
lines changed

8 files changed

+99
-20
lines changed

orthanc_api_client/job.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__(self, json_job: object):
3434
self.status = json_job.get('State')
3535
self.type = json_job.get('Type')
3636
self.content = json_job.get('Content')
37+
self.dimseErrorStatus = json_job.get('DimseErrorStatus') # new in Orthanc 1.12.10
3738

3839

3940
class Job:

orthanc_api_client/modalities.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,25 @@ def get_study(self, from_modality: str, dicom_id: str):
173173
from_modality=from_modality
174174
)
175175

176+
def get_study_async(self, from_modality: str, dicom_id: str) -> Job:
177+
"""
178+
retrieves a study from a remote modality (C-Get)
179+
180+
This call is asynchronous and returns a job.
181+
182+
:param from_modality: the modality alias configured in orthanc
183+
:param dicom_id: the StudyInstanceUid of the study to move
184+
"""
185+
return Job.from_json(self._api_client, self._get(
186+
level="Study",
187+
resource={
188+
"StudyInstanceUID": dicom_id
189+
},
190+
from_modality=from_modality,
191+
asynchronous=True
192+
))
193+
194+
176195
def get_series(self, from_modality: str, dicom_id: str, study_dicom_id: str):
177196
"""
178197
retrieves a series from a remote modality (C-Get)
@@ -232,6 +251,26 @@ def move_study(self, from_modality: str, dicom_id: str, to_modality_aet: str = N
232251
to_modality_aet=to_modality_aet
233252
)
234253

254+
def move_study_async(self, from_modality: str, dicom_id: str, to_modality_aet: str = None) -> Job:
255+
"""
256+
moves a study from a remote modality (C-Move) to a target modality (AET)
257+
258+
This call is asynchronous. It returns a job.
259+
260+
:param from_modality: the modality alias configured in orthanc
261+
:param dicom_id: the StudyInstanceUid of the study to move
262+
:param to_modality_aet: the AET of the target modality
263+
"""
264+
return Job.from_json(self._api_client, self._move(
265+
level="Study",
266+
resource={
267+
"StudyInstanceUID": dicom_id
268+
},
269+
from_modality=from_modality,
270+
to_modality_aet=to_modality_aet,
271+
asynchronous=True
272+
))
273+
235274
def move_series(self, from_modality: str, dicom_id: str, study_dicom_id: str, to_modality_aet: str = None):
236275
"""
237276
moves a series from a remote modality (C-Move) to a target modality (AET)
@@ -276,30 +315,29 @@ def move_instance(self, from_modality: str, dicom_id: str, series_dicom_id: str,
276315
to_modality_aet=to_modality_aet
277316
)
278317

279-
def _move(self, level: str, resource: object, from_modality: str, to_modality_aet: str = None):
318+
def _move(self, level: str, resource: object, from_modality: str, to_modality_aet: str = None, asynchronous: bool = False):
280319
"""
281320
moves a study from a remote modality (C-Move) to a target modality (AET)
282321
283-
this call is synchronous. It completes once the C-Move is complete.
284-
285322
:param from_modality: the modality alias configured in orthanc
286323
:param to_modality_aet: the AET of the target modality
287324
"""
288325

289326
payload = {
290327
'Level': level,
291-
'Resources': [resource]
328+
'Resources': [resource],
329+
'Asynchronous': asynchronous
292330
}
293331

294332
if to_modality_aet:
295333
payload['TargetAet'] = to_modality_aet
296334

297-
self._api_client.post(
335+
return self._api_client.post(
298336
endpoint=f"{self._url_segment}/{from_modality}/move",
299-
json=payload)
337+
json=payload).json()
300338

301339

302-
def _get(self, level: str, resource: object, from_modality: str):
340+
def _get(self, level: str, resource: object, from_modality: str, asynchronous: bool = False):
303341
"""
304342
retrieves a study from a remote modality (C-Get)
305343
@@ -310,12 +348,13 @@ def _get(self, level: str, resource: object, from_modality: str):
310348

311349
payload = {
312350
'Level': level,
313-
'Resources': [resource]
351+
'Resources': [resource],
352+
'Asynchronous': asynchronous
314353
}
315354

316-
self._api_client.post(
355+
return self._api_client.post(
317356
endpoint=f"{self._url_segment}/{from_modality}/get",
318-
json=payload)
357+
json=payload).json()
319358

320359

321360
def query_studies(self, from_modality: str, query: object) -> typing.List[RemoteModalityStudy]:

orthanc_api_client/resources/resources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ def print_daily_stats(self, from_date: datetime.date = None, to_date: datetime.d
450450
print(f"{current_date} - " + str(len(r.json())))
451451
current_date += datetime.timedelta(days=1)
452452

453-
def _lookup(self, filter: str, dicom_id: str) -> str:
453+
def _lookup(self, filter: str, dicom_id: str) -> Optional[str]:
454454
"""
455455
finds a resource in Orthanc based on its dicom id
456456

orthanc_api_client/resources/studies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime
2-
from typing import List, Any, Union, Set
2+
from typing import List, Any, Union, Set, Optional
33

44
from .resources import Resources
55
from ..tags import Tags
@@ -63,7 +63,7 @@ def get_modalities(self, orthanc_id: str) -> Set[str]:
6363
def get_parent_patient_id(self, orthanc_id: str) -> str:
6464
return self._api_client.get_json(f"{self._url_segment}/{orthanc_id}/patient")['ID']
6565

66-
def lookup(self, dicom_id: str) -> str:
66+
def lookup(self, dicom_id: str) -> Optional[str]:
6767
"""
6868
finds a study in Orthanc based on its StudyInstanceUid
6969

release-notes.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v 0.20.0
2+
========
3+
4+
- Added `Modalities.get_async()` & `Modalities.move_async()`
5+
16
v 0.19.0
27
========
38

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
# For a discussion on single-sourcing the version across setup.py and the
2929
# project code, see
3030
# https://packaging.python.org/guides/single-sourcing-package-version/
31-
version='0.19.0', # Required
31+
version='0.20.0', # Required
3232

3333
# This is a one-line description or tagline of what your project does. This
3434
# corresponds to the "Summary" metadata field:

tests/docker-setup/docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: "3"
21
services:
32
orthanc-a:
43
image: orthancteam/orthanc:25.8.2

tests/test_api_client.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,8 @@ def test_modalities_move(self):
443443
self.assertIsNotNone(self.ob.instances.lookup('7.8.9'))
444444

445445
#request C to move it from B to A
446-
447446
self.oc.modalities.move_study(from_modality='orthanc-b', dicom_id='1.2.3', to_modality_aet='ORTHANCA')
448-
self.assertIsNotNone(1, self.oa.studies.lookup('1.2.3'))
447+
self.assertIsNotNone(self.oa.studies.lookup('1.2.3'))
449448
self.oa.delete_all_content()
450449

451450
self.oc.modalities.move_series(from_modality='orthanc-b', dicom_id='4.5.6', study_dicom_id='1.2.3', to_modality_aet='ORTHANCA')
@@ -459,7 +458,7 @@ def test_modalities_move(self):
459458

460459
#request A to move it from B to A (retrieve)
461460
self.oa.modalities.move_study(from_modality='orthanc-b', dicom_id='1.2.3')
462-
self.assertIsNotNone(1, self.oa.studies.lookup('1.2.3'))
461+
self.assertIsNotNone(self.oa.studies.lookup('1.2.3'))
463462
self.oa.delete_all_content()
464463

465464
self.oa.modalities.move_series(from_modality='orthanc-b', dicom_id='4.5.6', study_dicom_id='1.2.3')
@@ -470,9 +469,28 @@ def test_modalities_move(self):
470469
self.assertIsNotNone(self.oa.instances.lookup('7.8.9'))
471470
self.oa.delete_all_content()
472471

472+
# move async
473+
job = self.oa.modalities.move_study_async(from_modality='orthanc-b', dicom_id='1.2.3')
474+
job.wait_completed()
475+
self.assertEqual(JobStatus.SUCCESS, job.info.status)
476+
self.assertIsNotNone(self.oa.studies.lookup('1.2.3'))
477+
self.oa.delete_all_content()
478+
479+
# move async (study that does not exists)
480+
job = self.oa.modalities.move_study_async(from_modality='orthanc-b', dicom_id='10.20.30')
481+
job.wait_completed()
482+
if self.oa.is_orthanc_version_at_least(1, 12, 10):
483+
self.assertEqual(JobStatus.FAILURE, job.info.status)
484+
# not validated yet
485+
# self.assertIsNotNone(job.info.dimseErrorStatus)
486+
# self.assertEqual(0xC000, job.info.dimseErrorStatus)
487+
self.assertIsNone(self.oa.studies.lookup('10.20.30'))
488+
self.oa.delete_all_content()
489+
490+
473491
#request A to get it from B
474492
self.oa.modalities.get_study(from_modality='orthanc-b', dicom_id='1.2.3')
475-
self.assertIsNotNone(1, self.oa.studies.lookup('1.2.3'))
493+
self.assertIsNotNone(self.oa.studies.lookup('1.2.3'))
476494
self.oa.delete_all_content()
477495

478496
self.oa.modalities.get_series(from_modality='orthanc-b', dicom_id='4.5.6', study_dicom_id='1.2.3')
@@ -483,9 +501,26 @@ def test_modalities_move(self):
483501
self.assertIsNotNone(self.oa.instances.lookup('7.8.9'))
484502
self.oa.delete_all_content()
485503

504+
# get async
505+
job = self.oa.modalities.get_study_async(from_modality='orthanc-b', dicom_id='1.2.3')
506+
job.wait_completed()
507+
self.assertEqual(JobStatus.SUCCESS, job.info.status)
508+
self.assertIsNotNone(self.oa.studies.lookup('1.2.3'))
509+
self.oa.delete_all_content()
510+
511+
# get async (study that does not exists)
512+
job = self.oa.modalities.get_study_async(from_modality='orthanc-b', dicom_id='10.20.30')
513+
job.wait_completed()
514+
if self.oa.is_orthanc_version_at_least(1, 12, 10):
515+
self.assertEqual(JobStatus.FAILURE, job.info.status)
516+
self.assertIsNotNone(job.info.dimseErrorStatus)
517+
self.assertEqual(0xC000, job.info.dimseErrorStatus)
518+
self.assertIsNone(self.oa.studies.lookup('10.20.30'))
519+
self.oa.delete_all_content()
520+
486521
#request A to get it from B
487522
self.oa.modalities.retrieve_study(from_modality='orthanc-b', dicom_id='1.2.3', retrieve_method=RetrieveMethod.GET)
488-
self.assertIsNotNone(1, self.oa.studies.lookup('1.2.3'))
523+
self.assertIsNotNone(self.oa.studies.lookup('1.2.3'))
489524
self.oa.delete_all_content()
490525

491526

0 commit comments

Comments
 (0)