large-image-source-dicom 1.27.2.dev2__tar.gz → 1.27.2.dev4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/PKG-INFO +1 -1
  2. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py +78 -17
  3. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom.egg-info/PKG-INFO +1 -1
  4. large-image-source-dicom-1.27.2.dev4/large_image_source_dicom.egg-info/requires.txt +5 -0
  5. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/test_dicom/web_client_specs/dicomWebSpec.js +30 -1
  6. large-image-source-dicom-1.27.2.dev2/large_image_source_dicom.egg-info/requires.txt +0 -5
  7. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/LICENSE +0 -0
  8. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/README.rst +0 -0
  9. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/__init__.py +0 -0
  10. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/assetstore/__init__.py +0 -0
  11. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/assetstore/rest.py +0 -0
  12. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/dicom_metadata.py +0 -0
  13. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/dicom_tags.py +0 -0
  14. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/dicomweb_utils.py +0 -0
  15. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/girder_plugin.py +0 -0
  16. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/girder_source.py +0 -0
  17. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/constants.js +0 -0
  18. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/main.js +0 -0
  19. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/models/AssetstoreModel.js +0 -0
  20. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/package.json +0 -0
  21. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/routes.js +0 -0
  22. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/templates/assetstoreImport.pug +0 -0
  23. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/templates/dicomwebAssetstoreCreate.pug +0 -0
  24. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/templates/dicomwebAssetstoreEditFields.pug +0 -0
  25. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/templates/dicomwebAssetstoreImportButton.pug +0 -0
  26. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/templates/dicomwebAssetstoreMixins.pug +0 -0
  27. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/views/AssetstoresView.js +0 -0
  28. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/views/AuthOptions.js +0 -0
  29. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/views/DICOMwebImportView.js +0 -0
  30. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/views/EditAssetstoreWidget.js +0 -0
  31. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom/web_client/views/NewAssetstoreWidget.js +0 -0
  32. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom.egg-info/SOURCES.txt +0 -0
  33. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom.egg-info/dependency_links.txt +0 -0
  34. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom.egg-info/entry_points.txt +0 -0
  35. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/large_image_source_dicom.egg-info/top_level.txt +0 -0
  36. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/pyproject.toml +0 -0
  37. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/setup.cfg +0 -0
  38. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/setup.py +0 -0
  39. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/test_dicom/__init__.py +0 -0
  40. {large-image-source-dicom-1.27.2.dev2 → large-image-source-dicom-1.27.2.dev4}/test_dicom/test_web_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image-source-dicom
3
- Version: 1.27.2.dev2
3
+ Version: 1.27.2.dev4
4
4
  Summary: A DICOM tilesource for large_image.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -1,3 +1,4 @@
1
+ import cherrypy
1
2
  import requests
2
3
  from large_image_source_dicom.dicom_tags import dicom_key_to_tag
3
4
  from large_image_source_dicom.dicomweb_utils import get_dicomweb_metadata
@@ -105,14 +106,39 @@ class DICOMwebAssetstoreAdapter(AbstractAssetstoreAdapter):
105
106
  # We don't actually need to do anything special
106
107
  pass
107
108
 
109
+ def setContentHeaders(self, file, offset, endByte, contentDisposition=None):
110
+ """
111
+ Sets the Content-Length, Content-Disposition, Content-Type, and also
112
+ the Content-Range header if this is a partial download.
113
+
114
+ :param file: The file being downloaded.
115
+ :param offset: The start byte of the download.
116
+ :type offset: int
117
+ :param endByte: The end byte of the download (non-inclusive).
118
+ :type endByte: int or None
119
+ :param contentDisposition: Content-Disposition response header
120
+ disposition-type value, if None, Content-Disposition will
121
+ be set to 'attachment; filename=$filename'.
122
+ :type contentDisposition: str or None
123
+ """
124
+ isRangeRequest = cherrypy.request.headers.get('Range')
125
+ setResponseHeader('Content-Type', file['mimeType'])
126
+ setContentDisposition(file['name'], contentDisposition or 'attachment')
127
+
128
+ if file.get('size') is not None:
129
+ # Only set Content-Length and range request headers if we have a file size
130
+ size = file['size']
131
+ if endByte is None or endByte > size:
132
+ endByte = size
133
+
134
+ setResponseHeader('Content-Length', max(endByte - offset, 0))
135
+
136
+ if offset or endByte < size or isRangeRequest:
137
+ setResponseHeader('Content-Range', f'bytes {offset}-{endByte - 1}/{size}')
138
+
108
139
  def downloadFile(self, file, offset=0, headers=True, endByte=None,
109
140
  contentDisposition=None, extraParameters=None, **kwargs):
110
141
 
111
- if offset != 0 or endByte is not None:
112
- # FIXME: implement range requests
113
- msg = 'Range requests are not yet implemented'
114
- raise NotImplementedError(msg)
115
-
116
142
  from dicomweb_client.web import _Transaction
117
143
 
118
144
  dicom_uids = file['dicom_uids']
@@ -123,15 +149,8 @@ class DICOMwebAssetstoreAdapter(AbstractAssetstoreAdapter):
123
149
  client = _create_dicomweb_client(self.assetstore_meta)
124
150
 
125
151
  if headers:
126
- setResponseHeader('Content-Type', file['mimeType'])
127
- setContentDisposition(file['name'], contentDisposition or 'attachment')
128
-
129
- # The filesystem assetstore calls the following function, which sets
130
- # the above and also sets the range and content-length headers:
131
- # `self.setContentHeaders(file, offset, endByte, contentDisposition)`
132
- # However, we can't call that since we don't have a great way of
133
- # determining the DICOM file size without downloading the whole thing.
134
- # FIXME: call that function if we find a way to determine file size.
152
+ setResponseHeader('Accept-Ranges', 'bytes')
153
+ self.setContentHeaders(file, offset, endByte, contentDisposition)
135
154
 
136
155
  # Create the URL
137
156
  url = client._get_instances_url(
@@ -148,14 +167,39 @@ class DICOMwebAssetstoreAdapter(AbstractAssetstoreAdapter):
148
167
  'type="application/dicom"',
149
168
  f'transfer-syntax={transfer_syntax}',
150
169
  ]
151
- headers = {
170
+ request_headers = {
152
171
  'Accept': '; '.join(accept_parts),
153
172
  }
154
173
 
155
174
  def stream():
156
175
  # Perform the request
157
- response = client._http_get(url, headers=headers, stream=True)
158
- yield from self._stream_retrieve_instance_response(response)
176
+ response = client._http_get(url, headers=request_headers, stream=True)
177
+
178
+ bytes_read = 0
179
+ for chunk in self._stream_retrieve_instance_response(response):
180
+ if bytes_read < offset:
181
+ # We haven't reached the start of the offset yet
182
+ bytes_needed = offset - bytes_read
183
+ if bytes_needed >= len(chunk):
184
+ # Skip over the whole chunk...
185
+ bytes_read += len(chunk)
186
+ continue
187
+ else:
188
+ # Discard all bytes before the offset
189
+ chunk = chunk[bytes_needed:]
190
+ bytes_read += bytes_needed
191
+
192
+ if endByte is not None and bytes_read + len(chunk) >= endByte:
193
+ # We have reached the end... remove all bytes after endByte
194
+ chunk = chunk[:endByte - bytes_read]
195
+ if chunk:
196
+ yield chunk
197
+
198
+ bytes_read += len(chunk)
199
+ break
200
+
201
+ yield chunk
202
+ bytes_read += len(chunk)
159
203
 
160
204
  return stream
161
205
 
@@ -374,6 +418,23 @@ class DICOMwebAssetstoreAdapter(AbstractAssetstoreAdapter):
374
418
  def auth_session(self):
375
419
  return _create_auth_session(self.assetstore_meta)
376
420
 
421
+ def getFileSize(self, file):
422
+ # This function will compute the size of the DICOM file (a potentially
423
+ # expensive operation, since it may have to stream the whole file),
424
+ # and cache the result in file['size'].
425
+ # This function is called when the size is needed, such as the girder
426
+ # fuse mount code, and range requests.
427
+ if file.get('size') is not None:
428
+ # It has already been computed once. Return the cached size.
429
+ return file['size']
430
+
431
+ size = 0
432
+ for chunk in self.downloadFile(file, headers=False)():
433
+ size += len(chunk)
434
+
435
+ # This should get cached in file['size'] in File().updateSize().
436
+ return size
437
+
377
438
 
378
439
  def _create_auth_session(meta):
379
440
  auth_type = meta.get('auth_type')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: large-image-source-dicom
3
- Version: 1.27.2.dev2
3
+ Version: 1.27.2.dev4
4
4
  Summary: A DICOM tilesource for large_image.
5
5
  Home-page: https://github.com/girder/large_image
6
6
  Author: Kitware, Inc.
@@ -0,0 +1,5 @@
1
+ large-image>=1.27.2.dev4
2
+ wsidicom>=0.9.0
3
+
4
+ [girder]
5
+ girder-large-image>=1.27.2.dev4
@@ -20,6 +20,7 @@ describe('DICOMWeb assetstore', function () {
20
20
  var destinationType;
21
21
  var itemId;
22
22
  var fileId;
23
+ var dicomFileContent;
23
24
 
24
25
  // After importing, we will verify that this item exists
25
26
  const verifyItemName = '1.3.6.1.4.1.5962.99.1.3205815762.381594633.1639588388306.2.0';
@@ -235,7 +236,35 @@ describe('DICOMWeb assetstore', function () {
235
236
  });
236
237
 
237
238
  // Should be larger than 500k bytes
238
- return resp.status === 200 && resp.responseText.length > 500000;
239
+ const success = resp.status === 200 && resp.responseText.length > 500000;
240
+ if (!success) {
241
+ return false;
242
+ }
243
+
244
+ // Save the response text so we can compare with range requests...
245
+ dicomFileContent = resp.responseText;
246
+ return true;
239
247
  }, 'Wait to download a single DICOM file');
248
+
249
+ // Verify that we can make a range request
250
+ waitsFor(function() {
251
+ const resp = girder.rest.restRequest({
252
+ url: 'file/' + fileId + '/download',
253
+ type: 'GET',
254
+ async: false,
255
+ headers: {
256
+ 'Range': 'bytes=1000-1250'
257
+ },
258
+ });
259
+
260
+ return (
261
+ // 206 is a partial content response
262
+ resp.status === 206 &&
263
+ // Should be exactly 251 bytes
264
+ resp.responseText.length === 251 &&
265
+ // It should be equivalent to the slice of the file content
266
+ resp.responseText === dicomFileContent.slice(1000, 1251)
267
+ );
268
+ }, 'Wait for DICOM range request');
240
269
  });
241
270
  });
@@ -1,5 +0,0 @@
1
- large-image>=1.27.2.dev2
2
- wsidicom>=0.9.0
3
-
4
- [girder]
5
- girder-large-image>=1.27.2.dev2