Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-pyorthanc for
openSUSE:Factory checked in at 2026-04-04 19:07:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyorthanc (Old)
and /work/SRC/openSUSE:Factory/.python-pyorthanc.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyorthanc"
Sat Apr 4 19:07:55 2026 rev:2 rq:1344562 version:1.22.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyorthanc/python-pyorthanc.changes
2025-05-13 20:05:52.867868174 +0200
+++
/work/SRC/openSUSE:Factory/.python-pyorthanc.new.21863/python-pyorthanc.changes
2026-04-04 19:09:50.947107069 +0200
@@ -1,0 +2,19 @@
+Sat Apr 4 08:13:32 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.22.1:
+ * Remove warning when importing pyorthanc
+- update to 1.22.0:
+ * `upload()` now returns a list the uploaded instances
+ `list[pyorthanc.Instance]`
+ * Add `get_internal_client()` to retrieve an Orthanc client
+ inside an orthanc script
+ * `upload()` now allows uploading DICOM files from a zip or
+ directory
+ * Add check_before_upload option
+- update to 1.21.0:
+ * Add URL properties
+- update to 1.20.1:
+ * Allows pydicom=3.x.x versions
+ * Add kwargs to find_patient/study/series/instance
+
+-------------------------------------------------------------------
Old:
----
pyorthanc-1.20.0.tar.gz
New:
----
pyorthanc-1.22.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-pyorthanc.spec ++++++
--- /var/tmp/diff_new_pack.DzfsN1/_old 2026-04-04 19:09:51.843143807 +0200
+++ /var/tmp/diff_new_pack.DzfsN1/_new 2026-04-04 19:09:51.847143971 +0200
@@ -1,6 +1,7 @@
#
-# spec file
+# spec file for package python-pyorthanc
#
+# Copyright (c) 2026 SUSE LLC and contributors
# Copyright (c) 2025 Dr. Axel Braun <[email protected]>
#
# All modifications and additions to the file contributed by third parties
@@ -15,28 +16,26 @@
# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
-%{?sle15_python_module_pythons}
%define modname pyorthanc
+%{?sle15_python_module_pythons}
Name: python-%{modname}
-Version: 1.20.0
+Version: 1.22.1
Release: 0
Summary: A comprehensive Python client for Orthanc
License: MIT
URL: https://github.com/gacou54/pyorthanc
Source0:
https://files.pythonhosted.org/packages/source/p/pyorthanc/%{modname}-%{version}.tar.gz
-BuildRequires: %{python_module setuptools}
-BuildRequires: %{python_module pip}
BuildRequires: %{python_module devel}
+BuildRequires: %{python_module pip}
+BuildRequires: %{python_module poetry-core >= 1.0.0}
+BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module wheel}
-BuildRequires: %{python_module poetry-core}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+Requires: python-httpx >= 0.24.1
+Requires: python-pydicom >= 2.3.0
BuildArch: noarch
-
-Requires: python-pydicom
-Requires: python-httpx
-
%python_subpackages
%description
++++++ pyorthanc-1.20.0.tar.gz -> pyorthanc-1.22.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/PKG-INFO
new/pyorthanc-1.22.1/PKG-INFO
--- old/pyorthanc-1.20.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/pyorthanc-1.22.1/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +1,7 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.3
Name: pyorthanc
-Version: 1.20.0
+Version: 1.22.1
Summary: Orthanc REST API python wrapper with additional utilities
-Home-page: https://gacou54.github.io/pyorthanc/
License: MIT
Keywords: Orthanc,DICOM,Medical-Imaging
Author: Gabriel Couture
@@ -15,11 +14,13 @@
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
Provides-Extra: all
Provides-Extra: progress
Requires-Dist: httpx (>=0.24.1,<1.0.0)
-Requires-Dist: pydicom (>=2.3.0,<3.0.0)
+Requires-Dist: pydicom (>=2.3.0,<4.0.0)
Requires-Dist: tqdm (>=4.66.1,<5.0.0) ; extra == "progress" or extra == "all"
+Project-URL: Homepage, https://gacou54.github.io/pyorthanc/
Project-URL: Repository, https://github.com/gacou54/pyorthanc
Description-Content-Type: text/markdown
@@ -78,8 +79,13 @@
patient_ids = client.get_patients()
studies = client.get_studies()
-# Upload DICOM file
-upload(client, 'image_path.dcm')
+# Upload DICOM files
+upload(client, 'image.dcm') # From a file
+upload(client, 'dicom_files.zip') # From a zip
+upload(client, 'path/to/directory') # Upload all dicom files in a directory
+upload(client, 'path/to/directory', recursive=True) # Upload all dicom files
in a directory recursively
+# Check if dicom is in Orthanc before upload
+upload(client, 'path/to/directory', recursive=True, check_before_upload=True)
```
## Working with DICOM Modalities
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/README.md
new/pyorthanc-1.22.1/README.md
--- old/pyorthanc-1.20.0/README.md 2025-03-28 15:08:18.035561600 +0100
+++ new/pyorthanc-1.22.1/README.md 1970-01-01 01:00:00.000000000 +0100
@@ -53,8 +53,13 @@
patient_ids = client.get_patients()
studies = client.get_studies()
-# Upload DICOM file
-upload(client, 'image_path.dcm')
+# Upload DICOM files
+upload(client, 'image.dcm') # From a file
+upload(client, 'dicom_files.zip') # From a zip
+upload(client, 'path/to/directory') # Upload all dicom files in a directory
+upload(client, 'path/to/directory', recursive=True) # Upload all dicom files
in a directory recursively
+# Check if dicom is in Orthanc before upload
+upload(client, 'path/to/directory', recursive=True, check_before_upload=True)
```
## Working with DICOM Modalities
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/__init__.py
new/pyorthanc-1.22.1/pyorthanc/__init__.py
--- old/pyorthanc-1.20.0/pyorthanc/__init__.py 2025-01-23 16:22:02.515639300
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/__init__.py 1970-01-01 01:00:00.000000000
+0100
@@ -5,6 +5,7 @@
from . import errors, util
from ._filtering import find, trim_patients
from ._find import find_instances, find_patients, find_series, find_studies,
query_orthanc
+from ._internal_client import get_internal_client
from ._modality import Modality, RemoteModality
from ._resources import Instance, Patient, Series, Study
from ._upload import async_upload, upload
@@ -31,6 +32,7 @@
'find_studies',
'find_series',
'find_instances',
+ 'get_internal_client',
'query_orthanc',
'Job',
'retrieve_and_write_patients',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_find.py
new/pyorthanc-1.22.1/pyorthanc/_find.py
--- old/pyorthanc-1.20.0/pyorthanc/_find.py 2025-01-16 20:46:34.417057000
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/_find.py 1970-01-01 01:00:00.000000000
+0100
@@ -14,7 +14,8 @@
def find_patients(client: Orthanc,
query: Dict[str, str] = None,
labels: Union[List[str], str] = None,
- labels_constraint: str = 'All') -> List[Patient]:
+ labels_constraint: str = 'All',
+ **kwargs) -> List[Patient]:
"""Finds patients in Orthanc according to queries and labels
Parameters
@@ -27,6 +28,8 @@
List of strings specifying which labels to look for in the resources.
labels_constraint
Constraint on the labels, can be 'All', 'Any', or 'None'.
+ **kwargs
+ Additional keyword arguments passed to `query_orthanc`
Returns
-------
@@ -51,14 +54,16 @@
level='Patient',
query=query,
labels=labels,
- labels_constraint=labels_constraint
+ labels_constraint=labels_constraint,
+ **kwargs
)
def find_studies(client: Orthanc,
query: Dict[str, str] = None,
labels: Union[List[str], str] = None,
- labels_constraint: str = 'All') -> List[Study]:
+ labels_constraint: str = 'All',
+ **kwargs) -> List[Study]:
"""Finds studies in Orthanc according to queries and labels
Parameters
@@ -71,6 +76,8 @@
List of strings specifying which labels to look for in the resources.
labels_constraint
Constraint on the labels, can be 'All', 'Any', or 'None'.
+ **kwargs
+ Additional keyword arguments passed to `query_orthanc`
Returns
-------
@@ -96,14 +103,16 @@
level='Study',
query=query,
labels=labels,
- labels_constraint=labels_constraint
+ labels_constraint=labels_constraint,
+ **kwargs
)
def find_series(client: Orthanc,
query: Dict[str, str] = None,
labels: Union[List[str], str] = None,
- labels_constraint: str = 'All') -> List[Series]:
+ labels_constraint: str = 'All',
+ **kwargs) -> List[Series]:
"""Finds series in Orthanc according to queries and labels
Parameters
@@ -116,6 +125,8 @@
List of strings specifying which labels to look for in the resources.
labels_constraint
Constraint on the labels, can be 'All', 'Any', or 'None'.
+ **kwargs
+ Additional keyword arguments passed to `query_orthanc`
Returns
-------
@@ -140,14 +151,16 @@
level='Series',
query=query,
labels=labels,
- labels_constraint=labels_constraint
+ labels_constraint=labels_constraint,
+ **kwargs
)
def find_instances(client: Orthanc,
query: Dict[str, str] = None,
labels: Union[List[str], str] = None,
- labels_constraint: str = 'All') -> List[Instance]:
+ labels_constraint: str = 'All',
+ **kwargs) -> List[Instance]:
"""Finds instances in Orthanc according to queries and labels
Parameters
@@ -160,6 +173,8 @@
List of strings specifying which labels to look for in the resources.
labels_constraint
Constraint on the labels, can be 'All', 'Any', or 'None'.
+ **kwargs
+ Additional keyword arguments passed to `query_orthanc`
Returns
-------
@@ -184,7 +199,8 @@
level='Instance',
query=query,
labels=labels,
- labels_constraint=labels_constraint
+ labels_constraint=labels_constraint,
+ **kwargs
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_internal_client.py
new/pyorthanc-1.22.1/pyorthanc/_internal_client.py
--- old/pyorthanc-1.20.0/pyorthanc/_internal_client.py 1970-01-01
01:00:00.000000000 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_internal_client.py 1970-01-01
01:00:00.000000000 +0100
@@ -0,0 +1,49 @@
+import json
+
+from . import Orthanc
+from .errors import NotInInternalEnvironmentError
+
+
+def get_internal_client() -> Orthanc:
+ """
+ Returns an Orthanc client that can be used inside the Orthanc server in
python scripts.
+
+ Examples
+ -------
+ This example shows how to use the Orthanc REST API inside an Orthanc
Python Script.
+ >>> from pyorthanc import get_internal_client, find_series, orthanc_sdk
+ >>> def get_modalities_in_orthanc(output: orthanc_sdk.RestOutput, *_,
**__):
+ ... '''This function returns all modalities in Orthanc.'''
+ ... client = get_internal_client()
+ ...
+ ... series = find_series(client)
+ ... modalities_in_orthanc = set([s.modality for s in series])
+ ...
+ ... output.AnswerBuffer(
+ ... json.dumps({'modalities': list(modalities_in_orthanc)}),
+ ... 'application/json'
+ ... )
+ ...
+ ... orthanc_sdk.RegisterRestCallback('/get-modalities-in-orthanc',
get_modalities_in_orthanc)
+ """
+ from pyorthanc import orthanc_sdk
+
+ config = orthanc_sdk.GetConfiguration()
+
+ if not config:
+ raise NotInInternalEnvironmentError(
+ 'This function is only available inside Orthanc server. '
+ 'Use `pyorthanc.get_internal_client()` inside a Orthanc Python
Script. '
+ 'An `pyorthanc.Orthanc()` instance should be defined when using
the '
+ 'Orthanc REST API outside of an Orthanc Oython Python Script.'
+ )
+
+ port = json.loads(config).get('HttpPort', 8042)
+ url = f'http://localhost:{port}'
+
+ client = Orthanc(url=url)
+
+ token = orthanc_sdk.GenerateRestApiAuthorizationToken()
+ client.headers['Authorization'] = token
+
+ return client
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/instance.py
new/pyorthanc-1.22.1/pyorthanc/_resources/instance.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/instance.py 2025-01-16
21:47:24.607246600 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/instance.py 1970-01-01
01:00:00.000000000 +0100
@@ -88,6 +88,17 @@
return self.client.get_instances_id(self.id_)
@property
+ def legacy_viewer_url(self) -> str:
+ """Get Instance (legacy viewer) URL
+
+ Returns
+ -------
+ str
+ URL of instance (legacy viewer)
+ """
+ return f'{self.client.url}/app/explorer.html#instance?uuid={self.id_}'
+
+ @property
def file_size(self) -> int:
"""Get the file size
@@ -138,7 +149,7 @@
@property
def acquisition_number(self) -> int:
return int(self._get_main_dicom_tag_value('AcquisitionNumber'))
-
+
@property
def image_index(self) -> int:
return int(self._get_main_dicom_tag_value('ImageIndex'))
@@ -158,11 +169,11 @@
@property
def image_comments(self) -> str:
return self._get_main_dicom_tag_value('ImageComments')
-
+
@property
def instance_number(self) -> int:
return int(self._get_main_dicom_tag_value('InstanceNumber'))
-
+
@property
def number_of_frames(self) -> int:
return int(self._get_main_dicom_tag_value('NumberOfFrames'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/patient.py
new/pyorthanc-1.22.1/pyorthanc/_resources/patient.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/patient.py 2025-01-16
20:46:34.419057000 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/patient.py 1970-01-01
01:00:00.000000000 +0100
@@ -28,6 +28,17 @@
return self.client.get_patients_id(self.id_)
@property
+ def legacy_viewer_url(self) -> str:
+ """Get Patient (legacy viewer) URL
+
+ Returns
+ -------
+ str
+ URL of patient (legacy viewer)
+ """
+ return f'{self.client.url}/app/explorer.html#patient?uuid={self.id_}'
+
+ @property
def patient_id(self) -> str:
"""Get patient ID"""
return self._get_main_dicom_tag_value('PatientID')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/resource.py
new/pyorthanc-1.22.1/pyorthanc/_resources/resource.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/resource.py 2025-01-16
20:46:34.422057000 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/resource.py 1970-01-01
01:00:00.000000000 +0100
@@ -51,6 +51,10 @@
return self._main_dicom_tags
@abc.abstractmethod
+ def legacy_viewer_url(self):
+ raise NotImplementedError
+
+ @abc.abstractmethod
def get_main_information(self):
raise NotImplementedError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/series.py
new/pyorthanc-1.22.1/pyorthanc/_resources/series.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/series.py 2025-01-16
20:46:34.423056800 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/series.py 1970-01-01
01:00:00.000000000 +0100
@@ -51,6 +51,17 @@
return self.client.get_series_id(self.id_)
@property
+ def legacy_viewer_url(self) -> str:
+ """Get Series (legacy viewer) URL
+
+ Returns
+ -------
+ str
+ URL of series (legacy viewer)
+ """
+ return f'{self.client.url}/app/explorer.html#series?uuid={self.id_}'
+
+ @property
def manufacturer(self) -> str:
"""Get the manufacturer"""
return self._get_main_dicom_tag_value('Manufacturer')
@@ -116,11 +127,11 @@
@property
def body_part_examined(self) -> str:
return self._get_main_dicom_tag_value('BodyPartExamined')
-
+
@property
def sequence_name(self) -> str:
return self._get_main_dicom_tag_value('SequenceName')
-
+
@property
def cardiac_number_of_images(self) -> int:
return int(self._get_main_dicom_tag_value('CardiacNumberOfImages'))
@@ -128,7 +139,7 @@
@property
def images_in_acquisition(self) -> int:
return int(self._get_main_dicom_tag_value('ImagesInAcquisition'))
-
+
@property
def number_of_temporal_positions(self) -> int:
return int(self._get_main_dicom_tag_value('NumberOfTemporalPositions'))
@@ -150,15 +161,15 @@
@property
def series_type(self) -> str:
return self._get_main_dicom_tag_value('SeriesType')
-
+
@property
def operators_name(self) -> str:
return self._get_main_dicom_tag_value('OperatorsName')
-
+
@property
def acquisition_device_processing_description(self) -> str:
return
self._get_main_dicom_tag_value('AcquisitionDeviceProcessingDescription')
-
+
@property
def contrast_bolus_agent(self) -> str:
return self._get_main_dicom_tag_value('ContrastBolusAgent')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/study.py
new/pyorthanc-1.22.1/pyorthanc/_resources/study.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/study.py 2025-01-16
20:46:34.423056800 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/study.py 1970-01-01
01:00:00.000000000 +0100
@@ -32,6 +32,17 @@
return self.client.get_studies_id(self.id_)
@property
+ def legacy_viewer_url(self) -> str:
+ """Get Study (legacy viewer) URL
+
+ Returns
+ -------
+ str
+ URL of study (legacy viewer)
+ """
+ return f'{self.client.url}/app/explorer.html#study?uuid={self.id_}'
+
+ @property
def referring_physician_name(self) -> str:
"""Get referring physician name"""
return self._get_main_dicom_tag_value('ReferringPhysicianName')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_upload.py
new/pyorthanc-1.22.1/pyorthanc/_upload.py
--- old/pyorthanc-1.20.0/pyorthanc/_upload.py 2025-01-16 21:47:24.607246600
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/_upload.py 1970-01-01 01:00:00.000000000
+0100
@@ -1,14 +1,22 @@
+import glob
+import os
from io import BytesIO
from pathlib import Path
-from typing import Dict, List, Union
+from typing import Dict, Generator, List, Tuple, Union
import httpx
import pydicom
+from pydicom.errors import InvalidDicomError
-from pyorthanc import AsyncOrthanc, Orthanc
+from pyorthanc import AsyncOrthanc, Instance, Orthanc
+from pyorthanc.util import ensure_non_raw_response,
to_orthanc_instance_id_from_ds
-def upload(client: Orthanc, path_or_ds: Union[str, Path, pydicom.Dataset]) ->
Union[Dict, httpx.Response]:
+def upload(
+ client: Orthanc,
+ path_or_ds: Union[str, Path, pydicom.Dataset],
+ recursive: bool = False,
+ check_before_upload: bool = False) -> List[Instance]:
"""Upload a DICOM file or dataset to Orthanc synchronously
Parameters
@@ -16,11 +24,52 @@
client : Orthanc
The Orthanc client to use for upload
path_or_ds : Union[str, Path, pydicom.Dataset]
- Either a path to a DICOM file or a pydicom Dataset object
+ Either a path to a DICOM file, directory, zip file or a pydicom
Dataset object
+ recursive : bool
+ When `path_or_ds` is a directory, whether to upload recursively all
the DICOM files in the directory
+ check_before_upload : bool
+ Verify if data is already in Orthanc before sending it. It verifies
if a file is stored, there is no file comparison.
"""
- dicom_bytes = _prepare_data_from_ds_or_file(path_or_ds)
+ client = ensure_non_raw_response(client)
+
+ instances = []
+
+ # If path_or_ds is a directory, upload all the DICOM files in the
directory.
+ if (isinstance(path_or_ds, str) or isinstance(path_or_ds, Path)) and
os.path.isdir(path_or_ds):
+ for dicom_bytes in _generate_dicom_bytes_from_directory(path_or_ds,
recursive=recursive):
+ if check_before_upload:
+ data_is_in_orthanc, instance =
_is_data_already_in_orthanc(client, dicom_bytes)
+
+ # If data is already in Orthanc, skip uploading it and go to
the next file.
+ if data_is_in_orthanc:
+ instances.append(instance)
+ continue
+
+ result = client.post_instances(dicom_bytes)
+ instance = Instance(result['ID'], client)
+ instances.append(instance)
+
+ # If path_or_ds is a DICOM file, zip file or a pydicom Dataset, upload it.
+ else:
+ dicom_bytes = _prepare_data_from_ds_or_file(path_or_ds)
+
+ if check_before_upload:
+ data_in_orthanc, instance = _is_data_already_in_orthanc(client,
dicom_bytes)
+ # If data is already in Orthanc, returns the instance directly.
+ if data_in_orthanc:
+ instances.append(instance)
+ return instances
+
+ result = client.post_instances(dicom_bytes)
+
+ # When a zip is uploaded, result can be a list of instances if the zip
contained multiple DICOM files.
+ if isinstance(result, list):
+ instances += [Instance(i['ID'], client) for i in result]
+ else:
+ instance = Instance(result['ID'], client)
+ instances.append(instance)
- return client.post_instances(dicom_bytes)
+ return instances
async def async_upload(client: AsyncOrthanc, path_or_ds: Union[str, Path,
pydicom.Dataset]) -> Union[Dict, httpx.Response]:
@@ -31,11 +80,11 @@
client : AsyncOrthanc
The async Orthanc client to use for upload
path_or_ds : Union[str, Path, pydicom.Dataset]
- Either a path to a DICOM file or a pydicom Dataset object
+ Either a path to a DICOM file, zip file or a pydicom Dataset object
"""
dicom_bytes = _prepare_data_from_ds_or_file(path_or_ds)
- return await client.post_instances()
+ return await client.post_instances(dicom_bytes)
def _prepare_data_from_ds_or_file(path_or_ds: Union[str, Path,
pydicom.Dataset]) -> bytes:
@@ -43,11 +92,49 @@
if isinstance(path_or_ds, str) or isinstance(path_or_ds, Path):
with open(path_or_ds, 'rb') as f:
dicom_bytes = f.read()
+
elif isinstance(path_or_ds, pydicom.Dataset):
buffer = BytesIO()
path_or_ds.save_as(buffer)
dicom_bytes = buffer.getvalue()
+
else:
- raise TypeError("path_or_ds must be either a file path or pydicom
Dataset")
+ raise TypeError('path_or_ds must be either a file path or pydicom
Dataset')
return dicom_bytes
+
+
+def _generate_dicom_bytes_from_directory(directory: str, recursive: bool) ->
Generator[bytes, None, None]:
+ filepaths = glob.glob(os.path.join(directory, '*.dcm'))
+ filepaths += glob.glob(os.path.join(directory, '*.DCM'))
+ filepaths += glob.glob(os.path.join(directory, '*.dcm.gz'))
+ filepaths += glob.glob(os.path.join(directory, '*.DCM.gz'))
+
+ if recursive is True:
+ filepaths += glob.glob(os.path.join(directory, '**', '*.dcm'),
recursive=recursive)
+ filepaths += glob.glob(os.path.join(directory, '**', '*.DCM'),
recursive=recursive)
+ filepaths += glob.glob(os.path.join(directory, '**', '*.dcm.gz'),
recursive=recursive)
+ filepaths += glob.glob(os.path.join(directory, '**', '*.DCM.gz'),
recursive=recursive)
+
+ filepaths = set(filepaths)
+
+ for filepath in filepaths:
+ yield _prepare_data_from_ds_or_file(filepath)
+
+
+def _is_data_already_in_orthanc(client: Orthanc, dicom_bytes: bytes) ->
Tuple[bool, Union[Instance, None]]:
+ try:
+ dicom_file_like = BytesIO(dicom_bytes)
+ ds = pydicom.dcmread(dicom_file_like)
+ orthanc_id = to_orthanc_instance_id_from_ds(ds)
+ except InvalidDicomError:
+ # If the file is not a valid DICOM file, likely it is a zip file. It
will be uploaded.
+ return False, None
+
+ try:
+ # Attempt to get metadata to verify if data is already in Orthanc.
+ client.get_instances_id_metadata(id_=orthanc_id)
+ return True, Instance(orthanc_id, client)
+
+ except httpx.HTTPError:
+ return False, None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/errors.py
new/pyorthanc-1.22.1/pyorthanc/errors.py
--- old/pyorthanc-1.20.0/pyorthanc/errors.py 2025-01-16 20:46:34.433056800
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/errors.py 1970-01-01 01:00:00.000000000
+0100
@@ -4,3 +4,7 @@
class ModificationError(Exception):
pass
+
+
+class NotInInternalEnvironmentError(Exception):
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/util.py
new/pyorthanc-1.22.1/pyorthanc/util.py
--- old/pyorthanc-1.20.0/pyorthanc/util.py 2025-01-23 16:22:02.515639300
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/util.py 1970-01-01 01:00:00.000000000
+0100
@@ -93,6 +93,15 @@
return _make_orthanc_id(patient_id, study_uid, series_uid, instance_uid)
+def to_orthanc_instance_id_from_ds(ds: pydicom.Dataset) -> str:
+ return _make_orthanc_id(
+ patient_id=ds.PatientID,
+ study_uid=ds.StudyInstanceUID,
+ series_uid=ds.SeriesInstanceUID,
+ instance_uid=ds.SOPInstanceUID
+ )
+
+
def _make_orthanc_id(patient_id: str, study_uid: str = None, series_uid: str =
None, instance_uid: str = None) -> str:
ids = [patient_id, study_uid, series_uid, instance_uid]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyproject.toml
new/pyorthanc-1.22.1/pyproject.toml
--- old/pyorthanc-1.20.0/pyproject.toml 2025-03-28 15:08:18.057561400 +0100
+++ new/pyorthanc-1.22.1/pyproject.toml 1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyorthanc"
-version = "1.20.0"
+version = "1.22.1"
description = "Orthanc REST API python wrapper with additional utilities"
authors = [
"Gabriel Couture <[email protected]>",
@@ -15,7 +15,7 @@
[tool.poetry.dependencies]
python = "^3.8"
httpx = ">=0.24.1,<1.0.0"
-pydicom = "^2.3.0"
+pydicom = ">=2.3.0,<4.0.0"
tqdm = { version = "^4.66.1", optional = true }
[tool.poetry.extras]