class _ToolsDock(ui_tools.StyledBar): def __init__(self, parent=None): super().__init__(parent) # Register signals connections # connections = ( # { # "target": "main_container", # "signal_name": "runFile", # "slot": self.execute_file # }, # ) # Buttons Widget self._buttons_widget = ui_tools.StyledBar() self._buttons_widget.setProperty("border", True) self._buttons_widget.setFixedHeight(26) self._buttons_widget.setLayout(QHBoxLayout()) self._buttons_widget.layout().setContentsMargins(2, 2, 0, 2) self._buttons_widget.layout().setSpacing(10) IDE.register_service('tools_dock', self) def install(self): self.setup_ui() ide = IDE.get_service('ide') ide.place_me_on('tools_dock', self, 'central') ui_tools.install_shortcuts(self, actions.ACTIONS, ide) ide.goingDown.connect(self._save_settings) settings = IDE.ninja_settings() index = int(settings.value("tools_dock/tool_visible", -1)) if index == -1: self.hide() else: self.set_current_index(index) def setup_ui(self): self._stack = QStackedWidget() self.__current_widget = None self.__last_index = -1 self.__buttons = [] main_layout = QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) # toolbar = StyledBar() tool_layout = QVBoxLayout() tool_layout.setContentsMargins(0, 0, 0, 0) tool_layout.setSpacing(0) self._run_widget = run_widget.RunWidget() main_layout.addLayout(tool_layout) self._tool_stack = QStackedWidget() # self._tool_stack.setMaximumHeight(22) self._tool_stack.setMaximumWidth(22) tool_layout.addWidget(self._tool_stack) # FIXME: poner en stack # clear_btn = QToolButton() # clear_btn.setIcon(QIcon(self.style().standardIcon(QStyle.SP_LineEditClearButton))) # clear_btn.clicked.connect(self._run_widget.output.clear) # tool_layout.addWidget(clear_btn) # tool_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding)) # tool_layout.addWidget(close_button) # main_layout.addWidget(toolbar) main_layout.addWidget(self._stack) # Widgets # errors_tree = IDE.get_service('errors_tree') from ninja_ide.gui.tools_dock import find_in_files self.widgets = [ self._run_widget, console_widget.ConsoleWidget(), find_in_files.FindInFilesWidget(), # errors_tree ] # Intall widgets number = 1 for wi in self.widgets: if wi is None: continue btn = ToolButton(number, wi.display_name()) btn.setCheckable(True) # Action # action = QAction(wi.display_name(), self) # action.triggered.connect(self._triggered) number += 1 self.__buttons.append(btn) self._buttons_widget.layout().addWidget(btn) self._stack.addWidget(wi) btn.clicked.connect(self._button_triggered) # Toolbar buttons container = ui_tools.StyledBar(self._tool_stack) container.setProperty('border', True) tool_buttons_layout = QVBoxLayout() tool_buttons_layout.setContentsMargins(0, 0, 0, 0) tool_buttons_layout.setSpacing(0) for b in wi.button_widgets(): tool_buttons_layout.addWidget(b) tool_buttons_layout.addStretch(5) container.setLayout(tool_buttons_layout) self._tool_stack.addWidget(container) self._buttons_widget.layout().addItem( QSpacerItem(0, 0, QSizePolicy.Expanding)) self.__current_widget = self._stack.currentWidget() self.set_current_index(0) def execute_file(self): """Execute the current file""" main_container = IDE.get_service("main_container") editor_widget = main_container.get_current_editor() if editor_widget is not None and editor_widget.is_modified or \ editor_widget.file_path: main_container.save_file(editor_widget) # FIXME: Emit a signal for plugin! # self.fileExecuted.emit(editor_widget.file_path) file_path = editor_widget.file_path extension = file_manager.get_file_extension(file_path) # TODO: Remove the IF statment and use Handlers if extension == "py": self._run_application(file_path) def _run_application(self, filename): """Execute the process to run the application""" self._show(0) # Show widget in index = 0 self._run_widget.start_process(filename) self._run_widget.input.setFocus() @pyqtSlot() def _button_triggered(self): # Get ToolButton index button = self.sender() index = self.__buttons.index(button) if index == self.current_index() and self._is_current_visible(): self._hide() else: self._show(index) def _show(self, index): self.show() self.widgets[index].setVisible(True) self.__current_widget = self.widgets[index] self.set_current_index(index) def _hide(self): self.__current_widget.setVisible(False) index = self.current_index() self.__buttons[index].setChecked(False) self.widgets[index].setVisible(False) self.hide() def showEvent(self, event): super().showEvent(event) self._stack.currentWidget().setFocus() def set_current_index(self, index): if self.__last_index != -1: self.__buttons[self.__last_index].setChecked(False) self.__buttons[index].setChecked(True) if index != -1: self._stack.setCurrentIndex(index) self._tool_stack.setCurrentIndex(index) tool = self.widgets[index] tool.setVisible(True) self.__last_index = index def current_index(self): return self._stack.currentIndex() def _is_current_visible(self): return self.__current_widget and self.__current_widget.isVisible() def _save_settings(self): settings = IDE.ninja_settings() visible_index = self.current_index() if not self.isVisible(): visible_index = -1 settings.setValue("tools_dock/tool_visible", visible_index)
class MainWindow(QMainWindow): errorSignal = pyqtSignal(str) #emitted when errors occur. error box with message msg is opened by main thread countingDoneSignal = pyqtSignal() triggeringDoneSignal = pyqtSignal() backToPreviewSignal = pyqtSignal() def __init__(self): super(MainWindow, self).__init__() self.errorSignal .connect(self.openErrorMessage) self.countingDoneSignal .connect(self.countingDone) self.triggeringDoneSignal.connect(self.triggeringDone) self.backToPreviewSignal .connect(self.backToPreview) self.cellsQueue = Queue.Queue() # queue to pass cell coordinates found by counting algorithm self.imageQueue = Queue.Queue() # Queue to hold images self.mode = None # "Color" or "UV" util.loadSettings() self.hardwareHandler = HardwareHandler(self.imageQueue) ##### shortcuts #### self.quitSC = QShortcut(QKeySequence('Ctrl+Q'), self) self.quitSC.activated.connect(QApplication.instance().quit) self.exitFS = QShortcut("ESC", self) self.exitFS.activated.connect(self.showMaximized) def switchMaxFS(): if self.isFullScreen(): self.showNormal() #workaround... if showNormal is not called in between, showMaxize does not work... self.showMaximized() else: self.showFullScreen() self.switchMaxFS = QShortcut(QKeySequence('Ctrl+F'), self) self.switchMaxFS.activated.connect(switchMaxFS) ############# layout ############# self.centralWidget = QWidget(self) self.hlayout = QHBoxLayout() ##### image view ##### self.imageWidget = ImageWidget(self.imageQueue, self) self.hlayout.addWidget(self.imageWidget) ##### control widget ##### self.controlWidget = QStackedWidget() self.controlWidget.setMaximumWidth(320) ## page 1 - main page## self.page1Widget = QWidget(self.controlWidget) self.page1Layout = QVBoxLayout() self.page1Widget.setLayout(self.page1Layout) buttonTrigger = QPushButton("&Trigger") buttonSettings = QPushButton("&Settings") buttonTriggerAndSave = QPushButton("Trigger + Save") self.buttonMode = QPushButton("Switch to ...") buttonTrigger .clicked.connect(self.trigger) buttonSettings .clicked.connect(lambda: self.controlWidget.setCurrentIndex(2)) buttonSettings .clicked.connect(lambda: self.infoTextBox.setText("")) buttonTriggerAndSave.clicked.connect(self.triggerAndSave) self.buttonMode .clicked.connect(lambda: self.changeMode()) self.page1Layout.addWidget(buttonTrigger) self.page1Layout.addWidget(self.buttonMode) self.page1Layout.addWidget(buttonSettings) self.page1Layout.addWidget(buttonTriggerAndSave) ## page 2 - image captured## self.page2Widget = QWidget(self.controlWidget) self.page2Layout = QVBoxLayout() self.page2Widget.setLayout(self.page2Layout) buttonBackToPreview = QPushButton("&Back") buttonSaveImage = QPushButton("&Save") buttonCount = QPushButton("&Count") buttonBackToPreview.clicked.connect(self.backToPreview) buttonSaveImage .clicked.connect(lambda: self.saveImage()) buttonCount .clicked.connect(self.startCounting) self.page2Layout.addWidget(buttonBackToPreview) self.page2Layout.addWidget(buttonSaveImage) self.page2Layout.addWidget(buttonCount) ## page 3 - settings ## self.settingsWidget = SettingsWidget(self.controlWidget) self.settingsWidget.OKButton.clicked.connect(lambda: self.controlWidget.setCurrentIndex(0)) self.settingsWidget.OKButton.clicked.connect(lambda: self.infoTextBox.setText("Live capturing")) # signals emitted when settings change self.settingsWidget.UVLEDSettingsUpdatedSignal .connect(self.hardwareHandler.updateLEDUV) self.settingsWidget.ColorLEDSettingsUpdatedSignal .connect(self.hardwareHandler.updateLEDColors) self.settingsWidget.captureSettingsUpdatedSignal .connect(lambda : self.hardwareHandler.updateCaptureSettings(mode = self.mode)) self.settingsWidget.resetSignal .connect(self.hardwareHandler.updateLEDUV) self.settingsWidget.resetSignal .connect(self.hardwareHandler.updateLEDColors) self.settingsWidget.resetSignal .connect(lambda : self.hardwareHandler.updateCaptureSettings(mode = self.mode)) #set mode if tab is changed in settings widget def setModeFromTabIndex(tabIndex: int): if tabIndex == 0: self.changeMode("Color") elif tabIndex == 1: self.changeMode("UV") self.settingsWidget.tabs.currentChanged.connect(setModeFromTabIndex) ## page 4 - counting ## self.page4Widget = QWidget(self.controlWidget) self.page4Layout = QVBoxLayout(self.page4Widget) # buttonStopCounting = QPushButton("&Stop Counting") # buttonStopCounting.clicked.connect(self.stopCounting) # self.page4Layout.addWidget(buttonStopCounting) countingLabel = QLabel("Counting..", alignment = Qt.AlignCenter) self.page4Layout.addWidget(countingLabel) ## page 5 - trigger and save ## self.page5Widget = QWidget(self.controlWidget) self.page5Layout = QVBoxLayout(self.page5Widget) self.triggerAndSaveLabel = QLabel("Capture color Image\t\n" "Save color Image\t\t\n" "Capture UV Image\t\n" "Save UV Image\t\t", alignment = Qt.AlignVCenter | Qt.AlignLeft) self.page5Layout.addWidget(self.triggerAndSaveLabel) self.controlWidget.addWidget(self.page1Widget) self.controlWidget.addWidget(self.page2Widget) self.controlWidget.addWidget(self.settingsWidget) self.controlWidget.addWidget(self.page4Widget) self.controlWidget.addWidget(self.page5Widget) self.hlayout.addWidget(self.controlWidget) self.hlayout.setContentsMargins(0,0,0,0) self.centralWidget.setLayout(self.hlayout) self.setCentralWidget(self.centralWidget) ## info in right bottom corner self.infoTextBox = QLabel(self.centralWidget) self.infoTextBox.setText("TEST") self.infoTextBox.setAlignment(Qt.AlignRight | Qt.AlignBottom) self.infoTextBox.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) # pylint: disable=no-member self.installEventFilter( util.ObjectResizer(self, self.infoTextBox)) logger.info("Gui started") # start capture and led self.changeMode("Color") self.imageWidget.startShowLive() self.infoTextBox.setText("Live capturing") # def stopCapturing(self): # self.imageWidget.stopShowLive() # self.hardwareHandler.stopCapturing() # self.capture_thread.join() # self.imageQueue.queue.clear() def changeMode(self, mode = None): if mode is None: if self.mode == "UV" : mode = "Color" elif self.mode == "Color": mode = "UV" if mode != self.mode: logger.info(f"Changing mode to '{mode}'.") self.mode = mode if self.mode == "UV": #update button text self.buttonMode.setText("Switch to Color") #update settings tab. block signals to acoid infinite cyclic signal calls self.settingsWidget.tabs.blockSignals(True) self.settingsWidget.tabs.setCurrentIndex(1) self.settingsWidget.tabs.blockSignals(False) #set leds self.hardwareHandler.switchCOLOR_LED(False) self.hardwareHandler.switchUV_LED (True) elif self.mode == "Color": #update button text self.buttonMode.setText("Switch to UV") #update settings tab. block signals to acoid infinite cyclic signal calls self.settingsWidget.tabs.blockSignals(True) self.settingsWidget.tabs.setCurrentIndex(0) self.settingsWidget.tabs.blockSignals(False) #set leds self.hardwareHandler.switchCOLOR_LED(True) self.hardwareHandler.switchUV_LED (False) self.hardwareHandler.updateCaptureSettings(mode = self.mode) ### button events ### def trigger(self): self.page1Widget.setEnabled(False) logger.info("Fetching image") self.infoTextBox.setText("Fetching image") self.imageWidget.stopShowLive() self.hardwareHandler.stopCapturing() def run(): fullImage = self.hardwareHandler.shootImage_fullResolution(mode = self.mode) self.imageWidget.shwoFullImage(fullImage) self.triggeringDoneSignal.emit() thread = threading.Thread(target = run) thread.start() def triggeringDone(self): self.controlWidget.setCurrentIndex(1) self.page1Widget.setEnabled(True) self.infoTextBox.setText("Ready") def backToPreview(self): self.infoTextBox.setText("Live capturing") self.controlWidget.setCurrentIndex(0) self.imageWidget.annotatedImage = None self.hardwareHandler.startCapturing(mode = self.mode) self.imageWidget.startShowLive() def triggerAndSave(self): def run(): #stop captureing self.imageWidget.stopShowLive() self.hardwareHandler.stopCapturing() #set gui info self.controlWidget.setCurrentIndex(4) self.triggerAndSaveLabel.setText("Capture color Image\t\n" "Save color Image\t\t\n" "Capture UV Image\t\n" "Save UV Image\t\t") # use timestamp for file names timeStamp = datetime.now().strftime("%d_%m_%Y_%H_%M_%S") ########## color image ########## # set color leds self.hardwareHandler.switchCOLOR_LED(True) self.hardwareHandler.switchUV_LED (False) #capture image fullImage = self.hardwareHandler.shootImage_fullResolution(mode = "Color") #show image self.imageWidget.shwoFullImage(fullImage) self.triggerAndSaveLabel.setText("Capture color Image\t-> done\n" "Save color Image\t\t\n" "Capture UV Image\t\n" "Save UV Image\t\t") self.saveImage(fileName=f"{timeStamp}_color") self.triggerAndSaveLabel.setText("Capture color Image\t-> done\n" "Save color Image\t\t-> done\n" "Capture UV Image\t\n" "Save UV Image\t\t") ########## UV image ########## # set UV leds self.hardwareHandler.switchCOLOR_LED(False) self.hardwareHandler.switchUV_LED (True) #capture image fullImage = self.hardwareHandler.shootImage_fullResolution(mode = "UV") #show image self.imageWidget.shwoFullImage(fullImage) self.triggerAndSaveLabel.setText("Capture color Image\t-> done\n" "Save color Image\t\t-> done\n" "Capture UV Image\t-> done\n" "Save UV Image\t\t") self.saveImage(fileName=f"{timeStamp}_UV") self.triggerAndSaveLabel.setText("Capture color Image\t-> done\n" "Save color Image\t\t-> done\n" "Capture UV Image\t-> done\n" "Save UV Image\t\t") self.backToPreviewSignal.emit() thread = threading.Thread(target = run) thread.start() def startCounting(self): logger.info("Counting...") self.infoTextBox.setText("Counting...") self.countingThread = threading.Thread(target = self.count) self.countingThread.start() self.controlWidget.setCurrentIndex(3) # def stopCounting(self): # logger.info("Counting stopped") # self.infoTextBox.setText("Counting stopped") # self.controlWidget.setCurrentIndex(1) def countingDone(self): logger.info("Counting done") self.infoTextBox.setText("Counting done") cells = self.cellsQueue.get() logger.info(f"{len(cells)} cells found") self.imageWidget.markCells(cells) self.controlWidget.setCurrentIndex(1) def count(self): cells = getCells(self.imageWidget.fullImage) self.cellsQueue.put(cells) self.countingDoneSignal.emit() @pyqtSlot(str) def openErrorMessage(self, msg): logger.fatal(msg) msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Critical) msgBox.setText(msg) msgBox.setWindowTitle("Error") msgBox.exec_() def saveImage(self, fileName = None): """save image to usb file. default file name is timestamp :param str fileName: ending .tiff is added automatically""" self.infoTextBox.setText("Saving image") self.page2Widget.setEnabled(False) try: usbPath = util.getUsbDevicePath() except IndexError: self.page2Widget.setEnabled(True) self.infoTextBox.setText("Saving failed") self.errorSignal.emit("No USB device found - file was not saved") return if fileName is None: fileName = datetime.now().strftime("%d_%m_%Y_%H_%M_%S") cv2.imwrite(os.path.join(usbPath, fileName + ".tiff"), cv2.cvtColor(self.imageWidget.fullImage , cv2.COLOR_RGB2BGR)) if isinstance(self.imageWidget.annotatedImage, np.ndarray): cv2.imwrite(os.path.join(usbPath, fileName + "_annotated.tiff"), cv2.cvtColor(self.imageWidget.annotatedImage, cv2.COLOR_RGB2BGR)) logger.info("Saved File to " + usbPath) self.page2Widget.setEnabled(True) self.infoTextBox.setText("Image saved")
class DiameterTab(QDialog): def __init__(self, ctx, par, *args, **kwargs): super(DiameterTab, self).__init__(*args, **kwargs) self.baseon_items = [ 'Effective Diameter (Deff)', 'Water Equivalent Diameter (Dw)' ] self.src_method_items = { 'Get from Image': ['Auto', 'Auto (Z-axis)', 'Manual'], 'Input Manually': ['Manual'], } self.ctx = ctx self.par = par self.deff_auto_method = 'area' self.deff_manual_method = 'deff' self.d_3d_method = 'slice step' self.is_truncated = False self.is_largest_only = False self.is_no_roi = False self.is_no_table = False self.is_corr = [False, False] self.all_slices = False self.minimum_area = 500 self.threshold = -300 self.bone_limit = 250 self.stissue_limit = -250 self.initVar() self.initModel() self.initData() self.initUI() self.sigConnect() self.on_set_opts_panel() def initUI(self): self.figure = PlotDialog() self._menu_ui() self._opts_ui() self._set_layout() def initVar(self): self.lineAP = self.lineLAT = 0 self.idxs = [] self.d_vals = [] self.show_graph = False def initModel(self): self.query_model = QSqlQueryModel() self.age_model = QSqlTableModel(db=self.ctx.database.deff_db) self.head_ap_model = QSqlTableModel(db=self.ctx.database.deff_db) self.head_lat_model = QSqlTableModel(db=self.ctx.database.deff_db) self.head_latap_model = QSqlTableModel(db=self.ctx.database.deff_db) self.thorax_ap_model = QSqlTableModel(db=self.ctx.database.deff_db) self.thorax_lat_model = QSqlTableModel(db=self.ctx.database.deff_db) self.thorax_latap_model = QSqlTableModel(db=self.ctx.database.deff_db) self.age_model.setTable("Age") self.head_ap_model.setTable("HeadAP") self.head_lat_model.setTable("HeadLAT") self.head_latap_model.setTable("HeadLATAP") self.thorax_ap_model.setTable("ThoraxAP") self.thorax_lat_model.setTable("ThoraxLAT") self.thorax_latap_model.setTable("ThoraxLATAP") self.age_model.select() self.head_ap_model.select() self.head_lat_model.select() self.head_latap_model.select() self.thorax_ap_model.select() self.thorax_lat_model.select() self.thorax_latap_model.select() def initData(self): self.age_data = self.getData(self.age_model) self.head_ap_data = self.getData(self.head_ap_model) self.head_lat_data = self.getData(self.head_lat_model) self.head_latap_data = self.getData(self.head_latap_model) self.thorax_ap_data = self.getData(self.thorax_ap_model) self.thorax_lat_data = self.getData(self.thorax_lat_model) self.thorax_latap_data = self.getData(self.thorax_latap_model) self.age_interp = interpolate.splrep(self.age_data[:, 0], self.age_data[:, 1]) self.head_ap_interp = interpolate.splrep(self.head_ap_data[:, 0], self.head_ap_data[:, 1]) self.head_lat_interp = interpolate.splrep(self.head_lat_data[:, 0], self.head_lat_data[:, 1]) self.head_latap_interp = interpolate.splrep(self.head_latap_data[:, 0], self.head_latap_data[:, 1]) self.thorax_ap_interp = interpolate.splrep(self.thorax_ap_data[:, 0], self.thorax_ap_data[:, 1]) self.thorax_lat_interp = interpolate.splrep(self.thorax_lat_data[:, 0], self.thorax_lat_data[:, 1]) self.thorax_latap_interp = interpolate.splrep( self.thorax_latap_data[:, 0], self.thorax_latap_data[:, 1]) def _menu_ui(self): self.baseon_cb = QComboBox() self.source_cb = QComboBox() self.method_cb = QComboBox() self.calculate_btn = QPushButton('Calculate') self.plot_chk = QCheckBox('Show Graph') self.all_slices_chk = QCheckBox('All Slices') self.d_edit = QLineEdit(f'{self.ctx.app_data.diameter}') self.next_tab_btn = QPushButton('Next') self.prev_tab_btn = QPushButton('Previous') self.all_slices_chk.setVisible(False) self.baseon_cb.addItems(self.baseon_items) self.source_cb.addItems(self.src_method_items.keys()) self.method_cb.addItems(self.src_method_items[list( self.src_method_items.keys())[0]]) self.d_edit.setMaximumWidth(50) self.d_edit.setValidator(QDoubleValidator()) self.d_edit.setReadOnly(True) self.calculate_btn.setAutoDefault(True) self.calculate_btn.setDefault(True) self.next_tab_btn.setAutoDefault(False) self.next_tab_btn.setDefault(False) self.prev_tab_btn.setAutoDefault(False) self.prev_tab_btn.setDefault(False) out_layout = QHBoxLayout() out_layout.addWidget(self.calculate_btn) out_layout.addWidget(QLabel('Diameter')) out_layout.addWidget(self.d_edit) out_layout.addWidget(QLabel('cm')) out_layout.addStretch() chk_layout = QHBoxLayout() chk_layout.addWidget(self.plot_chk) chk_layout.addWidget(self.all_slices_chk) chk_layout.addStretch() menu_layout = QVBoxLayout() menu_layout.addWidget(QLabel('Based on:')) menu_layout.addWidget(self.baseon_cb) menu_layout.addWidget(QLabel('Source:')) menu_layout.addWidget(self.source_cb) menu_layout.addWidget(QLabel('Method:')) menu_layout.addWidget(self.method_cb) menu_layout.addWidget(QLabel('')) menu_layout.addLayout(out_layout) menu_layout.addLayout(chk_layout) menu_layout.addStretch() self.menu_grpbox = QGroupBox('', self) self.menu_grpbox.setLayout(menu_layout) def _deff_correction_ui(self): self.bone_chk = QCheckBox("Bone") self.lung_chk = QCheckBox("Lung") self.bone_sb = QSpinBox() self.stissue_sb = QSpinBox() self.bone_sb.setRange(np.iinfo('int16').min, np.iinfo('int16').max) self.bone_sb.setValue(self.bone_limit) self.bone_sb.setMaximumWidth(60) self.stissue_sb.setRange(np.iinfo('int16').min, np.iinfo('int16').max) self.stissue_sb.setValue(self.stissue_limit) self.stissue_sb.setMaximumWidth(60) chk_layout = QHBoxLayout() chk_layout.addWidget(self.lung_chk) chk_layout.addWidget(self.bone_chk) corr_form = QFormLayout() corr_form.addRow(QLabel('Bone (HU)'), self.bone_sb) corr_form.addRow(QLabel('Soft Tissue (HU)'), self.stissue_sb) self.lower_bnd_grpbox = QGroupBox('Lower Bound') self.lower_bnd_grpbox.setLayout(corr_form) self.lower_bnd_grpbox.setEnabled(False) corr_layout = QVBoxLayout() corr_layout.addLayout(chk_layout) corr_layout.addWidget(self.lower_bnd_grpbox) self.deff_auto_corr_grpbox = QGroupBox('Correction', self) self.deff_auto_corr_grpbox.setLayout(corr_layout) def _3d_opts_ui(self): self.to_lbl = QLabel('to') self.slice1_sb = QSpinBox() self.slice2_sb = QSpinBox() self.slice_step_rbtn = QRadioButton('Slice Step') self.slice_nmbr_rbtn = QRadioButton('Slice Number') self.slice_regn_rbtn = QRadioButton('Regional') self.d_3d_rbtns = [ self.slice_step_rbtn, self.slice_nmbr_rbtn, self.slice_regn_rbtn ] self.d_3d_btngrp = QButtonGroup() [self.d_3d_btngrp.addButton(btn) for btn in self.d_3d_rbtns] self.slice_step_rbtn.setChecked(True) self.slice1_sb.setMaximum(self.ctx.total_img) self.slice1_sb.setMinimum(1) self.slice1_sb.setMinimumWidth(50) self.slice2_sb.setMaximum(self.ctx.total_img) self.slice2_sb.setMinimum(1) self.slice2_sb.setMinimumWidth(50) self.to_lbl.setHidden(True) self.slice2_sb.setHidden(True) slice_layout = QHBoxLayout() slice_layout.addWidget(self.slice1_sb) slice_layout.addWidget(self.to_lbl) slice_layout.addWidget(self.slice2_sb) slice_layout.addStretch() self.d_3d_grpbox = QGroupBox('Z-axis Options', self) d_3d_layout = QVBoxLayout() [d_3d_layout.addWidget(btn) for btn in self.d_3d_rbtns] d_3d_layout.addLayout(slice_layout) d_3d_layout.addStretch() self.d_3d_grpbox.setLayout(d_3d_layout) def _deff_auto_ui(self): self.deff_minimum_area_sb = QSpinBox() self.deff_threshold_sb = QSpinBox() self.ap_edit = QLineEdit('0 cm') self.lat_edit = QLineEdit('0 cm') self.area_rbtn = QRadioButton('Area') self.center_rbtn = QRadioButton('Center') self.max_rbtn = QRadioButton('Max') self.deff_auto_rbtns = [ self.area_rbtn, self.center_rbtn, self.max_rbtn ] self.deff_auto_btngrp = QButtonGroup() [self.deff_auto_btngrp.addButton(btn) for btn in self.deff_auto_rbtns] self.deff_threshold_sb.setRange( np.iinfo('int16').min, np.iinfo('int16').max) self.deff_threshold_sb.setValue(self.threshold) self.deff_threshold_sb.setMaximumWidth(60) self.deff_minimum_area_sb.setMaximum(512 * 512) self.deff_minimum_area_sb.setValue(self.minimum_area) self.deff_minimum_area_sb.setMaximumWidth(60) self.area_rbtn.setChecked(True) self.ap_edit.setMaximumWidth(60) self.lat_edit.setMaximumWidth(60) self.ap_edit.setReadOnly(True) self.lat_edit.setReadOnly(True) info_form = QFormLayout() info_form.addRow(QLabel('AP'), self.ap_edit) info_form.addRow(QLabel('LAT'), self.lat_edit) self.deff_auto_info_widget = QWidget() self.deff_auto_info_widget.setLayout(info_form) self.deff_auto_info_widget.setContentsMargins(0, 0, 0, 0) method_layout = QVBoxLayout() [method_layout.addWidget(btn) for btn in self.deff_auto_rbtns] method_layout.addStretch() method_info_layout = QHBoxLayout() method_info_layout.addLayout(method_layout) method_info_layout.addWidget(self.deff_auto_info_widget) deff_auto_method_grpbox = QGroupBox('Method', self) deff_auto_method_grpbox.setLayout(method_info_layout) self.deff_auto_grpbox = QGroupBox('Options', self) deff_auto_layout = QFormLayout() deff_auto_layout.addRow(QLabel('Threshold (HU)'), self.deff_threshold_sb) deff_auto_layout.addRow(QLabel('Min. pixel area'), self.deff_minimum_area_sb) deff_auto_layout.addRow(deff_auto_method_grpbox) self.deff_auto_grpbox.setLayout(deff_auto_layout) def _deff_img_manual_ui(self): self.deff_ap_edit = QLineEdit( f'{self.lineAP:#.2f} cm') if self.lineAP else QLineEdit('0 cm') self.deff_lat_edit = QLineEdit( f'{self.lineLAT:#.2f} cm') if self.lineLAT else QLineEdit('0 cm') self.deff_ap_btn = QPushButton('AP') self.deff_lat_btn = QPushButton('LAT') self.deff_clear_btn = QPushButton('Clear') btns = [self.deff_ap_btn, self.deff_lat_btn, self.deff_clear_btn] self.deff_clear_btn.setMaximumWidth(100) self.deff_ap_edit.setMaximumWidth(60) self.deff_lat_edit.setMaximumWidth(60) self.deff_ap_edit.setReadOnly(True) self.deff_lat_edit.setReadOnly(True) [btn.setAutoDefault(False) for btn in btns] [btn.setDefault(False) for btn in btns] form = QFormLayout() form.addRow(self.deff_ap_btn, self.deff_ap_edit) form.addRow(self.deff_lat_btn, self.deff_lat_edit) form.addRow(self.deff_clear_btn) self.deff_img_manual_grpbox = QGroupBox('Options', self) self.deff_img_manual_grpbox.setLayout(form) def _deff_manual_ui(self): self.deff_man_opts_cb = QComboBox() self.deff_man_opts_cb.addItems(['Deff', 'AP', 'LAT', 'AP+LAT', 'AGE']) self.year_sb = QSpinBox() self.month_sb = QSpinBox() self.deff_man_edit1 = QLineEdit() self.deff_man_edit2 = QLineEdit() self.deff_man_unit1 = QLabel('cm') self.deff_man_unit2 = QLabel('cm') self.year_sb.setRange(0, self.age_data[-1, 0]) self.month_sb.setRange(0, 11) self.month_sb.setWrapping(True) self.deff_man_edit1.setPlaceholderText('Deff') self.deff_man_edit1.setValidator(QDoubleValidator()) self.deff_man_edit2.setPlaceholderText('LAT') self.deff_man_edit2.setValidator(QDoubleValidator()) self.deff_man_stack1 = QStackedWidget() self.deff_man_stack1.setMaximumWidth(50) self.deff_man_stack1.setMaximumHeight(25) self.deff_man_stack1.addWidget(self.deff_man_edit1) self.deff_man_stack1.addWidget(self.year_sb) self.deff_man_stack2 = QStackedWidget() self.deff_man_stack2.setMaximumWidth(50) self.deff_man_stack2.setMaximumHeight(25) self.deff_man_stack2.addWidget(self.deff_man_edit2) self.deff_man_stack2.addWidget(self.month_sb) h1 = QHBoxLayout() h1.addWidget(self.deff_man_stack1) h1.addWidget(self.deff_man_unit1) h2 = QHBoxLayout() h2.addWidget(self.deff_man_stack2) h2.addWidget(self.deff_man_unit2) form = QVBoxLayout() form.addWidget(self.deff_man_opts_cb) form.addLayout(h1) form.addLayout(h2) form.addStretch() self.deff_manual_grpbox = QGroupBox('Options') self.deff_manual_grpbox.setLayout(form) # self.deff_man_edit2.setHidden(True) self.deff_man_stack2.setHidden(True) self.deff_man_unit2.setHidden(True) def _dw_auto_ui(self): self.dw_minimum_area_lbl = QLabel('Min. pixel area') self.dw_threshold_lbl = QLabel('Threshold (HU)') self.dw_minimum_area_sb = QSpinBox() self.dw_threshold_sb = QSpinBox() self.trunc_img_chk = QCheckBox('Truncated image') self.large_obj_chk = QCheckBox('Largest object only') self.no_roi_chk = QCheckBox('Without ROI') self.no_table_chk = QCheckBox('Remove table') self.dw_auto_grpbox = QGroupBox('Options', self) self.no_table_chk.setEnabled(False) self.dw_threshold_sb.setRange( np.iinfo('int16').min, np.iinfo('int16').max) self.dw_threshold_sb.setValue(self.threshold) self.dw_threshold_sb.setMaximumWidth(60) self.dw_minimum_area_sb.setMaximum(512 * 512) self.dw_minimum_area_sb.setValue(self.minimum_area) self.dw_minimum_area_sb.setMaximumWidth(60) dw_auto_layout = QFormLayout() dw_auto_layout.addRow(self.dw_threshold_lbl, self.dw_threshold_sb) dw_auto_layout.addRow(self.dw_minimum_area_lbl, self.dw_minimum_area_sb) dw_auto_layout.addRow(self.trunc_img_chk) dw_auto_layout.addRow(self.large_obj_chk) dw_auto_layout.addRow(self.no_roi_chk) dw_auto_layout.addRow(self.no_table_chk) # dw_auto_layout.addStretch() self.dw_auto_grpbox.setLayout(dw_auto_layout) def _dw_img_manual_ui(self): self.dw_polygon_btn = QPushButton('Polygon') self.dw_ellipse_btn = QPushButton('Ellipse') self.dw_clear_btn = QPushButton('Clear') btns = [self.dw_polygon_btn, self.dw_ellipse_btn, self.dw_clear_btn] [btn.setMaximumWidth(100) for btn in btns] [btn.setAutoDefault(False) for btn in btns] [btn.setDefault(False) for btn in btns] dw_img_manual_layout = QVBoxLayout() [dw_img_manual_layout.addWidget(btn) for btn in btns] dw_img_manual_layout.addStretch() self.dw_img_manual_grpbox = QGroupBox('Options') self.dw_img_manual_grpbox.setLayout(dw_img_manual_layout) def _opts_ui(self): self._3d_opts_ui() self._deff_auto_ui() self._deff_img_manual_ui() self._deff_manual_ui() self._dw_auto_ui() self._dw_img_manual_ui() self._deff_correction_ui() self.opts_stack = QStackedWidget() self.opts_stack.addWidget(self.deff_auto_grpbox) self.opts_stack.addWidget(self.deff_img_manual_grpbox) self.opts_stack.addWidget(self.deff_manual_grpbox) self.opts_stack.addWidget(self.dw_auto_grpbox) self.opts_stack.addWidget(self.dw_img_manual_grpbox) opts_layout = QVBoxLayout() opts_layout.addWidget(self.opts_stack) opts_layout.addWidget(self.deff_auto_corr_grpbox) opts_layout.addWidget(self.d_3d_grpbox) opts_layout.addStretch() opts_widget = QWidget() opts_widget.setContentsMargins(0, 0, 0, 0) opts_widget.setLayout(opts_layout) self.opts_scroll = QScrollArea() self.opts_scroll.setWidget(opts_widget) self.opts_scroll.setWidgetResizable(True) self.opts_scroll.horizontalScrollBar().setEnabled(False) self.d_3d_grpbox.setVisible(False) def _set_layout(self): menu_layout = QHBoxLayout() menu_layout.addWidget(self.menu_grpbox) menu_layout.addWidget(self.opts_scroll) tab_nav = QHBoxLayout() tab_nav.addWidget(self.prev_tab_btn) tab_nav.addStretch() tab_nav.addWidget(self.next_tab_btn) main_layout = QVBoxLayout() main_layout.addLayout(menu_layout) main_layout.addLayout(tab_nav) self.setLayout(main_layout) def sigConnect(self): self.baseon_cb.activated[str].connect(self.on_set_opts_panel) self.method_cb.activated[str].connect(self.on_set_opts_panel) self.source_cb.activated[str].connect(self.on_set_opts_panel) self.source_cb.activated[str].connect(self.on_source_changed) self.deff_man_opts_cb.activated[str].connect( self.on_deff_manual_method_changed) self.year_sb.valueChanged.connect(self.check_age) self.trunc_img_chk.stateChanged.connect(self.on_truncated_check) self.large_obj_chk.stateChanged.connect(self.on_largest_check) self.no_roi_chk.stateChanged.connect(self.on_roi_check) self.no_table_chk.stateChanged.connect(self.on_table_check) self.calculate_btn.clicked.connect(self.on_calculate) self.deff_clear_btn.clicked.connect(self.clearROIs) self.dw_clear_btn.clicked.connect(self.clearROIs) self.deff_minimum_area_sb.valueChanged.connect( self.on_minimum_area_changed) self.dw_minimum_area_sb.valueChanged.connect( self.on_minimum_area_changed) self.deff_threshold_sb.valueChanged.connect(self.on_threshold_changed) self.dw_threshold_sb.valueChanged.connect(self.on_threshold_changed) self.dw_ellipse_btn.clicked.connect(self.add_ellipse) self.dw_polygon_btn.clicked.connect(self.add_polygon) self.deff_ap_btn.clicked.connect(self.add_ap_line) self.deff_lat_btn.clicked.connect(self.add_lat_line) self.bone_chk.stateChanged.connect(self.on_corr_check) self.lung_chk.stateChanged.connect(self.on_corr_check) self.bone_sb.valueChanged.connect(self.on_bone_limit_changed) self.stissue_sb.valueChanged.connect(self.on_stissue_limit_changed) self.ctx.app_data.imgLoaded.connect(self.img_loaded_handle) self.ctx.app_data.imgChanged.connect(self.img_changed_handle) self.ctx.app_data.sliceOptChanged.connect(self.sliceopt_handle) self.ctx.app_data.mode3dChanged.connect(self.mode3d_handle) self.ctx.app_data.slice1Changed.connect(self.slice1_handle) self.ctx.app_data.slice2Changed.connect(self.slice2_handle) [ btn.toggled.connect(self.on_deff_auto_method_changed) for btn in self.deff_auto_rbtns ] [ btn.toggled.connect(self.on_3d_opts_changed) for btn in self.d_3d_rbtns ] self.plot_chk.stateChanged.connect(self.on_show_graph_check) self.all_slices_chk.stateChanged.connect(self.on_all_slices) def on_all_slices(self, state): self.all_slices = state == Qt.Checked self.ctx.app_data.d_mode = int(self.all_slices) def img_loaded_handle(self, state): if not state: self.all_slices = False self.all_slices_chk.setChecked(False) self.all_slices_chk.setEnabled(state) def on_show_graph_check(self, state): self.show_graph = state == Qt.Checked def plot_chk_handle(self, state): self.plot_chk.setEnabled(state) self.show_graph = False self.plot_chk.setCheckState(Qt.Unchecked) def getData(self, model): data = [[ model.data(model.index(i, j)) for i in range(model.rowCount()) ] for j in range(1, 3)] return np.array(data).T def check_age(self, val): if val == self.age_data[-1, 0]: self.month_sb.setMaximum(0) else: self.month_sb.setMaximum(11) def on_truncated_check(self, state): self.is_truncated = state == Qt.Checked def on_largest_check(self, state): self.is_largest_only = state == Qt.Checked def on_roi_check(self, state): self.is_no_roi = state == Qt.Checked if self.is_no_roi: self.large_obj_chk.setCheckState(Qt.Unchecked) self.trunc_img_chk.setCheckState(Qt.Unchecked) self.is_largest_only = False self.is_truncated = False else: self.no_table_chk.setCheckState(Qt.Unchecked) self.is_no_table = False self.no_table_chk.setEnabled(self.is_no_roi) self.large_obj_chk.setEnabled(not self.is_no_roi) self.trunc_img_chk.setEnabled(not self.is_no_roi) self.dw_minimum_area_lbl.setEnabled(not self.is_no_roi) self.dw_minimum_area_sb.setEnabled(not self.is_no_roi) self.dw_threshold_lbl.setEnabled(not self.is_no_roi) self.dw_threshold_sb.setEnabled(not self.is_no_roi) def on_table_check(self, state): self.is_no_table = state == Qt.Checked self.dw_threshold_lbl.setEnabled(self.is_no_table) self.dw_threshold_sb.setEnabled(self.is_no_table) def on_corr_check(self, state): self.is_corr[int( self.sender().text().lower() == 'bone')] = state == Qt.Checked self.lower_bnd_grpbox.setEnabled(any(self.is_corr)) def on_source_changed(self, src): self.method_cb.clear() self.method_cb.addItems(self.src_method_items[src]) self.on_set_opts_panel() def on_deff_auto_method_changed(self): self.clearROIs() sel = self.sender() if sel.isChecked(): self.deff_auto_method = sel.text().lower() is_area = self.deff_auto_method == 'area' self.deff_auto_corr_grpbox.setVisible(not is_area) if self.method == 0: self.deff_auto_info_widget.setVisible(not is_area) if is_area: self.bone_chk.setCheckState(Qt.Unchecked) self.lung_chk.setCheckState(Qt.Unchecked) self.is_corr = [False, False] print(self.deff_auto_method) def on_3d_opts_changed(self): sel = self.sender() if sel.isChecked(): self.d_3d_method = sel.text().lower() if self.d_3d_method == 'regional': self.to_lbl.setHidden(False) self.slice2_sb.setHidden(False) self.slice1_sb.setMinimum(1) self.slice1_sb.setMaximum(self.ctx.total_img) else: self.to_lbl.setHidden(True) self.slice2_sb.setHidden(True) def on_set_opts_panel(self): self.clearROIs() self.opts_stack.setVisible(True) self.deff_auto_info_widget.setVisible(False) self.d_3d_grpbox.setVisible(False) self.deff_auto_corr_grpbox.setVisible(False) self.d_edit.setReadOnly(True) self.baseon = self.baseon_cb.currentIndex() self.source = self.source_cb.currentIndex() self.method = self.method_cb.currentIndex() try: self.d_edit.textChanged.disconnect(self._on_dw_manual) except: pass self.all_slices_chk.setVisible(self.source == 1) if self.source == 1: # and (self.sender().tag is 'source' or self.sender().tag is 'based'): if self.baseon == 0 and self.method == 0: self.plot_chk_handle(True) self.opts_stack.setCurrentIndex(2) self.ctx.app_data.mode = DEFF_MANUAL else: self.opts_stack.setVisible(False) self.ctx.app_data.mode = DW self.d_edit.setReadOnly(False) self.d_edit.textChanged.connect(self._on_dw_manual) elif self.source == 0: # from img self.d_3d_grpbox.setVisible(self.method == 1) self.plot_chk_handle(self.method == 1) if self.baseon == 0: # deff if self.method == 0 or self.method == 1: self.deff_auto_corr_grpbox.setVisible( self.deff_auto_method != 'area') self.opts_stack.setCurrentIndex(0) if self.method == 0: self.deff_auto_info_widget.setVisible( self.deff_auto_method != 'area') elif self.method == 2: self.opts_stack.setCurrentIndex(1) self.ctx.app_data.mode = DEFF_IMAGE else: if self.method == 0 or self.method == 1: self.opts_stack.setCurrentIndex(3) elif self.method == 2: self.opts_stack.setCurrentIndex(4) self.ctx.app_data.mode = DW self.d_edit.setText('0') def on_deff_manual_method_changed(self, sel): self.deff_man_edit1.clear() self.deff_man_edit2.clear() if sel.lower() != 'ap+lat' and sel.lower() != 'age': self.deff_man_stack1.setCurrentIndex(0) self.deff_man_stack2.setCurrentIndex(0) self.deff_man_edit1.setPlaceholderText(sel) self.deff_man_unit1.setText('cm') self.deff_man_stack2.setHidden(True) self.deff_man_unit2.setHidden(True) if sel.lower() == 'deff': self.ctx.app_data.mode = DEFF_MANUAL else: self.ctx.app_data.mode = DEFF_AP if sel.lower( ) == 'ap' else DEFF_LAT else: self.deff_man_stack2.setHidden(False) self.deff_man_unit2.setHidden(False) if sel.lower() == 'age': self.deff_man_stack1.setCurrentIndex(1) self.deff_man_stack2.setCurrentIndex(1) self.deff_man_unit1.setText('year(s)') self.deff_man_unit2.setText('month(s)') self.ctx.app_data.mode = DEFF_AGE else: self.deff_man_stack1.setCurrentIndex(0) self.deff_man_stack2.setCurrentIndex(0) self.deff_man_edit1.setPlaceholderText('AP') self.deff_man_edit2.setPlaceholderText('LAT') self.deff_man_unit1.setText('cm') self.deff_man_unit2.setText('cm') self.ctx.app_data.mode = DEFF_APLAT self.d_edit.setReadOnly(True) self.deff_manual_method = sel.lower() print(self.deff_manual_method) self.d_edit.setText('0') def _on_dw_manual(self, text): try: self.ctx.app_data.diameter = float(text) except: self.ctx.app_data.diameter = 0 def on_minimum_area_changed(self): sender = self.sender() self.minimum_area = sender.value() if sender == self.deff_minimum_area_sb: self.dw_minimum_area_sb.setValue(self.minimum_area) else: self.deff_minimum_area_sb.setValue(self.minimum_area) def on_threshold_changed(self): sender = self.sender() self.threshold = sender.value() if sender == self.deff_threshold_sb: self.dw_threshold_sb.setValue(self.threshold) else: self.deff_threshold_sb.setValue(self.threshold) def on_bone_limit_changed(self): self.bone_limit = self.bone_sb.value() def on_stissue_limit_changed(self): self.stissue_limit = self.stissue_sb.value() def on_calculate(self): self.ctx.app_data.d_mode = 0 if self.source == 0: # from img if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return if self.method == 0: # auto self.calculate_auto() elif self.method == 1: # 3d self.ctx.app_data.d_mode = 1 self.calculate_auto_3d() elif self.method == 2: # manual self.calculate_img_manual() elif self.source == 1: # input manual self.calculate_manual() self.next_tab_btn.setAutoDefault(True) self.next_tab_btn.setDefault(True) self.calculate_btn.setAutoDefault(False) self.calculate_btn.setDefault(False) def calculate_auto(self): self.ctx.axes.clearGraph() img = self.ctx.get_current_img() dims = self.ctx.img_dims rd = self.ctx.recons_dim img_to_show = img.copy() dval = 0 mask = None correction = 0 if self.baseon == 0: # deff mask = self.get_img_mask(img, threshold=self.threshold, minimum_area=self.minimum_area, largest_only=True) if mask is None: return correction = sum(v << i for i, v in enumerate(self.is_corr[::-1])) if correction and self.deff_auto_method != 'area': center = get_center( mask ) if self.deff_auto_method == 'center' else get_center_max( mask) corr_mask = get_correction_mask(img, mask, lb_bone=self.bone_limit, lb_stissue=self.stissue_limit) dval, ap, lat = get_deff_correction(self.is_corr, corr_mask, center, rd) row, col = center img_to_show = corr_mask else: dval, row, col, ap, lat = get_deff_value( mask, dims, rd, self.deff_auto_method) if self.deff_auto_method != 'area': self.plot_ap_lat(mask, row, col) self.ap_edit.setText(f'{ap:#.2f} cm') self.lat_edit.setText(f'{lat:#.2f} cm') elif self.baseon == 1: if self.is_no_roi: mask = np.ones_like(img, dtype=bool) if self.is_no_table: img_to_show = get_img_no_table(img, threshold=self.threshold) img = img_to_show.copy() img[img < -1000] = -1000 else: mask = self.get_img_mask(img, threshold=self.threshold, minimum_area=self.minimum_area, largest_only=self.is_largest_only) if mask is None: return dval = get_dw_value(img, mask, dims, rd, self.is_truncated, self.is_largest_only) self.d_edit.setText(f'{dval:#.2f}') self.ctx.app_data.diameter = dval self.ctx.app_data.diameters[self.ctx.current_img] = dval self.ctx.app_data.emit_d_changed() if isinstance(self.par.window_width, int) and isinstance( self.par.window_level, int) and correction == 0: img_to_show = windowing(img_to_show, self.par.window_width, self.par.window_level) self.ctx.axes.add_alt_view(img_to_show) self.plot_mask(mask) def calculate_auto_3d(self): self.d_vals = [] self.idxs = [] nslice = self.slice1_sb.value() dcms = np.array(self.ctx.dicoms) index = list(range(len(dcms))) if self.d_3d_method == 'slice step': idxs = index[::nslice] imgs = dcms[::nslice] elif self.d_3d_method == 'slice number': tmps = np.array_split(np.arange(len(dcms)), nslice) idxs = [tmp[len(tmp) // 2] for tmp in tmps] imgs = dcms[idxs] elif self.d_3d_method == 'regional': nslice2 = self.slice2_sb.value() first = nslice if nslice <= nslice2 else nslice2 last = nslice2 if nslice <= nslice2 else nslice idxs = index[first - 1:last] imgs = dcms[first - 1:last] else: imgs = dcms idxs = index avg_dval, idxs = self.get_avg_diameter(imgs, idxs) self.d_edit.setText(f'{avg_dval:#.2f}') self.ctx.app_data.diameter = avg_dval self.idxs = [i + 1 for i in idxs] for k, v in zip(self.idxs, self.d_vals): self.ctx.app_data.diameters[k] = v self.ctx.app_data.emit_d_changed() if self.show_graph: self.plot_3d_auto() def calculate_img_manual(self): pass def calculate_manual(self): if self.baseon == 0: # deff if self.deff_manual_method == 'deff': label = 'Effective Diameter' unit = 'cm' try: dval = float(self.deff_man_edit1.text()) except: dval = 0 val1 = dval data = np.array( [np.arange(0, 2 * val1, .01) for _ in range(2)]).T elif self.deff_manual_method == 'age': label = 'Age' unit = 'year' year = self.year_sb.value() month = self.month_sb.value() val1 = year + month / 12 data = self.age_data dval = float(interpolate.splev(val1, self.age_interp)) else: unit = 'cm' try: val1 = float(self.deff_man_edit1.text()) except: val1 = 0 try: val2 = float(self.deff_man_edit2.text()) except: val2 = 0 if self.deff_manual_method == 'ap+lat': label = 'AP+LAT' val1 += val2 if self.ctx.phantom == HEAD: interp = self.head_latap_interp data = self.head_latap_data else: interp = self.thorax_latap_interp data = self.thorax_latap_data elif self.deff_manual_method == 'ap': label = 'AP' if self.ctx.phantom == HEAD: interp = self.head_ap_interp data = self.head_ap_data else: interp = self.thorax_ap_interp data = self.thorax_ap_data elif self.deff_manual_method == 'lat': label = 'LAT' if self.ctx.phantom == HEAD: interp = self.head_lat_interp data = self.head_lat_data else: interp = self.thorax_lat_interp data = self.thorax_lat_data if val1 < data[0, 0] or val1 > data[-1, 0]: QMessageBox.information( None, "Information", f"The result is an extrapolated value.\nFor the best result, input value between {data[0,0]} and {data[-1,0]}." ) dval = float(interpolate.splev(val1, interp)) if self.show_graph: self.figure = PlotDialog() self.figure.actionEnabled(True) self.figure.trendActionEnabled(False) self.figure.plot(data, pen={ 'color': "FFFF00", 'width': 2 }, symbol=None) self.figure.plot([val1], [dval], symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure.annotate( 'deff', pos=(val1, dval), text= f'{label}: {val1:#.2f} {unit}\nEffective Diameter: {dval:#.2f} cm' ) self.figure.axes.showGrid(True, True) self.figure.setLabels(label, 'Effective Diameter', unit, 'cm') self.figure.setTitle(f'{label} - Deff') self.figure.show() else: dval = float(self.d_edit.text()) self.d_edit.setText(f'{dval:#.2f}') self.ctx.app_data.diameter = dval if self.all_slices: self.idxs = list(range(1, self.ctx.total_img + 1)) for idx in self.idxs: self.ctx.app_data.diameters[idx] = dval else: self.ctx.app_data.diameters[self.ctx.current_img] = dval self.ctx.app_data.emit_d_changed() def clearROIs(self): if len(self.ctx.axes.rois) == 0: return print(self.ctx.axes.rois) self.ctx.axes.clearROIs() self.deff_ap_edit.setText('0 cm') self.deff_lat_edit.setText('0 cm') self.ap_edit.setText('0 cm') self.lat_edit.setText('0 cm') self.d_edit.setText('0') self.lineLAT = self.lineAP = 0 self.ctx.app_data.diameter = 0 def get_avg_diameter(self, dss, idxs): dval = 0 n = len(dss) n_seg = 0 progress = QProgressDialog(f"Calculating diameter of {n} images...", "Stop", 0, n, self) progress.setWindowModality(Qt.WindowModal) progress.setMinimumDuration(1000) for idx, dcm in enumerate(dss): img = self.ctx.get_img_from_ds(dcm) if self.baseon == 0: mask = get_mask(img, threshold=self.threshold, minimum_area=self.minimum_area, largest_only=True) if mask is not None: n_seg += 1 correction = sum(v << i for i, v in enumerate(self.is_corr[::-1])) if correction and self.deff_auto_method != 'area': center = get_center( mask ) if self.deff_auto_method == 'center' else get_center_max( mask) corr_mask = get_correction_mask( img, mask, lb_bone=self.bone_limit, lb_stissue=self.stissue_limit) res = get_deff_correction(self.is_corr, corr_mask, center, self.ctx.recons_dim) else: res = get_deff_value(mask, self.ctx.img_dims, self.ctx.recons_dim, self.deff_auto_method) d = res[0] else: d = 0 else: if self.is_no_roi: mask = np.ones_like(img, dtype=bool) if self.is_no_table: img = get_img_no_table(img, threshold=self.threshold) img[img < -1000] = -1000 else: mask = get_mask(img, threshold=self.threshold, minimum_area=self.minimum_area, largest_only=self.is_largest_only) if mask is not None: n_seg += 1 d = get_dw_value(img, mask, self.ctx.img_dims, self.ctx.recons_dim, self.is_truncated, self.is_largest_only) else: d = 0 dval += d self.d_vals.append(d) progress.setValue(idx) if progress.wasCanceled(): idxs = idxs[:idx + 1] break progress.setValue(n) return dval / n_seg, idxs def get_img_mask(self, *args, **kwargs): mask = get_mask(*args, **kwargs) if mask is None: QMessageBox.warning( None, 'Segmentation Failed', 'No object found during segmentation process.') return mask def plot_mask(self, mask): pos = get_mask_pos(mask) + .5 pos_col = pos[:, 1] pos_row = pos[:, 0] self.ctx.axes.immarker(pos_col, pos_row, pen=None, symbol='s', symbolPen=None, symbolSize=3, symbolBrush=(255, 0, 0, 255)) def plot_ap_lat(self, mask, row, col): pos = get_mask_pos(mask) + .5 pos_col = pos[:, 1] pos_row = pos[:, 0] col += .5 row += .5 id_row = [id for id, el in enumerate(pos_col) if el == col] id_col = [id for id, el in enumerate(pos_row) if el == row] line_v = np.array([pos_col[id_row], pos_row[id_row]]).T line_h = np.array([pos_col[id_col], pos_row[id_col]]).T self.ctx.axes.plot(line_v, pen={ 'color': "00FF7F", 'width': 2 }, symbol=None) self.ctx.axes.plot(line_h, pen={ 'color': "00FF7F", 'width': 2 }, symbol=None) self.ctx.axes.plot([col], [row], pen=None, symbol='o', symbolPen=None, symbolSize=10, symbolBrush=(255, 127, 0, 255)) def plot_3d_auto(self): xlabel = 'Dw' if self.baseon else 'Deff' title = 'Water Equivalent Diameter' if self.baseon else 'Effective Diameter' self.figure = PlotDialog() self.figure.actionEnabled(True) self.figure.trendActionEnabled(False) self.figure.axes.scatterPlot.clear() self.figure.plot(self.idxs, self.d_vals, pen={ 'color': "FFFF00", 'width': 2 }, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure.axes.showGrid(True, True) self.figure.setLabels('slice', xlabel, '', 'cm') self.figure.setTitle(f'Slice - {title}') self.figure.show() def get_distance(self, p1, p2): try: col, row = self.ctx.get_current_img().shape except: return rd = self.ctx.recons_dim return np.sqrt( ((np.array(p2) - np.array(p1))**2).sum()) * (0.1 * (rd / row)) def _roi_handle_to_tuple(self, handle): return (handle.pos().x(), handle.pos().y()) def add_lat_line(self): if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return x, y = self.ctx.get_current_img().shape self.ctx.axes.addLAT(((x / 2) - 0.25 * x, y / 2), ((x / 2) + 0.25 * x, y / 2)) self.ctx.axes.lineLAT.sigRegionChanged.connect(self.get_lat_from_line) pts = self.ctx.axes.lineLAT.getHandles() p1 = self._roi_handle_to_tuple(pts[0]) p2 = self._roi_handle_to_tuple(pts[1]) self.lineLAT = self.get_distance(p1, p2) self.deff_lat_edit.setText(f'{self.lineLAT:#.2f} cm') self.get_deff_from_line() def add_ap_line(self): if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return x, y = self.ctx.get_current_img().shape self.ctx.axes.addAP(((x / 2), (y / 2) - 0.25 * y), ((x / 2), (y / 2) + 0.25 * y)) self.ctx.axes.lineAP.sigRegionChanged.connect(self.get_ap_from_line) pts = self.ctx.axes.lineAP.getHandles() p1 = self._roi_handle_to_tuple(pts[0]) p2 = self._roi_handle_to_tuple(pts[1]) self.lineAP = self.get_distance(p1, p2) self.deff_ap_edit.setText(f'{self.lineAP:#.2f} cm') self.get_deff_from_line() def add_ellipse(self): if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return self.ctx.axes.addEllipse() self.ctx.axes.ellipse.sigRegionChangeFinished.connect( self.get_dw_from_ellipse) self.get_dw_from_ellipse(self.ctx.axes.ellipse) def add_polygon(self): if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return self.ctx.axes.addPolyFinished.connect(self.get_dw_from_ellipse) self.ctx.axes.addPoly() self.ctx.axes.poly.sigRegionChangeFinished.connect( self.get_dw_from_ellipse) def get_lat_from_line(self, roi): pts = roi.getHandles() p1 = self._roi_handle_to_tuple(pts[0]) p2 = self._roi_handle_to_tuple(pts[1]) self.lineLAT = self.get_distance(p1, p2) self.deff_lat_edit.setText(f'{self.lineLAT:#.2f} cm') self.get_deff_from_line() def get_ap_from_line(self, roi): pts = roi.getHandles() p1 = self._roi_handle_to_tuple(pts[0]) p2 = self._roi_handle_to_tuple(pts[1]) self.lineAP = self.get_distance(p1, p2) self.deff_ap_edit.setText(f'{self.lineAP:#.2f} cm') self.get_deff_from_line() def get_deff_from_line(self): dval = np.sqrt(self.lineAP * self.lineLAT) self.d_edit.setText(f'{dval:#.2f}') self.ctx.app_data.diameter = dval self.ctx.app_data.diameters[self.ctx.current_img] = dval self.ctx.app_data.emit_d_changed() def get_dw_from_ellipse(self, roi): dims = self.ctx.img_dims rd = self.ctx.recons_dim img = roi.getArrayRegion(self.ctx.get_current_img(), self.ctx.axes.image, returnMappedCoords=False) if img.size == 0: return mask = roi.renderShapeMask(img.shape[0], img.shape[1]) if not mask.any(): return dval = get_dw_value(img, mask, dims, rd) self.d_edit.setText(f'{dval:#.2f}') self.ctx.app_data.diameter = dval self.ctx.app_data.diameters[self.ctx.current_img] = dval self.ctx.app_data.emit_d_changed() def img_changed_handle(self, value): if value: self.reset_fields() def sliceopt_handle(self, value): self.source_cb.setCurrentIndex(0) src = self.source_cb.currentText() self.on_source_changed(src) self.method_cb.setCurrentIndex(value) self.on_set_opts_panel() def mode3d_handle(self, value): rb = [r for r in self.d_3d_rbtns if r.text().lower() == value] rb[0].setChecked(True) def slice1_handle(self, value): self.slice1_sb.setValue(value) def slice2_handle(self, value): self.slice2_sb.setValue(value) def reset_fields(self): self.initVar() self.ctx.app_data.diameter = 0 self.d_edit.setText('0') self.ap_edit.setText('0 cm') self.lat_edit.setText('0 cm') self.deff_ap_edit.setText('0 cm') self.deff_lat_edit.setText('0 cm') self.deff_man_edit1.clear() self.deff_man_edit2.clear() self.calculate_btn.setAutoDefault(True) self.calculate_btn.setDefault(True) self.next_tab_btn.setAutoDefault(False) self.next_tab_btn.setDefault(False) self.plot_chk.setCheckState(Qt.Unchecked)