Skip to content

Commit

Permalink
Merge pull request #1450 from psavery/dicomweb-idc-support
Browse files Browse the repository at this point in the history
Add support for Imaging Data Commons
  • Loading branch information
psavery authored Feb 15, 2024
2 parents 2d41f5d + e471abe commit 5c95411
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,6 @@ def importData(self, parent, parentType, params, progress, user, **kwargs):
msg = f'Invalid parent type: {parentType}'
raise RuntimeError(msg)

from wsidicom.uid import WSI_SOP_CLASS_UID

limit = params.get('limit')
search_filters = params.get('search_filters', {})

Expand All @@ -433,24 +431,33 @@ def importData(self, parent, parentType, params, progress, user, **kwargs):
series_uid_key = dicom_key_to_tag('SeriesInstanceUID')
instance_uid_key = dicom_key_to_tag('SOPInstanceUID')

# We are only searching for WSI datasets. Ignore all others.
# FIXME: is this actually working? For the SLIM server at
# https://imagingdatacommons.github.io/slim/, none of the series
# report a SOPClassUID, but we still get all results anyways.
search_filters = {
'SOPClassUID': WSI_SOP_CLASS_UID,
**search_filters,
}
# Search for studies. Apply the limit and search filters.
fields = [
study_uid_key,
]
if progress:
progress.update(message='Searching for studies...')

studies_results = client.search_for_studies(
limit=limit,
fields=fields,
search_filters=search_filters,
)

# Search for all series in the returned studies.
fields = [
study_uid_key,
series_uid_key,
]
if progress:
progress.update(message='Searching for series...')

# FIXME: might need to search in chunks for larger web servers
series_results = client.search_for_series(
fields=fields, limit=limit, search_filters=search_filters)
series_results = []
for study in studies_results:
study_uid = study[study_uid_key]['Value'][0]
series_results += client.search_for_series(study_uid, fields=fields)

# Create folders for each study, items for each series, and files for each instance.
items = []
for i, result in enumerate(series_results):
if progress:
Expand Down Expand Up @@ -496,8 +503,9 @@ def importData(self, parent, parentType, params, progress, user, **kwargs):
}
file['imported'] = True

# Try to infer the file size without streaming, if possible.
file['size'] = self._infer_file_size(file)
# Inferring the file size can take a long time, so don't
# do it right away, unless we figure out a way to make it faster.
# file['size'] = self._infer_file_size(file)
file = File().save(file)

items.append(item)
Expand Down
6 changes: 3 additions & 3 deletions sources/dicom/large_image_source_dicom/assetstore/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _importData(self, assetstore, params, progress):
)

if not items:
msg = 'No DICOM objects matching the search filters were found'
msg = 'No studies matching the search filters were found'
raise RestException(msg)

@access.admin(scope=TokenScope.DATA_WRITE)
Expand All @@ -77,9 +77,9 @@ def _importData(self, assetstore, params, progress):
.param('destinationType', 'The type of the parent object to import into.',
enum=('folder', 'user', 'collection'),
required=False, default='folder')
.param('limit', 'The maximum number of results to import.',
.param('limit', 'The maximum number of studies to import.',
required=False, default=None)
.param('filters', 'Any search parameters to filter DICOM objects.',
.param('filters', 'Any search parameters to filter the studies query.',
required=False, default='{}')
.param('progress', 'Whether to record progress on this operation.',
required=False, default=False, dataType='boolean')
Expand Down
15 changes: 11 additions & 4 deletions sources/dicom/large_image_source_dicom/dicomweb_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,25 @@ def get_first_wsi_volume_metadata(client, study_uid, series_uid):
image_type_tag = dicom_key_to_tag('ImageType')
instance_uid_tag = dicom_key_to_tag('SOPInstanceUID')

search_filters = {
'SOPClassUID': WSI_SOP_CLASS_UID,
}
# We can't include the SOPClassUID as a search filter because Imaging Data Commons
# produces an error if we do. Perform the filtering manually instead.
class_uid_tag = dicom_key_to_tag('SOPClassUID')

fields = [
image_type_tag,
instance_uid_tag,
class_uid_tag,
]
wsi_instances = client.search_for_instances(
study_uid, series_uid, search_filters=search_filters, fields=fields)
study_uid, series_uid, fields=fields)

volume_instance = None
for instance in wsi_instances:
class_type = instance.get(class_uid_tag, {}).get('Value')
if not class_type or class_type[0] != WSI_SOP_CLASS_UID:
# Only look at WSI classes
continue

image_type = instance.get(image_type_tag, {}).get('Value')
# It would be nice if we could have a search filter for this, but
# I didn't see one...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ form.g-dwas-import-form
.input-group-btn
button.g-open-browser.btn.btn-default(type="button")
i.icon-folder-open
label(for="g-dwas-import-limit") Limit (Series)
label(for="g-dwas-import-limit") Limit (Studies)
input#g-dwas-import-limit.form-control(
type="number", step="1", min="1", value="")
label(for="g-dwas-import-filters") Filters
textarea#g-dwas-import-filters.form-control(rows="10",
placeholder="Valid JSON. e.g.:\n\n{\n \"PatientID\": \"ABC123\"\n}")
type="number", step="1", min="1", value="10")
label(for="g-dwas-import-filters") Filters (Studies)
textarea#g-dwas-import-filters.form-control(rows="10")
| {
| "ModalitiesInStudy": "SM"
| }
.g-validation-failed-message
button.g-submit-assetstore-import.btn.btn-success(type="submit")
i.icon-link-ext
Expand Down
12 changes: 7 additions & 5 deletions sources/dicom/test_dicom/web_client_specs/dicomWebSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ describe('DICOMWeb assetstore', function () {
var dicomFileContent;

// After importing, we will verify that this item exists
const verifyItemName = '1.3.6.1.4.1.5962.99.1.3205815762.381594633.1639588388306.2.0';
const studyUID = '2.25.25644321580420796312527343668921514374';
const seriesUID = '1.3.6.1.4.1.5962.99.1.3205815762.381594633.1639588388306.2.0';
const verifyItemName = seriesUID;

runs(function () {
$('a.g-nav-link[g-target="admin"]').trigger('click');
Expand Down Expand Up @@ -173,20 +175,20 @@ describe('DICOMWeb assetstore', function () {

runs(function () {
// Perform a search where no results are returned
const filters = '{"SeriesInstanceUID": "DOES_NOT_EXIST"}';
const filters = '{"StudyInstanceUID": "DOES_NOT_EXIST"}';
$('#g-dwas-import-filters').val(filters);
$('.g-submit-assetstore-import').trigger('click');
});

waitsFor(function () {
const msg = 'No DICOM objects matching the search filters were found';
const msg = 'No studies matching the search filters were found';
return $('.g-validation-failed-message').html() === msg;
}, 'No results check');

runs(function () {
// Fix the filters
// We will only import this specific SeriesInstanceUID
const filters = '{"SeriesInstanceUID": "' + verifyItemName + '"}';
// We will only import this specific StudyInstanceUID
const filters = '{"StudyInstanceUID": "' + studyUID + '"}';
$('#g-dwas-import-filters').val(filters);

// This one should work fine
Expand Down

0 comments on commit 5c95411

Please sign in to comment.