How to load nifti file from web browser link?

Hi there,
I have a nifti file (xxx.nii.gz) and can open it in 3D slicer with drag and drop the file to the app.
I watched the video (Using 3D Slicer with cloud DICOMweb databases - YouTube) and found a DICOM can be opened from web browser lnk. Now, I want to load a nifti file in 3D slicer just like that. Is it possible?

I noticed a link looks like slicer://viewer?studyUID=xxx&access_token=xxx&dicomweb_endpoint=httpsxxx&dicomweb_uri_endpoint=httpsxxx, but couldn’t find a document what value should be passed for each argment.

This is easily doable. All you need to do is to create a small Python scripted module that handles this URL. You can generate the skeleton of the module using Extension Wizard and then you would only keep the module class that would set up the callback function as it is done in the DICOM module.

Your callback function could be something like this (you can copy-paste this code into the Python console to test it):

def reportProgress(message, level, sampleDataLogic):
    # Print progress in the console
    print("Loading... {0}%".format(sampleDataLogic.downloadPercent))
    # Abort download if cancel is clicked in progress bar
    progressWindow = sampleDataLogic.progressWindow
    if progressWindow.wasCanceled:
        raise Exception("download aborted")
    # Update progress window
    progressWindow.show()
    progressWindow.activateWindow()
    progressWindow.setValue(int(sampleDataLogic.downloadPercent))
    progressWindow.setLabelText("Downloading...")
    # Process events to allow screen to refresh
    slicer.app.processEvents()

def onURLReceived(urlString):
    """Process DICOM view requests. Example:
    urlString="slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd"
    """
    url = qt.QUrl(urlString)
    if url.authority().lower() != "viewer":
        return
    query = qt.QUrlQuery(url)
    queryMap = {}
    for key, value in query.queryItems(qt.QUrl.FullyDecoded):
        queryMap[key] = qt.QUrl.fromPercentEncoding(value)
    if not "download" in queryMap:
        return
    downloadUrl = qt.QUrl(queryMap["download"])
    nodeName, ext = os.path.splitext(os.path.basename(downloadUrl.path()))
    import uuid
    # Generate random filename to avoid reusing/overwriting older downloaded files that may have the same name
    filename = f"{nodeName}-{uuid.uuid4().hex}{ext}"
    print(filename)
    try:
        progressWindow = slicer.util.createProgressDialog()
        import SampleData
        sampleDataLogic = SampleData.SampleDataLogic()
        sampleDataLogic.progressWindow = progressWindow
        sampleDataLogic.logMessage = lambda message, level=None, sampleDataLogic=sampleDataLogic: reportProgress(message, level, sampleDataLogic)
        loadedNodes = sampleDataLogic.downloadFromURL(nodeNames=nodeName, fileNames=filename, uris=downloadUrl.toString())
    finally:
        progressWindow.close()

To test, run this in the Python console (to download this sample nrrd file: https://github.com/rbumm/SlicerLungCTAnalyzer/releases/download/SampleData/LungCTAnalyzerChestCT.nrrd)

onURLReceived("slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd")

You can of course tune the URL format, add checksum (SampleData module can automatically cache downloaded data sets to avoid repeated downloads if the checksum value matches), etc.

Thanks for the reply. Some parts are not clear to me as I’m new to 3D slicer and python. Appreaciate if you could help me bit more.

Firstly, I followed “Creating Extensions” section in the below page to create an extension/module named “loadRemoteFile”:
https://www.slicer.org/wiki/Documentation/Nightly/Developers/ExtensionWizard

I added slicer.app.connect("urlReceived(QString)", self.onURLReceived) line and the script you mentioned above in loadRemoteFile.py. Then loaded the module from “Module finder” (It should be loaded automatically anyway as I checked ‘Add selected module to search paths’ when creaed extension). No error occurs at this point. However, if I run "onURLReceived(“slicer…” in Python interactor, get an error “NameError: name ‘onURLReceived’ is not defined”. What am I missing?

Also, I would like to know the way to pass the module I created to somebody. Give the whole contents of loadRemoteFile folder then users load it from Extension wizard->Select extension?

onURLReceived function is not found in the Python console if you don’t copy it into the Python console.

To test your module, you can launch Slicer.exe with the URL as the only command-line argument:

Slicer.exe slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd

To test if the custom URL association is set up correctly, you can open a command terminal in Windows and execute this:

start slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd`

Probably the easiest is to copy your module files to the lib\Slicer-4.13\qt-scripted-modules folder in the Slicer installation.

mmm… I’m still struggling. 3D slicer won’t complain anything if open up normally, however open up with “Slicer.exe slicer://…” or “start slicer://…” gets “Missing dicomweb_endpoint” error. Sounds like the module is not loaded correctly.

DICOM module received URL: slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd
Missing dicomweb_endpoint

The app complains if I insert something meaningless text in loadRemoteFile.py intentionally, so apparently the app is reading the py file.
I share the py file below. Could you check if anything wrong please?
I commented out slicer.app.connect("startupCompleted()", registerSampleData) at line 35.
“slicer.app.connect” is in line 36 followed by reportProgress and onURLReceived functions.

I also tried to put “slicer.app.connect()”, “reportProgress()” and “onURLReceived()” functions just under the “def setup(self):” line like the example you mentioned - still same error.

You have forgot to add the self argument to the reporting function when you made it a class member. I’ve made a few simplifications and uploaded a complete working example here:

It is working finally! Thank you so much for your support lassoan!

Steps:
Put LoadRemoteFile.py in lib\Slicer-4.13\qt-scripted-modules folder
Load module from module finder
Open 3d slicer from slice:// link on browser

I noticed the error “Missing dicomweb_endpoint”, but guess it’s safe to ignore.

1 Like

Yes that’s logged by the DICOM module, as it notices that the application is started with a slicer:// URL but the link does not point to a DICOMweb server. It is not an actual error, it is safe to ignore it. I’ll change it to a debug-level message.

1 Like

Hi, I followed the above tutorial but still can’t work.

  1. Put LoadRemoteFile.py in lib\Slicer-4.13\qt-scripted-modules folder
  2. Load module from module finder --in Modules finder “load Remote File” – Switch to module.
  3. In Chrome input url: slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd

nothing happened.

İf the script doesnt work for you, there is an importfromUrl module in Slicermorph extension.

@muratmaga the question is about how to open an image in 3D Slicer by simply clicking on a link in a web browser (user do not have to launch Slicer manually and copy-paste a URL).

Most likely you have not associated the slicer:// custom browser protocol with the Slicer application. I’ve added more detailed instructions to the top of the example module. I’ve just tested this module with the latest Slicer Preview Release on Windows and it worked well.

Thank you for the reply. I reinstalled the latest slicer 4.13.0 and now it worked.
However, when I download url from DCM4CHEE retrieve link,
slicer://viewer/?download=http://192.168.8.113:8080/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.2.276.0.7230010.3.1.4.3928906.148.15392368.470/series/1.2.276.0.7230010.3.1.4.39968906.148.1539239634.469
It errors out.
So is it only support nrrd file not dcm?

Opening a file via DICOMweb is much simpler, as it is a built-in feature of Slicer’s DICOM module (no need to install a custom module). You need to use the slicer://viewer/... URL as described here.

Thank you, but please bear with me, I still can’t work out.

slicer://viewer/?download=http://192.168.8.113:8080/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.2.276.0.7230010.3.1.4.3929968906.148.1539239668.470

it did not work, with loading… 0% then
TypeError: reportProgress() takes 2 positional arguments but 3 were given

or if
slicer://viewer/?studyUID=1.2.276.0.7230010.3.1.4.1726399104.1960.1540283292.820
&dicomweb_endpoint=http://192.168.8.113:8080/dcm4chee-arc/aets/DCM4CHEE/rs
&dicomweb_uri_endpoint=http://192.168.8.113:8080/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.2.276.0.7230010.3.1.4.1726399104.1960.1540283292.820

i got : NameError: name ‘progressCallback’ is not defined

There was an untested code branch (when access token was not specified) that caused the error above. I’ve fixed it now, you can apply the code change to the same file in your Slicer installation.

1 Like

Yes, that fixed problem. Thanks.

1 Like

Great, thanks for the update!

DICOMweb downloads can take a while and we currently only update progress report once for each series. It would be great if you could add more detailed progress reporting to the script in Modules/Scripted/DICOMLib/DICOMUtils.py.

Is this part? I love to do it if you can give me more information.


It only shows 0% then jump to 100%, for len(seriesInstanceUIDs) if seriesInstanceUIDs only one then length always 1. So in this for loop, it will not show the values between 0-100.
The script you mentioned in Modules/Scripted/DICOMLib/DICOMUtils.py, is it a different file?

Also, is that possible to detect whether the slicer already opened before downloading? So that windows will not open slicer multiple instances.

I’ve had a look at the progress reporting and found an easy way to do make it more granular (using the iter_series method that was added not too long ago). I’ve also improved error reporting and currently working on selecting the imported series in the DICOM browser. I plan to submit a pull request with these improvements today or tomorrow.

While there are some operating system level support for singleton applications, there is nothing available on all supported platforms. A nice solution would be to implement a very lightweight launcher application (it could be implemented in Python) that starts Slicer with an extra script. The extra script would create a HTTP server (similarly to how it is done here) which would listen for HTTP requests. For now the only request that it would need to handle is opening of a URL. After Slicer is started, the launcher application would then send an HTTP request with the URL. The launcher application would always check if the HTTP request is responded and if it is then it would not launch Slicer again, it would just send the new URL to the running Slicer to open.

Since this would not be a high-speed operation, communication with a running Slicer instance could be performed via files. For example, we could have an “incoming” folder within the Slicer install folder that Slicer would monitor and if it detected a new file then it would get the URL from that. However, the HTTP request would be a bit more elegant and versatile (e.g., it could be used more easily in a container or from JavaScript applications).

@pieper What do you think? I know that we could just use SlicerWeb extension as is, but it seems more like a prototyping extension, contains lots of hardcoded, project-specific, experimental things and so it is quite complex. We could probably implement URL opening in 20-30 line of code and it could be part of Slicer core, so it could be used easily in various scenarios. For example SimpleITK notebooks use an environment variable to specify a viewer application. Specifying Slicer directly would mean that for opening each image, users need to wait for Slicer startup, which just takes too long. Specifying the launcher application as viewer would allow opening an image in Slicer very quickly.

1 Like