-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLaunch_ImGUI.py
More file actions
485 lines (396 loc) · 19.8 KB
/
Launch_ImGUI.py
File metadata and controls
485 lines (396 loc) · 19.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
import sys
import os
import qdarkstyle
from PyQt5.QtGui import QIcon, QSurfaceFormat
from PyQt5.QtWidgets import QApplication, QMainWindow, QToolBar
from PyQt5 import QtWidgets
from ImGUI import Ui_AMIGOpy # Assuming this is the name of your main window class in ImGUI.py
from fcn_load.sort_dcm import get_data_description
from fcn_load.org_fol_dcm import organize_files_into_folders
from fcn_breathing_curves.functions_plot import init_BrCv_plot, plotViewData_BrCv_plot
from fcn_breathing_curves.functions_edit import initXRange, init_BrCv_edit, plotViewData_BrCv_edit
from fcn_breathing_curves.functions_phantom_operation import set_fcn_MoVeTab_changed
from fcn_display.mouse_move_slicechanges import change_sliceAxial, change_sliceSagittal, change_sliceCoronal
from fcn_display.Data_tree_general import on_DataTreeView_clicked
from fcn_init.create_menu import initializeMenuBar
from fcn_init.vtk_comp import setup_vtk_comp
from fcn_init.vtk_comp_seg import setup_vtk_seg
from fcn_init.transp_slider_spin_set import set_transp_slider_fcn
from fcn_init.set_menu_bar_icons import menu_bar_icon_actions
from fcn_init.vtk_IrIS_eval_axes import setup_vtk_IrISEval
from fcn_display.display_images import update_layer_view
from fcn_display.display_images_seg import update_seg_slider, disp_seg_image_slice
from fcn_init.ModulesTab_change import set_fcn_tabModules_changed
from fcn_init.IrIS_cal_init import init_cal_markers_IrIS
from fcn_init.init_variables import initialize_software_variables
from fcn_init.init_tables import initialize_software_tables
from fcn_init.init_buttons import initialize_software_buttons
from fcn_init.init_load_files import load_Source_cal_csv_file
from fcn_init.init_list_menus import populate_list_menus
from fcn_init.init_drop_options import initialize_drop_fcn
from fcn_load.load_dcm import load_all_dcm
from fcn_segmentation.functions_segmentation import plot_hist
from fcn_init.init_vtk_3D_display import init_vtk3d_widget
import vtk
from PyQt5.QtCore import QEvent, Qt, QTimer, pyqtSignal, QObject
from fcn_3Dview.volume_3d_viewer import VTK3DViewerMixin
from fcn_3Dview.structures_3D_table import init_3D_Struct_table
from fcn_init.init_tool_tip import set_tooltip
from fcn_3Dview.surfaces_3D_table import init_STL_Surface_table
from fcn_3Dview.protons_3D_plan import init_3D_proton_table
from fcn_init.init_data_tree import set_context_menu
# from fcn_init.init_reg_elements import init_reg_elements
# ── constants in module / class scope ─────────────────────────────────────────
_ORIG_POS = { # Designer coordinates
"axial": {"pane": (0, 0), "slider": (1, 0)},
"coronal": {"pane": (0, 1), "slider": (1, 1)},
"sagittal": {"pane": (2, 0), "slider": (3, 0)},
}
_VIEW_ATTRS = {
"axial": ("VTK_view_01", "vtkWidgetAxial", "AxialSlider"),
"coronal": ("VTK_view_03", "vtkWidgetCoronal", "CoronalSlider"),
"sagittal": ("VTK_view_02", "vtkWidgetSagittal", "SagittalSlider"),
}
# ──────────────────────────────────────────────────────────────────────────────
class MyApp(QMainWindow, Ui_AMIGOpy, VTK3DViewerMixin): # or QWidget/Ui_Form, QDialog/Ui_Dialog, etc.
# emmit signal when the slice changes
# This signal can be connected to other functions to update the display when the slice changes.
sliceChanged = pyqtSignal(str, list)
def __init__(self,folder_path=None):
super(MyApp, self).__init__()
# Set up the user interface from Designer.
self.setupUi(self)
self.setWindowTitle("AMIGOpy")
#
#
# populate the list menus
populate_list_menus(self)
# initialize variables
initialize_software_variables(self)
# initialize tables
initialize_software_tables(self)
# initialize buttons
initialize_software_buttons(self)
# initialize drop functions
# Enable drag and drop
initialize_drop_fcn(self)
# load ref csv files
load_Source_cal_csv_file(self)
#
init_3D_Struct_table(self)
init_STL_Surface_table(self)
init_3D_proton_table(self)
#
self.LeftButtonSagittalDown = False
self.LeftButtonCoronalDown = False
self.LeftButtonRuler = False
# self.LeftButtonSegDown = False
#
self.layerTab = {}
self.transTab = {}
#
self.layerTab['View'] = 0
self.transTab['View'] = [1,0,0,0]
self.layerTab['_3Dview'] = 0
self.transTab['_3Dview'] = [1,0,0,0]
self.layerTab['Compare'] = 0
self.transTab['Compare'] = [1,0,0,0]
self.layerTab['IrIS'] = 0
self.transTab['IrIS'] = [1,0,0,0]
self.layerTab['DECT'] = 0
self.transTab['DECT'] = [1,0,0,0]
self.layerTab['Plan'] = 0
self.transTab['Plan'] = [1,0,0,0]
self.layerTab['CSV Files'] = 0
self.transTab['CSV Files'] = [1,0,0,0]
self.layerTab['Breathing curves'] = 0
self.transTab['Breathing curves'] = [1,0,0,0]
self.layerTab['Segmentation'] = 0
self.transTab['Segmentation'] = [1,0.99,0.99,0]
#
# This section initialize variables related to images dimentions, currentl displaying set
# slice index ... It is important so different element of the GUI can have access to them
#
self.medical_image = None # Initialize the attribute to store DICOM data
self.IrIS_data = None # Initialize the attribute to store IrIS data
self.STL_data = None # Initialize the attribute to store STL data
self.IrIS_corr = {} # Initialize the attribute to store IrIS correction data
self.current_slice_index = [-1,-1,-1] # axial, sagital and coronal slices
#
# information about dwell positions and dwell times
self.IrIS_Eval = {}
#
#
self.BrCvTab_index = 0
self.tabWidget_BrCv.currentChanged.connect(lambda: init_BrCv_plot(self))
self.plotXAxis_BrCv.currentTextChanged.connect(lambda: plotViewData_BrCv_plot(self))
self.tabWidget_BrCv.currentChanged.connect(lambda: init_BrCv_edit(self))
self.editXMinSlider_BrCv.valueChanged.connect(lambda: plotViewData_BrCv_edit(self))
self.editXMaxSlider_BrCv.valueChanged.connect(lambda: plotViewData_BrCv_edit(self))
self.editXAxis_BrCv.currentTextChanged.connect(lambda: initXRange(self))
self.DuetIPAddress.setText("192.168.0.1")
set_fcn_MoVeTab_changed(self)
#
self.LeftButtonAxialDown = False
self.LeftButtonSagittalDown = False
# self.LeftButtonSegDown = False
#
# This section conects GUI elemtns with functions
# slider to adjust the images
self.AxialSlider.valueChanged.connect(self.on_axialslider_change)
self.SagittalSlider.valueChanged.connect(self.on_sagittalslider_change)
self.CoronalSlider.valueChanged.connect(self.on_coronalslider_change)
# # Initialize VTK components
setup_vtk_comp(self)
setup_vtk_IrISEval(self)
setup_vtk_seg(self)
# Calibration module IrIS
init_cal_markers_IrIS(self)
self.threshMinHU.setText("-200")
self.threshMaxHU.setText("200")
self.threshMinHU.textChanged.connect(lambda: plot_hist(self))
self.threshMaxHU.textChanged.connect(lambda: plot_hist(self))
self.segSelectView.currentTextChanged.connect(lambda: update_seg_slider(self))
self.segViewSlider.valueChanged.connect(lambda: disp_seg_image_slice(self))
self.segBrushButton.setIcon(QIcon("./icons/brush.png"))
self.segEraseButton.setIcon(QIcon("./icons/eraser.png") )
self.undoSeg.setIcon(QIcon("./icons/undo.png"))
#
# VTK Comparison module
self.vtkWidgetsComp = []
self.renAxComp = []
self.dataImporterAxComp = {}
self.windowLevelAxComp = {}
self.imageActorAxComp = {}
#
self.DataTreeView.clicked.connect(lambda index: on_DataTreeView_clicked(self, index))
#
vtk.vtkObject.GlobalWarningDisplayOff()
set_transp_slider_fcn(self)
#
# # Initialize the 3D viewer
self.VTK3D_widget, self.VTK3D_renderer, self.VTK3D_interactor = \
init_vtk3d_widget(self, self.VTK_view_3D)
self.init_3d_viewer()
set_fcn_tabModules_changed(self)
# state flag: which axis is currently maximised (None → original layout)
self._max_axis = None
self._cycle_order = ["axial", "sagittal", "coronal"]
for axis, (_, vtk_name, _) in _VIEW_ATTRS.items():
vtkw = getattr(self, vtk_name)
vtkw._axis_name = axis
vtkw.installEventFilter(self)
# Install filter on the parent container for “show all”
self.im_display_tab.installEventFilter(self)
#
# 3D view:
self.VTK_view_3D.installEventFilter(self)
self.vtk3dWidget.installEventFilter(self)
self._vtk3d_is_maximized = False
#
initializeMenuBar(self)
self.DataType = "None"
# Create a toolbar
self.toolbar = QToolBar("My main toolbar")
self.addToolBar(self.toolbar)
# Set the path relative to the executable's location
base_path = os.path.dirname(os.path.abspath(__file__)) # Location of the script or the executable
menu_bar_icon_actions(self,base_path)
#
# Set the progress bar value to 0
self.progressBar.setValue(0)
# set tooltip
set_tooltip(self)
# set data tree context menu
set_context_menu(self)
#
# init_reg_elements(self)
def organize_dcm_folder(self):
self.label.setText("Reading folders")
detailed_files_info, unique_files_info, outputfolder = get_data_description(folder_path=None, progress_callback=self.progressBar.setValue,update_label=self.label,sort_folder=1)
total_steps = len(detailed_files_info)
self.label.setText(f"Copying {total_steps} files")
organize_files_into_folders(outputfolder, detailed_files_info,progress_callback=self.progressBar.setValue,update_label=self.label)
# Slot for the 'About' action
def on_about_click(self):
# Your logic for displaying info about the application goes here.
pass
def left_button_presssagittal_event(self, obj, event):
self.LeftButtonSagittalDown = True
def left_button_releasesagittal_event(self, obj, event):
self.LeftButtonSagittalDown = False
def left_button_presscoronal_event(self, obj, event):
self.LeftButtonCoronalDown = True
def left_button_releasecoronal_event(self, obj, event):
self.LeftButtonCoronalDown = False
def on_axialslider_change(self):
idx = self.layer_selection_box.currentIndex()
# Set the slice index based on the slider value.
self.current_axial_slice_index[idx] = self.AxialSlider.value()
change_sliceAxial(self,0)
def on_sagittalslider_change(self):
idx = self.layer_selection_box.currentIndex()
# Set the slice index based on the slider value.
self.current_sagittal_slice_index[idx] = self.SagittalSlider.value()
change_sliceSagittal(self,0)
def on_coronalslider_change(self):
idx = self.layer_selection_box.currentIndex()
# Set the slice index based on the slider value.
self.current_coronal_slice_index[idx] = self.CoronalSlider.value()
change_sliceCoronal(self,0)
def on_scroll_forwardAxial(self, obj, ev):
change_sliceAxial(self,1)
def on_scroll_backwardAxial(self, obj, ev):
change_sliceAxial(self,-1)
def on_scroll_forwardSagittal(self, obj, ev):
change_sliceSagittal(self,1)
def on_scroll_backwardSagittal(self, obj, ev):
change_sliceSagittal(self,-1)
def on_scroll_forwardCoronal(self, obj, ev):
change_sliceCoronal(self,1)
def on_scroll_backwardCoronal(self, obj, ev):
change_sliceCoronal(self,-1)
def update_progress(self, progress):
self.progressBar.setValue(int(progress))
def on_seg_hu_min_change(self):
min_ = self.segThreshMinHU.value()
if min_ >= self.segThreshMaxHU.value():
self.segThreshMinHU.setValue(self.segThreshMaxHU.value()-1)
plot_hist(self)
def on_seg_hu_max_change(self):
max_ = self.segThreshMaxHU.value()
if max_ <= self.segThreshMinHU.value():
self.segThreshMaxHU.setValue(self.segThreshMinHU.value()+1)
plot_hist(self)
def _get_next_axis(self):
if self._max_axis is None:
return "axial"
idx = self._cycle_order.index(self._max_axis)
return self._cycle_order[(idx + 1) % len(self._cycle_order)]
def eventFilter(self, watched, event):
if event.type() == QEvent.MouseButtonDblClick and event.button() == Qt.LeftButton:
if watched is self.im_display_tab:
self.set_view_mode("all")
return True
if watched is self.VTK_view_3D or watched is self.vtk3dWidget:
parent = self.VTK_view_3D.parentWidget()
current_tab = self.tabModules.tabText(self.tabModules.currentIndex())
if current_tab == "_3Dview":
layout = parent.layout()
if not self._vtk3d_is_maximized:
# Store original grid layout position
row, col, rowSpan, colSpan = _find_widget_in_gridlayout(layout, self.VTK_view_3D)
self._vtk3d_orig_grid = (row, col, rowSpan, colSpan)
self._vtk3d_orig_parent = parent
self._vtk3d_orig_geometry = self.VTK_view_3D.geometry()
# Maximize widget to fill parent
self.VTK_view_3D.setParent(parent)
self.VTK_view_3D.raise_()
self.VTK_view_3D.setGeometry(parent.rect())
self.VTK_view_3D.show()
self._vtk3d_is_maximized = True
else:
# Restore to original grid position and span
row, col, rowSpan, colSpan = self._vtk3d_orig_grid
layout.addWidget(self.VTK_view_3D, row, col, rowSpan, colSpan)
self.VTK_view_3D.setParent(parent)
self.VTK_view_3D.setMinimumSize(0, 0) # Reset min size
self.VTK_view_3D.updateGeometry()
self._vtk3d_is_maximized = False
return True
# Double click on any axis render widget
if hasattr(watched, "_axis_name"):
axis = watched._axis_name
if self._max_axis != axis:
# If not maximized, maximize this one
self.set_view_mode(axis)
else:
# If maximized, go to the next in cycle
next_axis = self._get_next_axis()
self.set_view_mode(next_axis)
return True
return super().eventFilter(watched, event)
def set_view_mode(self, mode: str = "all"):
"""
mode = "all" | "axial" | "coronal" | "sagittal"
"""
if mode not in {"all", "axial", "coronal", "sagittal"}:
print(f"[set_view_mode] unknown key {mode!r}")
return
gl = self.gridLayout_4
# ------ FIX: unpack 3-tuple, keep pane & slider -------------------------
views = {
k: (
getattr(self, pane_name), # placeholder QWidget
getattr(self, slider_name) # its QSlider
)
for k, (pane_name, _, slider_name) in _VIEW_ATTRS.items()
}
# ------------------------------------------------------------------------
# ── 1) hide everything first ────────────────────────────────────────────
for vw, sl in views.values():
vw.hide(); sl.hide()
self.tabView01.hide()
self.label_2.hide()
# ── 2) detach all layout items (keeps grid clean) ──────────────────────
while gl.count():
gl.takeAt(0)
# ── 3) branch on mode ──────────────────────────────────────────────────
if mode == "all":
# put every pane + slider back where Designer had them
for key, (vw, sl) in views.items():
r_pane, c_pane = _ORIG_POS[key]["pane"]
r_slider, c_slider = _ORIG_POS[key]["slider"]
gl.addWidget(vw, r_pane, c_pane, 1, 1)
gl.addWidget(sl, r_slider, c_slider, 1, 1)
vw.show(); sl.show()
# bottom-right extras
gl.addWidget(self.tabView01, 2, 1, 2, 1)
gl.addWidget(self.label_2, 3, 1)
self.tabView01.show(); self.label_2.show()
# original slider row keeps default min-height
gl.setRowMinimumHeight(3, 0)
else:
# maximise one pane
vw, sl = views[mode]
gl.addWidget(vw, 0, 0, 3, 2) # rows 0-2, both columns
gl.addWidget(sl, 3, 0, 1, 2) # slider row, both columns
vw.show(); sl.show()
# guarantee slider stays visible even if window shrinks
gl.setRowMinimumHeight(3, sl.sizeHint().height())
self._max_axis = None if mode == "all" else mode
def _find_widget_in_gridlayout(layout, widget):
for i in range(layout.count()):
item = layout.itemAt(i)
if item and item.widget() is widget:
row, col, rowSpan, colSpan = layout.getItemPosition(i)
return row, col, rowSpan, colSpan
return None, None, 1, 1
if __name__ == "__main__":
import sys
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtGui import QSurfaceFormat
from PyQt5.QtWidgets import QApplication
import qdarkstyle
# Pick ONE of these attributes. DesktopOpenGL first; if you’re on RDP/VM, use SoftwareOpenGL instead.
QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL, True)
# QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL, True) # ← use this line instead if needed
# Force a Compatibility profile (NOT Core), and set reasonable buffers
fmt = QSurfaceFormat()
fmt.setRenderableType(QSurfaceFormat.OpenGL)
fmt.setProfile(QSurfaceFormat.CompatibilityProfile)
# fmt.setVersion(3, 2) # optional; let Qt choose if unsure
fmt.setDepthBufferSize(24)
fmt.setStencilBufferSize(8)
QSurfaceFormat.setDefaultFormat(fmt)
app = QApplication(sys.argv)
folder_path = None
if len(sys.argv) > 1:
folder_path = sys.argv[1] # Capture folder path from the command-line arguments
window = MyApp(folder_path)
app.setStyleSheet(qdarkstyle.load_stylesheet())
window.show()
if folder_path is not None:
print(folder_path)
load_all_dcm(window,folder_path, progress_callback=None, update_label=None)
sys.exit(app.exec_())