44from slicer .ScriptedLoadableModule import *
55from WebServer import WebServerLogic
66from typing import Optional
7- from WebServerLib .BaseRequestHandler import BaseRequestHandler , BaseRequestLoggingFunction
7+ from WebServerLib .BaseRequestHandler import (
8+ BaseRequestHandler ,
9+ BaseRequestLoggingFunction ,
10+ )
811
912import os
1013import platform
2023import sys
2124import shlex
2225
26+
2327class IDCRequestHandler (BaseRequestHandler ):
2428
2529 def __init__ (self , logMessage : Optional [BaseRequestLoggingFunction ] = None ):
@@ -29,11 +33,13 @@ def __init__(self, logMessage: Optional[BaseRequestLoggingFunction] = None):
2933
3034 def canHandleRequest (self , uri : bytes , ** _kwargs ) -> float :
3135 parsedURL = urllib .parse .urlparse (uri )
32- if ( parsedURL .path .startswith (b"/idc" ) ):
36+ if parsedURL .path .startswith (b"/idc" ):
3337 return 0.5
3438 return 0.0
3539
36- def handleRequest (self , method : str , uri : bytes , requestBody : bytes , ** _kwargs ) -> tuple [bytes , bytes ]:
40+ def handleRequest (
41+ self , method : str , uri : bytes , requestBody : bytes , ** _kwargs
42+ ) -> tuple [bytes , bytes ]:
3743 parsedURL = urllib .parse .urlparse (uri )
3844 splitPath = parsedURL .path .split (b"/" )
3945
@@ -45,8 +51,10 @@ def handleRequest(self, method: str, uri: bytes, requestBody: bytes, **_kwargs)
4551 return self .handleDownload (series_uids )
4652 elif splitPath [2 ] == b"download" and splitPath [3 ] == b"studyInstanceUID" :
4753 study_uids = splitPath [4 ].decode ().split ("," )
48- filtered_df = self .index_df [self .index_df ['StudyInstanceUID' ].isin (study_uids )]
49- series_uids_from_study_uid = filtered_df ['SeriesInstanceUID' ].tolist ()
54+ filtered_df = self .index_df [
55+ self .index_df ["StudyInstanceUID" ].isin (study_uids )
56+ ]
57+ series_uids_from_study_uid = filtered_df ["SeriesInstanceUID" ].tolist ()
5058 return self .handleDownload (series_uids_from_study_uid )
5159 else :
5260 return b"text/plain" , b"Unhandled IDC request path"
@@ -63,14 +71,18 @@ def handleCollections(self) -> tuple[bytes, bytes]:
6371 contentType = b"text/plain"
6472 if self .logMessage :
6573 self .logMessage (responseBody .decode ())
66-
6774 return contentType , responseBody
6875
6976 def handleDownload (self , uids : list [str ]) -> tuple [bytes , bytes ]:
7077 destFolderPath = slicer .mrmlScene .GetCacheManager ().GetRemoteCacheDirectory ()
7178
7279 try :
73- self .client .download_from_selection (seriesInstanceUID = uids , downloadDir = destFolderPath , dirTemplate = "%SeriesInstanceUID" )
80+ self .client .download_from_selection (
81+ seriesInstanceUID = uids ,
82+ downloadDir = destFolderPath ,
83+ dirTemplate = "%SeriesInstanceUID" ,
84+ use_s5cmd_sync = True ,
85+ )
7486 indexer = ctk .ctkDICOMIndexer ()
7587 for uid in uids :
7688 download_folder_path = os .path .join (destFolderPath , uid )
@@ -83,16 +95,18 @@ def handleDownload(self, uids: list[str]) -> tuple[bytes, bytes]:
8395 volume = plugin .load (loadables [0 ])
8496 logging .debug ("Loaded volume: " + volume .GetName ())
8597 else :
86- raise Exception ("Unable to load DICOM content. Please retry from DICOM Browser!" )
87-
88- responseBody = f"Downloaded and indexed UID(s): { ', ' .join (uids )} " .encode ()
98+ raise Exception (
99+ "Unable to load DICOM content. Please retry from DICOM Browser!"
100+ )
101+ responseBody = (
102+ f"Downloaded and indexed UID(s): { ', ' .join (uids )} " .encode ()
103+ )
89104 contentType = b"text/plain"
90105 except Exception as e :
91106 responseBody = f"Error downloading or indexing UID(s): { e } " .encode ()
92107 contentType = b"text/plain"
93108 if self .logMessage :
94109 self .logMessage (responseBody .decode ())
95-
96110 return contentType , responseBody
97111
98112
@@ -101,9 +115,15 @@ def __init__(self, parent):
101115 ScriptedLoadableModule .__init__ (self , parent )
102116 self .parent .title = "IDC Handler Module"
103117 self .parent .categories = ["Examples" ]
104- self .parent .contributors = ["Your Name (Your Organization)" ]
118+ self .parent .contributors = [
119+ "Vamsi Thiriveedhi(ImagingDataCommons), Steve Pieper (Isomics, Inc.)"
120+ ]
105121 self .parent .helpText = """This module registers an IDCRequestHandler to handle IDC-related requests."""
106- self .parent .acknowledgementText = """This module was developed by Your Name, Your Organization."""
122+ self .parent .acknowledgementText = """This was a project born during PW 40 when @pieper once mentioned the idea of using Slicer just the way we use zoom.
123+ This post (https://discourse.slicer.org/t/how-to-load-nifti-file-from-web-browser-link/18664/5) showed that it was indeed possible, and the current implementation
124+ is inspired from it, and the slicerio package, which was originally developed by @lassoan and team.
125+ see https://github.com/ImagingDataCommons/SlicerIDCBrowser/pull/43 for more info.
126+ """
107127
108128 slicer .app .connect ("startupCompleted()" , self .onStartupCompleted )
109129
@@ -115,22 +135,28 @@ def onStartupCompleted(self):
115135 logic .stop ()
116136 except NameError :
117137 pass
118-
119138 logMessage = WebServerLogic .defaultLogMessage
120139 requestHandlers = [IDCRequestHandler ()]
121- logic = WebServerLogic (port = PORT , logMessage = logMessage , enableSlicer = True , enableStaticPages = False , enableDICOM = True , requestHandlers = requestHandlers )
140+ logic = WebServerLogic (
141+ port = PORT ,
142+ logMessage = logMessage ,
143+ enableSlicer = True ,
144+ enableStaticPages = False ,
145+ enableDICOM = True ,
146+ requestHandlers = requestHandlers ,
147+ )
122148
123149 logic .start ()
124150 print ("IDC Request Handler has been registered and server started." )
125-
151+
126152 self .writeResolverScript ()
127153 self .registerCustomProtocol ()
128154
129155 def writeResolverScript (self ):
130156 current_dir = os .path .dirname (os .path .realpath (__file__ ))
131- resolver_script_path = os .path .join (current_dir ,' Resources' , ' resolver.py' )
157+ resolver_script_path = os .path .join (current_dir , " Resources" , " resolver.py" )
132158
133- resolver_script_content = ''' import sys
159+ resolver_script_content = """ import sys
134160import urllib.parse
135161import requests
136162import webbrowser
@@ -167,98 +193,142 @@ def resolve_url(url):
167193
168194 # Resolve the URL
169195 resolve_url(url)
170- '''
196+ """
171197
172- with open (resolver_script_path , 'w' ) as f :
198+ with open (resolver_script_path , "w" ) as f :
173199 f .write (resolver_script_content )
174200 print (f"Resolver script written to { resolver_script_path } " )
175201
176202 def registerCustomProtocol (self ):
177203 if platform .system () == "Linux" :
178204 # Check if the protocol is already registered
179- if os .path .exists (os .path .expanduser ("~/.local/share/applications/idcbrowser.desktop" )):
205+
206+ if os .path .exists (
207+ os .path .expanduser ("~/.local/share/applications/idcbrowser.desktop" )
208+ ):
180209 print ("IDC Browser URL protocol is already registered." )
181210 return
182-
183211 # Get the current directory
212+
184213 current_dir = os .path .dirname (os .path .realpath (__file__ ))
185- python_script_path = os .path .join (current_dir , 'resolver.py' )
214+ python_script_path = shlex .quote (
215+ os .path .join (current_dir , "Resources" , "resolver.py" )
216+ )
217+
218+ python_dir = slicer .app .slicerHome
219+ normalized_python_dir = os .path .normpath (python_dir )
220+
221+ # Construct the path to PythonSlicer.exe in the same directory
186222
223+ python_path = shlex .quote (
224+ os .path .join (normalized_python_dir , "bin" , "PythonSlicer" )
225+ )
187226 # Register IDC Browser URL protocol
188- with open (os .path .expanduser ("~/.local/share/applications/idcbrowser.desktop" ), "w" ) as f :
189- f .write (f"""[Desktop Entry]
227+
228+ with open (
229+ os .path .expanduser ("~/.local/share/applications/idcbrowser.desktop" ),
230+ "w" ,
231+ ) as f :
232+ f .write (
233+ f"""[Desktop Entry]
190234Name=IDC Browser
191- Exec=python3 { python_script_path } %u
235+ Exec={ python_path } { python_script_path } %u
192236Type=Application
193237Terminal=false
194238MimeType=x-scheme-handler/idcbrowser;
195- """ )
196-
239+ """
240+ )
197241 # Update MIME database
242+
198243 os .system ("update-desktop-database ~/.local/share/applications/" )
199244 os .system ("xdg-mime default idcbrowser.desktop x-scheme-handler/idcbrowser" )
200-
201245 elif platform .system () == "Windows" :
202-
246+
203247 # Get the directory of the current Python executable
248+
204249 python_dir = os .path .dirname (sys .executable )
205250
206251 # Construct the path to PythonSlicer.exe in the same directory
207- python_path = os .path .join (python_dir , "PythonSlicer.exe" )
252+
253+ python_path = shlex .quote (os .path .join (python_dir , "PythonSlicer.exe" ))
208254
209255 current_dir = os .path .dirname (os .path .realpath (__file__ ))
210- python_script_path = os .path .join (current_dir ,'Resources' , 'resolver.py' )
256+ python_script_path = shlex .quote (
257+ os .path .join (current_dir , "Resources" , "resolver.py" )
258+ )
211259
212260 # Register IDC Browser URL protocol in Windows Registry
261+
213262 import winreg as reg
214263
215264 try :
216265 reg .CreateKey (reg .HKEY_CURRENT_USER , r"Software\Classes\idcbrowser" )
217- with reg .OpenKey (reg .HKEY_CURRENT_USER , r"Software\Classes\idcbrowser" , 0 , reg .KEY_WRITE ) as key :
266+ with reg .OpenKey (
267+ reg .HKEY_CURRENT_USER ,
268+ r"Software\Classes\idcbrowser" ,
269+ 0 ,
270+ reg .KEY_WRITE ,
271+ ) as key :
218272 reg .SetValue (key , None , reg .REG_SZ , "URL:IDC Browser Protocol" )
219273 reg .SetValueEx (key , "URL Protocol" , 0 , reg .REG_SZ , "" )
220-
221- reg .CreateKey (reg .HKEY_CURRENT_USER , r"Software\Classes\idcbrowser\shell\open\command" )
222- with reg .OpenKey (reg .HKEY_CURRENT_USER , r"Software\Classes\idcbrowser\shell\open\command" , 0 , reg .KEY_WRITE ) as key :
223- reg .SetValue (key , None , reg .REG_SZ , f'"{ python_path } " "{ python_script_path } " "%1"' )
224-
274+ reg .CreateKey (
275+ reg .HKEY_CURRENT_USER ,
276+ r"Software\Classes\idcbrowser\shell\open\command" ,
277+ )
278+ with reg .OpenKey (
279+ reg .HKEY_CURRENT_USER ,
280+ r"Software\Classes\idcbrowser\shell\open\command" ,
281+ 0 ,
282+ reg .KEY_WRITE ,
283+ ) as key :
284+ reg .SetValue (
285+ key ,
286+ None ,
287+ reg .REG_SZ ,
288+ f'"{ python_path } " "{ python_script_path } " "%1"' ,
289+ )
225290 print ("IDC Browser URL protocol has been registered on Windows." )
226291 except Exception as e :
227- print (f"Failed to register IDC Browser URL protocol on Windows: { e } " )
228-
229- elif platform .system () == "Darwin" :
292+ print (f"Failed to register IDC Browser URL protocol on Windows: { e } " )
293+ elif platform .system () == "Darwin" :
230294 slicer_exec_dir = os .path .dirname (sys .executable )
231295 parent_dir = os .path .dirname (slicer_exec_dir )
232-
296+
233297 # Now, you can construct the path to PythonSlicer
298+
234299 python_path = shlex .quote (os .path .join (parent_dir , "bin" , "PythonSlicer" ))
235300
236301 current_dir = os .path .dirname (os .path .realpath (__file__ ))
237- python_script_path = shlex .quote (os .path .join (current_dir ,'Resources' , 'resolver.py' ))
302+ python_script_path = shlex .quote (
303+ os .path .join (current_dir , "Resources" , "resolver.py" )
304+ )
238305
239306 def check_macos_slicer_protocol_registration ():
240- plist_path = os .path .expanduser ("/Applications/slicer-app.app/Contents/Info.plist" )
307+ plist_path = os .path .expanduser (
308+ "/Applications/slicer-app.app/Contents/Info.plist"
309+ )
241310 return os .path .exists (plist_path )
242311
243312 if check_macos_slicer_protocol_registration ():
244313 print ("Slicer URL protocol is already registered." )
245314 return
246-
247315 # Create the AppleScript
316+
248317 applescript_path = os .path .expanduser ("~/slicer.applescript" )
249318 with open (applescript_path , "w" ) as applescript_file :
250- applescript_file .write (f"""
319+ applescript_file .write (
320+ f"""
251321 on open location this_URL
252322 do shell script "{ python_path } { python_script_path } " & quoted form of this_URL
253323 end open location
254- """ )
255-
256-
324+ """
325+ )
257326 # Compile the AppleScript into an app
258- os .system (f"osacompile -o /Applications/slicer-app.app { applescript_path } " )
259327
328+ os .system (f"osacompile -o /Applications/slicer-app.app { applescript_path } " )
260329
261330 # Create or modify the plist file
331+
262332 plist_content = """
263333 <?xml version="1.0" encoding="UTF-8"?>
264334 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -328,11 +398,13 @@ def check_macos_slicer_protocol_registration():
328398 </plist>
329399 """
330400
331- plist_path = os .path .expanduser ("/Applications/slicer-app.app/Contents/Info.plist" )
401+ plist_path = os .path .expanduser (
402+ "/Applications/slicer-app.app/Contents/Info.plist"
403+ )
332404 with open (plist_path , "w" ) as plist_file :
333405 plist_file .write (plist_content )
334-
335406 print ("Slicer URL protocol registered successfully." )
336-
337407 else :
338- print ("IDC Browser URL protocol registration is not supported on this operating system." )
408+ print (
409+ "IDC Browser URL protocol registration is not supported on this operating system."
410+ )
0 commit comments