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()
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.
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?
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:
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.
Hi, I followed the above tutorial but still can’t work.
Put LoadRemoteFile.py in lib\Slicer-4.13\qt-scripted-modules folder
Load module from module finder --in Modules finder “load Remote File” – Switch to module.
In Chrome input url: slicer://viewer/?download=https%3A%2F%2Fgithub.com%2Frbumm%2FSlicerLungCTAnalyzer%2Freleases%2Fdownload%2FSampleData%2FLungCTAnalyzerChestCT.nrrd
@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.
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.
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.
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.