Code Monkey home page Code Monkey logo

dicomweb-client's People

Contributors

agharwal avatar ambrussimon avatar babaksamari avatar by3nrique avatar cgorman avatar codezizo avatar cpbridge avatar dimrozakis avatar hackermd avatar michaelloewenstein avatar ntenenz avatar pchristos avatar pieper avatar razorx89 avatar soft953 avatar vanossj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dicomweb-client's Issues

Make pillow and numpy optional

Hi @hackermd . In #27 we had discussed making pillow and numpy optional. The issue was closed after these two dependencies were removed.

#56 reintroduced these required dependencies. #56 explicitly mentions in the description:

The DICOMfileClient depends on the NumPy and Pillow libraries. Therefore, this PR would introduce a dependency on C Python. I don't think this is a major issue and most users will likely have NumPy and Pillow installed anyways.

If this should turn out to be an issue, we could either

  • make the dependencies optional (not a fan of that approach)
  • put the DICOMfileClient into a separate package (also not ideal because the class is supposed to have the same API as the DICOMwebClient and having both in one package is advantageous in this regard)

As explained in #27:

I'm using this project (the library, not the CLI) as part of an application that will be running under pypy instead of cpython, possibly in an alpine container. I'd have to build both pillow & numpy for a feature I don't intend on using.

I would therefore kindly request that these dependencies are either changed to optional, or that the DICOMfileClient is split into a separate package (which can still of course be developed in this repo alongside DICOMwebClient) but installed separately from PYPI.

noisy 'empty response' warning

The 'empty response' warning doesn't seem useful and is very noisy

logger.warning('empty response')

  1. 'empty response' prints after each call where get_remaining=True
  2. If my search returned 0 results I could just check the returned list is empty, printing 'empty response' just spams my terminal/notebook

Any reason to keep it around?

Transfer encoding headers not used

@dimrozakis raised the following issue:

chunked_headers isn't being used, the subsequent request uses the original headers. Also, according to requests' documentation on chunk-encoded requests, passing an iterator is all that's required so it seems like Transfer-Encoding: chunked is taken care of automatically by requests.

Error in doc string?

Hi,

I noticed that the Note section of the retrieve_instance-method (and in other similar methods) mentions that the default transfer syntax will be Implicit VR Little Endian. The standard states that the default transfer syntax for dicom web is Explicity VR Little Endian and that Implicit VR Little Endian is not allowed.

Retrive metadata is QIDO-RS or WADO-RS?

Many server support to retrieve metadata by QIDO.
But parameter of dicomweb-client retrieve metadata api is _Transaction.RETRIEVE.
It cause api to get wrong prefix of URI.

Does it should be changed the parameter to _Transaction.SEARCH?

Add generator based download of studies/series

Currently, when downloading a study or series, all instances are loaded and parsed with pydicom. For some use cases it is absolutely fine to process each instance individually and store the result on disk, especially when dealing with very large studies and/or image. Therefore the current functionality should be extended with generator based functions. In order to keep the current exposed functionality, I would propose to write generator based functions and just convert the generator to a list.

Example for STOW-RS

Hi,
Thanks for the sharing a great job! Could you provide any good example of STOW-RS usage, with either of dcm file or mutlipart mime format generation?
Thanks!
Jinserk

Unsupported Media Type

I don't really understand the error I got, maybe you can help me. I didn't have any problem with the library when installing it on my "general" environment (Anaconda, Python 3.7), but when I install dicomweb_client on a new virtual environment (python 3.7, with dicomweb_client installed only), I get the error below when using this: client.store_instances(datasets=[ds])

HTTPError: 415 Client Error: Unsupported Media Type for URL: http://localhost:8000/studies

I investigated a little with tcpdump in order to capture the requests. With the general environment, where I didn't have an error, here is the captured request :

h#..h#..POST /studies HTTP/1.1
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Host: localhost
Content-Type: multipart/related; type="application/dicom"; boundary=0f3cf5c0-70e0-41ef-baef-c6f9f65ec3e1
Content-Length: 525907

It seems to me as expected. Then, with my new virtual environment with only dicomweb_client (python 3.7), and with the exact same command, here is what I got:

h#~.h#~.POST /studies HTTP/1.1
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Host: localhost
Content-Length: 119
Content-Type: application/x-www-form-urlencoded

An I missing something or this is not the expected behavior?

Thank you very much,
Gabriel

Something broke for me going from 0.55.0 to 0.55.1

Hi,

@hackermd I have a code that pulls full dicom data using:

from dicomweb_client.api import DICOMwebClient

client = DICOMwebClient(url=url)

 pydicom_dataset = client.retrieve_instance(
                    study_instance_uid=study_uid,
                    series_instance_uid=series_uid,
                    sop_instance_uid=object_uid,
                     )

It broke going from 0.55.0 to 0.55.1. (I was able to trace this down by running with different versions)

With 0.55.1 I get errors of this form:

406 Client Error: Not Acceptable for url: http://......../studies/........../series/............../instances/.............

Sorry, I have no idea what the issue is. I just wanted to let you know. I will continue using 0.55.0 for now.

Cannot find reference 'load_json_dataset' in 'api.py'

Cannot find reference 'load_json_dataset' in 'api.py'

Step to reproduce

  • install dicomweb-client v0.57.1
  • on a python IDE, import "load_json_dataset" following below instruction
    Loading Json Data to pydicom
  • got a error message: Cannot find reference 'load_json_dataset' in 'api.py'

Success with partial content (status code 206)

According to DICOM PS3.18 2023e - Web Services Section 8.5 the server can respond with a 206 status code when for example not all images could be retrieved during a WADO-RS request. Is there some way to get this information programmatically when using dicomweb-client (e.g. when calling retrieve_study)?

I only found one location in web.py where a warning is logged to the console when a warning is in the response.headers. Is there a way to access the response status after e.g. retrieve_study is called?

Outdated JPEG-LS media type for retrieval of frames

When one passes media_types=('image/jls', '1.2.840.10008.1.2.4.80', ) to retrieve_instance_frames(), one gets the following error:

ValueError: Media type "image/jls" is not supported for requested resource

The reason for that is that the client still assumes image/x-jls instead of image/jls.

Error retrieving instance: Unexpected media type: "application/dicom". Expected "multipart/related".

Thank you for this great package, it makes it very easy to access data on DICOMweb endpoints. We have been using the dicomweb-client successfully with https://github.com/dcmjs-org/dicomweb-server up until version 0.41.2. All versions after that (0.50.0-0.50.3) fails to retrieve instance with this error: ValueError: Unexpected media type: "application/dicom". Expected "multipart/related".

I'll leave the server accessible for a while so that you can easily reproduce the error.

0.41.2 -> works

>>> import dicomweb_client
>>> dicomweb_client.__version__
'0.41.2'
>>> 
>>> from dicomweb_client.api import DICOMwebClient
>>> client = DICOMwebClient(url="http://skull.cs.queensu.ca:5995")
>>> study_instance_uid = client.search_for_studies()[0]['0020000D']['Value'][0]
>>> series_instance_uid = client.search_for_series(study_instance_uid)[0]['0020000E']['Value'][0]
>>> sop_instance_uid = client.search_for_instances(study_instance_uid, series_instance_uid)[0]['00080018']['Value'][0]
>>> ds = client.retrieve_instance(study_instance_uid, series_instance_uid, sop_instance_uid)
>>> ds.InstanceNumber
"1"

0.50.0 -> first version that does not work

>>> import dicomweb_client
>>> dicomweb_client.__version__
'0.50.0'
>>> 
>>> from dicomweb_client.api import DICOMwebClient
>>> client = DICOMwebClient(url="http://skull.cs.queensu.ca:5995")
>>> study_instance_uid = client.search_for_studies()[0]['0020000D']['Value'][0]
>>> series_instance_uid = client.search_for_series(study_instance_uid)[0]['0020000E']['Value'][0]
>>> sop_instance_uid = client.search_for_instances(study_instance_uid, series_instance_uid)[0]['00080018']['Value'][0]
>>> ds = client.retrieve_instance(study_instance_uid, series_instance_uid, sop_instance_uid)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2020-11-17\lib\Python\Lib\site-packages\dicomweb_client\api.py", line 2442, in retrieve_instance
    instances = list(iterator)
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2020-11-17\lib\Python\Lib\site-packages\dicomweb_client\api.py", line 1056, in <genexpr>
    pydicom.dcmread(BytesIO(part))
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2020-11-17\lib\Python\Lib\site-packages\dicomweb_client\api.py", line 725, in _decode_multipart_message
    f'Unexpected media type: "{media_type}". '
ValueError: Unexpected media type: "application/dicom". Expected "multipart/related".
>>> 

0.50.3 (current latest version on PyPI) -> still does not work

>>> 
>>> import dicomweb_client
>>> dicomweb_client.__version__
'0.50.3'
>>> 
>>> 
>>> from dicomweb_client.api import DICOMwebClient
>>> client = DICOMwebClient(url="http://skull.cs.queensu.ca:5995")
>>> study_instance_uid = client.search_for_studies()[0]['0020000D']['Value'][0]
>>> series_instance_uid = client.search_for_series(study_instance_uid)[0]['0020000E']['Value'][0]
>>> sop_instance_uid = client.search_for_instances(study_instance_uid, series_instance_uid)[0]['00080018']['Value'][0]
>>> ds = client.retrieve_instance(study_instance_uid, series_instance_uid, sop_instance_uid)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2020-11-17\lib\Python\Lib\site-packages\dicomweb_client\api.py", line 2443, in retrieve_instance
    instances = list(iterator)
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2020-11-17\lib\Python\Lib\site-packages\dicomweb_client\api.py", line 1057, in <genexpr>
    pydicom.dcmread(BytesIO(part))
  File "C:\Users\andra\AppData\Local\NA-MIC\Slicer 4.13.0-2020-11-17\lib\Python\Lib\site-packages\dicomweb_client\api.py", line 726, in _decode_multipart_message
    f'Unexpected media type: "{media_type}". '
ValueError: Unexpected media type: "application/dicom". Expected "multipart/related".

Media type of failure response message

@dimrozakis raised folllowing issue:

In _http_post_multipart_application_dicom, when the response.content returned by _http_post is parsed, Content-Type is checked to decide whether it should be loaded as json or xml. Here, in case of a response with an error status code, content is assumed to be xml. Is it not possible for the content of an error response to be in json?

Client error for STOW-RS

I lefted another question for the store_instances() but it's already closed and won't get any further notice to you, so please excuse me to make another new thread. I'm almost newbie to deal with the dicom standard, but the following example code has an error:

import os
import tempfile
import datetime

import pydicom
from pydicom.dataset import Dataset, FileDataset
from pydicom.sequence import Sequence

# Create some temporary filenames
suffix = '.dcm'
filename_little_endian = tempfile.NamedTemporaryFile(suffix=suffix).name
filename_big_endian = tempfile.NamedTemporaryFile(suffix=suffix).name

print("Setting file meta information...")
# Populate required values for file meta information
file_meta = Dataset()
file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
file_meta.MediaStorageSOPInstanceUID = "1.2.3"
file_meta.ImplementationClassUID = "1.2.3.4"

print("Setting dataset values...")
# Create the FileDataset instance (initially no data elements, but file_meta
# supplied)
ds = FileDataset(filename_little_endian, {},
                 file_meta=file_meta, preamble=b"\0" * 128)

# Add the data elements -- not trying to set all required here. Check DICOM
# standard
ds.PatientName = "Test^Firstname"
ds.PatientID = "123456"

# Set the transfer syntax
ds.is_little_endian = True
ds.is_implicit_VR = True

# Set creation date/time
dt = datetime.datetime.now()
ds.ContentDate = dt.strftime('%Y%m%d')
timeStr = dt.strftime('%H%M%S.%f')  # long format with micro seconds
ds.ContentTime = timeStr

print("Writing test file", filename_little_endian)
ds.save_as(filename_little_endian)
print("File saved.")


from dicomweb_client.api import DICOMwebClient

client = DICOMwebClient("http://172.19.2.123:8042/dicom-web")
STUDY_UID = '2.16.840.1.114488.0.4.123489834087.1330071425.0'
client.store_instances(datasets=[ds])

and the error message is:

Setting file meta information...
Setting dataset values...
Writing test file /tmp/tmph64u7s3j.dcm
File saved.
Traceback (most recent call last):
  File "test.py", line 56, in <module>
    client.store_instances(datasets=[ds])
  File "/home/jinserk/.pyenv/versions/3.7.2/lib/python3.7/site-packages/dicomweb_client/api.py", line 1797, in store_instances
    self._http_post_multipart_application_dicom(url, encoded_datasets)
  File "/home/jinserk/.pyenv/versions/3.7.2/lib/python3.7/site-packages/dicomweb_client/api.py", line 1207, in _http_post_multipart_application_dicom
    self._http_post(url, content, headers={'Content-Type': content_type})
  File "/home/jinserk/.pyenv/versions/3.7.2/lib/python3.7/site-packages/dicomweb_client/api.py", line 1186, in _http_post
    response.raise_for_status()
  File "/home/jinserk/.pyenv/versions/3.7.2/lib/python3.7/site-packages/requests/models.py", line 940, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: http://172.19.2.123:8042/dicom-web/studies

Here what I wonder that:

  • why the request added "studies"?
  • how can I give 'study uid' to store_instances function? do I need to add the info into the dataset?
  • how to add another series to existing study?

Support for WADO-URI

We have a need to deal with some equipment that does not expose its full WADO functionality through WADA-RS and requires the use of WADO-URI for some WADO features.

Would you entertain a PR that adds support for the WADO-URI? I think this should be pretty straightforward to implement and we would be happy to work on it.

If so, I would appreciate your thoughts on how the API could look. I envision that we would add the option to use WADO-URI to the existing retrieve_... methods. This could be done either as an optional parameter to the relevant methods, or as a global configuration option in the client's constructor. I think I favour the former despite it being more verbose. Thoughts? @hackermd

QIDO Authentication via token

Secure versions of dcm4che arc 5 use keycloak for authentication for both UI and webservices. Can other auth methods be used other than basic auth in DICOMweb-client? In particular client id / token

Thanks
Chris

WADO retrieve instance giving 406

Hi, I am trying to test the wado-rs functionality for retrieve instance, the functionality works fine on postman/browser. While testing using dicomweb-client I am getting the following error.

http://127.0.0.1:8000 "GET /api/internal/dicomweb/ec712b95-a5fb-43bc-9de1-3ae5024e7c39/wadors/studies/2.16.124.113543.6003.1719516381.65153.16773.3015806397/series/2.16.124.113543.6003.2297816378.33047.16917.2271512738/instances/2.16.124.113543.6003.3368520785.218.17135.699537048 HTTP/1.1" 406 57
DEBUG 2020-06-30 05:03:10,741 api 77 139832964693320  request status code: 406
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "/usr/local/lib/python3.7/site-packages/dicomweb_client/api.py", line 2084, in retrieve_instance
    return self._http_get_multipart_application_dicom(url)[0]
  File "/usr/local/lib/python3.7/site-packages/dicomweb_client/api.py", line 1045, in _http_get_multipart_application_dicom
    response = self._http_get(url, params, headers)
  File "/usr/local/lib/python3.7/site-packages/dicomweb_client/api.py", line 695, in _http_get
    response.raise_for_status()
  File "/usr/local/lib/python3.7/site-packages/requests/models.py", line 941, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 406 Client Error: Not Acceptable for url: http://127.0.0.1:8000/api/internal/dicomweb/ec712b95-a5fb-43bc-9de1-3ae5024e7c39/wadors/studies/2.16.124.113543.6003.1719516381.65153.16773.3015806397/series/2.16.124.113543.6003.2297816378.33047.16917.2271512738/instances/2.16.124.113543.6003.3368520785.218.17135.699537048

This is how I am testing the wado retrieve instance

from dicomweb_client.api import DICOMwebClient
study_instance_uid = '2.16.124.113543.6003.1719516381.65153.16773.3015806397'
series_instance_uid = '2.16.124.113543.6003.2297816378.33047.16917.2271512738'
sop_instance_uid = '2.16.124.113543.6003.3368520785.218.17135.699537048'
client = DICOMwebClient(url="http://127.0.0.1:8000/api/internal/dicomweb/ec712b95-a5fb-43bc-9de1-3ae5024e7c39", wado_url_prefix="wadors")
client.retrieve_instance(study_instance_uid=study_instance_uid, series_instance_uid=series_instance_uid, sop_instance_uid=sop_instance_uid)

The response from the endpoint has content_type multipart/related;type=application/dicom. Let me know if I am doing something wrong here.

image/dicom-rle media type

According to the standard image/dicom-rle should be used as media type for transfer syntax 1.2.840.10008.1.2.5 RLE Lossless. However DicomWebClient throws when using it as a media type when fetching frames:

url = 'https://idc-external-006.uc.r.appspot.com/dcm4chee-arc/aets/DCM4CHEE/rs'
dicomweb_client = DICOMwebClient(url)

dicomweb_client.retrieve_instance_frames(
    "1.3.6.1.4.1.5962.99.1.3456320082.756362073.1622659023442.3.0", 
    "1.3.6.1.4.1.5962.1.1.0.0.0.1595262375.18986.35", 
    "1.3.6.1.4.1.5962.99.1.3456320082.756362073.1622659023442.1478.0", 
    [1],
    ("image/dicom-rle",)
)

throws:

File [c:\Users\er-gac\.pyenv\pyenv-win\versions\3.11.2\Lib\site-packages\dicomweb_client\web.py:2961](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2961), in DICOMwebClient.retrieve_instance_frames(self, study_instance_uid, series_instance_uid, sop_instance_uid, frame_numbers, media_types)
   [2930](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2930) def retrieve_instance_frames(
   [2931](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2931)     self,
   [2932](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2932)     study_instance_uid: str,
   (...)
   [2936](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2936)     media_types: Optional[Tuple[Union[str, Tuple[str, str]], ...]] = None
   [2937](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2937) ) -> List[bytes]:
   [2938](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2938)     """Retrieve one or more frames of an image instance.
   [2939](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2939) 
   [2940](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2940)     Parameters
   (...)
   [2958](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2958) 
   [2959](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2959)     """  # noqa: E501
   [2960](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2960)     return list(
-> [2961](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2961)         self._get_instance_frames(
   [2962](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2962)             study_instance_uid=study_instance_uid,
   [2963](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2963)             series_instance_uid=series_instance_uid,
   [2964](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2964)             sop_instance_uid=sop_instance_uid,
   [2965](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2965)             frame_numbers=frame_numbers,
   [2966](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2966)             media_types=media_types,
   [2967](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2967)             stream=False
   [2968](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2968)         )
   [2969](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2969)     )

File [c:\Users\er-gac\.pyenv\pyenv-win\versions\3.11.2\Lib\site-packages\dicomweb_client\web.py:2907](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2907), in DICOMwebClient._get_instance_frames(self, study_instance_uid, series_instance_uid, sop_instance_uid, frame_numbers, media_types, stream)
   [2901](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2901)     return self._http_get_multipart_application_octet_stream(
   [2902](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2902)         url,
   [2903](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2903)         media_types=media_types,
   [2904](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2904)         stream=stream
   [2905](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2905)     )
   [2906](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2906) elif common_media_type.startswith('image'):
-> [2907](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2907)     return self._http_get_multipart_image(
   [2908](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2908)         url,
   [2909](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2909)         media_types=media_types,
   [2910](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2910)         stream=stream
   [2911](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2911)     )
   [2912](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2912) elif common_media_type.startswith('video'):
   [2913](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2913)     return self._http_get_multipart_video(
   [2914](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2914)         url,
   [2915](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2915)         media_types=media_types,
   [2916](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2916)         stream=stream
   [2917](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:2917)     )

File [c:\Users\er-gac\.pyenv\pyenv-win\versions\3.11.2\Lib\site-packages\dicomweb_client\web.py:1201](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1201), in DICOMwebClient._http_get_multipart_image(self, url, media_types, byte_range, params, stream)
   [1197](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1197) if byte_range is not None:
   [1198](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1198)     headers['Range'] = self._build_range_header_field_value(
   [1199](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1199)         byte_range
   [1200](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1200)     )
-> [1201](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1201) headers['Accept'] = self._build_multipart_accept_header_field_value(
   [1202](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1202)     media_types,
   [1203](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1203)     supported_media_types
   [1204](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1204) )
   [1205](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1205) response = self._http_get(
   [1206](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1206)     url,
   [1207](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1207)     params=params,
   [1208](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1208)     headers=headers,
   [1209](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1209)     stream=stream
   [1210](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1210) )
   [1211](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:1211) return self._decode_multipart_message(response, stream=stream)

File [c:\Users\er-gac\.pyenv\pyenv-win\versions\3.11.2\Lib\site-packages\dicomweb_client\web.py:901](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:901), in DICOMwebClient._build_multipart_accept_header_field_value(cls, media_types, supported_media_types)
    [898](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:898) if media_type not in supported_media_types.values():
    [899](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:899)     if not (media_type.endswith('/*') or
    [900](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:900)             media_type.endswith('/')):
--> [901](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:901)         raise ValueError(
    [902](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:902)             f'Media type "{media_type}" is not supported for '
    [903](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:903)             'requested resource.'
    [904](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:904)         )
    [905](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:905) if transfer_syntax_uid is not None:
    [906](file:///C:/Users/er-gac/.pyenv/pyenv-win/versions/3.11.2/Lib/site-packages/dicomweb_client/web.py:906)     if transfer_syntax_uid != '*':

ValueError: Media type "image/dicom-rle" is not supported for requested resource.

Not that I expect the frame to be available in RLE, but the expected error would be:

HTTPError: 406 Client Error: Not Acceptable for url: https://idc-external-006.uc.r.appspot.com/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.5962.99.1.3456320082.756362073.1622659023442.3.0/series/1.3.6.1.4.1.5962.1.1.0.0.0.1595262375.18986.35/instances/1.3.6.1.4.1.5962.99.1.3456320082.756362073.1622659023442.1478.0/frames/1

as I get if I change to media type image/x-dicom-rle.

Make pillow and numpy optional

Looking at setup.py I see that numpy & pillow are required dependencies. After a quick search of the codebase, I see that both of them are only used in the retrieve_instance_frames subparser of cli.py. Furthermore, in the case of numpy, only its (rather simple) fromstring function is used and only in the case where the the retrieved frame is compressed with JPEG LS.

Since these are two significant dependencies that may need to be built, and are only useful for a very specific feature, would you be open to making them optional dependencies (for example using setuptools extras)?

In my case, I'm considering using this project (the library, not the CLI) as part of an application that will be running under pypy instead of cpython, possibly in an alpine container. I'd have to build both pillow & numpy for a feature I don't intend on using.

It's totally understandable if you wish to keep these dependencies as required and I could always just build them or use a slightly modified fork that removes these dependencies.

Implementation of DICOMs3Client based on DICOMwebProtocol

@pieper @chafey As discussed in #56, it would be great to implement a DICOMs3Client based on DICOMwebProtocol.

In general, I think we should consider the following aspects for the design of an S3 client:

  1. Index of metadata for efficient query: DICOM attributes needed for response payloads of DICOMweb Search Transaction (Study Resource, Series Resource, and Instance Resource)
  2. Offset to pixel data and individual frames for efficient frame retrieval: for natively encoded Pixel Data, the client can just compute the offset of individual frames, but for an encapsulated Pixel Data element, the client would require the Basic Offset Table or Extended Offset Table
  3. Read and write: DICOMweb Store Transaction

(1) I've been thinking that we could put the metadata into the S3 Object Metadata. The PUT request header is limited to 8 KB in size, but that should be sufficient for the metadata. This could also include the offset to the Pixel Data, Float Pixel Data, or Double Float Pixel Data element (see 2).

(2) If the Pixel Data element contains a Basic Offset Table item, then the client could just read its value and cache the offsets locally. Otherwise the client would have to determine the offset of frame items by reading the header of each frame item. That could result in a lot of HTTP calls, so ideally we would make sure that the Basic Offset Table gets included into image data sets.

(3) To support writes (including parallel writes), we shouldn't store any aggregate study- or series-level information such as Number of Study Related Series in the object metadata, but dynamically compute it client-side.

What do you think?

Retrieve_series_metadata Hangs Indefinitely Without Timeout

The retrieve_series_metadata function is exhibiting unexpected behavior where it gets stuck and does not raise a timeout exception even after waiting for an extended period (hours) . This issue occurs when attempting to retrieve metadata for a specific series using the retrieve_series_metadata function with a certain series_uid and study_uid.

image

To improve the context:

  • The same series_uid and study_uid hang indefinetly alse with retrieve_series and retrieve_study method.

  • The same series_uid and study_uid return good result for the search_for_series method (<1s).

  • For different series_uid all the methods seem to work so a WADO-RS avaliabity issue on the server can be discarded (At least 1000 series tested).

Although we definitely cannot rule out a bug in our end (any suggestions in this regard are welcome) the library should have a method to hang a request that takes too long.

Support query and metadata retrieval from legacy origin servers

Im getting a JSONDECODE error when trying to use the QIDO against a dicom rs broker (dicomweb -> dicom)

Using the requests module in python if i change the header "Accept : application/json" i am able to get a response but the server i am calling with dicomweb-client throws the error "application/json+dicom" is not accepted

Is there a way to change the header (similar to how the requests module can) from application/json+dicom to just application/json ?

DICOMfileClient raises error for RLE Losssless files

Summary

When using DICOMfileClient to retrieve an instance with RLE Lossless encoding (Transfer Syntax UID 1.2.840.10008.1.2.5), an error is raised even though pydicom can deal with RLE Lossless encoding just fine. There is no error when using DICOMwebClient.

Steps to Reproduce

  1. Download the RLE Lossless sample image from https://barre.dev/medical/samples/#s-us and put it into a folder called dicom-samples.
  2. Put the script below next to the dicom-samples folder and run it.
from pathlib import Path

from dicomweb_client import DICOMfileClient
from pydicom.dataset import Dataset

samples_path = Path("./dicom-samples")
dicom_client = DICOMfileClient(f"file://{samples_path.absolute()}")

for series_json in dicom_client.search_for_series(get_remaining=True):
    series = Dataset.from_json(series_json)

    for instance in dicom_client.retrieve_series(
        study_instance_uid=series.StudyInstanceUID,
        series_instance_uid=series.SeriesInstanceUID,
    ):
        pass

Expected Behavior

The script succeeds without any output.

Actual Behavior

Traceback (most recent call last):
  File "/.../.venv/lib/python3.11/site-packages/dicomweb_client/file.py", line 3180, in retrieve_instance
    expected_media_type = transfer_syntax_uid_lut[transfer_syntax_uid]
                          ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
KeyError: '1.2.840.10008.1.2.5'

Query regarding the body of STOW-RS request

I was trying to parse the byte array send by store request using this client and I am getting an EOF error of reading. I checked the content from the request body and reading the test dicom file directly and found some difference at the beginning.

  • content from http post request
    http_body
  • content from reading file directly
    from_file
    What is this difference at the begnning? I am trying to pass the POST request body directly to a parsing function like this

Stow-RS fails with current verion sending to orthanc

I have a similar issue as #41 regarding the current version:
I am using this great package to send data to an Orthanc instance. Until version 0.41.2 this works.
For the current version, I get a response code 200 (this is probably an Orthanc mistake), but no data can be found on the Orthanc instance. I tested it with different Orthanc version and with the Ortanc docker container.
e.g. the current version:

docker run -p 4242:4242 -p 8042:8042 --rm jodogne/orthanc-plugins
Here the code snipped to reproduce:

from dicomweb_client.api import DICOMwebClient
from dicomweb_client.session_utils import create_session_from_auth
from requests.auth import HTTPBasicAuth
auth = HTTPBasicAuth('orthanc', 'orthanc')
session = create_session_from_auth(auth)
client = DICOMwebClient(url="http://localhost:8042/dicom-web", session=session)
path = "" # ADD path to a dicom file
dcmfile = pydicom.dcmread(path)
value = client.store_instances([dcmfile])

With version 0.50.3 there is no data anymore on the server:
http://localhost:8042/app/explorer.html#find-patients (login with orthanc/orthanc), there is no error message ...

Set headers request

Hi, I need to know, how I can set headers request in every dicomweb requests. Nowadays, I have this issue to solve: has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Error trying to traverse a complete DICOMweb-tree

I'm currently writing a course based on the writing of a Python 3 program that fetches, manipulates and presents images and metadata available at azurewebsites.net. The first thing to figure out is how to traverse the tree of studies,series,instances,frames,etc that is described at

http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.8-1

but some parameter to the call client.retrieve_instance() in the code below is incorrect and throws and error (in which we can see the resulting error):

https://dicomcloud.azurewebsites.net/wadors/studies/1.3.12.2.1107.5.4.3.4975316777216.19951114.94101.16/series/1.3.12.2.1107.5.4.3.4975316777216.19951114.94101.17/instances/1.3.12.2.1107.5.4.3.284980.19951129.170916.11

What have I done wrong?

Code follows:

#!/usr/bin/env python3

import argparse
from pprint import pprint

from dicomweb_client.api import DICOMwebClient

def main(server_name):
    'Main function of application.'

    # 1.
    print('Defaulting DICOM-server_name to {}'.format(server_name))
    client = DICOMwebClient(url=server_name,
                            qido_url_prefix='qidors',
                            wado_url_prefix='wadors',
                            stow_url_prefix='stowrs')
    print('Connected to DICOM-server_name {}'.format(server_name))

    # 2.
    studies = client.search_for_studies()
    print('There are {} studies available'.format(len(studies)))

    # 3.
    for study in studies:

        # Ref: http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.7.1-2
        study_instance_uid = study['0020000D']['Value'][0]  # StudyInstanceUID

        print('Study Instance UID: {}'.format(study_instance_uid))

        study_metadata = client.retrieve_study_metadata(study_instance_uid)
        # pprint(study_metadata)
        for series_metadata in study_metadata:
            series_instance_uid = series_metadata['0020000E']['Value'][0]  # SeriesInstanceUID
            sop_class_uid = series_metadata['00080016']['Value'][0]        # SOPClassUID
            sop_instance_uid = series_metadata['00080018']['Value'][0]     # SOPInstanceUID

            print('SOP Class UID: ', sop_class_uid)
            print('SOP Instance UID: ', sop_instance_uid)
            # print('Instance Number: ', series_metadata['00200013']['Value'])
            try:
                print('Rows: ', series_metadata['00280010']['Value'][0])
                print('Columns: ', series_metadata['00280010']['Value'][0])
            except:
                None

            instance = client.retrieve_instance(study_instance_uid,
                                                series_instance_uid,
                                                sop_instance_uid)

        # print('Study Metadata:')
        # pprint(study_metadata)

        # find all series_instance_uid

    # 4.
    patient_name = input('Enter patient name: ')

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--server',
                        help='DICOMweb server to connect to',
                        type=str,
                        default='https://dicomcloud.azurewebsites.net'
    )
    args = parser.parse_args()
    main(server_name=args.server)

HTTP Error: 406 Client Error: Not Acceptable for url

Hello,

I get a client error when connecting to an azure dicom webserver. I can see normally all the records and I can as well remove a selected series from the DICOMweb server through Slicer's extension. But when I try to "download and index" it I get:

DownloadSelectedSeries: Error in getting response from DICOMweb server.
HTTP Error:
406 Client Error: Not Acceptable for url: https://xxx-xxx.azurewebsites.net/studies/1.2.826.0.1.xxxxxx.x.xxx.x.x.xxxxxxxxxxx.xxxx.xxxx/series/1.2.826.0.1.xxxxxx.x.x.x.x.xxxx.xxx.xxxxx.xxxxxx/instances/1.2.826.0.1.xxxx.x.xxx.x.x.8xxxxx.xxx.xxxxxxxx.xxxxx

What could possibly go wrong?

OS: Win 10
Slicer version: 4.13.0-2021-03-03 r29745 / fca084e

Regards
Antonios

Orthanc Support

There seem to be issues when integrating with Orthanc. Orthanc uses SHA-1 hashes of Instance UIDs. This fails DICOM UID verification giving "DICOM UID has invalid format.". It is clearly obvious this is not a valid DICOM UID but support for Orthanc is mentioned.

Screenshot from 2022-01-24 20-32-25

See
https://book.orthanc-server.com/faq/orthanc-ids.html

Convert to more specific return types in DICOMwebClient

While it's common practice to accept Sequence[T] when a function can accept a list, tuple, etc., it's often best to be more specific with return types allowing those invoking the methods to have greater clarity of the type returned. In many of DICOMwebClient's methods, current type hints provide a return value of Sequence[T] where List[T] would be more specific. I propose updating these types to the more specific values provided the API is comfortable committing to these types (which seems like a fairly innocuous commitment).

Missing image data when using `retrieve_*` or `iter_*`

I switched from my own aiohttp based DicomWeb implementation to dicomwebclient for downloading image studies/series and observe quite often missing data in a single slice per image series (when using retrieve_series or iter_series). It can be reproduced for the very same image series and when retrieving more than one series in a row there is also some kind of buffer leakage introduced. The missing image data is replaced with data from probably a different series.

image

Two different download mechanisms, one based on aiohttp and the other on Scala and dcm4che, are able to retrieve the image data without any problems so I think it is actually a problem in the multi-part response parsing in this package.

ImportError: cannot import name 'Protocol' from 'typing' (python 3.7.11)

In new conda environment with only dicomweb-client 0.54.2 installed, when using python 3.7.11, following error has occured:

>>> import dicomweb_client.api
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jan/.conda/envs/test/lib/python3.7/site-packages/dicomweb_client/__init__.py", line 3, in <module>
    from dicomweb_client.api import DICOMwebClient, DICOMfileClient
  File "/home/jan/.conda/envs/test/lib/python3.7/site-packages/dicomweb_client/api.py", line 4, in <module>
    from dicomweb_client.protocol import DICOMClient
  File "/home/jan/.conda/envs/test/lib/python3.7/site-packages/dicomweb_client/protocol.py", line 1, in <module>
    from typing import (
ImportError: cannot import name 'Protocol' from 'typing' (/home/jan/.conda/envs/test/lib/python3.7/typing.py)

I guess, that typing-extensions is not used in case of python versions lower then 3.8.

Enable insecure connections to DICOMweb endpoints

Hi,
it would be really nice to add an verify_ssl attribute to the DICOMwebClient for servers with self-signed certificates.
This should be pretty straightforward to do by adding the verify=True/False parameter to all requests calls.

Double trailing slash problem

_get_transaction_url function cannot properly check if the value is null or empty.

https://github.com/herrmannlab/dicomweb-client/blob/a9d6b09a347d41b245d0bf2054ec323fdc7ddb33/src/dicomweb_client/web.py#L318
https://github.com/herrmannlab/dicomweb-client/blob/a9d6b09a347d41b245d0bf2054ec323fdc7ddb33/src/dicomweb_client/web.py#L321
this codes always return True, so it adds a double slash to the end of the address even if the url prefix is empty.

image

Related : Project-MONAI/MONAILabel#1045 (comment)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.