Skip to content

Commit b30252b

Browse files
committed
enh: use PythonSlicer instead of users python in ubuntu as well
use shell quote to be resilient to spaces in paths add acknowledgement text reformat code using black code formatter
1 parent c24b196 commit b30252b

File tree

1 file changed

+126
-54
lines changed

1 file changed

+126
-54
lines changed

IDCBrowser/IDCHandlerModule.py

Lines changed: 126 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
from slicer.ScriptedLoadableModule import *
55
from WebServer import WebServerLogic
66
from typing import Optional
7-
from WebServerLib.BaseRequestHandler import BaseRequestHandler, BaseRequestLoggingFunction
7+
from WebServerLib.BaseRequestHandler import (
8+
BaseRequestHandler,
9+
BaseRequestLoggingFunction,
10+
)
811

912
import os
1013
import platform
@@ -20,6 +23,7 @@
2023
import sys
2124
import shlex
2225

26+
2327
class 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
134160
import urllib.parse
135161
import requests
136162
import 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]
190234
Name=IDC Browser
191-
Exec=python3 {python_script_path} %u
235+
Exec={python_path} {python_script_path} %u
192236
Type=Application
193237
Terminal=false
194238
MimeType=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

Comments
 (0)