diff --git a/sources/dicom/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py b/sources/dicom/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py index 6a3e25261..158f4cbbd 100644 --- a/sources/dicom/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py +++ b/sources/dicom/large_image_source_dicom/assetstore/dicomweb_assetstore_adapter.py @@ -1,3 +1,5 @@ +import json + import cherrypy import requests from large_image_source_dicom.dicom_tags import dicom_key_to_tag @@ -394,31 +396,10 @@ def _infer_file_size_singlepart_content_length(self, file): 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) limit = params.get('limit') search_filters = params.get('search_filters', {}) @@ -512,6 +493,65 @@ def importData(self, parent, parentType, params, progress, user, **kwargs): 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) diff --git a/sources/dicom/large_image_source_dicom/assetstore/rest.py b/sources/dicom/large_image_source_dicom/assetstore/rest.py index a53f49987..e0e329902 100644 --- a/sources/dicom/large_image_source_dicom/assetstore/rest.py +++ b/sources/dicom/large_image_source_dicom/assetstore/rest.py @@ -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): @@ -34,39 +33,14 @@ def _importData(self, assetstore, params, progress): 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( 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') diff --git a/sources/dicom/large_image_source_dicom/web_client/main.js b/sources/dicom/large_image_source_dicom/web_client/main.js index d697a6181..3b64ee4aa 100644 --- a/sources/dicom/large_image_source_dicom/web_client/main.js +++ b/sources/dicom/large_image_source_dicom/web_client/main.js @@ -1,7 +1,6 @@ -import './routes'; - // Extends and overrides API import './constants'; +import './views/DICOMwebImportView'; import './views/AssetstoresView'; import './views/EditAssetstoreWidget'; import './views/NewAssetstoreWidget'; diff --git a/sources/dicom/large_image_source_dicom/web_client/models/AssetstoreModel.js b/sources/dicom/large_image_source_dicom/web_client/models/AssetstoreModel.js deleted file mode 100644 index c8313a140..000000000 --- a/sources/dicom/large_image_source_dicom/web_client/models/AssetstoreModel.js +++ /dev/null @@ -1,20 +0,0 @@ -import AssetstoreModel from '@girder/core/models/AssetstoreModel'; -import { restRequest } from '@girder/core/rest'; - -/** - * Extends the core assetstore model to add DICOMweb-specific functionality. - */ -AssetstoreModel.prototype.dicomwebImport = function (params) { - return restRequest({ - url: 'dicomweb_assetstore/' + this.get('_id') + '/import', - type: 'POST', - data: params, - error: null - }).done(() => { - this.trigger('g:imported'); - }).fail((err) => { - this.trigger('g:error', err); - }); -}; - -export default AssetstoreModel; diff --git a/sources/dicom/large_image_source_dicom/web_client/routes.js b/sources/dicom/large_image_source_dicom/web_client/routes.js deleted file mode 100644 index 22e84ad77..000000000 --- a/sources/dicom/large_image_source_dicom/web_client/routes.js +++ /dev/null @@ -1,17 +0,0 @@ -import router from '@girder/core/router'; -import events from '@girder/core/events'; - -import AssetstoreModel from './models/AssetstoreModel'; -import DICOMwebImportView from './views/DICOMwebImportView'; - -router.route('dicomweb_assetstore/:id/import', 'dwasImport', function (id) { - // Fetch the assetstore by id, then render the view. - const assetstore = new AssetstoreModel({ _id: id }); - assetstore.once('g:fetched', function () { - events.trigger('g:navigateTo', DICOMwebImportView, { - model: assetstore - }); - }).once('g:error', function () { - router.navigate('assetstores', { trigger: true }); - }).fetch(); -}); diff --git a/sources/dicom/large_image_source_dicom/web_client/templates/dicomwebAssetstoreImportButton.pug b/sources/dicom/large_image_source_dicom/web_client/templates/dicomwebAssetstoreImportButton.pug index ca55a7747..a5a909c33 100644 --- a/sources/dicom/large_image_source_dicom/web_client/templates/dicomwebAssetstoreImportButton.pug +++ b/sources/dicom/large_image_source_dicom/web_client/templates/dicomwebAssetstoreImportButton.pug @@ -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 diff --git a/sources/dicom/large_image_source_dicom/web_client/views/DICOMwebImportView.js b/sources/dicom/large_image_source_dicom/web_client/views/DICOMwebImportView.js index eceef92d8..b77fb3aad 100644 --- a/sources/dicom/large_image_source_dicom/web_client/views/DICOMwebImportView.js +++ b/sources/dicom/large_image_source_dicom/web_client/views/DICOMwebImportView.js @@ -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({ @@ -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, @@ -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', @@ -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; @@ -91,4 +95,6 @@ const DICOMwebImportView = View.extend({ } }); +assetstoreImportViewMap[AssetstoreType.DICOMWEB] = DICOMwebImportView; + export default DICOMwebImportView; diff --git a/sources/dicom/setup.py b/sources/dicom/setup.py index 5ca0fb120..dac379536 100644 --- a/sources/dicom/setup.py +++ b/sources/dicom/setup.py @@ -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', @@ -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', diff --git a/sources/dicom/test_dicom/web_client_specs/dicomWebSpec.js b/sources/dicom/test_dicom/web_client_specs/dicomWebSpec.js index a13f25a0b..d200ea5fb 100644 --- a/sources/dicom/test_dicom/web_client_specs/dicomWebSpec.js +++ b/sources/dicom/test_dicom/web_client_specs/dicomWebSpec.js @@ -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 () { @@ -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 () {