Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make DICOMweb assetstore imports compatible with Girder generics #1504

Merged
merged 9 commits into from
Apr 25, 2024
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

import cherrypy
import requests
from large_image_source_dicom.dicom_tags import dicom_key_to_tag
Expand Down Expand Up @@ -394,31 +396,10 @@
except ValueError:
return

def importData(self, parent, parentType, params, progress, user, **kwargs):
"""
Import DICOMweb WSI instances from a DICOMweb server.

:param parent: The parent object to import into.
:param parentType: The model type of the parent object.
:type parentType: str
:param params: Additional parameters required for the import process.
This dictionary may include the following keys:

:limit: (optional) limit the number of studies imported.
:search_filters: (optional) a dictionary of additional search
filters to use with dicomweb_client's `search_for_series()`
function.

:type params: dict
:param progress: Object on which to record progress if possible.
:type progress: :py:class:`girder.utility.progress.ProgressContext`
:param user: The Girder user performing the import.
:type user: dict or None
:return: a list of items that were created
"""
def _importData(self, parent, parentType, params, progress, user):
if parentType not in ('folder', 'user', 'collection'):
msg = f'Invalid parent type: {parentType}'
raise RuntimeError(msg)
raise ValidationException(msg)

Check warning on line 402 in sources/dicom/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py

View check run for this annotation

Codecov / codecov/patch

sources/dicom/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py#L402

Added line #L402 was not covered by tests

limit = params.get('limit')
search_filters = params.get('search_filters', {})
Expand Down Expand Up @@ -512,6 +493,65 @@

return items

def importData(self, parent, parentType, params, progress, user, **kwargs):
"""
Import DICOMweb WSI instances from a DICOMweb server.

:param parent: The parent object to import into.
:param parentType: The model type of the parent object.
:type parentType: str
:param params: Additional parameters required for the import process.
This dictionary may include the following keys:

:limit: (optional) limit the number of studies imported.
:filters: (optional) a dictionary/JSON string of additional search
filters to use with dicomweb_client's `search_for_series()`
function.

:type params: dict
:param progress: Object on which to record progress if possible.
:type progress: :py:class:`girder.utility.progress.ProgressContext`
:param user: The Girder user performing the import.
:type user: dict or None
:return: a list of items that were created
"""
# Validate the parameters
limit = params.get('limit') or None
if limit is not None:
error_msg = f'Invalid limit: {limit}'
try:
limit = int(limit)
except ValueError:
raise ValidationException(error_msg)

if limit < 1:
raise ValidationException(error_msg)

search_filters = params.get('filters', {})
if isinstance(search_filters, str):
try:
search_filters = json.loads(search_filters)
except json.JSONDecodeError as e:
msg = f'Invalid filters: "{params.get("filters")}". {e}'
raise ValidationException(msg)

items = self._importData(
parent,
parentType,
{
'limit': limit,
'search_filters': search_filters,
},
progress,
user,
)

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

return items

@property
def auth_session(self):
return _create_auth_session(self.assetstore_meta)
Expand Down
34 changes: 4 additions & 30 deletions sources/dicom/large_image_source_dicom/assetstore/rest.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import json

from girder.api import access
from girder.api.describe import Description, autoDescribeRoute
from girder.api.rest import Resource
from girder.constants import TokenScope
from girder.exceptions import RestException
from girder.models.assetstore import Assetstore
from girder.utility import assetstore_utilities
from girder.utility.model_importer import ModelImporter
from girder.utility.progress import ProgressContext

from .dicomweb_assetstore_adapter import DICOMwebAssetstoreAdapter


class DICOMwebAssetstoreResource(Resource):
def __init__(self):
Expand All @@ -34,39 +33,14 @@
parent = ModelImporter.model(destinationType).load(params['destinationId'], force=True,
exc=True)

limit = params.get('limit') or None
if limit is not None:
error_msg = 'Invalid limit'
try:
limit = int(limit)
except ValueError:
raise RestException(error_msg)

if limit < 1:
raise RestException(error_msg)

try:
search_filters = json.loads(params.get('filters') or '{}')
except json.JSONDecodeError as e:
msg = f'Invalid filters: {e}'
raise RestException(msg)

adapter = assetstore_utilities.getAssetstoreAdapter(assetstore)
items = adapter.importData(
return DICOMwebAssetstoreAdapter(assetstore).importData(

Check warning on line 36 in sources/dicom/large_image_source_dicom/assetstore/rest.py

View check run for this annotation

Codecov / codecov/patch

sources/dicom/large_image_source_dicom/assetstore/rest.py#L36

Added line #L36 was not covered by tests
parent,
destinationType,
{
'limit': limit,
'search_filters': search_filters,
},
params,
progress,
user,
)

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

@access.admin(scope=TokenScope.DATA_WRITE)
@autoDescribeRoute(
Description('Import references to DICOM objects from a DICOMweb server')
Expand Down
3 changes: 1 addition & 2 deletions sources/dicom/large_image_source_dicom/web_client/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import './routes';

// Extends and overrides API
import './constants';
import './views/DICOMwebImportView';
import './views/AssetstoresView';
import './views/EditAssetstoreWidget';
import './views/NewAssetstoreWidget';

This file was deleted.

17 changes: 0 additions & 17 deletions sources/dicom/large_image_source_dicom/web_client/routes.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.g-assetstore-import-button-container
a.g-dwas-import-button.btn.btn-sm.btn-success(
href=`#dicomweb_assetstore/${assetstore.get('_id')}/import`,
href=`#assetstore/${assetstore.get('_id')}/import`,
title="Import references to DICOM objects from a DICOMweb server")
i.icon-link-ext
| Import data
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import router from '@girder/core/router';
import View from '@girder/core/views/View';
import { restRequest } from '@girder/core/rest';

import { assetstoreImportViewMap } from '@girder/core/views/body/AssetstoresView';
import { AssetstoreType } from '@girder/core/constants';

import DWASImportTemplate from '../templates/assetstoreImport.pug';

const DICOMwebImportView = View.extend({
Expand All @@ -24,12 +27,12 @@ const DICOMwebImportView = View.extend({
}

this.$('.g-submit-dwas-import').addClass('disabled');
this.model.off().on('g:imported', function () {
this.assetstore.off().on('g:imported', function () {
router.navigate(destinationType + '/' + destinationId, { trigger: true });
}, this).on('g:error', function (err) {
this.$('.g-submit-dwas-import').removeClass('disabled');
this.$('.g-validation-failed-message').html(err.responseJSON.message);
}, this).dicomwebImport({
}, this).import({
destinationId,
destinationType,
limit,
Expand All @@ -40,7 +43,7 @@ const DICOMwebImportView = View.extend({
'click .g-open-browser': '_openBrowser'
},

initialize: function () {
initialize: function (settings) {
this._browserWidgetView = new BrowserWidget({
parentView: this,
titleText: 'Destination',
Expand Down Expand Up @@ -75,12 +78,13 @@ const DICOMwebImportView = View.extend({
}
});
});
this.assetstore = settings.assetstore;
this.render();
},

render: function () {
this.$el.html(DWASImportTemplate({
assetstore: this.model
assetstore: this.assetstore
}));

return this;
Expand All @@ -91,4 +95,6 @@ const DICOMwebImportView = View.extend({
}
});

assetstoreImportViewMap[AssetstoreType.DICOMWEB] = DICOMwebImportView;

export default DICOMwebImportView;
5 changes: 4 additions & 1 deletion sources/dicom/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ def prerelease_local_scheme(version):
],
}

girder_extras = [f'girder-large-image{limit_version}']

if sys.version_info >= (3, 9):
# For Python >= 3.9, include the DICOMweb plugin
entry_points['girder.plugin'] = [
'dicomweb = large_image_source_dicom.girder_plugin:DICOMwebPlugin',
]
girder_extras.append('girder>=3.2.3')

setup(
name='large-image-source-dicom',
Expand All @@ -71,7 +74,7 @@ def prerelease_local_scheme(version):
'wsidicom>=0.9.0',
],
extras_require={
'girder': f'girder-large-image{limit_version}',
'girder': girder_extras,
},
include_package_data=True,
keywords='large_image, tile source',
Expand Down
4 changes: 2 additions & 2 deletions sources/dicom/test_dicom/web_client_specs/dicomWebSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ describe('DICOMWeb assetstore', function () {
});

waitsFor(function () {
return $('.g-validation-failed-message').html() === 'Invalid limit';
return $('.g-validation-failed-message').html() === 'Invalid limit: 1.3';
}, 'Invalid limit check (float)');

runs(function () {
Expand All @@ -156,7 +156,7 @@ describe('DICOMWeb assetstore', function () {
});

waitsFor(function () {
return $('.g-validation-failed-message').html() === 'Invalid limit';
return $('.g-validation-failed-message').html() === 'Invalid limit: -1';
}, 'Invalid limit check (negative)');

runs(function () {
Expand Down