class Widget(QWidget): def __init__(self, parent=None): super(Widget, self).__init__(parent) self.uiTeachingWidget = teachingwidget.Widget() self.uiExperimentWidget = experimentwidget.Widget() self.uiStackedWidget = QStackedWidget() self.uiStackedWidget.addWidget(self.uiTeachingWidget) self.uiStackedWidget.addWidget(self.uiExperimentWidget) self.uiButtonNext = QPushButton('Next') self.uiButtonBack = QPushButton('Back') self.uiButtonBack.setEnabled(False) hboxLayout = QHBoxLayout() hboxLayout.addStretch(1) hboxLayout.addWidget(self.uiButtonBack) hboxLayout.addWidget(self.uiButtonNext) vboxLayout = QVBoxLayout() vboxLayout.addWidget(self.uiStackedWidget) vboxLayout.addLayout(hboxLayout) self.setLayout(vboxLayout) self.setWindowTitle('Teaching one linear neuron (example 3)') self.connect(self.uiButtonNext, SIGNAL('clicked()'), self.nextWidget) self.connect(self.uiButtonBack, SIGNAL('clicked()'), self.backWidget) def nextWidget(self): self.uiButtonNext.setEnabled(False) self.uiButtonBack.setEnabled(True) self.uiStackedWidget.setCurrentIndex(1) def backWidget(self): self.uiButtonNext.setEnabled(True) self.uiButtonBack.setEnabled(False) self.uiStackedWidget.setCurrentIndex(0)
class RangeSelection(QWidget): """ Allow to select a date, a build id, a release number or an arbitrary changeset. """ def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QHBoxLayout(self) self._create_widgets() layout.addWidget(self.stacked) layout.addWidget(self.select_combo) self.setLayout(layout) def _create_widgets(self): self.stacked = QStackedWidget() self.datew = QDateEdit() self.datew.setDisplayFormat("yyyy-MM-dd") self.stacked.addWidget(self.datew) self.buildidw = QLineEdit() self.stacked.addWidget(self.buildidw) self.releasew = QComboBox() self.releasew.addItems([str(k) for k in sorted(releases())]) self.stacked.addWidget(self.releasew) self.revw = QLineEdit() self.stacked.addWidget(self.revw) self.select_combo = QComboBox() self.select_combo.addItems(['date', 'buildid', 'release', 'changeset']) self.select_combo.activated.connect(self.stacked.setCurrentIndex) def get_value(self): currentw = self.stacked.currentWidget() if currentw == self.datew: return self.datew.date().toPyDate() elif currentw == self.buildidw: buildid = unicode(self.buildidw.text()) try: return parse_date(buildid) except DateFormatError: raise DateFormatError(buildid, "Not a valid build id: `%s`") elif currentw == self.releasew: return parse_date( date_of_release(str(self.releasew.currentText()))) elif currentw == self.revw: return unicode(self.revw.text())
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # codeCompletionBlock start from PyQt4.QtGui import QComboBox, QToolButton, QStackedWidget self.accountSelection = QComboBox() self.accountButton = QToolButton() self.accountStack = QStackedWidget() # codeCompletionBlock end uic.loadUi(getUiFile('MainWindow'), self) self.model = Model() self.model.loadLastSession() self.actionAdd_Account.triggered.connect(self.addSubAccount) self.accountSelection.currentIndexChanged.connect(self.accountStack.setCurrentIndex) def addSubAccount(self): data = inquireAccountData() account = self.model.createNewAccount(data['name'], data['description'], data['numbers']) self.model.addNewAccount(account) self.accountSelection.addItem(account.description) self.accountStack.addWidget(AccountWidget())
class ConfigWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QFormLayout() self.setLayout(layout) self.label = QLabel("Audio Input") self.inputLayout = QHBoxLayout() self.combobox = QComboBox() self.combobox.setMinimumWidth(150) self.inputSettingsToolButton = QToolButton() self.inputSettingsToolButton.setText("Settings") configIcon = QIcon.fromTheme("preferences-other") self.inputSettingsToolButton.setIcon(configIcon) self.inputSettingsToolButton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.inputSettingsToolButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.inputSettingsStack = QStackedWidget() blankWidget = QWidget() self.inputSettingsStack.addWidget(blankWidget) self.inputSettingsStack.addWidget(self.inputSettingsToolButton) self.inputLayout.addWidget(self.combobox) self.inputLayout.addWidget(self.inputSettingsStack) layout.addRow(self.label, self.inputLayout)
class FormComboWidget(QWidget): def __init__(self, datalist, comment="", parent=None): QWidget.__init__(self, parent) layout = QVBoxLayout() self.setLayout(layout) self.combobox = QComboBox() layout.addWidget(self.combobox) self.stackwidget = QStackedWidget(self) layout.addWidget(self.stackwidget) self.connect(self.combobox, SIGNAL("currentIndexChanged(int)"), self.stackwidget, SLOT("setCurrentIndex(int)")) self.widgetlist = [] for data, title, comment in datalist: self.combobox.addItem(title) widget = FormWidget(data, comment=comment, parent=self) self.stackwidget.addWidget(widget) self.widgetlist.append(widget) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [widget.get() for widget in self.widgetlist]
class FormComboWidget(QWidget): def __init__(self, datalist, comment="", parent=None): QWidget.__init__(self, parent) layout = QVBoxLayout() self.setLayout(layout) self.combobox = QComboBox() layout.addWidget(self.combobox) self.stackwidget = QStackedWidget(self) layout.addWidget(self.stackwidget) self.connect(self.combobox, SIGNAL("currentIndexChanged(int)"), self.stackwidget, SLOT("setCurrentIndex(int)")) self.widgetlist = [] for data, title, comment in datalist: self.combobox.addItem(title) widget = FormWidget(data, comment=comment, parent=self) self.stackwidget.addWidget(widget) self.widgetlist.append(widget) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [widget.get() for widget in self.widgetlist]
class ConfigWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QFormLayout() self.setLayout(layout) self.label = QLabel("Audio Input") self.inputLayout = QHBoxLayout() self.combobox = QComboBox() self.combobox.setMinimumWidth(150) self.inputSettingsToolButton = QToolButton() self.inputSettingsToolButton.setText("Settings") configIcon = QIcon.fromTheme("preferences-other") self.inputSettingsToolButton.setIcon(configIcon) self.inputSettingsToolButton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.inputSettingsToolButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.inputSettingsStack = QStackedWidget() blankWidget = QWidget() self.inputSettingsStack.addWidget(blankWidget) self.inputSettingsStack.addWidget(self.inputSettingsToolButton) self.inputLayout.addWidget(self.combobox) self.inputLayout.addWidget(self.inputSettingsStack) layout.addRow(self.label, self.inputLayout)
class ComboTabWidget(QWidget): def __init__(self, parent): super(ComboTabWidget, self).__init__(parent) layout = QVBoxLayout(self) layout.setSpacing(0) self.switchCombo = QComboBox(self) layout.addWidget(self.switchCombo, 0, Qt.AlignCenter) groupBox = QGroupBox(self) groupBoxLayout = QVBoxLayout(groupBox) groupBoxLayout.setSpacing(0) self.pageArea = QStackedWidget(groupBox) groupBoxLayout.addWidget(self.pageArea) layout.addWidget(groupBox, 1) self.switchCombo.currentIndexChanged.connect(self.pageArea.setCurrentIndex) def setTabPosition(self, tabPos): pass def addTab(self, w, tabText): self.pageArea.addWidget(w) self.switchCombo.addItem(tabText) def insertTab(self, pos, w, tabText): self.pageArea.insertWidget(pos, w) self.switchCombo.insertItem(pos, tabText) def removeTab(self, index=-1): if index < 0: index = self.currentIndex() w = self.pageArea.widget(index) self.pageArea.removeWidget(w) self.switchCombo.removeItem(index) def updateTab(self, w, tabText, index=-1): if index < 0: index = self.switchCombo.currentIndex() self.removeTab(index) self.insertTab(index, w, tabText) self.setCurrentIndex(index) def setCurrentIndex(self, index): self.switchCombo.setCurrentIndex(index) def widget(self, index): return self.pageArea.widget(index) def currentIndex(self): return self.switchCombo.currentIndex() def count(self): return self.switchCombo.count()
class RunConfigDialog(BaseRunConfigDialog): """Run configuration dialog box: multiple file version""" def __init__(self, parent=None): BaseRunConfigDialog.__init__(self, parent) self.file_to_run = None self.combo = None self.stack = None def run_btn_clicked(self): """Run button was just clicked""" self.file_to_run = unicode(self.combo.currentText()) def setup(self, fname): """Setup Run Configuration dialog with filename *fname*""" combo_label = QLabel(_("Select a run configuration:")) self.combo = QComboBox() self.combo.setMaxVisibleItems(20) self.combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength) self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.stack = QStackedWidget() configurations = _get_run_configurations() for index, (filename, options) in enumerate(configurations): if fname == filename: break else: # There is no run configuration for script *fname*: # creating a temporary configuration that will be kept only if # dialog changes are accepted by the user configurations.insert(0, (fname, RunConfiguration(fname).get())) index = 0 for filename, options in configurations: widget = RunConfigOptions(self) widget.set(options) self.combo.addItem(filename) self.stack.addWidget(widget) self.connect(self.combo, SIGNAL("currentIndexChanged(int)"), self.stack.setCurrentIndex) self.combo.setCurrentIndex(index) self.add_widgets(combo_label, self.combo, 10, self.stack) self.add_button_box(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.setWindowTitle(_("Run Settings")) def accept(self): """Reimplement Qt method""" configurations = [] for index in range(self.stack.count()): filename = unicode(self.combo.itemText(index)) runconfigoptions = self.stack.widget(index) if index == self.stack.currentIndex() and\ not runconfigoptions.is_valid(): return options = runconfigoptions.get() configurations.append((filename, options)) _set_run_configurations(configurations) QDialog.accept(self)
class RunConfigDialog(BaseRunConfigDialog): """Run configuration dialog box: multiple file version""" def __init__(self, parent=None): BaseRunConfigDialog.__init__(self, parent) self.file_to_run = None self.combo = None self.stack = None def run_btn_clicked(self): """Run button was just clicked""" self.file_to_run = unicode(self.combo.currentText()) def setup(self, fname): """Setup Run Configuration dialog with filename *fname*""" combo_label = QLabel(_("Select a run configuration:")) self.combo = QComboBox() self.combo.setMaxVisibleItems(20) self.combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength) self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.stack = QStackedWidget() configurations = _get_run_configurations() for index, (filename, options) in enumerate(configurations): if fname == filename: break else: # There is no run configuration for script *fname*: # creating a temporary configuration that will be kept only if # dialog changes are accepted by the user configurations.insert(0, (fname, RunConfiguration(fname).get())) index = 0 for filename, options in configurations: widget = RunConfigOptions(self) widget.set(options) self.combo.addItem(filename) self.stack.addWidget(widget) self.connect(self.combo, SIGNAL("currentIndexChanged(int)"), self.stack.setCurrentIndex) self.combo.setCurrentIndex(index) self.add_widgets(combo_label, self.combo, 10, self.stack) self.add_button_box(QDialogButtonBox.Ok|QDialogButtonBox.Cancel) self.setWindowTitle(_("Run Settings")) def accept(self): """Reimplement Qt method""" configurations = [] for index in range(self.stack.count()): filename = unicode(self.combo.itemText(index)) runconfigoptions = self.stack.widget(index) if index == self.stack.currentIndex() and\ not runconfigoptions.is_valid(): return options = runconfigoptions.get() configurations.append( (filename, options) ) _set_run_configurations(configurations) QDialog.accept(self)
class AccountWidget(QWidget): def __init__(self, parent=None): super(AccountWidget, self).__init__(parent) # codeCompletionBlock start from PyQt4.QtGui import QTreeWidget, QStackedWidget self.dateTree = QTreeWidget() self.monthStack = QStackedWidget() # codeCompletionBlock end uic.loadUi(getUiFile('AccountWidget'), self) self.dateTree.currentItemChanged.connect(self.syncronizeStack) self.addTopLevelPeriod('CurrentTotal') def addNewSubaccount(self): data = self.inquireSubaccountData() print self def inquireSubaccountData(self): pass def addNextMonth(self): # treewidget get toplevel items # find last item # add new toplevvelitem if toplevel item has 12 subitems # add new sublevel item last = self.getLastItem() month = last.monthwidget self.dateTree.addMonth(month) def addSecondLevelPeriod(self, text): # TODO: find previous item item = self.dateTree.currentItem() if item: item.addChildren([QTreeWidgetItem([text])]) self.monthStack.addWidget(MonthWidget()) print self.getLastItem() def getLastItem(self): return MonthItem(1, MonthWidget()) def addTopLevelPeriod(self, text): self.monthStack.addWidget(MonthWidget()) item = QTreeWidgetItem([text]) self.dateTree.addTopLevelItem(item) self.dateTree.setCurrentItem(item) def syncronizeStack(self, item): print item
class ConfigWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QFormLayout() self.setLayout(layout) self.inputLabel = QLabel("Video Input") self.inputLayout = QHBoxLayout() self.inputCombobox = QComboBox() self.inputSettingsToolButton = QToolButton() self.inputSettingsToolButton.setText("Settings") configIcon = QIcon.fromTheme("preferences-other") self.inputSettingsToolButton.setIcon(configIcon) self.inputSettingsToolButton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.inputSettingsToolButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.inputSettingsStack = QStackedWidget() blankWidget = QWidget() self.inputSettingsStack.addWidget(blankWidget) self.inputSettingsStack.addWidget(self.inputSettingsToolButton) self.inputLayout.addWidget(self.inputCombobox) self.inputLayout.addWidget(self.inputSettingsStack) layout.addRow(self.inputLabel, self.inputLayout) self.videocolourLabel = QLabel(self.tr("Colour Format")) self.videocolourComboBox = QComboBox() self.videocolourComboBox.addItem("video/x-raw-rgb") self.videocolourComboBox.addItem("video/x-raw-yuv") self.videocolourComboBox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) layout.addRow(self.videocolourLabel, self.videocolourComboBox) self.framerateLabel = QLabel("Framerate") self.framerateLayout = QHBoxLayout() self.framerateSlider = QSlider() self.framerateSlider.setOrientation(Qt.Horizontal) self.framerateSlider.setMinimum(1) self.framerateSlider.setMaximum(60) self.framerateSpinBox = QSpinBox() self.framerateSpinBox.setMinimum(1) self.framerateSpinBox.setMaximum(60) self.framerateLayout.addWidget(self.framerateSlider) self.framerateLayout.addWidget(self.framerateSpinBox) layout.addRow(self.framerateLabel, self.framerateLayout) self.videoscaleLabel = QLabel("Video Scale") self.videoscaleComboBox = QComboBox() for scale in resmap: self.videoscaleComboBox.addItem(scale) self.videoscaleComboBox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) layout.addRow(self.videoscaleLabel, self.videoscaleComboBox)
class ConfigWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QFormLayout() self.setLayout(layout) self.inputLabel = QLabel("Video Input") self.inputLayout = QHBoxLayout() self.inputCombobox = QComboBox() self.inputSettingsToolButton = QToolButton() self.inputSettingsToolButton.setText("Settings") configIcon = QIcon.fromTheme("preferences-other") self.inputSettingsToolButton.setIcon(configIcon) self.inputSettingsToolButton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.inputSettingsToolButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.inputSettingsStack = QStackedWidget() blankWidget = QWidget() self.inputSettingsStack.addWidget(blankWidget) self.inputSettingsStack.addWidget(self.inputSettingsToolButton) self.inputLayout.addWidget(self.inputCombobox) self.inputLayout.addWidget(self.inputSettingsStack) layout.addRow(self.inputLabel, self.inputLayout) self.videocolourLabel = QLabel(self.tr("Colour Format")) self.videocolourComboBox = QComboBox() self.videocolourComboBox.addItem("video/x-raw-rgb") self.videocolourComboBox.addItem("video/x-raw-yuv") self.videocolourComboBox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) layout.addRow(self.videocolourLabel, self.videocolourComboBox) self.framerateLabel = QLabel("Framerate") self.framerateLayout = QHBoxLayout() self.framerateSlider = QSlider() self.framerateSlider.setOrientation(Qt.Horizontal) self.framerateSlider.setMinimum(1) self.framerateSlider.setMaximum(60) self.framerateSpinBox = QSpinBox() self.framerateSpinBox.setMinimum(1) self.framerateSpinBox.setMaximum(60) self.framerateLayout.addWidget(self.framerateSlider) self.framerateLayout.addWidget(self.framerateSpinBox) layout.addRow(self.framerateLabel, self.framerateLayout) self.videoscaleLabel = QLabel("Video Scale") self.videoscaleComboBox = QComboBox() for scale in resmap: self.videoscaleComboBox.addItem(scale) self.videoscaleComboBox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) layout.addRow(self.videoscaleLabel, self.videoscaleComboBox)
class FormTabWidget(QWidget): def __init__(self, datalist, comment="", parent=None): QWidget.__init__(self, parent) layout = QHBoxLayout() self.contentsWidget = QListWidget() self.contentsWidget.setViewMode(QListView.ListMode) self.contentsWidget.setMovement(QListView.Static) self.contentsWidget.setMaximumWidth(128) self.pagesWidget = QStackedWidget() layout.addWidget(self.contentsWidget) layout.addWidget(self.pagesWidget) self.setLayout(layout) self.widgetlist = [] for elem in datalist: if len(elem) == 4: data, title, comment, icon = elem else: data, title, comment = elem icon = None if len(data[0]) == 3: widget = FormComboWidget(data, comment=comment, parent=self) else: widget = FormWidget(data, comment=comment, parent=self) #index = self.tabwidget.addTab(widget, title) #self.tabwidget.setTabToolTip(index, comment) self.pagesWidget.addWidget(widget) contentItem = QListWidgetItem(self.contentsWidget) if icon: contentItem.setIcon(icon) contentItem.setText(title) contentItem.setToolTip(comment) contentItem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.contentsWidget.addItem(contentItem) self.widgetlist.append(widget) self.contentsWidget.currentRowChanged.connect(self.changePage) def changePage(self, current): if current != -1: self.pagesWidget.setCurrentIndex(current) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [ widget.get() for widget in self.widgetlist]
def addWidgets(self): """Create main layout.""" logger = self.logger logger.debug('adding widgets') layout = QVBoxLayout() self.setLayout(layout) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) tabbar = QTabBar(self) tabbar.setFocusPolicy(Qt.NoFocus) tabbar.setVisible(False) tabbar.currentChanged.connect(self.changeVault) layout.addWidget(tabbar) self.tabbar = tabbar stack = QStackedWidget(self) layout.addWidget(stack) novault = NoVaultWidget(stack) stack.addWidget(novault) self.stack = stack
def addWidgets(self): """Create main layout.""" logger = self.logger logger.debug('adding widgets') layout = QVBoxLayout() self.setLayout(layout) layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) tabbar = QTabBar(self) tabbar.setFocusPolicy(Qt.NoFocus) tabbar.setVisible(False) tabbar.currentChanged.connect(self.changeVault) layout.addWidget(tabbar) self.tabbar = tabbar stack = QStackedWidget(self) layout.addWidget(stack) novault = NoVaultWidget(stack) stack.addWidget(novault) self.stack = stack
class QChatTab(QWidget): def __init__(self, chatWindow, nick): QWidget.__init__(self) self.chatWindow = chatWindow self.nick = nick self.unreadCount = 0 self.widgetStack = QStackedWidget(self) self.widgetStack.addWidget( QNickInputWidget('new_chat.png', 150, self.connectClicked, parent=self)) self.widgetStack.addWidget(QConnectingWidget(self)) self.widgetStack.addWidget( QChatWidget(self.chatWindow.connectionManager, self)) # Skip the chat layout if the nick was given denoting an incoming connection if self.nick is None or self.nick == '': self.widgetStack.setCurrentIndex(0) else: self.widgetStack.setCurrentIndex(2) layout = QHBoxLayout() layout.addWidget(self.widgetStack) self.setLayout(layout) def connectClicked(self, nick): # Check that the nick isn't already connected if self.chatWindow.isNickInTabs(nick): QMessageBox.warning(self, errors.TITLE_ALREADY_CONNECTED, errors.ALREADY_CONNECTED % (nick)) return self.nick = nick self.widgetStack.widget(1).setConnectingToNick(self.nick) self.widgetStack.setCurrentIndex(1) self.chatWindow.connectionManager.openChat(self.nick) def showNowChattingMessage(self): self.widgetStack.setCurrentIndex(2) self.widgetStack.widget(2).showNowChattingMessage(self.nick) def appendMessage(self, message, source): self.widgetStack.widget(2).appendMessage(message, source) def resetOrDisable(self): # If the connecting widget is showing, reset to the nick input widget # If the chat widget is showing, disable it to prevent sending of more messages curWidgetIndex = self.widgetStack.currentIndex() if curWidgetIndex == 1: self.widgetStack.setCurrentIndex(0) elif curWidgetIndex == 2: self.widgetStack.widget(2).disable() def enable(self): self.widgetStack.setCurrentIndex(2) self.widgetStack.widget(2).enable()
class QChatTab(QWidget): def __init__(self, chatWindow, nick): QWidget.__init__(self) self.chatWindow = chatWindow self.nick = nick self.unreadCount = 0 self.widgetStack = QStackedWidget(self) self.widgetStack.addWidget(QNickInputWidget('new_chat.png', 150, self.connectClicked, parent=self)) self.widgetStack.addWidget(QConnectingWidget(self)) self.widgetStack.addWidget(QChatWidget(self.chatWindow.connectionManager, self)) # Skip the chat layout if the nick was given denoting an incoming connection if self.nick is None or self.nick == '': self.widgetStack.setCurrentIndex(0) else: self.widgetStack.setCurrentIndex(2) layout = QHBoxLayout() layout.addWidget(self.widgetStack) self.setLayout(layout) def connectClicked(self, nick): # Check that the nick isn't already connected if self.chatWindow.isNickInTabs(nick): QMessageBox.warning(self, errors.TITLE_ALREADY_CONNECTED, errors.ALREADY_CONNECTED % (nick)) return self.nick = nick self.widgetStack.widget(1).setConnectingToNick(self.nick) self.widgetStack.setCurrentIndex(1) self.chatWindow.connectionManager.openChat(self.nick) def showNowChattingMessage(self): self.widgetStack.setCurrentIndex(2) self.widgetStack.widget(2).showNowChattingMessage(self.nick) def appendMessage(self, message, source): self.widgetStack.widget(2).appendMessage(message, source) def resetOrDisable(self): # If the connecting widget is showing, reset to the nick input widget # If the chat widget is showing, disable it to prevent sending of more messages curWidgetIndex = self.widgetStack.currentIndex() if curWidgetIndex == 1: self.widgetStack.setCurrentIndex(0) elif curWidgetIndex == 2: self.widgetStack.widget(2).disable() def enable(self): self.widgetStack.setCurrentIndex(2) self.widgetStack.widget(2).enable()
class ConfigWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QGridLayout() self.setLayout(layout) self.source1_label = QLabel('Source 1') self.source1_combobox = QComboBox() self.source1_combobox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.source1_button = QToolButton() self.source1_button.setText("Settings") configIcon = QIcon.fromTheme("preferences-other") self.source1_button.setIcon(configIcon) self.source1_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.source1_button.setToolButtonStyle(Qt.ToolButtonIconOnly) self.source1_stack = QStackedWidget() blankWidget = QWidget() self.source1_stack.addWidget(blankWidget) self.source1_stack.addWidget(self.source1_button) layout.addWidget(self.source1_label, 0, 0) layout.addWidget(self.source1_combobox, 0, 1) layout.addWidget(self.source1_stack, 0, 2) self.source2_label = QLabel('Source 2') self.source2_combobox = QComboBox() self.source2_combobox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.source2_button = QToolButton() self.source2_button.setText("Settings") self.source2_button.setIcon(configIcon) # reuse icon from source1 self.source2_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.source2_button.setToolButtonStyle(Qt.ToolButtonIconOnly) self.source2_stack = QStackedWidget() blankWidget = QWidget() self.source2_stack.addWidget(blankWidget) self.source2_stack.addWidget(self.source2_button) layout.addWidget(self.source2_label, 1, 0) layout.addWidget(self.source2_combobox, 1, 1) layout.addWidget(self.source2_stack, 1, 2)
class ConfigWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QGridLayout() self.setLayout(layout) self.mainInputLabel = QLabel("Main Source") self.mainInputComboBox = QComboBox() self.mainInputSetupButton = QToolButton() self.mainInputSetupButton.setText("Settings") configIcon = QIcon.fromTheme("preferences-other") self.mainInputSetupButton.setIcon(configIcon) self.mainInputSetupButton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.mainInputSetupButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.mainInputSetupStack = QStackedWidget() blankWidget = QWidget() self.mainInputSetupStack.addWidget(blankWidget) self.mainInputSetupStack.addWidget(self.mainInputSetupButton) layout.addWidget(self.mainInputLabel, 0, 0) layout.addWidget(self.mainInputComboBox, 0, 1) layout.addWidget(self.mainInputSetupStack, 0, 2) self.pipInputLabel = QLabel("PIP Source") self.pipInputComboBox = QComboBox() self.pipInputSetupButton = QToolButton() self.pipInputSetupButton.setText("Settings") self.pipInputSetupButton.setIcon(configIcon) # reuse the one from main input self.pipInputSetupButton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.pipInputSetupButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.pipInputSetupStack = QStackedWidget() blankWidget = QWidget() self.pipInputSetupStack.addWidget(blankWidget) self.pipInputSetupStack.addWidget(self.pipInputSetupButton) layout.addWidget(self.pipInputLabel, 1, 0) layout.addWidget(self.pipInputComboBox, 1, 1) layout.addWidget(self.pipInputSetupStack, 1, 2)
class LunchMenuWidget(QWidget): textViewIndex = 0 textViewAdditivesMap = {} def __init__(self, parent): super(LunchMenuWidget, self).__init__(parent) box = QVBoxLayout(self) box.addWidget(QLabel(u"Initializing...", self)) def initializeLayout(self): layout = self.layout() child = layout.takeAt(0) while child != None: child.widget().deleteLater() child = layout.takeAt(0) self.messages = LunchMenu.messages() self.toggleMessages = LunchMenu.toggleMessages() self.additives = LunchMenu.additives() self.toggleAdditives = LunchMenu.toggleAdditives() buttonBar = self.createButtonBar(self) layout.addLayout(buttonBar) self.menuNotebook = QStackedWidget(self) self.createNotebook() layout.addWidget(self.menuNotebook) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) def create_arrow_button(self, parent, arrow_type): button = QToolButton(parent) button.setArrowType(arrow_type) return button def goLeft(self): curIndex = self.combobox.currentIndex() if curIndex > 0: self.combobox.setCurrentIndex(curIndex - 1) def goRight(self): curIndex = self.combobox.currentIndex() if curIndex < 4: self.combobox.setCurrentIndex(curIndex + 1) def goToday(self): now = LunchMenu.today() minDelta = sys.maxint minDeltaI = 0 i = 0 for aLunchMenu in LunchMenu.allLunchMenus(): if aLunchMenu == None or isinstance(aLunchMenu, Exception): # parse error, use current day of week if now.weekday() < 5: minDeltaI = now.weekday() else: minDeltaI = 4 break td = now - aLunchMenu.lunchDate delta = abs((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6) if delta < minDelta: minDelta = delta minDeltaI = i i = i + 1 self.combobox.setCurrentIndex(minDeltaI) def goTodayClicked(self): self.goToday() def isToggled(self): index = self.menuNotebook.currentIndex() return (index >= 5) def changed_combo(self, index): if self.isToggled(): self.menuNotebook.setCurrentIndex(index + 5) else: self.menuNotebook.setCurrentIndex(index) self.leftButton.setEnabled(index != 0) self.rightButton.setEnabled(index != 4) def toggleLanguage(self): index = self.menuNotebook.currentIndex() isToggle = (index >= 5) if isToggle: self.switchLanguageButton.setText(self.messages["toggleLanguage"]) index = index - 5 else: self.switchLanguageButton.setText(self.messages["toggleLanguage2"]) index = index + 5 self.menuNotebook.setCurrentIndex(index) def createButtonBar(self, parent): self.combobox = QComboBox(parent) self.combobox.addItem(self.messages['monday']) self.combobox.addItem(self.messages['tuesday']) self.combobox.addItem(self.messages['wednesday']) self.combobox.addItem(self.messages['thursday']) self.combobox.addItem(self.messages['friday']) self.combobox.currentIndexChanged.connect(self.changed_combo) comboBoxHeight = self.combobox.sizeHint().height() self.leftButton = self.create_arrow_button(parent, Qt.LeftArrow) self.leftButton.clicked.connect(self.goLeft) self.leftButton.setMinimumSize(comboBoxHeight, comboBoxHeight) self.rightButton = self.create_arrow_button(parent, Qt.RightArrow) self.rightButton.clicked.connect(self.goRight) self.rightButton.setMinimumSize(comboBoxHeight, comboBoxHeight) navButtons = QHBoxLayout() navButtons.addWidget(self.leftButton, 0, Qt.AlignRight) navButtons.addWidget(self.combobox, 0, Qt.AlignCenter) navButtons.addWidget(self.rightButton, 0, Qt.AlignLeft) buttonBar = QHBoxLayout() todayButton = QPushButton(self.messages['today'], parent) todayButton.clicked.connect(self.goTodayClicked) todayButton.setMinimumHeight(comboBoxHeight) buttonBar.addWidget(todayButton) buttonBar.addWidget(QWidget(parent), 1) buttonBar.addLayout(navButtons, 1) buttonBar.addWidget(QWidget(parent), 1) self.switchLanguageButton = QPushButton( self.messages["toggleLanguage"], parent) self.switchLanguageButton.clicked.connect(self.toggleLanguage) self.switchLanguageButton.setMinimumHeight(comboBoxHeight) buttonBar.addWidget(self.switchLanguageButton, 0, Qt.AlignRight) return buttonBar def addMenuLine(self, parent, text, box, header=False): aLabel = QLabel(text, parent) if header: aLabel.setAlignment(Qt.AlignCenter) oldFont = aLabel.font() aLabel.setFont(QFont(oldFont.family(), 13, QFont.Bold)) box.addWidget(aLabel, 0, Qt.AlignBottom) def addLocaleErrorPage(self, parent, box, toggle): aLabel = QLabel(self.messages['parseLocaleError'], parent) aLabel.setWordWrap(True) box.addWidget(aLabel) aButton = QPushButton(self.messages['installLocaleButton'], parent) if toggle: aButton.clicked.connect(self.installLanguageSupportToggle) else: aButton.clicked.connect(self.installLanguageSupport) box.addWidget(aButton) def addExceptionPage(self, parent, box, error, _toggle): aLabel = QLabel( self.messages['otherException'] + u" " + unicode(error), parent) aLabel.setWordWrap(True) box.addWidget(aLabel) def installLanguageSupportForLocale(self, locale): locale = locale.partition("_")[0] if subprocess.call( ['gksu', "apt-get -q -y install language-pack-%s" % locale]) != 0: QMessageBox().critical(self.menuNotebook, "Installation Error", self.messages['installLocaleError'], buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok) else: QMessageBox().information(self.menuNotebook, "Success", self.messages['installLocaleSuccess'], buttons=QMessageBox.Ok, defaultButton=QMessageBox.Ok) def installLanguageSupport(self): self.installLanguageSupportForLocale(self.defaultLocaleString) def installLanguageSupportToggle(self): self.installLanguageSupportForLocale(self.messages['toggleLocale']) def formatTitleAndDescription(self, title, description, keyInfo): if title and description: result = "%s, %s" % (title, description) elif title: result = title else: result = description if keyInfo: return "%s: %s" % (keyInfo.title(), result) return result def addMenuContent(self, parent, desc, menuContents, box, messages, additivesDict): self.addMenuLine(parent, desc, box) if desc in menuContents: contentList = menuContents[desc] else: contentList = [(messages[u'noContents'], None, [], None)] log_debug("lunch menu does not contain key '%s'" % desc) textview = GrowingTextEdit(parent, messages, additivesDict) textview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) textview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) textview.setLineWrapMode(QTextEdit.WidgetWidth) textview.setReadOnly(True) textview.document().setIndentWidth(10) if len(contentList) == 1: title, description, additives, keyInfo = contentList[0] textview.append( self.formatTitleAndDescription(title, description, keyInfo), additives) elif len(contentList) > 1: cursor = textview.textCursor() listFormat = QTextListFormat() listFormat.setStyle(QTextListFormat.ListDisc) listFormat.setIndent(1) cursor.createList(listFormat) for title, description, additives, keyInfo in contentList: textview.append( self.formatTitleAndDescription(title, description, keyInfo), additives) box.addWidget(textview, 0) def createNotebook(self): self.combobox.setCurrentIndex(0) for _ in range(self.menuNotebook.count()): self.menuNotebook.removeWidget(self.menuNotebook.widget(0)) curMessages = self.messages curAdditives = self.additives for index in range(10): if index == 5: try: if getPlatform() != PLATFORM_WINDOWS: locale.setlocale( locale.LC_TIME, (self.messages["toggleLocale"], "UTF-8")) except: log_exception("error setting locale") curMessages = self.toggleMessages curAdditives = self.toggleAdditives pageWidget = QWidget(self.menuNotebook) page = QVBoxLayout(pageWidget) thisLunchMenu = LunchMenu.allLunchMenus()[index] if thisLunchMenu != None and type(thisLunchMenu) == LunchMenu: title = curMessages[ 'lunchMenuFor'] + u" " + thisLunchMenu.lunchDate.strftime( curMessages['dateFormatDisplayed']).decode("utf-8") self.addMenuLine(pageWidget, title, page, True) if thisLunchMenu.isValid(): self.addMenuContent(pageWidget, curMessages['soupDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives) self.addMenuContent(pageWidget, curMessages['mainDishesDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives) self.addMenuContent(pageWidget, curMessages['supplementsDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives) self.addMenuContent(pageWidget, curMessages['dessertsDisplayed'], thisLunchMenu.contents, page, curMessages, curAdditives) else: self.addMenuLine(pageWidget, curMessages['noLunchToday'], page) elif type(thisLunchMenu) == locale.Error: self.addLocaleErrorPage(pageWidget, page, index >= 5) pass elif isinstance(thisLunchMenu, Exception): self.addExceptionPage(pageWidget, page, thisLunchMenu, index >= 5) self.menuNotebook.addWidget(pageWidget) try: if getPlatform() != PLATFORM_WINDOWS: locale.setlocale(locale.LC_TIME, (LunchMenu.defaultLocaleString, "UTF-8")) except: log_exception("error setting locale") self.goToday()
class DataSelectionGui(QWidget): """ Manages all GUI elements in the data selection applet. This class itself is the central widget and also owns/manages the applet drawer widgets. """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def centralWidget( self ): return self def appletDrawer( self ): return self._drawer def menus( self ): return [] def viewerControlWidget(self): return self._viewerControlWidgetStack def setImageIndex(self, imageIndex): if imageIndex is not None: self.laneSummaryTableView.selectRow(imageIndex) for detailWidget in self._detailViewerWidgets: detailWidget.datasetDetailTableView.selectRow(imageIndex) def stopAndCleanUp(self): for editor in self.volumeEditors.values(): self.viewerStack.removeWidget( editor ) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() ) editor.stopAndCleanUp() self.volumeEditors.clear() def imageLaneAdded(self, laneIndex): if len(self.laneSummaryTableView.selectedIndexes()) == 0: self.laneSummaryTableView.selectRow(laneIndex) # We don't have any real work to do because this gui initiated the lane addition in the first place if self.guiMode != GuiMode.Batch: if(len(self.topLevelOperator.DatasetGroup) != laneIndex+1): import warnings warnings.warn("DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.Dataset), laneIndex)) def imageLaneRemoved(self, laneIndex, finalLength): # We assume that there's nothing to do here because THIS GUI initiated the lane removal if self.guiMode != GuiMode.Batch: assert len(self.topLevelOperator.DatasetGroup) == finalLength ########################################### ########################################### def __init__(self, dataSelectionOperator, serializer, guiControlSignal, guiMode=GuiMode.Normal, title="Input Selection"): with Tracer(traceLogger): super(DataSelectionGui, self).__init__() self.title = title self._viewerControls = QWidget() self.topLevelOperator = dataSelectionOperator self.guiMode = guiMode self.serializer = serializer self.guiControlSignal = guiControlSignal self.threadRouter = ThreadRouter(self) self._initCentralUic() self._initAppletDrawerUic() self._viewerControlWidgetStack = QStackedWidget(self) def handleImageRemoved(multislot, index, finalLength): # Remove the viewer for this dataset imageSlot = self.topLevelOperator.Image[index] if imageSlot in self.volumeEditors.keys(): editor = self.volumeEditors[imageSlot] self.viewerStack.removeWidget( editor ) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() ) editor.stopAndCleanUp() self.topLevelOperator.Image.notifyRemove( bind( handleImageRemoved ) ) def _initCentralUic(self): """ Load the GUI from the ui file into this class and connect it with event handlers. """ # Load the ui file into this class (find it in our own directory) localDir = os.path.split(__file__)[0]+'/' uic.loadUi(localDir+"/dataSelection.ui", self) self._initTableViews() self._initViewerStack() self.splitter.setSizes( [150, 850] ) def _initAppletDrawerUic(self): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0]+'/' self._drawer = uic.loadUi(localDir+"/dataSelectionDrawer.ui") def _initTableViews(self): self.fileInfoTabWidget.setTabText( 0, "Summary" ) self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator) ) self.laneSummaryTableView.dataLaneSelected.connect( self.showDataset ) self.laneSummaryTableView.addFilesRequested.connect( self.handleAddFiles ) self.laneSummaryTableView.addStackRequested.connect( self.handleAddStack ) self.laneSummaryTableView.addByPatternRequested.connect( self.handleAddByPattern ) self.removeLaneButton.clicked.connect( self.handleRemoveLaneButtonClicked ) self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked ) self._retained = [] # Retain menus so they don't get deleted self._detailViewerWidgets = [] for roleIndex, role in enumerate(self.topLevelOperator.DatasetRoles.value): detailViewer = DataDetailViewerWidget( self, self.topLevelOperator, roleIndex ) self._detailViewerWidgets.append( detailViewer ) # Button menu = QMenu(parent=self) menu.setObjectName("addFileButton_role_{}".format( roleIndex )) menu.addAction( "Add File(s)..." ).triggered.connect( partial(self.handleAddFiles, roleIndex) ) menu.addAction( "Add Volume from Stack..." ).triggered.connect( partial(self.handleAddStack, roleIndex) ) menu.addAction( "Add Many by Pattern..." ).triggered.connect( partial(self.handleAddByPattern, roleIndex) ) detailViewer.appendButton.setMenu( menu ) self._retained.append(menu) # Context menu detailViewer.datasetDetailTableView.replaceWithFileRequested.connect( partial(self.handleReplaceFile, roleIndex) ) detailViewer.datasetDetailTableView.replaceWithStackRequested.connect( partial(self.replaceWithStack, roleIndex) ) detailViewer.datasetDetailTableView.editRequested.connect( partial(self.editDatasetInfo, roleIndex) ) detailViewer.datasetDetailTableView.resetRequested.connect( partial(self.handleClearDatasets, roleIndex) ) # Drag-and-drop detailViewer.datasetDetailTableView.addFilesRequested.connect( partial( self.addFileNames, roleIndex=roleIndex ) ) # Selection handling def showFirstSelectedDataset( _roleIndex, lanes ): if lanes: self.showDataset( lanes[0], _roleIndex ) detailViewer.datasetDetailTableView.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex) ) self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role) self.fileInfoTabWidget.currentChanged.connect( self.handleSwitchTabs ) self.fileInfoTabWidget.setCurrentIndex(0) def handleSwitchTabs(self, tabIndex ): if tabIndex < len(self._detailViewerWidgets): roleIndex = tabIndex # If summary tab is moved to the front, change this line. detailViewer = self._detailViewerWidgets[roleIndex] selectedLanes = detailViewer.datasetDetailTableView.selectedLanes if selectedLanes: self.showDataset( selectedLanes[0], roleIndex ) def _initViewerStack(self): self.volumeEditors = {} self.viewerStack.addWidget( QWidget() ) def handleRemoveLaneButtonClicked(self): """ The user clicked the "Remove" button. Remove the currently selected row(s) from both the GUI and the top-level operator. """ # Figure out which lanes to remove selectedIndexes = self.laneSummaryTableView.selectedIndexes() rows = set() for modelIndex in selectedIndexes: rows.add( modelIndex.row() ) rows.discard( self.laneSummaryTableView.model().rowCount() ) # Remove in reverse order so row numbers remain consistent for row in reversed(sorted(rows)): # Remove from the GUI self.laneSummaryTableView.model().removeRow(row) # Remove from the operator finalSize = len(self.topLevelOperator.DatasetGroup) - 1 self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize) # The gui and the operator should be in sync assert self.laneSummaryTableView.model().rowCount() == len(self.topLevelOperator.DatasetGroup)+1 def showDataset(self, laneIndex, roleIndex=None): if laneIndex == -1: self.viewerStack.setCurrentIndex(0) return assert threading.current_thread().name == "MainThread" imageSlot = self.topLevelOperator.Image[laneIndex] # Create if necessary if imageSlot not in self.volumeEditors.keys(): class DatasetViewer(LayerViewerGui): def moveToTop(self, roleIndex): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return datasetRoles = opLaneView.DatasetRoles.value roleName = datasetRoles[roleIndex] try: layerIndex = [l.name for l in self.layerstack].index(roleName) except ValueError: return else: self.layerstack.selectRow(layerIndex) self.layerstack.moveSelectedToTop() def setupLayers(self): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return [] layers = [] datasetRoles = opLaneView.DatasetRoles.value for roleIndex, slot in enumerate(opLaneView.ImageGroup): if slot.ready(): roleName = datasetRoles[roleIndex] layer = self.createStandardLayerFromSlot(slot) layer.name = roleName layers.append(layer) return layers opLaneView = self.topLevelOperator.getLane(laneIndex) layerViewer = DatasetViewer(opLaneView, crosshair=False) # Maximize the x-y view by default. layerViewer.volumeEditorWidget.quadview.ensureMaximized(2) self.volumeEditors[imageSlot] = layerViewer self.viewerStack.addWidget( layerViewer ) self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() ) # Show the right one viewer = self.volumeEditors[imageSlot] displayedRole = self.fileInfoTabWidget.currentIndex() viewer.moveToTop(displayedRole) self.viewerStack.setCurrentWidget( viewer ) self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget() ) def handleAddFiles(self, roleIndex): self.addFiles(roleIndex) def handleReplaceFile(self, roleIndex, startingLane): self.addFiles(roleIndex, startingLane) def addFiles(self, roleIndex, startingLane=None): """ The user clicked the "Add File" button. Ask him to choose a file (or several) and add them to both the GUI table and the top-level operator inputs. """ # Find the directory of the most recently opened image file mostRecentImageFile = PreferencesManager().get( 'DataSelection', 'recent image' ) if mostRecentImageFile is not None: defaultDirectory = os.path.split(mostRecentImageFile)[0] else: defaultDirectory = os.path.expanduser('~') # Launch the "Open File" dialog fileNames = self.getImageFileNamesToOpen(defaultDirectory) # If the user didn't cancel if len(fileNames) > 0: PreferencesManager().set('DataSelection', 'recent image', fileNames[0]) try: self.addFileNames(fileNames, roleIndex, startingLane) except RuntimeError as e: QMessageBox.critical(self, "Error loading file", str(e)) def handleAddByPattern(self, roleIndex): # Find the most recent directory # TODO: remove code duplication mostRecentDirectory = PreferencesManager().get( 'DataSelection', 'recent mass directory' ) if mostRecentDirectory is not None: defaultDirectory = os.path.split(mostRecentDirectory)[0] else: defaultDirectory = os.path.expanduser('~') fileNames = self.getMass(defaultDirectory) # If the user didn't cancel if len(fileNames) > 0: PreferencesManager().set('DataSelection', 'recent mass directory', os.path.split(fileNames[0])[0]) try: self.addFileNames(fileNames, roleIndex) except RuntimeError as e: QMessageBox.critical(self, "Error loading file", str(e)) def getImageFileNamesToOpen(self, defaultDirectory): """ Launch an "Open File" dialog to ask the user for one or more image files. """ extensions = OpDataSelection.SupportedExtensions filt = "Image files (" + ' '.join('*.' + x for x in extensions) + ')' options = QFileDialog.Options() if ilastik_config.getboolean("ilastik", "debug"): options |= QFileDialog.DontUseNativeDialog fileNames = QFileDialog.getOpenFileNames( self, "Select Images", defaultDirectory, filt, options=options ) # Convert from QtString to python str fileNames = [str(s) for s in fileNames] return fileNames def _findFirstEmptyLane(self, roleIndex): opTop = self.topLevelOperator # Determine the number of files this role already has # Search for the last valid value. firstNewLane = 0 for laneIndex, slot in reversed(zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)): if slot[roleIndex].ready(): firstNewLane = laneIndex+1 break return firstNewLane def addFileNames(self, fileNames, roleIndex, startingLane=None): """ Add the given filenames to both the GUI table and the top-level operator inputs. If startingLane is None, the filenames will be *appended* to the role's list of files. """ infos = [] if startingLane is None: startingLane = self._findFirstEmptyLane(roleIndex) endingLane = startingLane+len(fileNames)-1 else: assert startingLane < len(self.topLevelOperator.DatasetGroup) endingLane = startingLane+len(fileNames)-1 # Assign values to the new inputs we just allocated. # The GUI will be updated by callbacks that are listening to slot changes for i, filePath in enumerate(fileNames): datasetInfo = DatasetInfo() cwd = self.topLevelOperator.WorkingDirectory.value if not areOnSameDrive(filePath,cwd): QMessageBox.critical(self, "Drive Error","Data must be on same drive as working directory.") return absPath, relPath = getPathVariants(filePath, cwd) # Relative by default, unless the file is in a totally different tree from the working directory. if len(os.path.commonprefix([cwd, absPath])) > 1: datasetInfo.filePath = relPath else: datasetInfo.filePath = absPath datasetInfo.nickname = PathComponents(absPath).filenameBase h5Exts = ['.ilp', '.h5', '.hdf5'] if os.path.splitext(datasetInfo.filePath)[1] in h5Exts: datasetNames = self.getPossibleInternalPaths( absPath ) if len(datasetNames) > 0: datasetInfo.filePath += str(datasetNames[0]) else: raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath) # Allow labels by default if this gui isn't being used for batch data. datasetInfo.allowLabels = ( self.guiMode == GuiMode.Normal ) infos.append(datasetInfo) # if no exception was thrown, set up the operator now opTop = self.topLevelOperator originalSize = len(opTop.DatasetGroup) if len( opTop.DatasetGroup ) < endingLane+1: opTop.DatasetGroup.resize( endingLane+1 ) for laneIndex, info in zip(range(startingLane, endingLane+1), infos): try: self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue( info ) except DatasetConstraintError as ex: # Give the user a chance to fix the problem if not self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex): opTop.DatasetGroup.resize( originalSize ) break except: QMessageBox.critical( self, "Dataset Load Error", "Wasn't able to load your dataset into the workflow. See console for details." ) opTop.DatasetGroup.resize( originalSize ) raise self.updateInternalPathVisiblity() @threadRouted def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex): msg = "Can't use default properties for dataset:\n\n" + \ filename + "\n\n" + \ "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \ ex.message + "\n\n" + \ "Please enter valid dataset properties to continue." QMessageBox.warning( self, "Dataset Needs Correction", msg ) return self.repairDatasetInfo( info, roleIndex, laneIndex ) def repairDatasetInfo(self, info, roleIndex, laneIndex): defaultInfos = {} defaultInfos[laneIndex] = info editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos) return ( editorDlg.exec_() == QDialog.Accepted ) def getPossibleInternalPaths(self, absPath): datasetNames = [] # Open the file as a read-only so we can get a list of the internal paths with h5py.File(absPath, 'r') as f: # Define a closure to collect all of the dataset names in the file. def accumulateDatasetPaths(name, val): if type(val) == h5py._hl.dataset.Dataset and 3 <= len(val.shape) <= 5: datasetNames.append( '/' + name ) # Visit every group/dataset in the file f.visititems(accumulateDatasetPaths) return datasetNames def handleAddStack(self, roleIndex): self.replaceWithStack(roleIndex, laneIndex=None) def replaceWithStack(self, roleIndex, laneIndex): """ The user clicked the "Import Stack Files" button. """ stackDlg = StackFileSelectionWidget(self) stackDlg.exec_() if stackDlg.result() != QDialog.Accepted : return files = stackDlg.selectedFiles if len(files) == 0: return info = DatasetInfo() info.filePath = "//".join( files ) prefix = os.path.commonprefix(files) info.nickname = PathComponents(prefix).filenameBase + "..." # Allow labels by default if this gui isn't being used for batch data. info.allowLabels = ( self.guiMode == GuiMode.Normal ) info.fromstack = True originalNumLanes = len(self.topLevelOperator.DatasetGroup) if laneIndex is None: laneIndex = self._findFirstEmptyLane(roleIndex) if len(self.topLevelOperator.DatasetGroup) < laneIndex+1: self.topLevelOperator.DatasetGroup.resize(laneIndex+1) def importStack(): self.guiControlSignal.emit( ControlCommand.DisableAll ) # Serializer will update the operator for us, which will propagate to the GUI. try: self.serializer.importStackAsLocalDataset( info ) try: self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue(info) except DatasetConstraintError as ex: # Give the user a chance to repair the problem. filename = files[0] + "\n...\n" + files[-1] if not self.handleDatasetConstraintError( info, filename, ex, roleIndex, laneIndex ): self.topLevelOperator.DatasetGroup.resize(originalNumLanes) finally: self.guiControlSignal.emit( ControlCommand.Pop ) req = Request( importStack ) req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes ) ) req.submit() @threadRouted def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info): import traceback traceback.print_tb(exc_info[2]) msg = "Failed to load stack due to the following error:\n{}".format( exc ) msg += "Attempted stack files were:" for f in files: msg += f + "\n" QMessageBox.critical(self, "Failed to load image stack", msg) self.topLevelOperator.DatasetGroup.resize(originalNumLanes) def getMass(self, defaultDirectory): # TODO: launch dialog and get files # Convert from QtString to python str loader = MassFileLoader(defaultDirectory=defaultDirectory) loader.exec_() if loader.result() == QDialog.Accepted: fileNames = [str(s) for s in loader.filenames] else: fileNames = [] return fileNames def handleClearDatasets(self, roleIndex, selectedRows): for row in selectedRows: self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect() # Remove all operators that no longer have any connected slots last_valid = -1 laneIndexes = range( len(self.topLevelOperator.DatasetGroup) ) for laneIndex, multislot in reversed(zip(laneIndexes, self.topLevelOperator.DatasetGroup)): any_ready = False for slot in multislot: any_ready |= slot.ready() if not any_ready: self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup)-1 ) def editDatasetInfo(self, roleIndex, laneIndexes): editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, laneIndexes) editorDlg.exec_() def updateInternalPathVisiblity(self): for widget in self._detailViewerWidgets: view = widget.datasetDetailTableView model = view.model() view.setColumnHidden(DatasetDetailedInfoColumn.InternalID, not model.hasInternalPaths())
class NewSimulationWindow(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.core = NewSimulationCore(self) self.create_widgets(parent) self.environments = [] self.env_wid = {} # Environment Name to Environment Widget self.env_obj = {} # Environment Name to Environment Objects self.exec() def create_widgets(self, parent): # Layout GBuilder().set_props(self, min_sz_x=900, min_sz_y=700) self.setWindowTitle("Simulation Configuration") self.setFixedHeight(700) self.setFixedWidth(1350) main_lo = QVBoxLayout() self.setLayout(main_lo) # Label self.desc_label = GBuilder().label(self, "Create a new simulation. Create one or more environments and"\ " fill them with components. Then connect the components.", None, None) self.desc_label.setFixedHeight(20) main_lo.addWidget(self.desc_label) # Horizontal Layout hl_1 = QHBoxLayout() main_lo.addLayout(hl_1) # Groupboxes self.components_gb = GBuilder().hand_groupbox(self, "Components", None, None) self.components_gb.setFixedWidth(300) self.environment_gb = GBuilder().groupbox(self, "Environments", None, None) hl_1.addWidget(self.components_gb) hl_1.addWidget(self.environment_gb) # Components Boxes self.comps_layout = QVBoxLayout() self.components_gb.setLayout(self.comps_layout) self._add_component_boxes() # Buttons main_lo.addWidget(GBuilder().hor_line(self)) hl = QHBoxLayout() hl.addWidget(GBuilder().label(self, "")) ok_but = GBuilder().pushbutton(self, "OK", self._ok_hit) ok_but.setFixedWidth(150) ok_but.setFixedHeight(25) cancel_but = GBuilder().pushbutton(self, "Cancel", self._ok_hit) cancel_but.setFixedWidth(150) cancel_but.setFixedHeight(25) hl.addWidget(ok_but) hl.addWidget(cancel_but) main_lo.addLayout(hl) # Fill Components Boxes self._fill_ecu_boxes() self._fill_bus_boxes() self._fill_others_boxes() # Fill environment self._fill_environment_box() # Style self._set_stle() def _add_component_boxes(self): # ECU Box self.ecu_box_wid = QScrollArea() wid = QWidget() self.ecu_box_wid.setWidget(wid) self.ecu_box_wid.setWidgetResizable(True) self.ecu_box = QGridLayout() wid.setLayout(self.ecu_box) self.ecu_box_wid_wid = wid self.comps_layout.addWidget(self.ecu_box_wid) # Bus Box self.bus_box_wid = QScrollArea() wid = QWidget() self.bus_box_wid.setWidget(wid) self.bus_box_wid.setWidgetResizable(True) self.bus_box = QGridLayout() wid.setLayout(self.bus_box) self.bus_box_wid_wid = wid self.comps_layout.addWidget(self.bus_box_wid) # Others Box self.others_box_wid = QScrollArea() wid = QWidget() self.others_box_wid.setWidget(wid) self.others_box_wid.setWidgetResizable(True) self.others_box = QGridLayout() wid.setLayout(self.others_box) self.others_box_wid_wid = wid self.comps_layout.addWidget(self.others_box_wid) def _fill_ecu_boxes(self): # Creatable Objects kys = ECUFactory().createable_objects() row = 0 col = 0 for ky in list(kys): # Information gathering name = self._load_gui_name(ky, ECUFactory()) ico = self._load_gui_icon(ky, ECUFactory()) # New Element per Object gb = GBuilder().groupbox(self, name, None, None) gb.setFont(QtGui.QFont('SansSerif', 7)) gb.setFixedHeight(70) gb.setFixedWidth(70) db = GBuilder().dragbutton(gb, '', self._add_component_boxes, ico, icon_x=45, icon_y=45, size_x=60, size_y=50, pos_x=5, pos_y=15) db.set_drop_func(self.core.add_ecu_box_to_env) db.set_move_icon_context_acts(self.core.load_context_menu_actions(ky)) db.ecu_key = ky db.setCheckable(True) db.setStyleSheet('QPushButton {background-color: #F2F2F2; color: red;border: 0px solid gray;border-radius: 12px;}') # Add to Layout self.ecu_box.addWidget(gb, row, col, Qt.AlignTop) col += 1 if col == 3: row += 1 col = 0 # Add Widget self.ecu_box.addWidget(QWidget()) def _fill_bus_boxes(self): kys = BusFactory().createable_objects() row = 0 col = 0 for ky in list(kys): # Information gathering name = self._load_gui_name(ky, BusFactory()) ico = self._load_gui_icon(ky, BusFactory()) # New Element per Object gb = GBuilder().groupbox(self, name, None, None) gb.setFont(QtGui.QFont('SansSerif', 7)) gb.setFixedHeight(70) gb.setFixedWidth(70) db = GBuilder().dragbutton(gb, '', self._add_component_boxes, ico, icon_x=45, icon_y=45, size_x=60, size_y=50, pos_x=5, pos_y=15) db.set_drop_func(self.core.add_bus_box_to_env) db.set_move_icon_context_acts(self.core.load_context_menu_actions(ky)) db.ecu_key = ky db.setCheckable(True) db.setStyleSheet('QPushButton {background-color: #F2F2F2; color: red;border: 0px solid gray;border-radius: 12px;}') # Add to Layout self.bus_box.addWidget(gb, row, col, Qt.AlignTop) col += 1 if col == 3: row += 1 col = 0 # Add Widget self.bus_box.addWidget(QWidget()) def _fill_others_boxes(self): kys = ECUFactory().createable_objects() row = 0 col = 0 def _fill_environment_box(self): # Main Layout main_lo = QVBoxLayout() self.environment_gb.setLayout(main_lo) # Combobox lo, self.env_select_cb, lab = GBuilder().label_combobox(self, "Current Environment:", [], self._env_select_changed) hl = QHBoxLayout() hl.addLayout(lo) but = GBuilder().pushbutton(self, "New", self._new_env_hit) but.setFixedWidth(40) hl.addWidget(but) lab.setFixedWidth(140) self.env_select_cb.setFixedHeight(22) self.env_select_cb.setFixedWidth(350) main_lo.addLayout(hl) # Groupbox (to make it look nicer) self.content_gb = EnvironmentView(self.environment_gb) main_lo.addWidget(self.content_gb) self.content_gb.setFixedHeight(550) self.content_gb.setFixedWidth(1000) self.content_gb.setLayout(lo) # QStackedWidget with environments self.stacked_env = QStackedWidget() lo.addWidget(self.stacked_env) def _set_stle(self): self.content_gb.setStyleSheet('QGroupBox {background-color: #F2F2F2; color: red; border: 2px solid gray;border-radius: 12px;} EnvironmentView {background-color: #F2F2F2; color: red; border: 2px solid gray;border-radius: 12px;}') self.ecu_box_wid_wid.setStyleSheet('QWidget { border: 0.5px solid gray;border-radius: 3px;}') self.ecu_box_wid.setStyleSheet('QWidget { border: 0.5px solid gray;border-radius: 3px;}') self.bus_box_wid_wid.setStyleSheet('QWidget { border: 0.5px solid gray;border-radius: 3px;}') self.bus_box_wid.setStyleSheet('QWidget { border: 0.5px solid gray;border-radius: 3px;}') self.others_box_wid_wid.setStyleSheet('QWidget { border: 0.5px solid gray;border-radius: 3px;}') self.others_box_wid.setStyleSheet('QWidget { border: 0.5px solid gray;border-radius: 3px;}') def _ok_hit(self): # 1. Create environments using defined processing methods environments_list = NewSimulationCore().run_processes() # 2. Save environments via API / Load them via API as well self.close() def _cancel_hit(self): print("Cancel") self.close() def _new_env_hit(self): # Add to list nr = len(self.environments) self.environments.append("Environment %s" % nr) self.env_select_cb.addItem(self.environments[-1]) self.env_select_cb.setCurrentIndex(nr) # Create new Stacked widget entry wid = QWidget() self.stacked_env.addWidget(wid) self.env_wid["Environment %s" % nr] = wid lo = QVBoxLayout() wid.setLayout(lo) self.stacked_env.setCurrentIndex(nr) def _env_select_changed(self): idx = self.env_select_cb.currentIndex() self.stacked_env.setCurrentIndex(idx) NewSimulationCore().selected_env = self.env_select_cb.currentText() self.content_gb.selected_env = self.env_select_cb.currentText() self._show_env(NewSimulationCore().selected_env, NewSimulationCore().env_map) def _get_env_elem_by_icon(self, env_elems, move_icon): for elem in env_elems: if str(elem.move_icon) == str(move_icon): return elem return None def _load_gui_name(self, ky, factory): try: cls = factory.get_class(ky) name = cls.GUI_NAME except: name = ky return name def _load_gui_icon(self, ky, factory): try: cls = factory.get_class(ky) name = cls.GUI_ICON except: name = os.getcwd() + r'/icons/standard_ecu.png' return name def _show_env(self, selected_env, env_map): GBuilder().update_connected(self.content_gb, self.environment_gb.pos(), self.content_gb.pos(), self.content_gb.selected_env) # Mainwindow for chil in self.children(): if isinstance(chil, DragLabel): try: a = self._get_env_elem_by_icon(env_map[selected_env], chil) if a == None: chil.hide() else: chil.show() except: chil.hide()
class EditorWidget(QWidget): # Señales allFilesClosed = pyqtSignal() def __init__(self): super(EditorWidget, self).__init__() self._recents_files = [] box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) box.setSpacing(0) # Combo container self.combo = ComboContainer(self) box.addWidget(self.combo) # Stacked self.stack = QStackedWidget() box.addWidget(self.stack) self.connect(self.combo.combo_file, SIGNAL("currentIndexChanged(int)"), self.change_item) def add_widget(self, widget): index = self.stack.addWidget(widget) if not self.combo.isVisible(): self.combo.setVisible(True) self.stack.setCurrentIndex(index) def add_item_combo(self, text): self.combo.combo_file.addItem(text) self.combo.combo_file.setCurrentIndex( self.combo.combo_file.count() - 1) def remove_item_combo(self, index): self.combo.combo_file.removeItem(index) def change_item(self, index): self.stack.setCurrentIndex(index) self.emit(SIGNAL("currentWidgetChanged(int)"), index) def current_widget(self): return self.stack.currentWidget() def current_index(self): return self.stack.currentIndex() def widget(self, index): return self.stack.widget(index) def count(self): return self.stack.count() def close_file(self): self.remove_widget(self.current_widget(), self.current_index()) def close_file_project(self, widget, index): #FIXME: unir con close file self.remove_widget(widget, index) def close_all(self): for index in range(self.count()): self.remove_widget(self.current_widget(), 0) def editor_modified(self, value): weditor = self.current_widget() index = self.current_index() self.combo.set_modified(weditor, index, value) def _add_to_recent(self, filename): if filename == 'Untitled': return if filename not in self._recents_files: self._recents_files.append(filename) self.emit(SIGNAL("recentFile(QStringList)"), self._recents_files) def check_files_not_saved(self): value = False for index in range(self.count()): weditor = self.widget(index) value = value or weditor.is_modified return value def files_not_saved(self): files = [] for index in range(self.count()): weditor = self.widget(index) if weditor.is_modified: files.append(weditor.filename) return files def opened_files(self): files = [] for index in range(self.count()): weditor = self.widget(index) path = weditor.filename if path == 'Untitled': continue files.append(path) return files def remove_widget(self, widget, index): if not isinstance(widget, editor.Editor): return if index != -1: self.stack.setCurrentIndex(index) flags = QMessageBox.Yes flags |= QMessageBox.No flags |= QMessageBox.Cancel result = QMessageBox.No if widget.is_modified: result = QMessageBox.question(self, self.tr( "Archivo no guardado!"), self.tr("El archivo <b>{0}</b> " "tiene cambios sin guardar. " "Quieres guardarlos?").format(widget.filename), QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel) if result == QMessageBox.Cancel: return elif result == QMessageBox.Yes: self.emit(SIGNAL("saveCurrentFile()")) self._add_to_recent(widget.filename) self.stack.removeWidget(widget) self.emit(SIGNAL("fileClosed(int)"), index) self.remove_item_combo(index) widget.obj_file.stop_system_watcher() if self.current_widget() is not None: self.current_widget().setFocus() else: self.allFilesClosed.emit() def add_symbols(self, symbols): self.combo.add_symbols_combo(symbols)
class PlotScalesWidget(QWidget): plotScaleChanged = pyqtSignal() def __init__(self, type_key, title, select_min_time_value=False): QWidget.__init__(self) self.__type_key = type_key self.__type = None self.__double_spinner = self.createDoubleSpinner(minimum=-999999999.0, maximum=999999999.0) self.__integer_spinner = self.createIntegerSpinner(minimum=0, maximum=999999999) self.__time_map = ReportStepsModel().getList() self.__time_index_map = {} for index in range(len(self.__time_map)): time = self.__time_map[index] self.__time_index_map[time] = index self.__time_spinner = self.createTimeSpinner( select_minimum_value=select_min_time_value) layout = QVBoxLayout() self.setLayout(layout) self.__label = QLabel(title) self.__label.setAlignment(Qt.AlignHCenter) self.__stack = QStackedWidget() self.__stack.setSizePolicy(QSizePolicy(QSizePolicy.Preferred)) self.__stack.addWidget(self.__integer_spinner) self.__stack.addWidget(self.__double_spinner) self.__stack.addWidget(self.__time_spinner) layout.addWidget(self.__stack) layout.addWidget(self.__label) self.setLayout(layout) def createDoubleSpinner(self, minimum, maximum): spinner = QDoubleSpinBox() spinner.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) spinner.setMinimumWidth(105) spinner.setRange(minimum, maximum) spinner.setKeyboardTracking(False) spinner.setDecimals(8) spinner.editingFinished.connect(self.plotScaleChanged) spinner.valueChanged.connect(self.plotScaleChanged) return spinner def createIntegerSpinner(self, minimum, maximum): spinner = QSpinBox() spinner.setMinimumWidth(75) spinner.setRange(minimum, maximum) spinner.setKeyboardTracking(False) spinner.editingFinished.connect(self.plotScaleChanged) spinner.valueChanged.connect(self.plotScaleChanged) return spinner def createTimeSpinner(self, select_minimum_value): def converter(item): return "%s" % (str(item.date())) spinner = ListSpinBox(self.__time_map) spinner.setMinimumWidth(75) if select_minimum_value: spinner.setValue(0) spinner.valueChanged[int].connect(self.plotScaleChanged) spinner.editingFinished.connect(self.plotScaleChanged) spinner.setStringConverter(converter) return spinner def getValue(self): if self.__type is int: return self.__integer_spinner.value() elif self.__type is float: return self.__double_spinner.value() elif self.__type is CTime: index = self.__time_spinner.value() return self.__time_map[index] else: raise TypeError("Unsupported spinner type: %s" % self.__type) def setValue(self, value): if value is not None: if self.__type is int: self.__integer_spinner.setValue(int(value)) elif self.__type is float: self.__double_spinner.setValue(value) elif self.__type is CTime: index = self.__time_index_map[value] self.__time_spinner.setValue(index) else: raise TypeError("Unsupported spinner type: %s" % self.__type) def setFontSize(self, size): font = self.__double_spinner.font() font.setPointSize(size) self.__double_spinner.setFont(font) font = self.__integer_spinner.font() font.setPointSize(size) self.__integer_spinner.setFont(font) font = self.__time_spinner.font() font.setPointSize(size) self.__time_spinner.setFont(font) font = self.__label.font() font.setPointSize(size) self.__label.setFont(font) def setType(self, spinner_type): self.__type = spinner_type if spinner_type is int: self.__stack.setCurrentWidget(self.__integer_spinner) elif spinner_type is float: self.__stack.setCurrentWidget(self.__double_spinner) elif spinner_type is CTime: self.__stack.setCurrentWidget(self.__time_spinner) else: raise TypeError("Unsupported spinner type: %s" % spinner_type) def getType(self): return self.__type
class DataSelectionGui(QWidget): """ Manages all GUI elements in the data selection applet. This class itself is the central widget and also owns/manages the applet drawer widgets. """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def centralWidget( self ): return self def appletDrawer( self ): return self._drawer def menus( self ): return [] def viewerControlWidget(self): return self._viewerControlWidgetStack def setImageIndex(self, imageIndex): if imageIndex is not None: self.laneSummaryTableView.selectRow(imageIndex) for detailWidget in self._detailViewerWidgets: detailWidget.selectRow(imageIndex) def stopAndCleanUp(self): self._cleaning_up = True for editor in self.volumeEditors.values(): self.viewerStack.removeWidget( editor ) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() ) editor.stopAndCleanUp() self.volumeEditors.clear() def imageLaneAdded(self, laneIndex): if len(self.laneSummaryTableView.selectedIndexes()) == 0: self.laneSummaryTableView.selectRow(laneIndex) # We don't have any real work to do because this gui initiated the lane addition in the first place if self.guiMode != GuiMode.Batch: if(len(self.topLevelOperator.DatasetGroup) != laneIndex+1): import warnings warnings.warn("DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.DatasetGroup), laneIndex)) def imageLaneRemoved(self, laneIndex, finalLength): # There's nothing to do here because the GUI already # handles operator resizes via slot callbacks. pass def allowLaneSelectionChange(self): return False ########################################### ########################################### class UserCancelledError(Exception): # This exception type is raised when the user cancels the # addition of dataset files in the middle of the process somewhere. # It isn't an error -- it's used for control flow. pass def __init__(self, parentApplet, dataSelectionOperator, serializer, instructionText, guiMode=GuiMode.Normal, max_lanes=None, show_axis_details=False): """ Constructor. :param dataSelectionOperator: The top-level operator. Must be of type :py:class:`OpMultiLaneDataSelectionGroup`. :param serializer: The applet's serializer. Must be of type :py:class:`DataSelectionSerializer` :param instructionText: A string to display in the applet drawer. :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``. Currently, there is no difference between normal and batch mode. :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow. If ``None``, there is no maximum. """ super(DataSelectionGui, self).__init__() self._cleaning_up = False self.parentApplet = parentApplet self._max_lanes = max_lanes self._default_h5_volumes = {} self.show_axis_details = show_axis_details self._viewerControls = QWidget() self.topLevelOperator = dataSelectionOperator self.guiMode = guiMode self.serializer = serializer self.threadRouter = ThreadRouter(self) self._initCentralUic() self._initAppletDrawerUic(instructionText) self._viewerControlWidgetStack = QStackedWidget(self) def handleImageRemove(multislot, index, finalLength): # Remove the viewer for this dataset datasetSlot = self.topLevelOperator.DatasetGroup[index] if datasetSlot in self.volumeEditors.keys(): editor = self.volumeEditors[datasetSlot] self.viewerStack.removeWidget( editor ) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() ) editor.stopAndCleanUp() self.topLevelOperator.DatasetGroup.notifyRemove( bind( handleImageRemove ) ) opWorkflow = self.topLevelOperator.parent assert hasattr(opWorkflow.shell, 'onSaveProjectActionTriggered'), \ "This class uses the IlastikShell.onSaveProjectActionTriggered function. Did you rename it?" def _initCentralUic(self): """ Load the GUI from the ui file into this class and connect it with event handlers. """ # Load the ui file into this class (find it in our own directory) localDir = os.path.split(__file__)[0]+'/' uic.loadUi(localDir+"/dataSelection.ui", self) self._initTableViews() self._initViewerStack() self.splitter.setSizes( [150, 850] ) def _initAppletDrawerUic(self, instructionText): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0]+'/' self._drawer = uic.loadUi(localDir+"/dataSelectionDrawer.ui") self._drawer.instructionLabel.setText( instructionText ) def _initTableViews(self): self.fileInfoTabWidget.setTabText( 0, "Summary" ) self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator) ) self.laneSummaryTableView.dataLaneSelected.connect( self.showDataset ) self.laneSummaryTableView.addFilesRequested.connect( self.addFiles ) self.laneSummaryTableView.addStackRequested.connect( self.addStack ) self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked ) # These two helper functions enable/disable an 'add files' button for a given role # based on the the max lane index for that role and the overall permitted max_lanes def _update_button_status(viewer, role_index): if self._max_lanes: viewer.setEnabled( self._findFirstEmptyLane(role_index) < self._max_lanes ) def _handle_lane_added( button, role_index, lane_slot, lane_index ): def _handle_role_slot_added( role_slot, added_slot_index, *args ): if added_slot_index == role_index: role_slot.notifyReady( bind(_update_button_status, button, role_index) ) role_slot.notifyUnready( bind(_update_button_status, button, role_index) ) lane_slot[lane_index].notifyInserted( _handle_role_slot_added ) self._retained = [] # Retain menus so they don't get deleted self._detailViewerWidgets = [] for roleIndex, role in enumerate(self.topLevelOperator.DatasetRoles.value): detailViewer = DatasetDetailedInfoTableView(self) detailViewer.setModel(DatasetDetailedInfoTableModel(self, self.topLevelOperator, roleIndex)) self._detailViewerWidgets.append( detailViewer ) # Button detailViewer.addFilesRequested.connect( partial(self.addFiles, roleIndex)) detailViewer.addStackRequested.connect( partial(self.addStack, roleIndex)) detailViewer.addRemoteVolumeRequested.connect( partial(self.addDvidVolume, roleIndex)) # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, detailViewer, roleIndex ) ) self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, detailViewer, roleIndex ) ) # While we're at it, do the same for the buttons in the summary table, too self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) ) self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) ) # Context menu detailViewer.replaceWithFileRequested.connect( partial(self.handleReplaceFile, roleIndex) ) detailViewer.replaceWithStackRequested.connect( partial(self.addStack, roleIndex) ) detailViewer.editRequested.connect( partial(self.editDatasetInfo, roleIndex) ) detailViewer.resetRequested.connect( partial(self.handleClearDatasets, roleIndex) ) # Drag-and-drop detailViewer.addFilesRequestedDrop.connect( partial( self.addFileNames, roleIndex=roleIndex ) ) # Selection handling def showFirstSelectedDataset( _roleIndex, lanes ): if lanes: self.showDataset( lanes[0], _roleIndex ) detailViewer.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex) ) self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role) self.fileInfoTabWidget.currentChanged.connect( self.handleSwitchTabs ) self.fileInfoTabWidget.setCurrentIndex(0) def handleSwitchTabs(self, tabIndex ): if tabIndex < len(self._detailViewerWidgets): roleIndex = tabIndex # If summary tab is moved to the front, change this line. detailViewer = self._detailViewerWidgets[roleIndex] selectedLanes = detailViewer.selectedLanes if selectedLanes: self.showDataset( selectedLanes[0], roleIndex ) def _initViewerStack(self): self.volumeEditors = {} self.viewerStack.addWidget( QWidget() ) def handleRemoveLaneButtonClicked(self): """ The user clicked the "Remove" button. Remove the currently selected row(s) from both the GUI and the top-level operator. """ # Figure out which lanes to remove selectedIndexes = self.laneSummaryTableView.selectedIndexes() rows = set() for modelIndex in selectedIndexes: rows.add( modelIndex.row() ) # Don't remove the last row, which is just buttons. rows.discard( self.laneSummaryTableView.model().rowCount()-1 ) # Remove in reverse order so row numbers remain consistent for row in reversed(sorted(rows)): # Remove lanes from the operator. # The table model will notice the changes and update the rows accordingly. finalSize = len(self.topLevelOperator.DatasetGroup) - 1 self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize) @threadRouted def showDataset(self, laneIndex, roleIndex=None): if self._cleaning_up: return if laneIndex == -1: self.viewerStack.setCurrentIndex(0) return assert threading.current_thread().name == "MainThread" if laneIndex >= len(self.topLevelOperator.DatasetGroup): return datasetSlot = self.topLevelOperator.DatasetGroup[laneIndex] # Create if necessary if datasetSlot not in self.volumeEditors.keys(): class DatasetViewer(LayerViewerGui): def moveToTop(self, roleIndex): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return datasetRoles = opLaneView.DatasetRoles.value if roleIndex >= len(datasetRoles): return roleName = datasetRoles[roleIndex] try: layerIndex = [l.name for l in self.layerstack].index(roleName) except ValueError: return else: self.layerstack.selectRow(layerIndex) self.layerstack.moveSelectedToTop() def setupLayers(self): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return [] layers = [] datasetRoles = opLaneView.DatasetRoles.value for roleIndex, slot in enumerate(opLaneView.ImageGroup): if slot.ready(): roleName = datasetRoles[roleIndex] layer = self.createStandardLayerFromSlot(slot) layer.name = roleName layers.append(layer) return layers opLaneView = self.topLevelOperator.getLane(laneIndex) layerViewer = DatasetViewer(self.parentApplet, opLaneView, crosshair=False) # Maximize the x-y view by default. layerViewer.volumeEditorWidget.quadview.ensureMaximized(2) self.volumeEditors[datasetSlot] = layerViewer self.viewerStack.addWidget( layerViewer ) self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() ) # Show the right one viewer = self.volumeEditors[datasetSlot] displayedRole = self.fileInfoTabWidget.currentIndex() viewer.moveToTop(displayedRole) self.viewerStack.setCurrentWidget( viewer ) self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget() ) def handleReplaceFile(self, roleIndex, startingLane): self.addFiles(roleIndex, startingLane) def addFiles(self, roleIndex, startingLane=None): """ The user clicked the "Add File" button. Ask him to choose a file (or several) and add them to both the GUI table and the top-level operator inputs. """ # Find the directory of the most recently opened image file mostRecentImageFile = PreferencesManager().get( 'DataSelection', 'recent image' ) if mostRecentImageFile is not None: defaultDirectory = os.path.split(mostRecentImageFile)[0] else: defaultDirectory = os.path.expanduser('~') # Launch the "Open File" dialog fileNames = self.getImageFileNamesToOpen(self, defaultDirectory) # If the user didn't cancel if len(fileNames) > 0: PreferencesManager().set('DataSelection', 'recent image', fileNames[0]) try: self.addFileNames(fileNames, roleIndex, startingLane) except Exception as ex: log_exception( logger ) QMessageBox.critical(self, "Error loading file", str(ex)) @classmethod def getImageFileNamesToOpen(cls, parent_window, defaultDirectory): """ Launch an "Open File" dialog to ask the user for one or more image files. """ extensions = OpDataSelection.SupportedExtensions filter_strs = ["*." + x for x in extensions] filters = ["{filt} ({filt})".format(filt=x) for x in filter_strs] filt_all_str = "Image files (" + ' '.join(filter_strs) + ')' fileNames = [] if ilastik_config.getboolean("ilastik", "debug"): # use Qt dialog in debug mode (more portable?) file_dialog = QFileDialog(parent_window, "Select Images") file_dialog.setOption(QFileDialog.DontUseNativeDialog, True) # do not display file types associated with a filter # the line for "Image files" is too long otherwise file_dialog.setFilters([filt_all_str] + filters) file_dialog.setNameFilterDetailsVisible(False) # select multiple files file_dialog.setFileMode(QFileDialog.ExistingFiles) file_dialog.setDirectory( defaultDirectory ) if file_dialog.exec_(): fileNames = file_dialog.selectedFiles() else: # otherwise, use native dialog of the present platform fileNames = QFileDialog.getOpenFileNames(parent_window, "Select Images", defaultDirectory, filt_all_str) # Convert from QtString to python str fileNames = map(encode_from_qstring, fileNames) return fileNames def _findFirstEmptyLane(self, roleIndex): opTop = self.topLevelOperator # Determine the number of files this role already has # Search for the last valid value. firstNewLane = 0 for laneIndex, slot in reversed(zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)): if slot[roleIndex].ready(): firstNewLane = laneIndex+1 break return firstNewLane def addFileNames(self, fileNames, roleIndex, startingLane=None, rois=None): """ Add the given filenames to both the GUI table and the top-level operator inputs. If startingLane is None, the filenames will be *appended* to the role's list of files. If rois is provided, it must be a list of (start,stop) tuples (one for each fileName) """ # What lanes will we touch? startingLane, endingLane = self._determineLaneRange(fileNames, roleIndex, startingLane) if startingLane is None: # Something went wrong. return # If we're only adding new lanes, NOT modifying existing lanes... adding_only = startingLane == len(self.topLevelOperator) # Create a list of DatasetInfos try: infos = self._createDatasetInfos(roleIndex, fileNames, rois) except DataSelectionGui.UserCancelledError: return # If no exception was thrown so far, set up the operator now loaded_all = self._configureOpWithInfos(roleIndex, startingLane, endingLane, infos) if loaded_all: # Now check the resulting slots. # If they should be copied to the project file, say so. self._reconfigureDatasetLocations(roleIndex, startingLane, endingLane) self._checkDataFormatWarnings(roleIndex, startingLane, endingLane) # If we succeeded in adding all images, show the first one. self.showDataset(startingLane, roleIndex) # Notify the workflow that we just added some new lanes. if adding_only: workflow = self.parentApplet.topLevelOperator.parent workflow.handleNewLanesAdded() # Notify the workflow that something that could affect applet readyness has occurred. self.parentApplet.appletStateUpdateRequested.emit() self.updateInternalPathVisiblity() def _determineLaneRange(self, fileNames, roleIndex, startingLane=None): """ Determine which lanes should be configured if the user wants to add the given fileNames to the specified role, starting at startingLane. If startingLane is None, assume the user wants to APPEND the files to the role's slots. """ if startingLane is None or startingLane == -1: startingLane = len(self.topLevelOperator.DatasetGroup) endingLane = startingLane+len(fileNames)-1 else: assert startingLane < len(self.topLevelOperator.DatasetGroup) max_files = len(self.topLevelOperator.DatasetGroup) - \ startingLane if len(fileNames) > max_files: msg = "You selected {num_selected} files for {num_slots} "\ "slots. To add new files use the 'Add new...' option "\ "in the context menu or the button in the last row."\ .format(num_selected=len(fileNames), num_slots=max_files) QMessageBox.critical( self, "Too many files", msg ) return (None, None) endingLane = min(startingLane+len(fileNames)-1, len(self.topLevelOperator.DatasetGroup)) if self._max_lanes and endingLane >= self._max_lanes: msg = "You may not add more than {} file(s) to this workflow. Please try again.".format( self._max_lanes ) QMessageBox.critical( self, "Too many files", msg ) return (None, None) return (startingLane, endingLane) def _createDatasetInfos(self, roleIndex, filePaths, rois): """ Create a list of DatasetInfos for the given filePaths and rois rois may be None, in which case it is ignored. """ if rois is None: rois = [None]*len(filePaths) assert len(rois) == len(filePaths) infos = [] for filePath, roi in zip(filePaths, rois): info = self._createDatasetInfo(roleIndex, filePath, roi) infos.append(info) return infos def _createDatasetInfo(self, roleIndex, filePath, roi): """ Create a DatasetInfo object for the given filePath and roi. roi may be None, in which case it is ignored. """ cwd = self.topLevelOperator.WorkingDirectory.value datasetInfo = DatasetInfo(filePath, cwd=cwd) datasetInfo.subvolume_roi = roi # (might be None) absPath, relPath = getPathVariants(filePath, cwd) # If the file is in a totally different tree from the cwd, # then leave the path as absolute. Otherwise, override with the relative path. if relPath is not None and len(os.path.commonprefix([cwd, absPath])) > 1: datasetInfo.filePath = relPath h5Exts = ['.ilp', '.h5', '.hdf5'] if os.path.splitext(datasetInfo.filePath)[1] in h5Exts: datasetNames = self.getPossibleInternalPaths( absPath ) if len(datasetNames) == 0: raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath) elif len(datasetNames) == 1: datasetInfo.filePath += str(datasetNames[0]) else: # If exactly one of the file's datasets matches a user's previous choice, use it. if roleIndex not in self._default_h5_volumes: self._default_h5_volumes[roleIndex] = set() previous_selections = self._default_h5_volumes[roleIndex] possible_auto_selections = previous_selections.intersection(datasetNames) if len(possible_auto_selections) == 1: datasetInfo.filePath += str(list(possible_auto_selections)[0]) else: # Ask the user which dataset to choose dlg = H5VolumeSelectionDlg(datasetNames, self) if dlg.exec_() == QDialog.Accepted: selected_index = dlg.combo.currentIndex() selected_dataset = str(datasetNames[selected_index]) datasetInfo.filePath += selected_dataset self._default_h5_volumes[roleIndex].add( selected_dataset ) else: raise DataSelectionGui.UserCancelledError() # Allow labels by default if this gui isn't being used for batch data. datasetInfo.allowLabels = ( self.guiMode == GuiMode.Normal ) return datasetInfo def _configureOpWithInfos(self, roleIndex, startingLane, endingLane, infos): """ Attempt to configure the specified role and lanes of the top-level operator with the given DatasetInfos. Returns True if all lanes were configured successfully, or False if something went wrong. """ opTop = self.topLevelOperator originalSize = len(opTop.DatasetGroup) # Resize the slot if necessary if len( opTop.DatasetGroup ) < endingLane+1: opTop.DatasetGroup.resize( endingLane+1 ) # Configure each subslot for laneIndex, info in zip(range(startingLane, endingLane+1), infos): try: self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue( info ) except DatasetConstraintError as ex: return_val = [False] # Give the user a chance to fix the problem self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex, return_val) if not return_val[0]: # Not successfully repaired. Roll back the changes and give up. opTop.DatasetGroup.resize( originalSize ) return False except OpDataSelection.InvalidDimensionalityError as ex: opTop.DatasetGroup.resize( originalSize ) QMessageBox.critical( self, "Dataset has different dimensionality", ex.message ) return False except Exception as ex: msg = "Wasn't able to load your dataset into the workflow. See error log for details." log_exception( logger, msg ) QMessageBox.critical( self, "Dataset Load Error", msg ) opTop.DatasetGroup.resize( originalSize ) return False return True def _reconfigureDatasetLocations(self, roleIndex, startingLane, endingLane): """ Check the metadata for the given slots. If the data is stored a format that is poorly optimized for 3D access, then configure it to be copied to the project file. Finally, save the project if we changed something. """ save_needed = False opTop = self.topLevelOperator for lane_index in range(startingLane, endingLane+1): output_slot = opTop.ImageGroup[lane_index][roleIndex] if output_slot.meta.prefer_2d and 'z' in output_slot.meta.axistags: shape = numpy.array(output_slot.meta.shape) total_volume = numpy.prod(shape) # Only copy to the project file if the total volume is reasonably small if total_volume < 0.5e9: info_slot = opTop.DatasetGroup[lane_index][roleIndex] info = info_slot.value info.location = DatasetInfo.Location.ProjectInternal info_slot.setValue( info, check_changed=False ) save_needed = True if save_needed: logger.info("Some of your data cannot be accessed efficiently in 3D in its current format." " It will now be copied to the project file.") opWorkflow = self.topLevelOperator.parent opWorkflow.shell.onSaveProjectActionTriggered() def _checkDataFormatWarnings(self, roleIndex, startingLane, endingLane): warn_needed = False opTop = self.topLevelOperator for lane_index in range(startingLane, endingLane+1): output_slot = opTop.ImageGroup[lane_index][roleIndex] if output_slot.meta.inefficient_format: warn_needed = True if warn_needed: QMessageBox.warning( self, "Inefficient Data Format", "Your data cannot be accessed efficiently in its current format. " "Check the console output for details.\n" "(For HDF5 files, be sure to enable chunking on your dataset.)" ) @threadRouted def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex, return_val=[False]): msg = "Can't use default properties for dataset:\n\n" + \ filename + "\n\n" + \ "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \ ex.message + "\n\n" + \ "Please enter valid dataset properties to continue." QMessageBox.warning( self, "Dataset Needs Correction", msg ) # The success of this is 'returned' via our special out-param # (We can't return a value from this func because it is @threadRouted. successfully_repaired = self.repairDatasetInfo( info, roleIndex, laneIndex ) return_val[0] = successfully_repaired def repairDatasetInfo(self, info, roleIndex, laneIndex): """Open the dataset properties editor and return True if the new properties are acceptable.""" defaultInfos = {} defaultInfos[laneIndex] = info editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos, show_axis_details=self.show_axis_details) dlg_state = editorDlg.exec_() return ( dlg_state == QDialog.Accepted ) @classmethod def getPossibleInternalPaths(cls, absPath, min_ndim=3, max_ndim=5): datasetNames = [] # Open the file as a read-only so we can get a list of the internal paths with h5py.File(absPath, 'r') as f: # Define a closure to collect all of the dataset names in the file. def accumulateDatasetPaths(name, val): if type(val) == h5py._hl.dataset.Dataset and min_ndim <= len(val.shape) <= max_ndim: datasetNames.append( '/' + name ) # Visit every group/dataset in the file f.visititems(accumulateDatasetPaths) return datasetNames def addStack(self, roleIndex, laneIndex): """ The user clicked the "Import Stack Files" button. """ stackDlg = StackFileSelectionWidget(self) stackDlg.exec_() if stackDlg.result() != QDialog.Accepted : return files = stackDlg.selectedFiles sequence_axis = stackDlg.sequence_axis if len(files) == 0: return cwd = self.topLevelOperator.WorkingDirectory.value info = DatasetInfo(os.path.pathsep.join(files), cwd=cwd) originalNumLanes = len(self.topLevelOperator.DatasetGroup) if laneIndex is None or laneIndex == -1: laneIndex = len(self.topLevelOperator.DatasetGroup) if len(self.topLevelOperator.DatasetGroup) < laneIndex+1: self.topLevelOperator.DatasetGroup.resize(laneIndex+1) def importStack(): self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() # Serializer will update the operator for us, which will propagate to the GUI. try: self.serializer.importStackAsLocalDataset( info, sequence_axis ) try: self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue(info) except DatasetConstraintError as ex: # Give the user a chance to repair the problem. filename = files[0] + "\n...\n" + files[-1] return_val = [False] self.handleDatasetConstraintError( info, filename, ex, roleIndex, laneIndex, return_val ) if not return_val[0]: # Not successfully repaired. Roll back the changes and give up. self.topLevelOperator.DatasetGroup.resize(originalNumLanes) finally: self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() req = Request( importStack ) req.notify_finished( lambda result: self.showDataset(laneIndex, roleIndex) ) req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes ) ) req.submit() @threadRouted def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info): msg = "Failed to load stack due to the following error:\n{}".format( exc ) msg += "\nAttempted stack files were:\n" msg += "\n".join(files) log_exception( logger, msg, exc_info ) QMessageBox.critical(self, "Failed to load image stack", msg) self.topLevelOperator.DatasetGroup.resize(originalNumLanes) def handleClearDatasets(self, roleIndex, selectedRows): for row in selectedRows: self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect() # Remove all operators that no longer have any connected slots laneIndexes = range( len(self.topLevelOperator.DatasetGroup) ) for laneIndex, multislot in reversed(zip(laneIndexes, self.topLevelOperator.DatasetGroup)): any_ready = False for slot in multislot: any_ready |= slot.ready() if not any_ready: self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup)-1 ) # Notify the workflow that something that could affect applet readyness has occurred. self.parentApplet.appletStateUpdateRequested.emit() def editDatasetInfo(self, roleIndex, laneIndexes): editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, laneIndexes, show_axis_details=self.show_axis_details) editorDlg.exec_() self.parentApplet.appletStateUpdateRequested.emit() def updateInternalPathVisiblity(self): for view in self._detailViewerWidgets: model = view.model() view.setColumnHidden(DatasetDetailedInfoColumn.InternalID, not model.hasInternalPaths()) def addDvidVolume(self, roleIndex, laneIndex): # TODO: Provide list of recently used dvid hosts, loaded from user preferences recent_hosts_pref = PreferencesManager.Setting("DataSelection", "Recent DVID Hosts") recent_hosts = recent_hosts_pref.get() if not recent_hosts: recent_hosts = ["localhost:8000"] recent_hosts = filter(lambda h: h, recent_hosts) from dvidDataSelectionBrowser import DvidDataSelectionBrowser browser = DvidDataSelectionBrowser(recent_hosts, parent=self) if browser.exec_() == DvidDataSelectionBrowser.Rejected: return if None in browser.get_selection(): QMessageBox.critical("Couldn't use your selection.") return rois = None hostname, dset_uuid, volume_name, uuid = browser.get_selection() dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format( **locals() ) subvolume_roi = browser.get_subvolume_roi() # Relocate host to top of 'recent' list, and limit list to 10 items. try: i = recent_hosts.index(recent_hosts) del recent_hosts[i] except ValueError: pass finally: recent_hosts.insert(0, hostname) recent_hosts = recent_hosts[:10] # Save pref recent_hosts_pref.set(recent_hosts) if subvolume_roi is None: self.addFileNames([dvid_url], roleIndex, laneIndex) else: # In ilastik, we display the dvid volume axes in C-order, despite the dvid convention of F-order # Transpose the subvolume roi to match # (see implementation of OpDvidVolume) start, stop = subvolume_roi start = tuple(reversed(start)) stop = tuple(reversed(stop)) self.addFileNames([dvid_url], roleIndex, laneIndex, [(start, stop)])
class SimulationPanel(QWidget): def __init__(self): QWidget.__init__(self) layout = QVBoxLayout() self._simulation_mode_combo = QComboBox() addHelpToWidget(self._simulation_mode_combo, "run/simulation_mode") self._simulation_mode_combo.currentIndexChanged.connect(self.toggleSimulationMode) simulation_mode_layout = QHBoxLayout() simulation_mode_layout.addSpacing(10) simulation_mode_layout.addWidget(QLabel("Simulation mode:"), 0, Qt.AlignVCenter) simulation_mode_layout.addWidget(self._simulation_mode_combo, 0, Qt.AlignVCenter) simulation_mode_layout.addSpacing(20) self.run_button = QToolButton() self.run_button.setIconSize(QSize(32, 32)) self.run_button.setText("Start Simulation") self.run_button.setIcon(resourceIcon("ide/gear_in_play")) self.run_button.clicked.connect(self.runSimulation) self.run_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) addHelpToWidget(self.run_button, "run/start_simulation") simulation_mode_layout.addWidget(self.run_button) simulation_mode_layout.addStretch(1) layout.addSpacing(5) layout.addLayout(simulation_mode_layout) layout.addSpacing(10) self._simulation_stack = QStackedWidget() self._simulation_stack.setLineWidth(1) self._simulation_stack.setFrameStyle(QFrame.StyledPanel) layout.addWidget(self._simulation_stack) self._simulation_widgets = OrderedDict() """ :type: OrderedDict[BaseRunModel,SimulationConfigPanel]""" self.addSimulationConfigPanel(EnsembleExperimentPanel()) self.addSimulationConfigPanel(EnsembleSmootherPanel()) self.addSimulationConfigPanel(IteratedEnsembleSmootherPanel(advanced_option=True)) self.addSimulationConfigPanel(MultipleDataAssimilationPanel()) self.setLayout(layout) def addSimulationConfigPanel(self, panel): assert isinstance(panel, SimulationConfigPanel) panel.toggleAdvancedOptions(False) self._simulation_stack.addWidget(panel) simulation_model = panel.getSimulationModel() self._simulation_widgets[simulation_model] = panel if not panel.is_advanced_option: self._simulation_mode_combo.addItem(str(simulation_model), simulation_model) panel.simulationConfigurationChanged.connect(self.validationStatusChanged) def getActions(self): return [] def toggleAdvancedOptions(self, show_advanced): current_model = self.getCurrentSimulationModel() self._simulation_mode_combo.clear() for model, panel in self._simulation_widgets.iteritems(): if show_advanced or not panel.is_advanced_option: self._simulation_mode_combo.addItem(str(model), model) old_index = self._simulation_mode_combo.findText(str(current_model)) self._simulation_mode_combo.setCurrentIndex(old_index if old_index > -1 else 0) def toggleAdvancedMode(self, show_advanced): for panel in self._simulation_widgets.values(): panel.toggleAdvancedOptions(show_advanced) self.toggleAdvancedOptions(show_advanced) def getCurrentSimulationModel(self): data = self._simulation_mode_combo.itemData(self._simulation_mode_combo.currentIndex(), Qt.UserRole) return data.toPyObject() def getSimulationArguments(self): """ @rtype: dict[str,object]""" simulation_widget = self._simulation_widgets[self.getCurrentSimulationModel()] return simulation_widget.getSimulationArguments() def runSimulation(self): case_name = getCurrentCaseName() message = "Are you sure you want to use case '%s' for initialization of the initial ensemble when running the simulations?" % case_name start_simulations = QMessageBox.question(self, "Start simulations?", message, QMessageBox.Yes | QMessageBox.No ) if start_simulations == QMessageBox.Yes: run_model = self.getCurrentSimulationModel() arguments = self.getSimulationArguments() dialog = RunDialog(run_model, arguments, self) dialog.startSimulation() dialog.exec_() ERT.emitErtChange() # simulations may have added new cases. def toggleSimulationMode(self): current_model = self.getCurrentSimulationModel() if current_model is not None: widget = self._simulation_widgets[self.getCurrentSimulationModel()] self._simulation_stack.setCurrentWidget(widget) self.validationStatusChanged() def validationStatusChanged(self): widget = self._simulation_widgets[self.getCurrentSimulationModel()] self.run_button.setEnabled(widget.isConfigurationValid())
class DigitalerRoboter(QWidget): 'Signale definieren' signal_animation_aktiv = Signal(bool) signal_koordinaten = Signal(int, int, int) 'Methode __init__ ' def __init__(self, parent=None): 'Vererbung aller Attribute und Methoden von QWidget' super(DigitalerRoboter, self).__init__(parent) 'Zeichenflächen mit Koordinatensystem instanziieren' 'Hintergrundfarbe der Zeichenflächen festlegen' farbe_zeichenflaeche = '#b5afb5' 'Zeichenfläche mit Koordinatensystem instanziieren - Draufsicht' self.zeichenflaeche_draufsicht = QPlotWidget() #Hintergrundfarbe festlegen self.zeichenflaeche_draufsicht.figure.set_facecolor( \ farbe_zeichenflaeche) #Koordinatensystem an die Zeichenfläche anpassen self.zeichenflaeche_draufsicht.figure.tight_layout() #gleiche Skalierung der Achsen des Koordinatensystems self.zeichenflaeche_draufsicht.axes.set_aspect('equal') #Grenzen des Koordinatensystems festlegen self.zeichenflaeche_draufsicht.axes.set_xlim([-650, 650]) self.zeichenflaeche_draufsicht.axes.set_ylim([-225, 625]) #Achsen des Koordinatensystems und Beschriftung ausblenden self.zeichenflaeche_draufsicht.axes.axis('off') #Zeichenfläche deaktivieren self.zeichenflaeche_draufsicht.setEnabled(False) 'Zeichenfläche mit Koordinatensystem instanziieren - Seitenansicht' self.zeichenflaeche_seitenansicht = QPlotWidget() #Hintergrundfarbe festlegen self.zeichenflaeche_seitenansicht.figure.set_facecolor( \ farbe_zeichenflaeche) #Koordinatensystem an die Zeichenfläche anpassen self.zeichenflaeche_seitenansicht.figure.tight_layout() #gleiche Skalierung der Achsen des Koordinatensystems self.zeichenflaeche_seitenansicht.axes.set_aspect('equal') #Grenzen des Koordinatensystems festlegen self.zeichenflaeche_seitenansicht.axes.set_xlim(-250, 650) self.zeichenflaeche_seitenansicht.axes.set_ylim([-225, 625]) #Achsen des Koordinatensystems und Beschriftung ausblenden self.zeichenflaeche_seitenansicht.axes.axis('off') #Zeichenfläche deaktivieren self.zeichenflaeche_seitenansicht.setEnabled(False) 'Zeichenfläche mit Koordinatensystem instanziieren - Greifer' self.zeichenflaeche_ansicht_greifer = QPlotWidget() #Hintergrundfarbe festlegen self.zeichenflaeche_ansicht_greifer.figure.set_facecolor( \ farbe_zeichenflaeche) #Koordinatensystem an die Zeichenfläche anpassen self.zeichenflaeche_ansicht_greifer.figure.tight_layout() #gleiche Skalierung der Achsen des Koordinatensystems self.zeichenflaeche_ansicht_greifer.axes.set_aspect('equal') #Grenzen des Koordinatensystems festlegen self.zeichenflaeche_ansicht_greifer.axes.set_xlim([-350, 350]) self.zeichenflaeche_ansicht_greifer.axes.set_ylim([-350, 350]) #Achsen des Koordinatensystems und Beschriftung ausblenden self.zeichenflaeche_ansicht_greifer.axes.axis('off') #Zeichenfläche deaktivieren self.zeichenflaeche_ansicht_greifer.setEnabled(False) '''Die Zeichenflächen werden gestapelt, da immer nur eine Ansicht angezeigt werden kann.''' 'QStackedWidget-Objekt instanziieren' self.ansicht = QStackedWidget() #Draufischt: CurrentIndex(0) self.ansicht.addWidget(self.zeichenflaeche_draufsicht) #Seitenansicht: CurrentIndex(1) self.ansicht.addWidget(self.zeichenflaeche_seitenansicht) #Greifer: CurrentIndex(2) self.ansicht.addWidget(self.zeichenflaeche_ansicht_greifer) 'Layout festlegen' layout = QHBoxLayout() layout.addWidget(self.ansicht) self.setLayout(layout) '''Lage und Orientierung des Werkzeugkoordinatensystem sowie die Öffnungsradien des Greifers in Ausgangsposition laden''' 'Denavit-Hartenberg-Parameter laden' (d1, d2, d3, d4, d5, a1, a2, a3, a4, a5, \ alpha1, alpha2, alpha3, alpha4, alpha5) = denavit_hartenberg() self.a3 = a3 'Orientierung des Werkzeugkoordinatensystems laden' self.x5in0, self.y5in0, self.z5in0 = \ self.orientierung_laden('programmEin') 'Lagevektor des Werkzeugkoordinatensystems laden' self.P05in0 = self.lage_laden('programmEin') 'Öffnungsradien des Greifers laden' self.r6, self.r7 = self.greifer_laden('programmEin') 'Mittelpunktkoordinaten der Punkte berechnen' self.modellrechnung() 'Methode animation_anzeigen aufrufen' self.animation_anzeigen() '''Methode animation_anzeigen - Die Methode dient dem Instanziieren der drei Ansichten. Jeder Animation wird die entsprechenden Zeichen- fläche als Parent und die Mittelpunktkoordinaten der Circle-Objekte übergeben. Weiter werden die Signale mit den Slots verbunden.''' def animation_anzeigen(self): 'DraufsichtRoboter-Objekt instanziieren' self.animation_draufsicht = DraufsichtRoboter( \ self.zeichenflaeche_draufsicht, \ self.xP1in0, self.yP1in0, self.xP2in0, self.yP2in0, \ self.xP4in0, self.yP4in0, self.xP5in0, self.yP5in0) 'Signal und Slot verbinden' self.animation_draufsicht.xy_neu.connect( \ self.koordinaten_draufsicht) 'SeitenansichtRoboter-Objekt instanziieren' self.animation_seitenansicht = SeitenansichtRoboter( \ self.zeichenflaeche_seitenansicht, \ self.xP1in1, self.yP1in1, self.xP2in1, self.yP2in1, self.xP3in1, \ self.yP3in1, self.xP4in1, self.yP4in1, self.xP5in1, self.yP5in1) 'Signal und Slot verbinden' self.animation_seitenansicht.xy_neu.connect( \ self.koordinaten_seitenansicht) 'RueckansichtGreifer-Objekt instanziieren' self.animation_greifer = RueckansichtGreifer( \ self.zeichenflaeche_ansicht_greifer, \ self.xP6in4z, self.yP6in4z, self.xP7in4z, self.yP7in4z) 'Signal und Slot verbinden' self.animation_greifer.xy_neu.connect( \ self.koordinaten_greifer) '''Methode animation_aktivieren - Die Methode aktiviert die Zeichen- flächen. In Folge kann der digitale Roboter durch Verschieben der Punkte bewegt werden. Die bewegbaren Punkte erscheinen in Farbe.''' def animation_aktivieren(self, b): 'Zeichenflächen aktivieren' self.zeichenflaeche_draufsicht.setEnabled(b) self.zeichenflaeche_seitenansicht.setEnabled(b) self.zeichenflaeche_ansicht_greifer.setEnabled(b) 'Einfärben der bewegbaren Punkte' self.animation_draufsicht.punkte_faerben(b) self.animation_seitenansicht.punkte_faerben(b) self.animation_greifer.punkte_faerben(b) '''Methode animation_aktualisieren - Die Methode ermöglicht das Aktualisieren des digitalen Roboters. Dabei sind die Lage (Zeilenvektor vom Format 1x3) und Orientierung (Zeilenvektor Format 1x9) des Werkzeug- koordinatensystems in 0-Koordinaten sowie der Öffnungsradius des Greifers (Zeilenvektor vom Format 1x1) zu übergeben.''' def animation_aktualisieren(self, orientierung, lage, greifer): 'Orientierung des Werkzeugkoordinatensystems' 'Einheitsvektoren zuordnen' x5in0, y5in0, z5in0 = hsplit(orientierung, 3) #Format 1x3 'Format anpassen' x5in0 = x5in0.transpose() #Format 3x1 self.x5in0 = vstack((x5in0, array([[0]]))) #Format 4x1 y5in0 = y5in0.transpose() #Format 3x1 self.y5in0 = vstack((y5in0, array([[0]]))) #Format 4x1 z5in0 = z5in0.transpose() #Format 3x1 self.z5in0 = vstack((z5in0, array([[0]]))) #Format 4x1 'Lage des Werkzeugkoordinatensystems' 'Lagevektor zuordnen und Format anpassen' P05in0 = lage #Format 1x3 P05in0 = P05in0.transpose() #Format 3x1 self.P05in0 = vstack((P05in0, array([[1]]))) #Format 4x1 'Öffnungsradien des Greifers zuordnen und Datentyp ändern' self.r6 = float(greifer) self.r7 = self.r6 'Mittelpunktkoordinaten der Circle-Objekte berechnen' self.modellrechnung() 'Draufsicht aktualisieren' self.animation_draufsicht.ansicht_aktualisieren( \ self.xP2in0, self.yP2in0, self.xP4in0, \ self.yP4in0, self.xP5in0, self.yP5in0) 'Seitenansicht aktualisieren' self.animation_seitenansicht.ansicht_aktualisieren( self.xP2in1, self.yP2in1, self.xP3in1, self.yP3in1, \ self.xP4in1, self.yP4in1, self.xP5in1, self.yP5in1) 'Greifer aktualisieren' self.animation_greifer.ansicht_aktualisieren( \ self.xP6in4z, self.yP6in4z, self.xP7in4z, self.yP7in4z) 'Lagekoordinaten des Werkzeugkoordinatensystems' x5in0 = int(self.xP5in0) y5in0 = int(self.yP5in0) z5in0 = int(self.zP5in0) 'Signal zum Aktualisieren der Lagekoordinaten senden' self.signal_koordinaten.emit(x5in0, y5in0, z5in0) '''Methode animation_zuruecksetzen - Die Methode setzt den digitalen Roboter auf die Ausgangsposition zurück. Weiter werden die Bewegungs- möglichkeit deaktiviert und die Punkte entfärbt.''' def animation_zuruecksetzen(self, b): if b == True: 'Orientierung des Werkzeugkoordinatensystems laden' self.x5in0, self.y5in0, self.z5in0 = self.orientierung_laden( \ 'programmEin') 'Lagevektor des Werkzeugkoordinatensystems laden' self.P05in0 = self.lage_laden('programmEin') 'Öffnungsradien des Greifers laden' self.r6, self.r7 = self.greifer_laden('programmEin') 'Mittelpunktkoordinaten der Punkte berechnen' self.modellrechnung() 'Draufsicht aktualisieren' self.animation_draufsicht.ansicht_aktualisieren( \ self.xP2in0, self.yP2in0, self.xP4in0, \ self.yP4in0, self.xP5in0, self.yP5in0) 'Seitenansicht aktualisieren' self.animation_seitenansicht.ansicht_aktualisieren( self.xP2in1, self.yP2in1, self.xP3in1, self.yP3in1, \ self.xP4in1, self.yP4in1, self.xP5in1, self.yP5in1) 'Greifer aktualisieren' self.animation_greifer.ansicht_aktualisieren( \ self.xP6in4z, self.yP6in4z, self.xP7in4z, self.yP7in4z) 'Geist ausblenden und Koordinaten aktualisieren' self.geisterstunde(False) 'Entfärben der bewegbaren Punkte' self.animation_draufsicht.punkte_faerben(False) self.animation_seitenansicht.punkte_faerben(False) self.animation_greifer.punkte_faerben(False) 'Lagekoordinaten des Werkzeugkoordinatensystems' x5in0 = int(self.xP5in0) y5in0 = int(self.yP5in0) z5in0 = int(self.zP5in0) 'Signal zum Aktualisieren der Lagekoordinaten senden' self.signal_koordinaten.emit(x5in0, y5in0, z5in0) '''Methode ansicht_wechseln - Die Methode ermöglicht den Wechsel zwischen den drei Ansichten.''' def ansicht_wechseln(self, index): self.ansicht.setCurrentIndex(index) '''Methode bilder_aufnehmen - Die Methode ermöglicht die Aufnahme und das Abspeichern von Bildern der Drauf- und Seitenansicht.''' def bilder_aufnehmen(self): 'Ansichten zentrieren' self.zeichenflaeche_draufsicht.axes.set_xlim([-625, 625]) self.zeichenflaeche_draufsicht.axes.set_ylim([-325, 525]) self.zeichenflaeche_seitenansicht.axes.set_xlim(-525, 725) self.zeichenflaeche_seitenansicht.axes.set_ylim([-325, 525]) 'Arbeitsverzeichnis' workdir = getcwd() 'Dateiname und Pfad' dateiname = 'bild_draufsicht.svg' dir = path.join(workdir, 'speicher', 'bildspeicher', dateiname) 'Draufsicht als Bild speichern' self.zeichenflaeche_draufsicht.figure.savefig(dir, \ dpi = 1200, facecolor = self.farbe_zeichenflaeche) 'Dateiname und Pfad' dateiname = 'bild_seitenansicht.svg' dir = path.join(workdir, 'speicher', 'bildspeicher', dateiname) 'Seitenansicht als Bild speichern' self.zeichenflaeche_seitenansicht.figure.savefig(dir, \ dpi = 1200, facecolor = self.farbe_zeichenflaeche) '''Methode geisterstunde - Bei der Bewegung des digitalen Roboters wird die zuletzt gespeicherte Position als Schatten eingeblendet. Die Methode ermöglicht das Ein- oder Ausblenden des Geistes.''' def geisterstunde(self, b): self.animation_draufsicht.geisterstunde(b) self.animation_seitenansicht.geisterstunde(b) self.animation_greifer.geisterstunde(b) '''Methode greifer_laden - Die Methode lädt den Öffnungsradius des Greifers (Zeilenvektor vom Format 1x1) und gibt diesen als Gleit- kommazahl zurück. Da sich der Greifer synchron öffnet und schließt sind die Radien von Punkt6 und Punkt 7 gleich.''' def greifer_laden(self, name): 'Arbeitsverzeichnis' workdir = getcwd() 'Dateiname und Pfad' dateiname = name + '_greifer.npy' dir = path.join(workdir, 'speicher', 'programmspeicher', dateiname) 'Öffnungsradius laden' greifer = load(dir) 'Datentyp ändern' r6 = float(greifer) r7 = r6 return r6, r7 '''Methode koordinaten - Die Methode zerlegt einen Spaltenvektor in die Koordinaten und gibt diese als Gleitkommazahl zurück.''' def koordinaten(self, r): 'Format des Vektors' zeilenzahl, spaltenzahl = r.shape 'Vektor in Zeilen zerlegen' zeilen = vsplit(r, zeilenzahl) 'Koordinaten zuordnen' x = float(zeilen[0]) y = float(zeilen[1]) z = float(zeilen[2]) return x, y, z '''Methode koordinaten_draufsicht - Die Bewegung des digitalen Roboters ändert die x0,y0-Koordinaten der Punkte 2, 4 und 5. Diese werden der Methode übergeben. Die z0-Koordinaten der Punkte 2, 4 und 5, die 1-Koordinaten der Punkte 2, 3, 4, und 5 sowie die die 4z-Koordinaten der Punkte 6 und 7 bleiben unverändert. In Folge sind die x0,y0-Koord- inaten der drei Punkte und der Winkel theta1 zu aktualisieren sowie die von theta1 abhängigen Transformationsvorschriften und die 0-Koordinaten von Punkt3 neu zu berechnen.''' def koordinaten_draufsicht(self, xP2, yP2, xP4, yP4, xP5, yP5): 'Mittelpunktkoordinaten aktualisieren' self.xP2in0 = xP2 self.yP2in0 = yP2 self.xP4in0 = xP4 self.yP4in0 = yP4 self.xP5in0 = xP5 self.yP5in0 = yP5 'Theta1 (Denavit-Hartenberg-Parameter) aktualisieren' self.theta1 = self.animation_draufsicht.theta1 'Transformation = f(theta1)' 'Neuberechnung der Transformtionsmatrizen Aij' self.A01 = A01(self.theta1) self.A02 = A02(self.theta1, self.theta2) self.A03 = A03(self.theta1, self.theta2, self.theta3) self.A04 = A04(self.theta1, self.theta2, self.theta3, self.theta4) self.A05 = A05(self.theta1, self.theta2, self.theta3, self.theta4, \ self.theta5) 'Neuberechnung der Mittelpunktkoordinaten' 'Ortsvektor von Punkt3' #in 1-Koordinaten rP3in1 = array([[self.xP3in1], [self.yP3in1], [self.zP3in1], [1]]) #in 0-Koordinaten rP3in0 = around(dot(self.A01, rP3in1), 3) 'Koordinaten von Punkt3' #in 0-Koordinaten self.xP3in0, self.yP3in0, self.zP3in0 = self.koordinaten(rP3in0) 'Lagekoordinaten des Werkzeugkoordinatensystems' x5in0 = int(self.xP5in0) y5in0 = int(self.yP5in0) z5in0 = int(self.zP5in0) 'Signal zum Aktualisieren der Lagekoordinaten senden' self.signal_koordinaten.emit(x5in0, y5in0, z5in0) 'Signal, das die Bewegung des digitalen Roboters signalisiert, senden' self.signal_animation_aktiv.emit(True) '''Methode koordinaten_greifer - Die Bewegung des Greifers ändert die x4z,y4z-Koordinaten der Punkte 6 und 7. Diese werden der Methode übergeben. Die 4z-Koordinaten der Punkte 6 und 7 bleiben unverändert. In Folge sind die x4z,y4z-Koordinaten der beiden Punkte und der Winkel theta5 zu aktualisieren sowie die von theta5 abhängigen Transformations- vorschriften neu zu berechnen.''' def koordinaten_greifer(self, xP6in4z, yP6in4z, xP7in4z, yP7in4z): 'Mittelpunktkoordinaten aktualisieren' self.xP6in4z = xP6in4z self.yP6in4z = yP6in4z self.xP7in4z = xP7in4z self.yP7in4z = yP7in4z 'Theta5 (Denavit-Hartenberg-Parameter) aktualisieren' self.theta5 = self.animation_greifer.theta5 'Radien aktualisieren' self.r6 = self.animation_greifer.r6 self.r7 = self.r6 'Transformation = f(theta5)' 'Neuberechnung der Transformtionsmatrizen Aij' self.A05 = A05(self.theta1, self.theta2, self.theta3, self.theta4, \ self.theta5) self.A15 = A15(self.theta2, self.theta3, self.theta4, self.theta5) self.A45 = A45(self.theta5) 'Signal, das die Bewegung des digitalen Roboters signalisiert, senden' self.signal_animation_aktiv.emit(True) '''Methode koordinaten_seitenansicht - Die Bewegung des digitalen Roboters ändert die x1,y1-Koordinaten der Punkte 2, 3, 4, und 5. Diese werden der Methode übergeben. Die z1-Koordinaten der Punkte 2, 3, 4 und 5 und die 4z-Koordinaten der Punkte 6 und 7 bleiben erhalten. In Folge sind die x1,y1-Koordinaten der vier Punkte und die Winkel theta2, theta3 und theta4 zu aktualisieren sowie die von den Winkeln abhängigen Transformationsvorschriften und die 0-Koordinaten der vier Punkte neu zu berechnen.''' def koordinaten_seitenansicht(self, xP2in1, yP2in1, xP3in1, yP3in1, \ xP4in1, yP4in1, xP5in1, yP5in1): 'Mittelpunktkoordinaten aktualisieren' self.xP2in1 = xP2in1 self.yP2in1 = yP2in1 self.xP3in1 = xP3in1 self.yP3in1 = yP3in1 self.xP4in1 = xP4in1 self.yP4in1 = yP4in1 self.xP5in1 = xP5in1 self.yP5in1 = yP5in1 'Theta2, Theta3 und Theta4 (Denavit-Hartenberg-P.) aktualisieren' self.theta2 = self.animation_seitenansicht.theta2 self.theta3 = self.animation_seitenansicht.theta3 self.theta4 = self.animation_seitenansicht.theta4 'Transformation = f(theta2, theta3, theta4)' 'Neuberechnung der Transformationsmatrizen Aij' self.A02 = A02(self.theta1, self.theta2) self.A03 = A03(self.theta1, self.theta2, self.theta3) self.A04 = A04(self.theta1, self.theta2, self.theta3, self.theta4) self.A05 = A05(self.theta1, self.theta2, self.theta3, self.theta4, \ self.theta5) self.A12 = A12(self.theta2) self.A13 = A13(self.theta2, self.theta3) self.A14 = A14(self.theta2, self.theta3, self.theta4) self.A15 = A15(self.theta2, self.theta3, self.theta4, self.theta5) 'Neuberechnung der Mittelpunktkoordinaten' 'Ortsvektor von Punkt2' #in 1-Koordinaten rP2in1 = array([[self.xP2in1], [self.yP2in1], [self.zP2in1], [1]]) #in 0-Koordinaten rP2in0 = around(dot(self.A01, rP2in1), 3) 'Ortsvektor von Punkt3' #in 1-Koordinaten rP3in1 = array([[self.xP3in1], [self.yP3in1], [self.zP3in1], [1]]) #in 0-Koordinaten rP3in0 = around(dot(self.A01, rP3in1), 3) 'Ortsvektor von Punkt4' #in 1-Koordinaten rP4in1 = array([[self.xP4in1], [self.yP4in1], [self.zP4in1], [1]]) #in 0-Koordinaten rP4in0 = around(dot(self.A01, rP4in1), 3) 'Ortsvektor von Punkt5' #in 1-Koordinaten rP5in1 = array([[self.xP5in1], [self.yP5in1], [self.zP5in1], [1]]) #in 0-Koordinaten rP5in0 = around(dot(self.A01, rP5in1), 3) 'Koordinaten von Punkt2' #in 0-Koordinaten self.xP2in0, self.yP2in0, self.zP2in0 = self.koordinaten(rP2in0) 'Koordinaten von Punkt3' #in 0-Koordinaten self.xP3in0, self.yP3in0, self.zP3in0 = self.koordinaten(rP3in0) 'Koordinaten von Punkt4' #in 0-Koordinaten self.xP4in0, self.yP4in0, self.zP4in0 = self.koordinaten(rP4in0) 'Koordinaten von Punkt5' #in 0-Koordinaten self.xP5in0, self.yP5in0, self.zP5in0 = self.koordinaten(rP5in0) 'Bewegungen des digitalen Roboters synchronisieren' self.animation_draufsicht.pointG2.set_visible(True) self.animation_draufsicht.pointG4.set_visible(True) self.animation_draufsicht.pointG5.set_visible(True) self.animation_draufsicht.lineG1.set_visible(True) self.animation_draufsicht.lineG2.set_visible(True) self.animation_draufsicht.lineG3.set_visible(True) 'Draufsicht aktualisieren' self.animation_draufsicht.ansicht_aktualisieren( \ self.xP2in0, self.yP2in0, self.xP4in0, \ self.yP4in0, self.xP5in0, self.yP5in0) 'Lagekoordinaten des Werkzeugkoordinatensystems' x5in0 = int(self.xP5in0) y5in0 = int(self.yP5in0) z5in0 = int(self.zP5in0) 'Signal zum Aktualisieren der Lagekoordinaten senden' self.signal_koordinaten.emit(x5in0, y5in0, z5in0) 'Signal, das die Bewegung des digitalen Roboters signalisiert, senden' self.signal_animation_aktiv.emit(True) '''Methode koordinaten_abfragen - Die Methode gibt die aktuellen Lagekoordinaten des Werkzeugkoordinatensystems zurück.''' def koordinaten_abfragen(self): xP5in0 = self.xP5in0 yP5in0 = self.yP5in0 zP5in0 = self.zP5in0 return xP5in0, yP5in0, zP5in0 '''Methode lage_berechnen - Die Methode gibt die Lage (Zeilenvektor vom Format 1x3) und Orientierung (Zeilenvektor vom Format 1x9) des Werkzeugkoordinatensystems in 0-Koordinaten, die Winkel (Denavit- Hartenberg-Parameter) (Zeilenvektor vom Format 1x5) und den Öffnungsradius des Greifers (Zeilenvektor vom Format 1x1) zurück.''' def lage_berechnen(self): 'Geist ausblenden' self.geisterstunde(False) '''Kinematische Vorwärtstransformation zur Berechnung von Lage und Orientierung des Werkzeugkoordinatensystems in 0-Koordinaten. Die Vektoren werden im Format 3x1 zurückgegeben.''' x5in0, y5in0, z5in0, P05in0 = kinematik_vor(self.theta1, \ self.theta2, self.theta3, self.theta4, self.theta5) 'Orientierung des Werkzeugkoordinatensystems' 'Format der Einheitsvektoren anpassen' x5in0 = x5in0.transpose() #Format 1x3 y5in0 = y5in0.transpose() #Format 1x3 z5in0 = z5in0.transpose() #Format 1x3 'Orientierungsvektor erstellen' orientierung = hstack((x5in0, y5in0, z5in0)) #Format 1x9 orientierung = around(orientierung, 3) 'Lage des Werkzeugkoordinatensystems' 'Format des Lagevektors anpassen und Vektor zuweisen' lage = P05in0.transpose() #Format 1x3 lage = around(lage, 3) 'Winkelvektor erstellen' winkel = array([[self.theta1, self.theta2, \ self.theta3, self.theta4, self.theta5]]) #Format 1x5 winkel = around(winkel, 3) 'Öffnungsradius des Greifers' greifer = array([[self.r6]]) #Format 1x1 return orientierung, lage, winkel, greifer '''Methode modellrechnung - Die Methode berechnet aus der Lage und Orientierung des Werkzeugkoordinatensystems in 0-Koordinaten sowie dem Öffnungsradius des Greifers die Mittelpunktkoordinaten der Circle- Objekte. Insgesamt gibt es sieben Circle-Objekte und drei Ansichten. Die Draufsicht ist die Projektion des digitalen Roboters auf die x0,y0-Ebene. Dargestellt werden Punkt1, Punkt2, Punkt4 und Punkt5. Die Seitenansicht ist die Projektion des digitalen Roboters auf die x1,y1-Ebene. Dargestellt werden Punkt1, Punkt2, Punkt3, Punkt4 und Punkt5. Die Ansicht des Greifers ist die Projektion auf eine Zeichenebene parallel zur x4,y4-Ebene. Dargestellt werden Punkt6 und Punkt7. Während die Mittelpunktkoordinaten von Punkt6 und Punkt 7 nur in Zeichenkoordinaten berechnet werden, werden die Koordinaten der Punkte 1 bis 5 auch in 0-Koordinaten berechnet.''' def modellrechnung(self): '''Kinematische Rückwärtstransformation zur Berechnung der Winkel (Denavit-Hartenberg-Parameter) bei gegebener Lage und Orientierung des Werkzeugkoordinatensystems''' theta = kinematik_inv(self.x5in0, self.y5in0, self.z5in0, self.P05in0) 'Drehwinkel zuordnen' self.theta1 = theta[0] self.theta2 = theta[1] self.theta3 = theta[2] self.theta4 = theta[3] self.theta5 = theta[4] 'Transformationsmatrizen Aij zur Überführung von Ki in Kj' self.A01 = A01(self.theta1) self.A02 = A02(self.theta1, self.theta2) self.A03 = A03(self.theta1, self.theta2, self.theta3) self.A04 = A04(self.theta1, self.theta2, self.theta3, self.theta4) self.A05 = A05(self.theta1, self.theta2, self.theta3, self.theta4, \ self.theta5) self.A12 = A12(self.theta2) self.A13 = A13(self.theta2, self.theta3) self.A14 = A14(self.theta2, self.theta3, self.theta4) self.A15 = A15(self.theta2, self.theta3, self.theta4, self.theta5) self.A45 = A45(self.theta5) 'Ortsvektor von Punkt2' #in 2-Koordinaten rP2in2 = array([[0], [0], [0], [1]]) #in 1-Koordinaten rP2in1 = around(dot(self.A12, rP2in2), 3) #in 0-Koordinaten rP2in0 = around(dot(self.A02, rP2in2), 3) 'Ortsvektor von Punkt3' l = self.a3 * (cos(8 * pi / 180) - sin(8 * pi / 180)) #in 3-Koordinaten rP3in3 = array([[-l * cos(8 * pi / 180)], [l * sin(8 * pi / 180)], [0], [1]]) #in 1-Koordinaten rP3in1 = around(dot(self.A13, rP3in3), 3) #in 0-Koordinaten rP3in0 = around(dot(self.A03, rP3in3), 3) 'Ortsvektor von Punk4' #in 4-Koordinaten rP4in4 = array([[0], [0], [0], [1]]) #in 1-Koordinaten rP4in1 = around(dot(self.A14, rP4in4), 3) #in 0-Koordinaten rP4in0 = around(dot(self.A04, rP4in4), 3) 'Ortsvektor von Punk5' #in 5-Koordinaten rP5in5 = array([[0], [0], [0], [1]]) #in 1-Koordinaten rP5in1 = around(dot(self.A15, rP5in5), 3) #in 0-Koordinaten rP5in0 = self.P05in0 phi6 = pi - self.theta5 phi7 = phi6 + pi 'Ortsvektor von Punkt6' #in 4z-Koordinaten rP6in4z = array([[self.r6 * cos(phi6)], [self.r6 * sin(phi6)], [0], [1]]) 'Ortsvektor von Punkt7' #in 4z-Koordinaten rP7in4z = array([[self.r7 * cos(phi7)], [self.r7 * sin(phi7)], [0], [1]]) 'Koordinaten von Punkt1' #in 1-Koordinaten self.xP1in1, self.yP1in1, self.zP1in1 = (0, 0, 0) #in 0-Koordinaten self.xP1in0, self.yP1in0, self.zP1in0 = (0, 0, 0) 'Koordinaten von Punkt2' #in 1-Koordinaten self.xP2in1, self.yP2in1, self.zP2in1 = self.koordinaten(rP2in1) #in 0-Koordinaten self.xP2in0, self.yP2in0, self.zP2in0 = self.koordinaten(rP2in0) 'Koordinaten von Punkt3' #in 1-Koordinaten self.xP3in1, self.yP3in1, self.zP3in1 = self.koordinaten(rP3in1) #in 0-Koordinaten self.xP3in0, self.yP3in0, self.zP3in0 = self.koordinaten(rP3in0) 'Koordinaten von Punkt4' #in 1-Koordinaten self.xP4in1, self.yP4in1, self.zP4in1 = self.koordinaten(rP4in1) #in 0-Koordinaten self.xP4in0, self.yP4in0, self.zP4in0 = self.koordinaten(rP4in0) 'Koordinaten von Punkt5' #in 1-Koordinaten self.xP5in1, self.yP5in1, self.zP5in1 = self.koordinaten(rP5in1) #in 0-Koordinaten self.xP5in0, self.yP5in0, self.zP5in0 = self.koordinaten(rP5in0) 'Zeichenkoordinaten von Punkt6' #in 4z-Koordinaten self.xP6in4z, self.yP6in4z, self.zP6in4z = self.koordinaten(rP6in4z) 'Zeichenkoordinaten von Punkt7' #in 4z-Koordinaten self.xP7in4z, self.yP7in4z, self.zP7in4z = self.koordinaten(rP7in4z) '''Methode orientierung_laden - Die Methode lädt die Orientierung des Werkzeugkoordinatensystems in 0-Koordinaten (Zeilenvektor vom Format 1x9) und gibt die Einheitsvektoren des Werkzeugkoordinatensystems als Spaltenvektoren vom Format 4x1 zurück.''' def orientierung_laden(self, name): 'Arbeitsverzeichnis' workdir = getcwd() 'Dateiname und Pfad' dateiname = name + '_orientierung.npy' dir = path.join(workdir, 'speicher', 'programmspeicher', dateiname) 'Orientierung laden' orientierung = load(dir) 'Einheitsvektoren zuordnen' x5in0, y5in0, z5in0 = hsplit(orientierung, 3) #Format 1x3 'Format anpassen' x5in0 = x5in0.transpose() #Format 3x1 x5in0 = vstack((x5in0, array([[0]]))) #Format 4x1 y5in0 = y5in0.transpose() #Format 3x1 y5in0 = vstack((y5in0, array([[0]]))) #Format 4x1 z5in0 = z5in0.transpose() #Format 3x1 z5in0 = vstack((z5in0, array([[0]]))) #Format 4x1 return x5in0, y5in0, z5in0 '''Methode lage_laden - Die Methode lädt den Lagevektor des Werkzeugkoordinatensystems in 0-Koordinaten (Zeilenvektor vom Format 1x3) und gibt diese als Spaltenvektor vom Format 4x1 zurück.''' def lage_laden(self, name): 'Arbeitsverzeichnis' workdir = getcwd() 'Dateiname und Pfad' dateiname = name + '_lage.npy' dir = path.join(workdir, 'speicher', 'programmspeicher', dateiname) 'Lagevektor laden' lage = load(dir) 'Format anpassen' P05in0 = lage #Format 1x3 P05in0 = P05in0.transpose() #Format 3x1 P05in0 = vstack((P05in0, array([[1]]))) #Format 4x1 return P05in0
class ConfigDialog(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.contents_widget = QListWidget() self.contents_widget.setMovement(QListView.Static) self.contents_widget.setMinimumWidth(120) self.contents_widget.setMaximumWidth(120) self.contents_widget.setSpacing(1) bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Apply | QDialogButtonBox.Cancel) self.apply_btn = bbox.button(QDialogButtonBox.Apply) self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()")) self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()")) self.connect(bbox, SIGNAL("clicked(QAbstractButton*)"), self.button_clicked) self.pages_widget = QStackedWidget() self.connect(self.pages_widget, SIGNAL("currentChanged(int)"), self.current_page_changed) self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"), self.pages_widget.setCurrentIndex) self.contents_widget.setCurrentRow(0) hlayout = QHBoxLayout() hlayout.addWidget(self.contents_widget) hlayout.addWidget(self.pages_widget) hlayout.setStretch(1, 1) btnlayout = QHBoxLayout() btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addLayout(btnlayout) self.setLayout(vlayout) self.setWindowTitle("Preferences") self.setWindowIcon(get_icon("configure.png")) def get_current_index(self): """Return current page index""" return self.contents_widget.currentRow() def set_current_index(self, index): """Set current page index""" self.contents_widget.setCurrentRow(index) def accept(self): """Reimplement Qt method""" for index in range(self.pages_widget.count()): configpage = self.pages_widget.widget(index) if not configpage.is_valid(): return configpage.apply_changes() QDialog.accept(self) def button_clicked(self, button): if button is self.apply_btn: # Apply button was clicked configpage = self.pages_widget.currentWidget() if not configpage.is_valid(): return configpage.apply_changes() def current_page_changed(self, index): widget = self.pages_widget.widget(index) self.apply_btn.setVisible(widget.apply_callback is not None) self.apply_btn.setEnabled(widget.is_modified) def add_page(self, widget): self.connect(self, SIGNAL('check_settings()'), widget.check_settings) self.connect(widget, SIGNAL('show_this_page()'), lambda row=self.contents_widget.count(): self. contents_widget.setCurrentRow(row)) self.connect(widget, SIGNAL("apply_button_enabled(bool)"), self.apply_btn.setEnabled) self.pages_widget.addWidget(widget) item = QListWidgetItem(self.contents_widget) item.setIcon(widget.get_icon()) item.setText(widget.get_name()) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setSizeHint(QSize(0, 25)) def check_all_settings(self): """This method is called to check all configuration page settings after configuration dialog has been shown""" self.emit(SIGNAL('check_settings()'))
class LeapFlow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.controller = Controller() self.listener = LeapListener(self) self.controller.add_listener(self.listener) self.mode = "gallery" self.scroll = False self.direction = "" self.direction_x = 0 self.scroll_velocity = 0 self.current_index = 0 # List containing images for the gallery self.list_view = QListWidget() self.list_view.setFlow(0) self.list_view.setHorizontalScrollMode(1) self.list_view.setMouseTracking(True) self.list_view.itemClicked.connect(self.show_image) # Setting the style of the ListView, background, item selected, etc self.list_view.setStyleSheet( """ QListWidget::item:hover {background: transparent;} QListWidget::item:disabled:hover {background: transparent;} QListWidget::item:hover:!active {background: transparent;} QListWidget::item:selected:active {background: transparent;} QListWidget::item:selected:!active {background: transparent;} QListWidget::item:selected:disabled {background: transparent;} QListWidget::item:selected:!disabled {background: transparent;} QListWidget {background: #2C3539} """ ) # Image viewer self.scene = QGraphicsScene() self.viewer = QGraphicsView(self.scene) self.stackedWidget = QStackedWidget() self.stackedWidget.addWidget(self.list_view) self.stackedWidget.addWidget(self.viewer) self.setCentralWidget(self.stackedWidget) self.resize(500, 400) self.showMaximized() scan = ScanLibrary("/home/chris/Example") threads.append(scan) self.connect(scan, SIGNAL(scan.signal), self.add_images_to_list) scan.start() self.connect(self, SIGNAL("scrollChanged(bool)"), self.scroll_view) def setScroll(self, scroll): """Emit signal to scroll the view""" if self.scroll != scroll: self.scroll = scroll self.emit(SIGNAL("scrollChanged(bool)"), scroll) def scroll_view(self): """Scroll the view based on scroll velocity and direction""" x = self.direction_x * self.scroll_velocity / 100 bar_x = self.list_view.horizontalScrollBar().value() self.list_view.horizontalScrollBar().setValue(bar_x + x) def add_images_to_list(self): """To add a widget to the listview you must add a QListWidgetItem and replace with your widget""" for image in library: item = QListWidgetItem() pixmap = QPixmap.fromImage(QImage(library[image])) label = QLabel() label.setPixmap(pixmap.scaled(600, 400)) item.setSizeHint(label.sizeHint()) item.setData(0, library[image]) self.list_view.addItem(item) self.list_view.setItemWidget(item, label) def show_image(self, item): """"Display the selected image""" self.current_index = self.list_view.indexFromItem(item).row() self.scene.addPixmap((QPixmap.fromImage(QImage(item.text()))).scaled(self.viewer.size())) self.stackedWidget.setCurrentIndex(1) self.mode = "viewer" def previous_image(self): """Load previous image""" if self.current_index - 1 >= 0: self.current_index -= 1 self.load_image() def next_image(self): """Load next image""" if self.current_index + 1 <= len(library.keys()) - 1: self.current_index += 1 self.load_image() def load_image(self): """Load the image in self.current_index""" self.scene.addPixmap(QPixmap.fromImage(QImage(library[library.keys()[self.current_index]])))
class ViewSpace(QWidget): """A ViewSpace manages a stack of views, one of them is visible. The ViewSpace also has a statusbar, accessible in the status attribute. The viewChanged(View) signal is emitted when the current view for this ViewSpace changes. Also, when a ViewSpace is created (e.g. when a window is created or split), the app.viewSpaceCreated(space) signal is emitted. You can use the app.viewSpaceCreated() and the ViewSpace.viewChanged() signals to implement things on a per ViewSpace basis, e.g. in the statusbar of a ViewSpace. """ viewChanged = pyqtSignal(view_.View) def __init__(self, manager, parent=None): super(ViewSpace, self).__init__(parent) self.manager = weakref.ref(manager) self.views = [] layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) self.stack = QStackedWidget(self) layout.addWidget(self.stack) self.status = ViewStatusBar(self) self.status.setEnabled(False) layout.addWidget(self.status) app.languageChanged.connect(self.updateStatusBar) app.viewSpaceCreated(self) def activeView(self): if self.views: return self.views[-1] def document(self): """Returns the currently active document in this space. If there are no views, returns None. """ if self.views: return self.views[-1].document() def showDocument(self, doc): """Shows the document, creating a View if necessary.""" if doc is self.document(): return cur = self.activeView() for view in self.views[:-1]: if doc is view.document(): self.views.remove(view) break else: view = view_.View(doc) self.stack.addWidget(view) self.views.append(view) if cur: self.disconnectView(cur) self.connectView(view) self.stack.setCurrentWidget(view) self.updateStatusBar() def removeDocument(self, doc): active = doc is self.document() if active: self.disconnectView(self.activeView()) for view in self.views: if doc is view.document(): self.views.remove(view) view.deleteLater() break else: return if active and self.views: self.connectView(self.views[-1]) self.stack.setCurrentWidget(self.views[-1]) self.updateStatusBar() def connectView(self, view): view.installEventFilter(self) view.cursorPositionChanged.connect(self.updateCursorPosition) view.modificationChanged.connect(self.updateModificationState) view.document().urlChanged.connect(self.updateDocumentName) self.viewChanged.emit(view) def disconnectView(self, view): view.removeEventFilter(self) view.cursorPositionChanged.disconnect(self.updateCursorPosition) view.modificationChanged.disconnect(self.updateModificationState) view.document().urlChanged.disconnect(self.updateDocumentName) def eventFilter(self, view, ev): if ev.type() == QEvent.FocusIn: self.setActiveViewSpace() return False def setActiveViewSpace(self): self.manager().setActiveViewSpace(self) def updateStatusBar(self): """Update all info in the statusbar, e.g. on document change.""" if self.views: self.updateCursorPosition() self.updateModificationState() self.updateDocumentName() def updateCursorPosition(self): cur = self.activeView().textCursor() line = cur.blockNumber() + 1 try: column = cur.positionInBlock() except AttributeError: # only in very recent PyQt4 column = cur.position() - cur.block().position() self.status.pos.setText(_("Line: {line}, Col: {column}").format( line = line, column = column)) def updateModificationState(self): modified = self.document().isModified() pixmap = icons.get('document-save').pixmap(16) if modified else QPixmap() self.status.state.setPixmap(pixmap) def updateDocumentName(self): self.status.info.setText(self.document().documentName())
class LeapFlow (QMainWindow): def __init__ (self): QMainWindow.__init__ (self) self.controller = Controller () self.listener = LeapListener (self) self.controller.add_listener (self.listener) self.mode = "gallery" self.scroll = False self.direction = "" self.direction_x = 0 self.scroll_velocity = 0 self.current_index = 0 # List containing images for the gallery self.list_view = QListWidget () self.list_view.setFlow (0) self.list_view.setHorizontalScrollMode (1) self.list_view.setMouseTracking (True) self.list_view.itemClicked.connect (self.show_image) # Setting the style of the ListView, background, item selected, etc self.list_view.setStyleSheet (""" QListWidget::item:hover {background: transparent;} QListWidget::item:disabled:hover {background: transparent;} QListWidget::item:hover:!active {background: transparent;} QListWidget::item:selected:active {background: transparent;} QListWidget::item:selected:!active {background: transparent;} QListWidget::item:selected:disabled {background: transparent;} QListWidget::item:selected:!disabled {background: transparent;} QListWidget {background: #2C3539} """) # Image viewer self.scene = QGraphicsScene () self.viewer = QGraphicsView (self.scene) self.stackedWidget = QStackedWidget () self.stackedWidget.addWidget (self.list_view) self.stackedWidget.addWidget (self.viewer) self.setCentralWidget (self.stackedWidget) self.resize (500, 400) self.showMaximized () scan = ScanLibrary ("/home/chris/Example") threads.append (scan) self.connect (scan, SIGNAL (scan.signal), self.add_images_to_list) scan.start () self.connect (self, SIGNAL ("scrollChanged(bool)"), self.scroll_view) def setScroll (self, scroll): """Emit signal to scroll the view""" if (self.scroll != scroll): self.scroll = scroll self.emit (SIGNAL("scrollChanged(bool)"), scroll) def scroll_view (self): """Scroll the view based on scroll velocity and direction""" x = self.direction_x * self.scroll_velocity / 100 bar_x = self.list_view.horizontalScrollBar ().value () self.list_view.horizontalScrollBar ().setValue (bar_x + x) def add_images_to_list (self): """To add a widget to the listview you must add a QListWidgetItem and replace with your widget""" for image in library: item = QListWidgetItem () pixmap = QPixmap.fromImage (QImage (library[image])) label = QLabel () label.setPixmap (pixmap.scaled (600, 400)) item.setSizeHint (label.sizeHint ()) item.setData (0, library[image]) self.list_view.addItem (item) self.list_view.setItemWidget (item, label) def show_image (self, item): """"Display the selected image""" self.current_index = self.list_view.indexFromItem (item).row () self.scene.addPixmap ((QPixmap.fromImage (QImage (item.text()))).scaled (self.viewer.size())) self.stackedWidget.setCurrentIndex (1) self.mode = "viewer" def previous_image (self): """Load previous image""" if self.current_index - 1 >= 0: self.current_index -= 1 self.load_image () def next_image (self): """Load next image""" if self.current_index + 1 <= len(library.keys ()) - 1: self.current_index += 1 self.load_image () def load_image (self): """Load the image in self.current_index""" self.scene.addPixmap (QPixmap.fromImage (QImage (library[library.keys()[self.current_index]])))
class Preferences(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(self.tr("Configuraciones - Edis")) self.__sections = [] # Opacity effect self.effect = QGraphicsOpacityEffect() self.setGraphicsEffect(self.effect) self.animation = QPropertyAnimation(self.effect, "opacity") Edis.load_component("preferences", self) # Install sections #lint:disable from src.ui.dialogs.preferences import (environment_configuration, editor_configuration, compiler_configuration) #lint:enable self.load_ui() key_escape = QShortcut(QKeySequence(Qt.Key_Escape), self) self.connect(key_escape, SIGNAL("activated()"), self.close) self.connect(self.btn_cancel, SIGNAL("clicked()"), self.close) self.connect(self.btn_guardar, SIGNAL("clicked()"), self._save) def install_section(self, obj): self.__sections.append(obj) def load_ui(self): container = QVBoxLayout(self) box = QHBoxLayout() box.setContentsMargins(0, 0, 0, 0) box.setSpacing(0) toolbar = QToolBar() toolbar.setToolButtonStyle(3) toolbar.setOrientation(Qt.Vertical) toolbar.setIconSize(QSize(30, 30)) toolbar.setObjectName("preferencias") environment_section = toolbar.addAction(QIcon(":image/general-pref"), "Entorno") editor_section = toolbar.addAction(QIcon(":image/editor-pref"), "Editor") compiler_section = toolbar.addAction(QIcon(":image/compiler-pref"), "Compilador") self.connect(environment_section, SIGNAL("triggered()"), lambda: self.change_widget(0)) self.connect(editor_section, SIGNAL("triggered()"), lambda: self.change_widget(1)) self.connect(compiler_section, SIGNAL("triggered()"), lambda: self.change_widget(2)) # Set size for action in toolbar.actions(): widget = toolbar.widgetForAction(action) widget.setFixedSize(80, 50) box.addWidget(toolbar) self.stack = QStackedWidget() box.addWidget(self.stack) # Load sections and subsections for section in self.__sections: for name, obj in list(section.get_tabs().items()): section.install_tab(obj, name) self.stack.addWidget(section) box_buttons = QHBoxLayout() box_buttons.setMargin(10) box_buttons.setSpacing(10) box_buttons.addStretch(1) self.btn_cancel = QPushButton(self.tr("Cancelar")) self.btn_guardar = QPushButton(self.tr("Guardar")) box_buttons.addWidget(self.btn_cancel) box_buttons.addWidget(self.btn_guardar) container.addLayout(box) container.addLayout(box_buttons) def change_widget(self, index): if not self.isVisible(): self.show() self.stack.setCurrentIndex(index) def _save(self): for index in range(self.stack.count()): self.stack.widget(index).save() self.close() def close(self): super(Preferences, self).close() self.emit(SIGNAL("configurationsClose(PyQt_PyObject)"), self) def showEvent(self, event): super(Preferences, self).showEvent(event) self.animation.setDuration(400) self.animation.setStartValue(0) self.animation.setEndValue(1) self.animation.start()
class MixerPanel(QtGui.QFrame): '''A color mixer to hook up to an image. You pass the image you the panel to operate on and it operates on that image in place. You also pass a callback to be called to trigger a refresh. This callback is called every time the mixer modifies your image.''' def __init__(self, img): QtGui.QFrame.__init__(self) #self.setFrameStyle(QtGui.QFrame.Box|QtGui.QFrame.Sunken) self.img = img self.mixer = ColorMixer(self.img) self.callback = None #--------------------------------------------------------------- # ComboBox #--------------------------------------------------------------- self.combo_box_entries = ['RGB Color', 'HSV Color', 'Brightness/Contrast', 'Gamma', 'Gamma (Sigmoidal)'] self.combo_box = QtGui.QComboBox() for entry in self.combo_box_entries: self.combo_box.addItem(entry) self.combo_box.currentIndexChanged.connect(self.combo_box_changed) #--------------------------------------------------------------- # RGB color sliders #--------------------------------------------------------------- # radio buttons self.rgb_add = QtGui.QRadioButton('Additive') self.rgb_mul = QtGui.QRadioButton('Multiplicative') self.rgb_mul.toggled.connect(self.rgb_radio_changed) self.rgb_add.toggled.connect(self.rgb_radio_changed) # sliders rs = IntelligentSlider('R', 0.51, -255, self.rgb_changed) gs = IntelligentSlider('G', 0.51, -255, self.rgb_changed) bs = IntelligentSlider('B', 0.51, -255, self.rgb_changed) self.rs = rs self.gs = gs self.bs = bs self.rgb_widget = QWidget() self.rgb_widget.layout = QGridLayout(self.rgb_widget) self.rgb_widget.layout.addWidget(self.rgb_add, 0, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rgb_mul, 1, 0, 1, 3) self.rgb_widget.layout.addWidget(self.rs, 2, 0) self.rgb_widget.layout.addWidget(self.gs, 2, 1) self.rgb_widget.layout.addWidget(self.bs, 2, 2) #--------------------------------------------------------------- # HSV sliders #--------------------------------------------------------------- # radio buttons self.hsv_add = QtGui.QRadioButton('Additive') self.hsv_mul = QtGui.QRadioButton('Multiplicative') self.hsv_mul.toggled.connect(self.hsv_radio_changed) self.hsv_mul.toggled.connect(self.hsv_radio_changed) # sliders hs = IntelligentSlider('H', 0.36, -180, self.hsv_changed) ss = IntelligentSlider('S', 0.002, 0, self.hsv_changed) vs = IntelligentSlider('V', 0.002, 0, self.hsv_changed) self.hs = hs self.ss = ss self.vs = vs self.hsv_widget = QWidget() self.hsv_widget.layout = QGridLayout(self.hsv_widget) self.hsv_widget.layout.addWidget(self.hsv_add, 0, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hsv_mul, 1, 0, 1, 3) self.hsv_widget.layout.addWidget(self.hs, 2, 0) self.hsv_widget.layout.addWidget(self.ss, 2, 1) self.hsv_widget.layout.addWidget(self.vs, 2, 2) #--------------------------------------------------------------- # Brightness/Contrast sliders #--------------------------------------------------------------- # sliders cont = IntelligentSlider('x', 0.002, 0, self.bright_changed) bright = IntelligentSlider('+', 0.51, -255, self.bright_changed) self.cont = cont self.bright = bright # layout self.bright_widget = QWidget() self.bright_widget.layout = QtGui.QGridLayout(self.bright_widget) self.bright_widget.layout.addWidget(self.cont, 0, 0) self.bright_widget.layout.addWidget(self.bright, 0, 1) #----------------------------------------------------------------------- # Gamma Slider #----------------------------------------------------------------------- gamma = IntelligentSlider('gamma', 0.005, 0, self.gamma_changed) self.gamma = gamma # layout self.gamma_widget = QWidget() self.gamma_widget.layout = QtGui.QGridLayout(self.gamma_widget) self.gamma_widget.layout.addWidget(self.gamma, 0, 0) #--------------------------------------------------------------- # Sigmoid Gamma sliders #--------------------------------------------------------------- # sliders alpha = IntelligentSlider('alpha', 0.011, 1, self.sig_gamma_changed) beta = IntelligentSlider('beta', 0.012, 0, self.sig_gamma_changed) self.a_gamma = alpha self.b_gamma = beta # layout self.sig_gamma_widget = QWidget() self.sig_gamma_widget.layout = QtGui.QGridLayout(self.sig_gamma_widget) self.sig_gamma_widget.layout.addWidget(self.a_gamma, 0, 0) self.sig_gamma_widget.layout.addWidget(self.b_gamma, 0, 1) #--------------------------------------------------------------- # Buttons #--------------------------------------------------------------- self.commit_button = QtGui.QPushButton('Commit') self.commit_button.clicked.connect(self.commit_changes) self.revert_button = QtGui.QPushButton('Revert') self.revert_button.clicked.connect(self.revert_changes) #--------------------------------------------------------------- # Mixer Layout #--------------------------------------------------------------- self.sliders = QStackedWidget() self.sliders.addWidget(self.rgb_widget) self.sliders.addWidget(self.hsv_widget) self.sliders.addWidget(self.bright_widget) self.sliders.addWidget(self.gamma_widget) self.sliders.addWidget(self.sig_gamma_widget) self.layout = QtGui.QGridLayout(self) self.layout.addWidget(self.combo_box, 0, 0) self.layout.addWidget(self.sliders, 1, 0) self.layout.addWidget(self.commit_button, 2, 0) self.layout.addWidget(self.revert_button, 3, 0) #--------------------------------------------------------------- # State Initialization #--------------------------------------------------------------- self.combo_box.setCurrentIndex(0) self.rgb_mul.setChecked(True) self.hsv_mul.setChecked(True) def set_callback(self, callback): self.callback = callback def combo_box_changed(self, index): self.sliders.setCurrentIndex(index) self.reset() def rgb_radio_changed(self): self.reset() def hsv_radio_changed(self): self.reset() def reset(self): self.reset_sliders() self.mixer.set_to_stateimg() if self.callback: self.callback() def reset_sliders(self): # handle changing the conversion factors necessary if self.rgb_add.isChecked(): self.rs.set_conv_fac(0.51, -255) self.rs.set_value(0) self.gs.set_conv_fac(0.51, -255) self.gs.set_value(0) self.bs.set_conv_fac(0.51, -255) self.bs.set_value(0) else: self.rs.set_conv_fac(0.002, 0) self.rs.set_value(1.) self.gs.set_conv_fac(0.002, 0) self.gs.set_value(1.) self.bs.set_conv_fac(0.002, 0) self.bs.set_value(1.) self.hs.set_value(0) if self.hsv_add.isChecked(): self.ss.set_conv_fac(0.002, -1) self.ss.set_value(0) self.vs.set_conv_fac(0.002, -1) self.vs.set_value(0) else: self.ss.set_conv_fac(0.002, 0) self.ss.set_value(1.) self.vs.set_conv_fac(0.002, 0) self.vs.set_value(1.) self.bright.set_value(0) self.cont.set_value(1.) self.gamma.set_value(1) self.a_gamma.set_value(1) self.b_gamma.set_value(0.5) def rgb_changed(self, name, val): if name == 'R': channel = self.mixer.RED elif name == 'G': channel = self.mixer.GREEN else: channel = self.mixer.BLUE if self.rgb_mul.isChecked(): self.mixer.multiply(channel, val) elif self.rgb_add.isChecked(): self.mixer.add(channel, val) else: pass if self.callback: self.callback() def hsv_changed(self, name, val): h = self.hs.val() s = self.ss.val() v = self.vs.val() if self.hsv_mul.isChecked(): self.mixer.hsv_multiply(h, s, v) elif self.hsv_add.isChecked(): self.mixer.hsv_add(h, s, v) else: pass if self.callback: self.callback() def bright_changed(self, name, val): b = self.bright.val() c = self.cont.val() self.mixer.brightness(c, b) if self.callback: self.callback() def gamma_changed(self, name, val): self.mixer.gamma(val) if self.callback: self.callback() def sig_gamma_changed(self, name, val): ag = self.a_gamma.val() bg = self.b_gamma.val() self.mixer.sigmoid_gamma(ag, bg) if self.callback: self.callback() def commit_changes(self): self.mixer.commit_changes() self.reset_sliders() def revert_changes(self): self.mixer.revert() self.reset_sliders() if self.callback: self.callback()
class DataExportGui(QWidget): """ Manages all GUI elements in the data selection applet. This class itself is the central widget and also owns/manages the applet drawer widgets. """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def centralWidget(self): return self def appletDrawer(self): return self.drawer def menus(self): return [] def viewerControlWidget(self): return self._viewerControlWidgetStack def setImageIndex(self, index): pass def stopAndCleanUp(self): for editor in self.layerViewerGuis.values(): self.viewerStack.removeWidget(editor) editor.stopAndCleanUp() self.layerViewerGuis.clear() def imageLaneAdded(self, laneIndex): pass def imageLaneRemoved(self, laneIndex, finalLength): pass def allowLaneSelectionChange(self): return False ########################################### ########################################### def __init__(self, parentApplet, topLevelOperator): super(DataExportGui, self).__init__() self.drawer = None self.topLevelOperator = topLevelOperator self.threadRouter = ThreadRouter(self) self._thunkEventHandler = ThunkEventHandler(self) self._initAppletDrawerUic() self.initCentralUic() self.initViewerControls() self.parentApplet = parentApplet self.progressSignal = parentApplet.progressSignal self.overwrite = False @threadRoutedWithRouter(self.threadRouter) def handleNewDataset(multislot, index): # Make room in the GUI table self.batchOutputTableWidget.insertRow(index) # Update the table row data when this slot has new data # We can't bind in the row here because the row may change in the meantime. multislot[index].notifyReady(bind(self.updateTableForSlot)) if multislot[index].ready(): self.updateTableForSlot(multislot[index]) multislot[index].notifyUnready(self._updateExportButtons) multislot[index].notifyReady(self._updateExportButtons) self.topLevelOperator.ExportPath.notifyInserted(bind(handleNewDataset)) # For each dataset that already exists, update the GUI for i, subslot in enumerate(self.topLevelOperator.ExportPath): handleNewDataset(self.topLevelOperator.ExportPath, i) if subslot.ready(): self.updateTableForSlot(subslot) @threadRoutedWithRouter(self.threadRouter) def handleLaneRemoved(multislot, index, finalLength): if self.batchOutputTableWidget.rowCount() <= finalLength: return # Remove the row we don't need any more self.batchOutputTableWidget.removeRow(index) # Remove the viewer for this dataset imageMultiSlot = self.topLevelOperator.Inputs[index] if imageMultiSlot in self.layerViewerGuis.keys(): layerViewerGui = self.layerViewerGuis[imageMultiSlot] self.viewerStack.removeWidget(layerViewerGui) self._viewerControlWidgetStack.removeWidget( layerViewerGui.viewerControlWidget()) layerViewerGui.stopAndCleanUp() self.topLevelOperator.Inputs.notifyRemove(bind(handleLaneRemoved)) def _initAppletDrawerUic(self, drawerPath=None): """ Load the ui file for the applet drawer, which we own. """ if drawerPath is None: localDir = os.path.split(__file__)[0] drawerPath = os.path.join(localDir, "dataExportDrawer.ui") self.drawer = uic.loadUi(drawerPath) self.drawer.settingsButton.clicked.connect(self._chooseSettings) self.drawer.exportAllButton.clicked.connect(self.exportAllResults) self.drawer.exportAllButton.setIcon(QIcon(ilastikIcons.Save)) self.drawer.deleteAllButton.clicked.connect(self.deleteAllResults) self.drawer.deleteAllButton.setIcon(QIcon(ilastikIcons.Clear)) @threadRoutedWithRouter(self.threadRouter) def _handleNewSelectionNames(*args): input_names = self.topLevelOperator.SelectionNames.value self.drawer.inputSelectionCombo.addItems(input_names) self.topLevelOperator.SelectionNames.notifyDirty( _handleNewSelectionNames) _handleNewSelectionNames() self.drawer.inputSelectionCombo.currentIndexChanged.connect( self._handleInputComboSelectionChanged) def _handleInputComboSelectionChanged(self, index): assert index < len(self.topLevelOperator.SelectionNames.value) if self.drawer.inputSelectionCombo.currentText( ) == self.topLevelOperator.TableOnlyName.value: self.topLevelOperator.TableOnly.setValue(True) else: self.topLevelOperator.TableOnly.setValue(False) self.topLevelOperator.InputSelection.setValue(index) def initCentralUic(self): """ Load the GUI from the ui file into this class and connect it with event handlers. """ # Load the ui file into this class (find it in our own directory) localDir = os.path.split(__file__)[0] uic.loadUi(localDir + "/dataExport.ui", self) self.batchOutputTableWidget.resizeRowsToContents() self.batchOutputTableWidget.resizeColumnsToContents() self.batchOutputTableWidget.setAlternatingRowColors(True) self.batchOutputTableWidget.setShowGrid(False) self.batchOutputTableWidget.horizontalHeader().setResizeMode( 0, QHeaderView.Interactive) self.batchOutputTableWidget.horizontalHeader().resizeSection( Column.Dataset, 200) self.batchOutputTableWidget.horizontalHeader().resizeSection( Column.ExportLocation, 250) self.batchOutputTableWidget.horizontalHeader().resizeSection( Column.Action, 100) self.batchOutputTableWidget.verticalHeader().hide() # Set up handlers self.batchOutputTableWidget.itemSelectionChanged.connect( self.handleTableSelectionChange) # Set up the viewer area self.initViewerStack() self.splitter.setSizes([150, 850]) def initViewerStack(self): self.layerViewerGuis = {} self.viewerStack.addWidget(QWidget()) def initViewerControls(self): self._viewerControlWidgetStack = QStackedWidget(parent=self) def showEvent(self, event): super(DataExportGui, self).showEvent(event) self.showSelectedDataset() def hideEvent(self, event): super(DataExportGui, self).hideEvent(event) # Make sure all 'on disk' layers are discarded so we aren't using those files any more. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() def _chooseSettings(self): opExportModelOp, opSubRegion = get_model_op(self.topLevelOperator) if opExportModelOp is None: QMessageBox.information( self, "Image not ready for export", "Export isn't possible yet: No images are ready for export. " "Please configure upstream pipeline with valid settings, " "check that images were specified in the (batch) input applet and try again." ) return settingsDlg = DataExportOptionsDlg(self, opExportModelOp) if settingsDlg.exec_() == DataExportOptionsDlg.Accepted: # Copy the settings from our 'model op' into the real op setting_slots = [ opExportModelOp.RegionStart, opExportModelOp.RegionStop, opExportModelOp.InputMin, opExportModelOp.InputMax, opExportModelOp.ExportMin, opExportModelOp.ExportMax, opExportModelOp.ExportDtype, opExportModelOp.OutputAxisOrder, opExportModelOp.OutputFilenameFormat, opExportModelOp.OutputInternalPath, opExportModelOp.OutputFormat ] # Disconnect the special 'transaction' slot to prevent these # settings from triggering many calls to setupOutputs. self.topLevelOperator.TransactionSlot.disconnect() for model_slot in setting_slots: real_inslot = getattr(self.topLevelOperator, model_slot.name) if model_slot.ready(): real_inslot.setValue(model_slot.value) else: real_inslot.disconnect() # Re-connect the 'transaction' slot to apply all settings at once. self.topLevelOperator.TransactionSlot.setValue(True) # Discard the temporary model op opExportModelOp.cleanUp() opSubRegion.cleanUp() # Update the gui with the new export paths for index, slot in enumerate(self.topLevelOperator.ExportPath): self.updateTableForSlot(slot) def getSlotIndex(self, multislot, subslot): # Which index is this slot? for index, slot in enumerate(multislot): if slot == subslot: return index return -1 @threadRouted def updateTableForSlot(self, slot): """ Update the table row that corresponds to the given slot of the top-level operator (could be either input slot) """ row = self.getSlotIndex(self.topLevelOperator.ExportPath, slot) assert row != -1, "Unknown input slot!" if not self.topLevelOperator.ExportPath[row].ready() or\ not self.topLevelOperator.RawDatasetInfo[row].ready(): return try: nickname = self.topLevelOperator.RawDatasetInfo[row].value.nickname exportPath = self.topLevelOperator.ExportPath[row].value except Slot.SlotNotReadyError: # Sadly, it is possible to get here even though we checked for .ready() immediately beforehand. # That's because the graph has a diamond-shaped DAG of connections, but the graph has no transaction mechanism # (It's therefore possible for RawDatasetInfo[row] to be ready() even though it's upstream partner is NOT ready. return self.batchOutputTableWidget.setItem( row, Column.Dataset, QTableWidgetItem(decode_to_qstring(nickname, 'utf-8'))) self.batchOutputTableWidget.setItem( row, Column.ExportLocation, QTableWidgetItem(decode_to_qstring(exportPath))) exportNowButton = QPushButton("Export") exportNowButton.setToolTip("Generate individual batch output dataset.") exportNowButton.clicked.connect( bind(self.exportResultsForSlot, self.topLevelOperator[row])) self.batchOutputTableWidget.setCellWidget(row, Column.Action, exportNowButton) # Select a row if there isn't one already selected. selectedRanges = self.batchOutputTableWidget.selectedRanges() if len(selectedRanges) == 0: self.batchOutputTableWidget.selectRow(0) def setEnabledIfAlive(self, widget, enable): if not sip.isdeleted(widget): widget.setEnabled(enable) def _updateExportButtons(self, *args): """Called when at least one dataset became 'unready', so we have to disable the export button.""" all_ready = True # Enable/disable the appropriate export buttons in the table. # Use ThunkEvents to ensure that this happens in the Gui thread. for row, slot in enumerate(self.topLevelOperator.ImageToExport): all_ready &= slot.ready() export_button = self.batchOutputTableWidget.cellWidget( row, Column.Action) if export_button is not None: executable_event = ThunkEvent( partial(self.setEnabledIfAlive, export_button, slot.ready())) QApplication.instance().postEvent(self, executable_event) # Disable the "Export all" button unless all slots are ready. executable_event = ThunkEvent( partial(self.setEnabledIfAlive, self.drawer.exportAllButton, all_ready)) QApplication.instance().postEvent(self, executable_event) def handleTableSelectionChange(self): """ Any time the user selects a new item, select the whole row. """ self.selectEntireRow() self.showSelectedDataset() def selectEntireRow(self): # FIXME: There is a better way to do this... # Figure out which row is selected selectedItemRows = set() selectedRanges = self.batchOutputTableWidget.selectedRanges() for rng in selectedRanges: for row in range(rng.topRow(), rng.bottomRow() + 1): selectedItemRows.add(row) # Disconnect from selection change notifications while we do this self.batchOutputTableWidget.itemSelectionChanged.disconnect( self.handleTableSelectionChange) for row in selectedItemRows: self.batchOutputTableWidget.selectRow(row) # Reconnect now that we're finished self.batchOutputTableWidget.itemSelectionChanged.connect( self.handleTableSelectionChange) def exportSlots(self, laneViewList): try: # Set the busy flag so the workflow knows not to allow # upstream changes or shell changes while we're exporting self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() # Disable our own gui QApplication.instance().postEvent( self, ThunkEvent(partial(self.setEnabledIfAlive, self.drawer, False))) QApplication.instance().postEvent( self, ThunkEvent(partial(self.setEnabledIfAlive, self, False))) # Start with 1% so the progress bar shows up self.progressSignal.emit(0) self.progressSignal.emit(1) def signalFileProgress(slotIndex, percent): self.progressSignal.emit( (100 * slotIndex + percent) / len(laneViewList)) # Client hook self.parentApplet.prepare_for_entire_export() for i, opLaneView in enumerate(laneViewList): lane_index = self.topLevelOperator.innerOperators.index( opLaneView) logger.debug("Exporting result {}".format(i)) # If the operator provides a progress signal, use it. slotProgressSignal = opLaneView.progressSignal slotProgressSignal.subscribe(partial(signalFileProgress, i)) try: # Client hook self.parentApplet.prepare_lane_for_export(lane_index) # Export the image opLaneView.run_export() # Client hook if self.parentApplet.postprocessCanCheckForExistingFiles(): exportSuccessful = self.parentApplet.post_process_lane_export( lane_index, checkOverwriteFiles=True) if not exportSuccessful: userSelection = [None] self.showOverwriteQuestion(userSelection) if userSelection[0]: self.parentApplet.post_process_lane_export( lane_index, checkOverwriteFiles=False) else: self.parentApplet.post_process_lane_export(lane_index) except Exception as ex: if opLaneView.ExportPath.ready(): msg = "Failed to generate export file: \n" msg += opLaneView.ExportPath.value msg += "\n{}".format(ex) else: msg = "Failed to generate export file." msg += "\n{}".format(ex) log_exception(logger, msg) self.showExportError(msg) # We're finished with this file. self.progressSignal.emit(100 * (i + 1) / float(len(laneViewList))) # Client hook self.parentApplet.post_process_entire_export() # Ensure the shell knows we're really done. self.progressSignal.emit(100) except: # Cancel our progress. self.progressSignal.emit(0, True) raise finally: # We're not busy any more. Tell the workflow. self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() # Re-enable our own gui QApplication.instance().postEvent( self, ThunkEvent(partial(self.setEnabledIfAlive, self.drawer, True))) QApplication.instance().postEvent( self, ThunkEvent(partial(self.setEnabledIfAlive, self, True))) def postProcessLane(self, lane_index): """ Called immediately after the result for each lane is exported. Can be overridden by subclasses for post-processing purposes. """ pass @threadRouted def showExportError(self, msg): QMessageBox.critical(self, "Failed to export", msg) @threadRouted def showOverwriteQuestion(self, userSelection): assert isinstance(userSelection, list) reply = QMessageBox.question( self, 'Warning!', 'This filename already exists. Are you sure you want to overwrite?', QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: userSelection[0] = True else: userSelection[0] = False def exportResultsForSlot(self, opLane): # Make sure all 'on disk' layers are discarded so we aren't using those files any more. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() # Do this in a separate thread so the UI remains responsive exportThread = threading.Thread(target=bind(self.exportSlots, [opLane]), name="DataExportThread") exportThread.start() def exportAllResults(self): # Make sure all 'on disk' layers are discarded so we aren't using those files any more. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() # Do this in a separate thread so the UI remains responsive exportThread = threading.Thread(target=bind(self.exportSlots, self.topLevelOperator), name="DataExportThread") exportThread.start() def deleteAllResults(self): for innerOp in self.topLevelOperator: operatorView = innerOp operatorView.cleanupOnDiskView() pathComp = PathComponents(operatorView.ExportPath.value, operatorView.WorkingDirectory.value) if os.path.exists(pathComp.externalPath): os.remove(pathComp.externalPath) operatorView.setupOnDiskView() # we need to toggle the dirts state in order to enforce a frech dirty signal operatorView.Dirty.setValue(False) operatorView.Dirty.setValue(True) def showSelectedDataset(self): """ Show the exported file in the viewer """ # Get the selected row and corresponding slot value selectedRanges = self.batchOutputTableWidget.selectedRanges() if len(selectedRanges) == 0: return row = selectedRanges[0].topRow() # Hide all layers that come from the disk. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() # Activate the 'on disk' layers for this lane (if possible) opLane = self.topLevelOperator.getLane(row) opLane.setupOnDiskView() # Create if necessary imageMultiSlot = self.topLevelOperator.Inputs[row] if imageMultiSlot not in self.layerViewerGuis.keys(): layerViewer = self.createLayerViewer(opLane) # Maximize the x-y view by default. layerViewer.volumeEditorWidget.quadview.ensureMaximized(2) self.layerViewerGuis[imageMultiSlot] = layerViewer self.viewerStack.addWidget(layerViewer) self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget()) # Show the right one layerViewer = self.layerViewerGuis[imageMultiSlot] self.viewerStack.setCurrentWidget(layerViewer) self._viewerControlWidgetStack.setCurrentWidget( layerViewer.viewerControlWidget()) def createLayerViewer(self, opLane): """ This method provides an instance of LayerViewerGui for the given data lane. If this GUI class is subclassed, this method can be reimplemented to provide custom layer types for the exported layers. """ return DataExportLayerViewerGui(self.parentApplet, opLane)
class ConfigDialog(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self.contents_widget = QListWidget() self.contents_widget.setMovement(QListView.Static) self.contents_widget.setMinimumWidth(120) self.contents_widget.setMaximumWidth(120) self.contents_widget.setSpacing(1) bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Apply |QDialogButtonBox.Cancel) self.apply_btn = bbox.button(QDialogButtonBox.Apply) self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()")) self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()")) self.connect(bbox, SIGNAL("clicked(QAbstractButton*)"), self.button_clicked) self.pages_widget = QStackedWidget() self.connect(self.pages_widget, SIGNAL("currentChanged(int)"), self.current_page_changed) self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"), self.pages_widget.setCurrentIndex) self.contents_widget.setCurrentRow(0) hlayout = QHBoxLayout() hlayout.addWidget(self.contents_widget) hlayout.addWidget(self.pages_widget) hlayout.setStretch(1,1) btnlayout = QHBoxLayout() btnlayout.addStretch(1) btnlayout.addWidget(bbox) vlayout = QVBoxLayout() vlayout.addLayout(hlayout) vlayout.addLayout(btnlayout) self.setLayout(vlayout) self.setWindowTitle("Preferences") self.setWindowIcon(get_icon("configure.png")) def get_current_index(self): """Return current page index""" return self.contents_widget.currentRow() def set_current_index(self, index): """Set current page index""" self.contents_widget.setCurrentRow(index) def accept(self): """Reimplement Qt method""" for index in range(self.pages_widget.count()): configpage = self.pages_widget.widget(index) if not configpage.is_valid(): return configpage.apply_changes() QDialog.accept(self) def button_clicked(self, button): if button is self.apply_btn: # Apply button was clicked configpage = self.pages_widget.currentWidget() if not configpage.is_valid(): return configpage.apply_changes() def current_page_changed(self, index): widget = self.pages_widget.widget(index) self.apply_btn.setVisible(widget.apply_callback is not None) self.apply_btn.setEnabled(widget.is_modified) def add_page(self, widget): self.connect(self, SIGNAL('check_settings()'), widget.check_settings) self.connect(widget, SIGNAL('show_this_page()'), lambda row=self.contents_widget.count(): self.contents_widget.setCurrentRow(row)) self.connect(widget, SIGNAL("apply_button_enabled(bool)"), self.apply_btn.setEnabled) self.pages_widget.addWidget(widget) item = QListWidgetItem(self.contents_widget) item.setIcon(widget.get_icon()) item.setText(widget.get_name()) item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled) item.setSizeHint(QSize(0, 25)) def check_all_settings(self): """This method is called to check all configuration page settings after configuration dialog has been shown""" self.emit(SIGNAL('check_settings()'))
class Declaration(QDialog, ui_declaration.Ui_Declaration): def __init__(self, parent, noi): super(Declaration, self).__init__(parent) self.setupUi(self) self.noidec = noi self.parent = parent self.scenario = parent.scenario self.pages_widget = QStackedWidget() self.connect(self.pages_widget, SIGNAL("currentChanged(int)"), self.current_page_changed) self.connect(self.contents_widget, SIGNAL("currentRowChanged(int)"), self.pages_widget.setCurrentIndex) self.scrollArea.setWidget(self.pages_widget) self.connect(self.next_btn, SIGNAL('clicked()'), self.next_page) self.connect(self.prev_btn, SIGNAL('clicked()'), self.prev_page) self.pages = [Page01(self), Page02(self), Page03(self), Page04(self), Page05(self), Page06(self), Page07(self), PageIsf(self)] for widget in self.pages: self.add_page(widget) self.set_current_index(0) self.current_page_changed(0) def current_page_changed(self, index): nb = self.pages_widget.count() - 1 self.prev_btn.setEnabled(True) self.next_btn.setEnabled(True) if index == nb: self.next_btn.setEnabled(False) if index == 0: self.prev_btn.setEnabled(False) def next_page(self): idx = self.pages_widget.currentIndex() self.set_current_index(idx + 1) def prev_page(self): idx = self.pages_widget.currentIndex() self.set_current_index(idx - 1) def get_current_index(self): """Return current page index""" return self.contents_widget.currentRow() def set_current_index(self, index): """Set current page index""" self.contents_widget.setCurrentRow(index) self.pages_widget.setCurrentIndex(index) def accept(self): for page in self.pages: for key in page.__dict__: widget = getattr(page,key) if isinstance(widget, QSpinBox): var = str(widget.objectName()) val = widget.value() page.updateFoyer(var, val) elif isinstance(widget, QCheckBox): var = str(widget.objectName()) val = 1*(widget.checkState()>=1) page.updateFoyer(var, val) QDialog.accept(self) def add_page(self, widget): self.pages_widget.addWidget(widget) item = QListWidgetItem(self.contents_widget) item.setText(widget.get_name()) item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
class FltsSearchDockWidget(QDockWidget): """ Dock widget for showing search widgets with each widget corresponding to a search configuration. """ def __init__(self, *args, **kwargs): super(FltsSearchDockWidget, self).__init__(*args, **kwargs) self.setAllowedAreas(Qt.BottomDockWidgetArea) self._search_reg = SearchConfigurationRegistry.instance() self._stack_widget = QStackedWidget(self) self.setWidget(self._stack_widget) # Index data source to their location in the stack widget self._search_widget_idx = dict() def show_search_widget(self, data_source): """ Shows the search widget associated with the given data source. If not found then it will create a new one by using the factory method in the search configuration. :param data_source: Data source name. :type data_source: str :return: Returns True if the operation was successful else False. It will be false if the data source is not found in the registry. :rtype: bool """ config = self._search_reg.search_config(data_source) if not config: return False # Set current search widget self._set_current_widget(config) return True def _set_current_widget(self, config): # Updates dock widget based on the specified config. data_source = config.data_source # Create widget if it does not exist in the stack if not data_source in self._search_widget_idx: search_widget = config.create_widget() idx = self._stack_widget.addWidget(search_widget) self._search_widget_idx[data_source] = idx else: idx = self._search_widget_idx[data_source] self._stack_widget.setCurrentIndex(idx) # Set title self.setWindowTitle(u'Search {0}'.format(config.display_name)) def current_widget(self): """ :return: Returns the current search widget or None if there are no widgets in the stack. :rtype: QWidget """ return self._stack_widget.currentWidget() def clear(self): """ Removes all the search widgets and resets the indices. """ while self._stack_widget.count() > 0: sw = self._stack_widget.widget(0) self._stack_widget.removeWidget(sw) del sw self._search_widget_idx = dict()
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): super(ArrayEditor, self).__init__(parent) def setup_and_check(self, data, title='', xy=False, readonly=False): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.arraywidget = None self.is_record_array = data.dtype.names is not None if data.ndim > 2: self.error(self.tr("Arrays with more than 2 dimensions " "are not supported")) return False if not self.is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('string') \ and not dtn.startswith('unicode'): arr = self.tr("%1 arrays").arg(data.dtype.name) self.error(self.tr("%1 are currently not supported").arg(arr)) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(get_icon('arredit.png')) title = self.tr("Array editor") + \ "%s" % (" - "+str(title) if str(title) else "") if readonly: title += ' (' + self.tr('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if self.is_record_array: for name in data.dtype.names: self.stack.addWidget(ArrayEditorWidget(self, data[name], xy, readonly)) else: self.stack.addWidget(ArrayEditorWidget(self, data, xy, readonly)) self.arraywidget = self.stack.currentWidget() self.connect(self.stack, SIGNAL('currentChanged(int)'), self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if self.is_record_array: btn_layout.addWidget(QLabel(self.tr("Record array fields:"))) ra_combo = QComboBox(self) self.connect(ra_combo, SIGNAL('currentIndexChanged(int)'), self.stack.setCurrentIndex) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not isinstance(title, basestring): title = repr(title) text += ' - '+title names.append(text) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()")) self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()")) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, self.tr("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
def initGUI(self): self.setWindowTitle(QApplication.applicationName()) self.fmt = QFontMetrics(QFont()) layout = QVBoxLayout() toplayout = QHBoxLayout() currentLayout = QGridLayout() currentLayout.setContentsMargins(0,20,0,20) currentLayout.setHorizontalSpacing(100) currentLayout.setVerticalSpacing(20) # current song information currentLayout.addWidget(QLabel("<b>Artist</b>"),1,0) self.artist = QLabel() currentLayout.addWidget(self.artist,1,1) currentLayout.addWidget(QLabel("<b>Title</b>"),2,0) self.title = QLabel() currentLayout.addWidget(self.title,2,1) currentLayout.addWidget(QLabel("<b>Albumartist</b>"),3,0) self.albumartist = QLabel() currentLayout.addWidget(self.albumartist,3,1) currentLayout.addWidget(QLabel("<b>Album</b>"),4,0) self.album = QLabel() currentLayout.addWidget(self.album,4,1) # playlist and song position self.playlistposition = QLabel() currentLayout.addWidget(self.playlistposition,5,0) poslayout = QHBoxLayout() poslayout.setSpacing(10) self.time = QLabel("00:00") poslayout.addWidget(self.time) self.position = QSlider(Qt.Horizontal) self.position.setTracking(False) self.position.setSingleStep(10) self.position.sliderReleased.connect( self.seek) self.position.sliderMoved.connect( lambda x: self.time.setText(self.mpd.timeString(x))) poslayout.addWidget(self.position) self.length = QLabel("00:00") poslayout.addWidget(self.length) currentLayout.addLayout(poslayout,5,1) toplayout.addLayout(currentLayout) layout.addLayout(toplayout) layout.addStretch(1) self.settingsWidget = QMenu() self.consumeBtn = self.settingsWidget.addAction("Consume") self.consumeBtn.setCheckable(True) self.consumeBtn.triggered.connect( lambda x: self.mpd.consume(int(x))) self.singleBtn = self.settingsWidget.addAction("Single") self.singleBtn.setCheckable(True) self.singleBtn.triggered.connect( lambda x: self.mpd.single(int(x))) toolLayout = QHBoxLayout() self.settingsBtn = QToolButton() self.settingsBtn.setFixedSize(64,64) self.settingsBtn.setIcon( self.ih.settingsButton) self.settingsBtn.clicked.connect(self.showAdditionalControls) toolLayout.addWidget(self.settingsBtn) toolWidget = QStackedWidget() transpWidget = QWidget() transpLayout = QHBoxLayout() self.prevBtn = self.createButton( self.ih.prevButton, self.ih.prevButtonPressed) self.prevBtn.clicked.connect( lambda x: self.mpd.previous()) transpLayout.addWidget(self.prevBtn) self.playBtn = self.createCheckButton( self.ih.playButton, self.ih.pauseButton) self.playBtn.clicked.connect( self.playPressed) transpLayout.addWidget(self.playBtn) self.stopBtn = self.createButton( self.ih.stopButton, self.ih.stopButtonPressed) self.stopBtn.clicked.connect( lambda x: self.mpd.stop()) transpLayout.addWidget(self.stopBtn) self.nextBtn = self.createButton( self.ih.nextButton, self.ih.nextButtonPressed) self.nextBtn.clicked.connect( lambda x: self.mpd.next()) transpLayout.addWidget(self.nextBtn) self.shuffleBtn = self.createCheckButton( self.ih.shuffleButton, self.ih.shuffleButtonPressed) self.shuffleBtn.toggled.connect( lambda x: self.mpd.random(1) if x else self.mpd.random(0)) transpLayout.addWidget(self.shuffleBtn) self.repeatBtn = self.createCheckButton( self.ih.repeatButton, self.ih.repeatButtonPressed) self.repeatBtn.toggled.connect( lambda x: self.mpd.repeat(1) if x else self.mpd.repeat(0)) transpLayout.addWidget(self.repeatBtn) transpLayout.addSpacing(64) transpWidget.setLayout(transpLayout) toolWidget.addWidget( transpWidget) self.volume = QSlider(Qt.Horizontal) self.volume.valueChanged.connect(self.mpd.setvol) toolWidget.addWidget(self.volume) toolLayout.addWidget(toolWidget) self.volumeBtn = QToolButton() self.volumeBtn.setFixedSize(64,64) self.volumeBtn.setCheckable(True) self.volumeBtn.setIcon( self.ih.volumeButton) self.volumeBtn.toggled.connect( lambda x: toolWidget.setCurrentIndex(x)) toolLayout.addWidget(self.volumeBtn) layout.addLayout(toolLayout) self.setLayout(layout)
class GUI(object): def __init__(self, MainWindow): MainWindow.setObjectName("MainWindow") # Set size of window MainWindow.resize(800, 589) MainWindow.setFocusPolicy(QtCore.Qt.NoFocus) MainWindow.setWindowTitle("Text to Kill") self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout.setMargin(0) self.gridLayout.setObjectName("gridLayout") self.stackedWidget = QStackedWidget(self.centralwidget) self.stackedWidget.setEnabled(True) self.stackedWidget.setObjectName("stackedWidget") font = QFont() font.setFamily("Times New Roman") # Main menu page self.menuPage = QWidget() self.menuPage.setObjectName("menuPage") self.titleLabel = QLabel(self.menuPage) self.titleLabel.setGeometry(QtCore.QRect(250, 60, 300, 50)) font.setPointSize(45) self.titleLabel.setFont(font) self.titleLabel.setAlignment(QtCore.Qt.AlignCenter) self.titleLabel.setObjectName("titleLabel") self.titleLabel.setText("Text to Kill") self.subtitleLabel = QLabel(self.menuPage) self.subtitleLabel.setGeometry(QtCore.QRect(100, 140, 600, 40)) font.setPointSize(25) self.subtitleLabel.setFont(font) self.subtitleLabel.setAlignment(QtCore.Qt.AlignCenter) self.subtitleLabel.setObjectName("subtitleLabel") self.subtitleLabel.setText("The Murder Mystery Automation System") self.createButton = QPushButton(self.menuPage) self.createButton.setGeometry(QtCore.QRect(310, 260, 180, 60)) self.createButton.setObjectName("createButton") self.createButton.setText("Create Game") self.runButton = QPushButton(self.menuPage) self.runButton.setGeometry(QtCore.QRect(310, 350, 180, 60)) self.runButton.setObjectName("runButton") self.runButton.setText("Run Game") self.stackedWidget.addWidget(self.menuPage) # Create page self.createPage = QWidget() self.createPage.setObjectName("createPage") self.createTabWidget = QTabWidget(self.createPage) self.createTabWidget.setGeometry(QtCore.QRect(0, 0, 800, 600)) self.createTabWidget.setFocusPolicy(QtCore.Qt.NoFocus) self.createTabWidget.setObjectName("createTabWidget") # Create game tab self.createTab = QWidget() self.createTab.setObjectName("createTab") self.createDoneButton = QPushButton(self.createTab) self.createDoneButton.setGeometry(QtCore.QRect(580, 470, 180, 60)) self.createDoneButton.setObjectName("createDoneButton") self.createDoneButton.setText("Done") self.gameNameEdit = QLineEdit(self.createTab) self.gameNameEdit.setGeometry(QtCore.QRect(140, 20, 160, 30)) self.gameNameEdit.setObjectName("gameNameEdit") self.gameNameLabel = QLabel(self.createTab) self.gameNameLabel.setGeometry(QtCore.QRect(20, 25, 110, 20)) font.setPointSize(15) self.gameNameLabel.setFont(font) self.gameNameLabel.setAlignment(QtCore.Qt.AlignCenter) self.gameNameLabel.setObjectName("gameNameLabel") self.gameNameLabel.setText("Game name") self.line = QFrame(self.createTab) self.line.setGeometry(QtCore.QRect(20, 150, 311, 20)) self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.line.setObjectName("line") self.addCharLabel = QLabel(self.createTab) self.addCharLabel.setGeometry(QtCore.QRect(20, 180, 160, 20)) font.setPointSize(20) self.addCharLabel.setFont(font) self.addCharLabel.setAlignment(QtCore.Qt.AlignCenter) self.addCharLabel.setObjectName("addCharLabel") self.addCharLabel.setText("Add Character") self.charNameLabel = QLabel(self.createTab) self.charNameLabel.setGeometry(QtCore.QRect(20, 230, 66, 20)) font.setPointSize(15) self.charNameLabel.setFont(font) self.charNameLabel.setAlignment(QtCore.Qt.AlignCenter) self.charNameLabel.setObjectName("charNameLabel") self.charNameLabel.setText("Name") self.charNameEdit = QLineEdit(self.createTab) self.charNameEdit.setGeometry(QtCore.QRect(140, 220, 160, 30)) self.charNameEdit.setObjectName("charNameEdit") self.charAbilScroll = QListWidget(self.createTab) self.charAbilScroll.setGeometry(QtCore.QRect(140, 260, 161, 51)) self.charAbilScroll.setObjectName("charAbilScroll") self.characterTable = QTableWidget(self.createTab) self.characterTable.setGeometry(QtCore.QRect(405, 20, 381, 401)) self.characterTable.setFocusPolicy(QtCore.Qt.NoFocus) self.characterTable.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) self.characterTable.setRowCount(1) self.characterTable.setColumnCount(2) self.characterTable.setObjectName("characterTable") self.characterTable.horizontalHeader().setVisible(False) self.characterTable.horizontalHeader().setCascadingSectionResizes(False) self.characterTable.horizontalHeader().setMinimumSectionSize(50) self.characterTable.horizontalHeader().setStretchLastSection(True) self.characterTable.verticalHeader().setVisible(False) self.characterTable.verticalHeader().setDefaultSectionSize(30) self.scrollArea = QListWidget(self.createTab) self.scrollArea.setGeometry(QtCore.QRect(140, 60, 161, 71)) self.scrollArea.setObjectName("scrollArea") self.scrollArea.setSelectionMode(3) self.createSaveButton = QPushButton(self.createTab) self.createSaveButton.setGeometry(QtCore.QRect(380, 470, 180, 60)) self.createSaveButton.setObjectName("createSaveButton") self.createSaveButton.setText("Save") self.charAbilitiesLabel = QLabel(self.createTab) self.charAbilitiesLabel.setGeometry(QtCore.QRect(30, 280, 71, 20)) font.setPointSize(15) self.charAbilitiesLabel.setFont(font) self.charAbilitiesLabel.setObjectName("charAbilitiesLabel") self.charAbilitiesLabel.setText("Abilities") self.abilitiesDropdown = QComboBox(self.createTab) self.abilitiesDropdown.setGeometry(QtCore.QRect(140, 330, 151, 25)) self.abilitiesDropdown.setObjectName("abilitiesDropdown") self.addCharButton = QPushButton(self.createTab) self.addCharButton.setGeometry(QtCore.QRect(30, 370, 98, 27)) self.addCharButton.setObjectName("addCharButton") self.addCharButton.setText("Add") self.gameAbilitiesLabel = QLabel(self.createTab) self.gameAbilitiesLabel.setGeometry(QtCore.QRect(30, 80, 71, 20)) self.setGameAbilButton = QPushButton(self.createTab) self.setGameAbilButton.setGeometry(QtCore.QRect(30, 110, 71, 27)) self.setGameAbilButton.setObjectName("setGameAbilButton") self.setGameAbilButton.setText("Set") self.saveCharButton = QPushButton(self.createTab) self.saveCharButton.setGeometry(QtCore.QRect(70, 430, 180, 60)) self.saveCharButton.setObjectName("saveCharButton") self.saveCharButton.setText("Save Character") font.setPointSize(15) self.gameAbilitiesLabel.setFont(font) self.gameAbilitiesLabel.setObjectName("gameAbilitiesLabel") self.gameAbilitiesLabel.setText("Abilities") self.createTabWidget.addTab(self.createTab, "") # Setup tab widget self.setupTab = QWidget() self.setupTab.setObjectName("setupTab") self.setupDoneButton = QPushButton(self.setupTab) self.setupDoneButton.setGeometry(QtCore.QRect(580, 470, 180, 60)) self.setupDoneButton.setObjectName("setupDoneButton") self.setupDoneButton.setText("Done") self.setupTable = QTableWidget(self.setupTab) self.setupTable.setGeometry(QtCore.QRect(20, 20, 750, 400)) self.setupTable.setFocusPolicy(QtCore.Qt.TabFocus) self.setupTable.setRowCount(1) self.setupTable.setColumnCount(3) self.setupTable.setObjectName("setupTable") self.setupTable.horizontalHeader().setVisible(False) self.setupTable.horizontalHeader().setCascadingSectionResizes(False) self.setupTable.horizontalHeader().setDefaultSectionSize(187) self.setupTable.horizontalHeader().setHighlightSections(False) self.setupTable.horizontalHeader().setStretchLastSection(True) self.setupTable.verticalHeader().setVisible(False) self.setupTable.verticalHeader().setHighlightSections(False) self.setupSaveButton = QPushButton(self.setupTab) self.setupSaveButton.setGeometry(QtCore.QRect(380, 470, 180, 60)) self.setupSaveButton.setObjectName("setupSaveButton") self.setupSaveButton.setText("Save") self.createTabWidget.addTab(self.setupTab, "") self.createTabWidget.setTabText(self.createTabWidget.indexOf(self.createTab), "Create New Game") self.createTabWidget.setTabText(self.createTabWidget.indexOf(self.setupTab), "Set Up Game") self.stackedWidget.addWidget(self.createPage) # Game page self.gamePage = QWidget() self.gamePage.setObjectName("gamePage") self.gameTabWidget = QTabWidget(self.gamePage) self.gameTabWidget.setGeometry(QtCore.QRect(0, 0, 800, 600)) self.gameTabWidget.setFocusPolicy(QtCore.Qt.NoFocus) self.gameTabWidget.setObjectName("gameTabWidget") self.statusTab = QWidget() self.statusTab.setObjectName("statusTab") self.startGameButton = QPushButton(self.statusTab) self.startGameButton.setGeometry(QtCore.QRect(60, 180, 180, 60)) self.startGameButton.setObjectName("startGameButton") self.startGameButton.setText("Start Game") self.endGameButton = QPushButton(self.statusTab) self.endGameButton.setGeometry(QtCore.QRect(60, 260, 180, 60)) self.endGameButton.setObjectName("endGameButton") self.endGameButton.setText("End Game") self.loadGameLabel = QLabel(self.statusTab) self.loadGameLabel.setGeometry(QtCore.QRect(20, 65, 101, 21)) font.setPointSize(15) self.loadGameLabel.setFont(font) self.loadGameLabel.setObjectName("loadGameLabel") self.loadGameLabel.setText("Load Game") self.gameTabWidget.addTab(self.statusTab, "") self.logTab = QWidget() self.logTab.setObjectName("logTab") self.logList = QListWidget(self.logTab) self.logList.setGeometry(QtCore.QRect(30, 30, 730, 500)) self.logList.setObjectName("logList") self.gameTabWidget.addTab(self.logTab, "") self.inputTab = QWidget() self.inputTab.setObjectName("inputTab") self.gameTabWidget.addTab(self.inputTab, "") self.gameTabWidget.setTabText(self.gameTabWidget.indexOf(self.statusTab), "Game Status") self.gameTabWidget.setTabText(self.gameTabWidget.indexOf(self.logTab), "Game Log") self.gameTabWidget.setTabText(self.gameTabWidget.indexOf(self.inputTab), "Input") self.stackedWidget.addWidget(self.gamePage) self.gridLayout.addWidget(self.stackedWidget, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) self.stackedWidget.setCurrentIndex(0) self.createTabWidget.setCurrentIndex(0) self.gameTabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): pass
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): super(ArrayEditor, self).__init__(parent) def setup_and_check(self, data, title='', xy=False, readonly=False): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.arraywidget = None self.is_record_array = data.dtype.names is not None if len(data.shape) > 2: self.error( self.tr("Arrays with more than 2 dimensions " "are not supported")) return False if not self.is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('string') \ and not dtn.startswith('unicode'): arr = self.tr("%1 arrays").arg(data.dtype.name) self.error(self.tr("%1 are currently not supported").arg(arr)) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(get_icon('arredit.png')) title = self.tr("Array editor") + \ "%s" % (" - "+str(title) if str(title) else "") if readonly: title += ' (' + self.tr('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if self.is_record_array: for name in data.dtype.names: self.stack.addWidget( ArrayEditorWidget(self, data[name], xy, readonly)) else: self.stack.addWidget(ArrayEditorWidget(self, data, xy, readonly)) self.arraywidget = self.stack.currentWidget() self.connect(self.stack, SIGNAL('currentChanged(int)'), self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if self.is_record_array: btn_layout.addWidget(QLabel(self.tr("Record array fields:"))) ra_combo = QComboBox(self) self.connect(ra_combo, SIGNAL('currentIndexChanged(int)'), self.stack.setCurrentIndex) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not isinstance(title, basestring): title = repr(title) text += ' - ' + title names.append(text) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()")) self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()")) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, self.tr("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
class QgsTextAnnotationDialog(QDialog): def __init__(self, item): QDialog.__init__(self) self.gridLayout = QGridLayout(self) self.gridLayout.setObjectName(("gridLayout")) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName(("horizontalLayout")) self.mFontComboBox = QFontComboBox(self) self.mFontComboBox.setObjectName(("mFontComboBox")) self.horizontalLayout.addWidget(self.mFontComboBox) spacerItem = QSpacerItem(38, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.mFontSizeSpinBox = QSpinBox(self) self.mFontSizeSpinBox.setObjectName(("mFontSizeSpinBox")) self.horizontalLayout.addWidget(self.mFontSizeSpinBox) self.mBoldPushButton = QPushButton(self) self.mBoldPushButton.setMinimumSize(QSize(50, 0)) self.mBoldPushButton.setCheckable(True) self.mBoldPushButton.setObjectName(("mBoldPushButton")) self.horizontalLayout.addWidget(self.mBoldPushButton) self.mItalicsPushButton = QPushButton(self) self.mItalicsPushButton.setMinimumSize(QSize(50, 0)) self.mItalicsPushButton.setCheckable(True) self.mItalicsPushButton.setObjectName(("mItalicsPushButton")) self.horizontalLayout.addWidget(self.mItalicsPushButton) self.mFontColorButton = QgsColorButton(self) self.mFontColorButton.setText(("")) self.mFontColorButton.setAutoDefault(False) self.mFontColorButton.setObjectName(("mFontColorButton")) self.horizontalLayout.addWidget(self.mFontColorButton) self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) self.mButtonBox = QDialogButtonBox(self) self.mButtonBox.setOrientation(Qt.Horizontal) self.mButtonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) self.mButtonBox.setObjectName(("mButtonBox")) self.gridLayout.addWidget(self.mButtonBox, 3, 0, 1, 1) self.mTextEdit = QTextEdit(self) self.mTextEdit.setObjectName(("mTextEdit")) self.gridLayout.addWidget(self.mTextEdit, 1, 0, 1, 1) self.mStackedWidget = QStackedWidget(self) self.mStackedWidget.setObjectName(("mStackedWidget")) self.page = QWidget() self.page.setObjectName(("page")) self.mStackedWidget.addWidget(self.page) self.page_2 = QWidget() self.page_2.setObjectName(("page_2")) self.mStackedWidget.addWidget(self.page_2) self.gridLayout.addWidget(self.mStackedWidget, 2, 0, 1, 1) self.setLayout(self.gridLayout) self.mStackedWidget.setCurrentIndex(0) QObject.connect(self.mButtonBox, SIGNAL(("accepted()")), self.accept) QObject.connect(self.mButtonBox, SIGNAL(("rejected()")), self.reject) self.setTabOrder(self.mFontComboBox, self.mFontSizeSpinBox) self.setTabOrder(self.mFontSizeSpinBox, self.mBoldPushButton) self.setTabOrder(self.mBoldPushButton, self.mItalicsPushButton) self.setTabOrder(self.mItalicsPushButton, self.mFontColorButton) self.setTabOrder(self.mFontColorButton, self.mTextEdit) self.setTabOrder(self.mTextEdit, self.mButtonBox) self.setWindowTitle("Annotation text") self.mBoldPushButton.setText("B") self.mItalicsPushButton.setText("I") self.mTextDocument = None self.mItem = item self.mEmbeddedWidget = QgsAnnotationWidget(self, self.mItem ) self.mEmbeddedWidget.show() self.mStackedWidget.addWidget( self.mEmbeddedWidget ) self.mStackedWidget.setCurrentWidget( self.mEmbeddedWidget ) if ( self.mItem != None ): self.mTextDocument = self.mItem.document() self.mTextEdit.setDocument( self.mTextDocument ) self.mFontColorButton.setColorDialogTitle( "Select font color" ) self.mFontColorButton.setColorDialogOptions( QColorDialog.ShowAlphaChannel ) self.setCurrentFontPropertiesToGui() QObject.connect( self.mButtonBox, SIGNAL("accepted()"), self.applyTextToItem) # QObject.connect( self.mFontComboBox, SIGNAL( "currentFontChanged(QFont())"), self.changeCurrentFormat) self.mFontComboBox.currentFontChanged.connect(self.changeCurrentFormat) QObject.connect( self.mFontSizeSpinBox, SIGNAL( "valueChanged( int )" ), self.changeCurrentFormat ) QObject.connect( self.mBoldPushButton, SIGNAL( "toggled( bool )" ), self.changeCurrentFormat) QObject.connect( self.mItalicsPushButton, SIGNAL( "toggled( bool )" ), self.changeCurrentFormat) QObject.connect( self.mTextEdit, SIGNAL( "cursorPositionChanged()" ), self.setCurrentFontPropertiesToGui ) # QObject.connect( self.mButtonBox, SIGNAL( "accepted()" ), self.applySettingsToItem) deleteButton = QPushButton( "Delete" ) QObject.connect( deleteButton, SIGNAL( "clicked()" ), self.deleteItem ) self.mButtonBox.addButton( deleteButton, QDialogButtonBox.RejectRole ) def applyTextToItem(self): if ( self.mItem != None and self.mTextDocument !=None ): if ( self.mEmbeddedWidget != None): self.mEmbeddedWidget.apply() self.mItem.setDocument( self.mTextDocument ) self.mItem.update() def changeCurrentFormat(self): newFont = QFont() newFont.setFamily( self.mFontComboBox.currentFont().family() ) #bold if ( self.mBoldPushButton.isChecked() ): newFont.setBold( True ) else: newFont.setBold( False ) #italic if ( self.mItalicsPushButton.isChecked() ): newFont.setItalic( True ) else: newFont.setItalic( False ) #size newFont.setPointSize( self.mFontSizeSpinBox.value() ) self.mTextEdit.setCurrentFont( newFont ) #color self.mTextEdit.setTextColor( self.mFontColorButton.color() ) def on_mFontColorButton_colorChanged(self, color ): self.changeCurrentFormat() def setCurrentFontPropertiesToGui(self): self.blockAllSignals( True ) currentFont = self.mTextEdit.currentFont() self.mFontComboBox.setCurrentFont( currentFont ) self.mFontSizeSpinBox.setValue( currentFont.pointSize() ) self.mBoldPushButton.setChecked( currentFont.bold() ) self.mItalicsPushButton.setChecked( currentFont.italic() ) self.mFontColorButton.setColor( self.mTextEdit.textColor() ) self.blockAllSignals( False ) def blockAllSignals(self, block ): self.mFontComboBox.blockSignals( block ) self.mFontSizeSpinBox.blockSignals( block ) self.mBoldPushButton.blockSignals( block ) self.mItalicsPushButton.blockSignals( block ) self.mFontColorButton.blockSignals( block ) def deleteItem(self): scene = self.mItem.scene() if ( scene != None ): scene.removeItem( self.mItem ) self.mItem = None
class OWImportImages(widget.OWWidget): name = "Import Images" description = "Import images from a directory(s)" icon = "icons/ImportImages.svg" priority = 110 outputs = [("Data", Orange.data.Table)] #: list of recent paths recent_paths = settings.Setting([]) # type: List[RecentPath] currentPath = settings.Setting(None) want_main_area = False resizing_enabled = False Modality = Qt.ApplicationModal # Modality = Qt.WindowModal MaxRecentItems = 20 def __init__(self): super().__init__() #: widget's runtime state self.__state = State.NoState self._imageMeta = [] self._imageCategories = {} self.__invalidated = False self.__pendingTask = None vbox = gui.vBox(self.controlArea) hbox = gui.hBox(vbox) self.recent_cb = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=16, ) self.recent_cb.activated[int].connect(self.__onRecentActivated) icons = standard_icons(self) browseaction = QAction( "Open/Load Images", self, iconText="\N{HORIZONTAL ELLIPSIS}", icon=icons.dir_open_icon, toolTip="Select a directory from which to load the images" ) browseaction.triggered.connect(self.__runOpenDialog) reloadaction = QAction( "Reload", self, icon=icons.reload_icon, toolTip="Reload current image set" ) reloadaction.triggered.connect(self.reload) self.__actions = namespace( browse=browseaction, reload=reloadaction, ) browsebutton = QPushButton( browseaction.iconText(), icon=browseaction.icon(), toolTip=browseaction.toolTip(), clicked=browseaction.trigger ) reloadbutton = QPushButton( reloadaction.iconText(), icon=reloadaction.icon(), clicked=reloadaction.trigger, default=True, ) hbox.layout().addWidget(self.recent_cb) hbox.layout().addWidget(browsebutton) hbox.layout().addWidget(reloadbutton) self.addActions([browseaction, reloadaction]) reloadaction.changed.connect( lambda: reloadbutton.setEnabled(reloadaction.isEnabled()) ) box = gui.vBox(vbox, "Info") self.infostack = QStackedWidget() self.info_area = QLabel( text="No image set selected", wordWrap=True ) self.progress_widget = QProgressBar( minimum=0, maximum=0 ) self.cancel_button = QPushButton( "Cancel", icon=icons.cancel_icon, ) self.cancel_button.clicked.connect(self.cancel) w = QWidget() vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.addWidget(self.progress_widget) hlayout.addWidget(self.cancel_button) vlayout.addLayout(hlayout) self.pathlabel = TextLabel() self.pathlabel.setTextElideMode(Qt.ElideMiddle) self.pathlabel.setAttribute(Qt.WA_MacSmallSize) vlayout.addWidget(self.pathlabel) w.setLayout(vlayout) self.infostack.addWidget(self.info_area) self.infostack.addWidget(w) box.layout().addWidget(self.infostack) self.__initRecentItemsModel() self.__invalidated = True self.__executor = ThreadExecutor(self) QApplication.postEvent(self, QEvent(RuntimeEvent.Init)) def __initRecentItemsModel(self): if self.currentPath is not None and \ not os.path.isdir(self.currentPath): self.currentPath = None recent_paths = [] for item in self.recent_paths: if os.path.isdir(item.abspath): recent_paths.append(item) recent_paths = recent_paths[:OWImportImages.MaxRecentItems] recent_model = self.recent_cb.model() for pathitem in recent_paths: item = RecentPath_asqstandarditem(pathitem) recent_model.appendRow(item) self.recent_paths = recent_paths if self.currentPath is not None and \ os.path.isdir(self.currentPath) and self.recent_paths and \ os.path.samefile(self.currentPath, self.recent_paths[0].abspath): self.recent_cb.setCurrentIndex(0) else: self.currentPath = None self.recent_cb.setCurrentIndex(-1) self.__actions.reload.setEnabled(self.currentPath is not None) def customEvent(self, event): """Reimplemented.""" if event.type() == RuntimeEvent.Init: if self.__invalidated: try: self.start() finally: self.__invalidated = False super().customEvent(event) def __runOpenDialog(self): startdir = os.path.expanduser("~/") if self.recent_paths: startdir = self.recent_paths[0].abspath if OWImportImages.Modality == Qt.WindowModal: dlg = QFileDialog( self, "Select Top Level Directory", startdir, acceptMode=QFileDialog.AcceptOpen, modal=True, ) dlg.setFileMode(QFileDialog.Directory) dlg.setOption(QFileDialog.ShowDirsOnly) dlg.setDirectory(startdir) dlg.setAttribute(Qt.WA_DeleteOnClose) @dlg.accepted.connect def on_accepted(): dirpath = dlg.selectedFiles() if dirpath: self.setCurrentPath(dirpath[0]) self.start() dlg.open() else: dirpath = QFileDialog.getExistingDirectory( self, "Select Top Level Directory", startdir ) if dirpath: self.setCurrentPath(dirpath) self.start() def __onRecentActivated(self, index): item = self.recent_cb.itemData(index) if item is None: return assert isinstance(item, RecentPath) self.setCurrentPath(item.abspath) self.start() def __updateInfo(self): if self.__state == State.NoState: text = "No image set selected" elif self.__state == State.Processing: text = "Processing" elif self.__state == State.Done: nvalid = sum(imeta.isvalid for imeta in self._imageMeta) ncategories = len(self._imageCategories) if ncategories < 2: text = "{} images".format(nvalid) else: text = "{} images / {} categories".format(nvalid, ncategories) elif self.__state == State.Cancelled: text = "Cancelled" elif self.__state == State.Error: text = "Error state" else: assert False self.info_area.setText(text) if self.__state == State.Processing: self.infostack.setCurrentIndex(1) else: self.infostack.setCurrentIndex(0) def setCurrentPath(self, path): """ Set the current root image path to path If the path does not exists or is not a directory the current path is left unchanged Parameters ---------- path : str New root import path. Returns ------- status : bool True if the current root import path was successfully changed to path. """ if self.currentPath is not None and path is not None and \ os.path.isdir(self.currentPath) and os.path.isdir(path) and \ os.path.samefile(self.currentPath, path): return True if not os.path.exists(path): warnings.warn("'{}' does not exist".format(path), UserWarning) return False elif not os.path.isdir(path): warnings.warn("'{}' is not a directory".format(path), UserWarning) return False newindex = self.addRecentPath(path) self.recent_cb.setCurrentIndex(newindex) if newindex >= 0: self.currentPath = path else: self.currentPath = None self.__actions.reload.setEnabled(self.currentPath is not None) if self.__state == State.Processing: self.cancel() return True def addRecentPath(self, path): """ Prepend a path entry to the list of recent paths If an entry with the same path already exists in the recent path list it is moved to the first place Parameters ---------- path : str """ existing = None for pathitem in self.recent_paths: if os.path.samefile(pathitem.abspath, path): existing = pathitem break model = self.recent_cb.model() if existing is not None: selected_index = self.recent_paths.index(existing) assert model.item(selected_index).data(Qt.UserRole) is existing self.recent_paths.remove(existing) row = model.takeRow(selected_index) self.recent_paths.insert(0, existing) model.insertRow(0, row) else: item = RecentPath(path, None, None) self.recent_paths.insert(0, item) model.insertRow(0, RecentPath_asqstandarditem(item)) return 0 def __setRuntimeState(self, state): assert state in State self.setBlocking(state == State.Processing) message = "" if state == State.Processing: assert self.__state in [State.Done, State.NoState, State.Error, State.Cancelled] message = "Processing" elif state == State.Done: assert self.__state == State.Processing elif state == State.Cancelled: assert self.__state == State.Processing message = "Cancelled" elif state == State.Error: message = "Error during processing" elif state == State.NoState: message = "" else: assert False self.__state = state if self.__state == State.Processing: self.infostack.setCurrentIndex(1) else: self.infostack.setCurrentIndex(0) self.setStatusMessage(message) self.__updateInfo() def reload(self): """ Restart the image scan task """ if self.__state == State.Processing: self.cancel() self._imageMeta = [] self._imageCategories = {} self.start() def start(self): """ Start/execute the image indexing operation """ self.error() self.__invalidated = False if self.currentPath is None: return if self.__state == State.Processing: assert self.__pendingTask is not None log.info("Starting a new task while one is in progress. " "Cancel the existing task (dir:'{}')" .format(self.__pendingTask.startdir)) self.cancel() startdir = self.currentPath self.__setRuntimeState(State.Processing) report_progress = methodinvoke( self, "__onReportProgress", (object,)) task = ImageScan(startdir, report_progress=report_progress) # collect the task state in one convenient place self.__pendingTask = taskstate = namespace( task=task, startdir=startdir, future=None, watcher=None, cancelled=False, cancel=None, ) def cancel(): # Cancel the task and disconnect if taskstate.future.cancel(): pass else: taskstate.task.cancelled = True taskstate.cancelled = True try: taskstate.future.result(timeout=3) except UserInterruptError: pass except TimeoutError: log.info("The task did not stop in in a timely manner") taskstate.watcher.finished.disconnect(self.__onRunFinished) taskstate.cancel = cancel def run_image_scan_task_interupt(): try: return task.run() except UserInterruptError: # Suppress interrupt errors, so they are not logged return taskstate.future = self.__executor.submit(run_image_scan_task_interupt) taskstate.watcher = FutureWatcher(taskstate.future) taskstate.watcher.finished.connect(self.__onRunFinished) @Slot() def __onRunFinished(self): assert QThread.currentThread() is self.thread() assert self.__state == State.Processing assert self.__pendingTask is not None assert self.sender() is self.__pendingTask.watcher assert self.__pendingTask.future.done() task = self.__pendingTask self.__pendingTask = None try: image_meta = task.future.result() except Exception as err: sys.excepthook(*sys.exc_info()) state = State.Error image_meta = [] self.error(traceback.format_exc()) else: state = State.Done self.error() categories = {} for imeta in image_meta: # derive categories from the path relative to the starting dir dirname = os.path.dirname(imeta.path) relpath = os.path.relpath(dirname, task.startdir) categories[dirname] = relpath self._imageMeta = image_meta self._imageCategories = categories self.__setRuntimeState(state) self.commit() def cancel(self): """ Cancel current pending task (if any). """ if self.__state == State.Processing: assert self.__pendingTask is not None self.__pendingTask.cancel() self.__pendingTask = None self.__setRuntimeState(State.Cancelled) @Slot(object) def __onReportProgress(self, arg): # report on scan progress from a worker thread # arg must be a namespace(count: int, lastpath: str) assert QThread.currentThread() is self.thread() if self.__state == State.Processing: self.pathlabel.setText(prettyfypath(arg.lastpath)) def commit(self): """ Create and commit a Table from the collected image meta data. """ if self._imageMeta: categories = self._imageCategories if len(categories) > 1: cat_var = Orange.data.DiscreteVariable( "category", values=list(sorted(categories.values())) ) else: cat_var = None # Image name (file basename without the extension) imagename_var = Orange.data.StringVariable("image name") # Full fs path image_var = Orange.data.StringVariable("image") image_var.attributes["type"] = "image" # file size/width/height size_var = Orange.data.ContinuousVariable( "size", number_of_decimals=0) width_var = Orange.data.ContinuousVariable( "width", number_of_decimals=0) height_var = Orange.data.ContinuousVariable( "height", number_of_decimals=0) domain = Orange.data.Domain( [], [cat_var] if cat_var is not None else [], [imagename_var, image_var, size_var, width_var, height_var] ) cat_data = [] meta_data = [] for imgmeta in self._imageMeta: if imgmeta.isvalid: if cat_var is not None: category = categories.get(os.path.dirname(imgmeta.path)) cat_data.append([cat_var.to_val(category)]) else: cat_data.append([]) basename = os.path.basename(imgmeta.path) imgname, _ = os.path.splitext(basename) meta_data.append( [imgname, imgmeta.path, imgmeta.size, imgmeta.width, imgmeta.height] ) cat_data = numpy.array(cat_data, dtype=float) meta_data = numpy.array(meta_data, dtype=object) table = Orange.data.Table.from_numpy( domain, numpy.empty((len(cat_data), 0), dtype=float), cat_data, meta_data ) else: table = None self.send("Data", table) def onDeleteWidget(self): self.cancel() self.__executor.shutdown(wait=True)
class NodeWindow(QMainWindow): def __init__(self, data, scheme, parent=None): super(NodeWindow, self).__init__(parent) self.pathWidget = PathWidget(self.openWidgetByPath, data.path()) self.setStatusBar(self.pathWidget) # layout_set_sm_and_mrg(self.layout) self.cachedWidgets = {} self.currentStructuredWidget = None self.stacked = QStackedWidget(self) self.setCentralWidget(self.stacked) self.data, self.scheme = data, scheme self.data.add_set_notify(self.change_caption) self.toolbar = QToolBar() self.toolbar.addActions((self.parent().actionSave,self.parent().actionSaveAs,)) self.addToolBar(self.toolbar) self.setUnifiedTitleAndToolBarOnMac(True) self.messageBoxChanged = None self.reallyQuit = False self.change_caption() if "ExcelScheme" in self.scheme.get_meta(): actionExcelExport = QAction("Export to excel", self) self.toolbar.addAction(actionExcelExport) actionExcelExport.triggered.connect(self.excel_export) actionExcelMerge = QAction("Merge from excel", self) actionExcelMerge.triggered.connect(self.excel_import) self.toolbar.addAction(actionExcelMerge) self.tree_widget = DataTreeWidget(self.data, self) dock = QDockWidget(self) dock.setWidget(self.tree_widget) self.addDockWidget(Qt.LeftDockWidgetArea, dock) self.tree_widget.pathChanged.connect(self._open_widget_by_path) self.openWidgetByPath(Path()) def change_caption(self): changed = "" if self.data.changed: changed = "* " self.setWindowTitle("{} {}".format(changed, self.get_window_caption())) def get_window_caption(self): return os.path.basename(self.parent().save_filename or "New Data") def openWidgetByPath(self, path): self._open_widget_by_path(path) self.tree_widget.pathChange(path) def _open_widget_by_path(self, path): # #fixme # try: if path in self.cachedWidgets: # if self.currentStructuredWidget: # self.currentStructuredWidget.hide() self.currentStructuredWidget = self.cachedWidgets[path] self.stacked.setCurrentWidget(self.currentStructuredWidget) self.pathWidget.setPath(path) else: if "Type" not in path.get(self.scheme): #fimxe soon self.cachedWidgets[path] = StructuredWidget(unicode(path), path.get(self.data, reduce_sub_elements=True), path.get(self.scheme), self.openWidgetByPath, self) self.stacked.addWidget(self.cachedWidgets[path]) self._open_widget_by_path(path) else: print "" pass # except KeyError: # pass def closeEvent(self, event): if self.reallyQuit or not self.data.changed: event.accept() else: self.dialogChanged() event.ignore() def dialogChanged(self): if not self.messageBoxChanged: self.messageBoxChanged = QMessageBox("SDI", "The document has been modified.\n"+ "Do you want to save your changes?", QMessageBox.Warning, QMessageBox.Yes | QMessageBox.Default, QMessageBox.No, QMessageBox.Cancel | QMessageBox.Escape, self ) self.messageBoxChanged.setWindowModality (Qt.WindowModal ) self.messageBoxChanged.finished.connect(self.finishClose) self.messageBoxChanged.show() def finishClose(self, value): if value==QMessageBox.Yes: self.reallyQuit = self.parent().save_data() if not self.reallyQuit: return elif value==QMessageBox.No: self.reallyQuit = True elif value==QMessageBox.Cancel: return self.close() def excel_export(self): excel_filename = unicode(QFileDialog.getSaveFileName(self, "Save File", "New excel file.xls", "Excel files (*.xls)")) if excel_filename: export_to_excel(self.data, self.scheme, excel_filename) def excel_import(self): excel_filename = unicode(QFileDialog.getOpenFileName(self, "Open File", get_home_dir(), "Excel files (*.xls)")) if excel_filename: data_to_merge = import_from_excel(self.scheme, excel_filename) self.parent()._merge_data(data_to_merge)
class OWImportImages(widget.OWWidget): name = "Import Images" description = "Import images from a directory(s)" icon = "icons/ImportImages.svg" priority = 110 outputs = [("Data", Orange.data.Table)] #: list of recent paths recent_paths = settings.Setting([]) # type: List[RecentPath] currentPath = settings.Setting(None) want_main_area = False resizing_enabled = False Modality = Qt.ApplicationModal # Modality = Qt.WindowModal MaxRecentItems = 20 def __init__(self): super().__init__() #: widget's runtime state self.__state = State.NoState self._imageMeta = [] self._imageCategories = {} self.__invalidated = False self.__pendingTask = None vbox = gui.vBox(self.controlArea) hbox = gui.hBox(vbox) self.recent_cb = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=16, ) self.recent_cb.activated[int].connect(self.__onRecentActivated) icons = standard_icons(self) browseaction = QAction( "Open/Load Images", self, iconText="\N{HORIZONTAL ELLIPSIS}", icon=icons.dir_open_icon, toolTip="Select a directory from which to load the images") browseaction.triggered.connect(self.__runOpenDialog) reloadaction = QAction("Reload", self, icon=icons.reload_icon, toolTip="Reload current image set") reloadaction.triggered.connect(self.reload) self.__actions = namespace( browse=browseaction, reload=reloadaction, ) browsebutton = QPushButton(browseaction.iconText(), icon=browseaction.icon(), toolTip=browseaction.toolTip(), clicked=browseaction.trigger) reloadbutton = QPushButton( reloadaction.iconText(), icon=reloadaction.icon(), clicked=reloadaction.trigger, default=True, ) hbox.layout().addWidget(self.recent_cb) hbox.layout().addWidget(browsebutton) hbox.layout().addWidget(reloadbutton) self.addActions([browseaction, reloadaction]) reloadaction.changed.connect( lambda: reloadbutton.setEnabled(reloadaction.isEnabled())) box = gui.vBox(vbox, "Info") self.infostack = QStackedWidget() self.info_area = QLabel(text="No image set selected", wordWrap=True) self.progress_widget = QProgressBar(minimum=0, maximum=0) self.cancel_button = QPushButton( "Cancel", icon=icons.cancel_icon, ) self.cancel_button.clicked.connect(self.cancel) w = QWidget() vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.addWidget(self.progress_widget) hlayout.addWidget(self.cancel_button) vlayout.addLayout(hlayout) self.pathlabel = TextLabel() self.pathlabel.setTextElideMode(Qt.ElideMiddle) self.pathlabel.setAttribute(Qt.WA_MacSmallSize) vlayout.addWidget(self.pathlabel) w.setLayout(vlayout) self.infostack.addWidget(self.info_area) self.infostack.addWidget(w) box.layout().addWidget(self.infostack) self.__initRecentItemsModel() self.__invalidated = True self.__executor = ThreadExecutor(self) QApplication.postEvent(self, QEvent(RuntimeEvent.Init)) def __initRecentItemsModel(self): if self.currentPath is not None and \ not os.path.isdir(self.currentPath): self.currentPath = None recent_paths = [] for item in self.recent_paths: if os.path.isdir(item.abspath): recent_paths.append(item) recent_paths = recent_paths[:OWImportImages.MaxRecentItems] recent_model = self.recent_cb.model() for pathitem in recent_paths: item = RecentPath_asqstandarditem(pathitem) recent_model.appendRow(item) self.recent_paths = recent_paths if self.currentPath is not None and \ os.path.isdir(self.currentPath) and self.recent_paths and \ os.path.samefile(self.currentPath, self.recent_paths[0].abspath): self.recent_cb.setCurrentIndex(0) else: self.currentPath = None self.recent_cb.setCurrentIndex(-1) self.__actions.reload.setEnabled(self.currentPath is not None) def customEvent(self, event): """Reimplemented.""" if event.type() == RuntimeEvent.Init: if self.__invalidated: try: self.start() finally: self.__invalidated = False super().customEvent(event) def __runOpenDialog(self): startdir = os.path.expanduser("~/") if self.recent_paths: startdir = self.recent_paths[0].abspath if OWImportImages.Modality == Qt.WindowModal: dlg = QFileDialog( self, "Select Top Level Directory", startdir, acceptMode=QFileDialog.AcceptOpen, modal=True, ) dlg.setFileMode(QFileDialog.Directory) dlg.setOption(QFileDialog.ShowDirsOnly) dlg.setDirectory(startdir) dlg.setAttribute(Qt.WA_DeleteOnClose) @dlg.accepted.connect def on_accepted(): dirpath = dlg.selectedFiles() if dirpath: self.setCurrentPath(dirpath[0]) self.start() dlg.open() else: dirpath = QFileDialog.getExistingDirectory( self, "Select Top Level Directory", startdir) if dirpath: self.setCurrentPath(dirpath) self.start() def __onRecentActivated(self, index): item = self.recent_cb.itemData(index) if item is None: return assert isinstance(item, RecentPath) self.setCurrentPath(item.abspath) self.start() def __updateInfo(self): if self.__state == State.NoState: text = "No image set selected" elif self.__state == State.Processing: text = "Processing" elif self.__state == State.Done: nvalid = sum(imeta.isvalid for imeta in self._imageMeta) ncategories = len(self._imageCategories) if ncategories < 2: text = "{} images".format(nvalid) else: text = "{} images / {} categories".format(nvalid, ncategories) elif self.__state == State.Cancelled: text = "Cancelled" elif self.__state == State.Error: text = "Error state" else: assert False self.info_area.setText(text) if self.__state == State.Processing: self.infostack.setCurrentIndex(1) else: self.infostack.setCurrentIndex(0) def setCurrentPath(self, path): """ Set the current root image path to path If the path does not exists or is not a directory the current path is left unchanged Parameters ---------- path : str New root import path. Returns ------- status : bool True if the current root import path was successfully changed to path. """ if self.currentPath is not None and path is not None and \ os.path.isdir(self.currentPath) and os.path.isdir(path) and \ os.path.samefile(self.currentPath, path): return True if not os.path.exists(path): warnings.warn("'{}' does not exist".format(path), UserWarning) return False elif not os.path.isdir(path): warnings.warn("'{}' is not a directory".format(path), UserWarning) return False newindex = self.addRecentPath(path) self.recent_cb.setCurrentIndex(newindex) if newindex >= 0: self.currentPath = path else: self.currentPath = None self.__actions.reload.setEnabled(self.currentPath is not None) if self.__state == State.Processing: self.cancel() return True def addRecentPath(self, path): """ Prepend a path entry to the list of recent paths If an entry with the same path already exists in the recent path list it is moved to the first place Parameters ---------- path : str """ existing = None for pathitem in self.recent_paths: if os.path.samefile(pathitem.abspath, path): existing = pathitem break model = self.recent_cb.model() if existing is not None: selected_index = self.recent_paths.index(existing) assert model.item(selected_index).data(Qt.UserRole) is existing self.recent_paths.remove(existing) row = model.takeRow(selected_index) self.recent_paths.insert(0, existing) model.insertRow(0, row) else: item = RecentPath(path, None, None) self.recent_paths.insert(0, item) model.insertRow(0, RecentPath_asqstandarditem(item)) return 0 def __setRuntimeState(self, state): assert state in State self.setBlocking(state == State.Processing) message = "" if state == State.Processing: assert self.__state in [ State.Done, State.NoState, State.Error, State.Cancelled ] message = "Processing" elif state == State.Done: assert self.__state == State.Processing elif state == State.Cancelled: assert self.__state == State.Processing message = "Cancelled" elif state == State.Error: message = "Error during processing" elif state == State.NoState: message = "" else: assert False self.__state = state if self.__state == State.Processing: self.infostack.setCurrentIndex(1) else: self.infostack.setCurrentIndex(0) self.setStatusMessage(message) self.__updateInfo() def reload(self): """ Restart the image scan task """ if self.__state == State.Processing: self.cancel() self._imageMeta = [] self._imageCategories = {} self.start() def start(self): """ Start/execute the image indexing operation """ self.error() self.__invalidated = False if self.currentPath is None: return if self.__state == State.Processing: assert self.__pendingTask is not None log.info("Starting a new task while one is in progress. " "Cancel the existing task (dir:'{}')".format( self.__pendingTask.startdir)) self.cancel() startdir = self.currentPath self.__setRuntimeState(State.Processing) report_progress = methodinvoke(self, "__onReportProgress", (object, )) task = ImageScan(startdir, report_progress=report_progress) # collect the task state in one convenient place self.__pendingTask = taskstate = namespace( task=task, startdir=startdir, future=None, watcher=None, cancelled=False, cancel=None, ) def cancel(): # Cancel the task and disconnect if taskstate.future.cancel(): pass else: taskstate.task.cancelled = True taskstate.cancelled = True try: taskstate.future.result(timeout=3) except UserInterruptError: pass except TimeoutError: log.info("The task did not stop in in a timely manner") taskstate.watcher.finished.disconnect(self.__onRunFinished) taskstate.cancel = cancel def run_image_scan_task_interupt(): try: return task.run() except UserInterruptError: # Suppress interrupt errors, so they are not logged return taskstate.future = self.__executor.submit(run_image_scan_task_interupt) taskstate.watcher = FutureWatcher(taskstate.future) taskstate.watcher.finished.connect(self.__onRunFinished) @Slot() def __onRunFinished(self): assert QThread.currentThread() is self.thread() assert self.__state == State.Processing assert self.__pendingTask is not None assert self.sender() is self.__pendingTask.watcher assert self.__pendingTask.future.done() task = self.__pendingTask self.__pendingTask = None try: image_meta = task.future.result() except Exception as err: sys.excepthook(*sys.exc_info()) state = State.Error image_meta = [] self.error(traceback.format_exc()) else: state = State.Done self.error() categories = {} for imeta in image_meta: # derive categories from the path relative to the starting dir dirname = os.path.dirname(imeta.path) relpath = os.path.relpath(dirname, task.startdir) categories[dirname] = relpath self._imageMeta = image_meta self._imageCategories = categories self.__setRuntimeState(state) self.commit() def cancel(self): """ Cancel current pending task (if any). """ if self.__state == State.Processing: assert self.__pendingTask is not None self.__pendingTask.cancel() self.__pendingTask = None self.__setRuntimeState(State.Cancelled) @Slot(object) def __onReportProgress(self, arg): # report on scan progress from a worker thread # arg must be a namespace(count: int, lastpath: str) assert QThread.currentThread() is self.thread() if self.__state == State.Processing: self.pathlabel.setText(prettyfypath(arg.lastpath)) def commit(self): """ Create and commit a Table from the collected image meta data. """ if self._imageMeta: categories = self._imageCategories if len(categories) > 1: cat_var = Orange.data.DiscreteVariable( "category", values=list(sorted(categories.values()))) else: cat_var = None # Image name (file basename without the extension) imagename_var = Orange.data.StringVariable("image name") # Full fs path image_var = Orange.data.StringVariable("image") image_var.attributes["type"] = "image" # file size/width/height size_var = Orange.data.ContinuousVariable("size", number_of_decimals=0) width_var = Orange.data.ContinuousVariable("width", number_of_decimals=0) height_var = Orange.data.ContinuousVariable("height", number_of_decimals=0) domain = Orange.data.Domain( [], [cat_var] if cat_var is not None else [], [imagename_var, image_var, size_var, width_var, height_var]) cat_data = [] meta_data = [] for imgmeta in self._imageMeta: if imgmeta.isvalid: if cat_var is not None: category = categories.get(os.path.dirname( imgmeta.path)) cat_data.append([cat_var.to_val(category)]) else: cat_data.append([]) basename = os.path.basename(imgmeta.path) imgname, _ = os.path.splitext(basename) meta_data.append([ imgname, imgmeta.path, imgmeta.size, imgmeta.width, imgmeta.height ]) cat_data = numpy.array(cat_data, dtype=float) meta_data = numpy.array(meta_data, dtype=object) table = Orange.data.Table.from_numpy( domain, numpy.empty((len(cat_data), 0), dtype=float), cat_data, meta_data) else: table = None self.send("Data", table) def onDeleteWidget(self): self.cancel() self.__executor.shutdown(wait=True)
class DataSelectionGui(QWidget): """ Manages all GUI elements in the data selection applet. This class itself is the central widget and also owns/manages the applet drawer widgets. """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def centralWidget(self): return self def appletDrawer(self): return self._drawer def menus(self): return [] def viewerControlWidget(self): return self._viewerControlWidgetStack def setImageIndex(self, imageIndex): if imageIndex is not None: self.laneSummaryTableView.selectRow(imageIndex) for detailWidget in self._detailViewerWidgets: detailWidget.selectRow(imageIndex) def stopAndCleanUp(self): self._cleaning_up = True for editor in self.volumeEditors.values(): self.viewerStack.removeWidget(editor) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget()) editor.stopAndCleanUp() self.volumeEditors.clear() def imageLaneAdded(self, laneIndex): if len(self.laneSummaryTableView.selectedIndexes()) == 0: self.laneSummaryTableView.selectRow(laneIndex) # We don't have any real work to do because this gui initiated the lane addition in the first place if self.guiMode != GuiMode.Batch: if (len(self.topLevelOperator.DatasetGroup) != laneIndex + 1): import warnings warnings.warn( "DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.DatasetGroup), laneIndex)) def imageLaneRemoved(self, laneIndex, finalLength): # There's nothing to do here because the GUI already # handles operator resizes via slot callbacks. pass def allowLaneSelectionChange(self): return False ########################################### ########################################### class UserCancelledError(Exception): # This exception type is raised when the user cancels the # addition of dataset files in the middle of the process somewhere. # It isn't an error -- it's used for control flow. pass def __init__(self, parentApplet, dataSelectionOperator, serializer, instructionText, guiMode=GuiMode.Normal, max_lanes=None, show_axis_details=False): """ Constructor. :param dataSelectionOperator: The top-level operator. Must be of type :py:class:`OpMultiLaneDataSelectionGroup`. :param serializer: The applet's serializer. Must be of type :py:class:`DataSelectionSerializer` :param instructionText: A string to display in the applet drawer. :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``. Currently, there is no difference between normal and batch mode. :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow. If ``None``, there is no maximum. """ super(DataSelectionGui, self).__init__() self._cleaning_up = False self.parentApplet = parentApplet self._max_lanes = max_lanes self._default_h5_volumes = {} self.show_axis_details = show_axis_details self._viewerControls = QWidget() self.topLevelOperator = dataSelectionOperator self.guiMode = guiMode self.serializer = serializer self.threadRouter = ThreadRouter(self) self._initCentralUic() self._initAppletDrawerUic(instructionText) self._viewerControlWidgetStack = QStackedWidget(self) def handleImageRemove(multislot, index, finalLength): # Remove the viewer for this dataset datasetSlot = self.topLevelOperator.DatasetGroup[index] if datasetSlot in self.volumeEditors.keys(): editor = self.volumeEditors[datasetSlot] self.viewerStack.removeWidget(editor) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget()) editor.stopAndCleanUp() self.topLevelOperator.DatasetGroup.notifyRemove( bind(handleImageRemove)) opWorkflow = self.topLevelOperator.parent assert hasattr(opWorkflow.shell, 'onSaveProjectActionTriggered'), \ "This class uses the IlastikShell.onSaveProjectActionTriggered function. Did you rename it?" def _initCentralUic(self): """ Load the GUI from the ui file into this class and connect it with event handlers. """ # Load the ui file into this class (find it in our own directory) localDir = os.path.split(__file__)[0] + '/' uic.loadUi(localDir + "/dataSelection.ui", self) self._initTableViews() self._initViewerStack() self.splitter.setSizes([150, 850]) def _initAppletDrawerUic(self, instructionText): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0] + '/' self._drawer = uic.loadUi(localDir + "/dataSelectionDrawer.ui") self._drawer.instructionLabel.setText(instructionText) def _initTableViews(self): self.fileInfoTabWidget.setTabText(0, "Summary") self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator)) self.laneSummaryTableView.dataLaneSelected.connect(self.showDataset) self.laneSummaryTableView.addFilesRequested.connect(self.addFiles) self.laneSummaryTableView.addStackRequested.connect(self.addStack) self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked) # These two helper functions enable/disable an 'add files' button for a given role # based on the the max lane index for that role and the overall permitted max_lanes def _update_button_status(viewer, role_index): if self._max_lanes: viewer.setEnabled( self._findFirstEmptyLane(role_index) < self._max_lanes) def _handle_lane_added(button, role_index, lane_slot, lane_index): def _handle_role_slot_added(role_slot, added_slot_index, *args): if added_slot_index == role_index: role_slot.notifyReady( bind(_update_button_status, button, role_index)) role_slot.notifyUnready( bind(_update_button_status, button, role_index)) lane_slot[lane_index].notifyInserted(_handle_role_slot_added) self._retained = [] # Retain menus so they don't get deleted self._detailViewerWidgets = [] for roleIndex, role in enumerate( self.topLevelOperator.DatasetRoles.value): detailViewer = DatasetDetailedInfoTableView(self) detailViewer.setModel( DatasetDetailedInfoTableModel(self, self.topLevelOperator, roleIndex)) self._detailViewerWidgets.append(detailViewer) # Button detailViewer.addFilesRequested.connect( partial(self.addFiles, roleIndex)) detailViewer.addStackRequested.connect( partial(self.addStack, roleIndex)) detailViewer.addRemoteVolumeRequested.connect( partial(self.addDvidVolume, roleIndex)) # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab self.topLevelOperator.DatasetGroup.notifyInserted( bind(_handle_lane_added, detailViewer, roleIndex)) self.topLevelOperator.DatasetGroup.notifyRemoved( bind(_update_button_status, detailViewer, roleIndex)) # While we're at it, do the same for the buttons in the summary table, too self.topLevelOperator.DatasetGroup.notifyInserted( bind(_handle_lane_added, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex)) self.topLevelOperator.DatasetGroup.notifyRemoved( bind(_update_button_status, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex)) # Context menu detailViewer.replaceWithFileRequested.connect( partial(self.handleReplaceFile, roleIndex)) detailViewer.replaceWithStackRequested.connect( partial(self.addStack, roleIndex)) detailViewer.editRequested.connect( partial(self.editDatasetInfo, roleIndex)) detailViewer.resetRequested.connect( partial(self.handleClearDatasets, roleIndex)) # Drag-and-drop detailViewer.addFilesRequestedDrop.connect( partial(self.addFileNames, roleIndex=roleIndex)) # Selection handling def showFirstSelectedDataset(_roleIndex, lanes): if lanes: self.showDataset(lanes[0], _roleIndex) detailViewer.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex)) self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role) self.fileInfoTabWidget.currentChanged.connect(self.handleSwitchTabs) self.fileInfoTabWidget.setCurrentIndex(0) def handleSwitchTabs(self, tabIndex): if tabIndex < len(self._detailViewerWidgets): roleIndex = tabIndex # If summary tab is moved to the front, change this line. detailViewer = self._detailViewerWidgets[roleIndex] selectedLanes = detailViewer.selectedLanes if selectedLanes: self.showDataset(selectedLanes[0], roleIndex) def _initViewerStack(self): self.volumeEditors = {} self.viewerStack.addWidget(QWidget()) def handleRemoveLaneButtonClicked(self): """ The user clicked the "Remove" button. Remove the currently selected row(s) from both the GUI and the top-level operator. """ # Figure out which lanes to remove selectedIndexes = self.laneSummaryTableView.selectedIndexes() rows = set() for modelIndex in selectedIndexes: rows.add(modelIndex.row()) # Don't remove the last row, which is just buttons. rows.discard(self.laneSummaryTableView.model().rowCount() - 1) # Remove in reverse order so row numbers remain consistent for row in reversed(sorted(rows)): # Remove lanes from the operator. # The table model will notice the changes and update the rows accordingly. finalSize = len(self.topLevelOperator.DatasetGroup) - 1 self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize) @threadRouted def showDataset(self, laneIndex, roleIndex=None): if self._cleaning_up: return if laneIndex == -1: self.viewerStack.setCurrentIndex(0) return assert threading.current_thread().name == "MainThread" if laneIndex >= len(self.topLevelOperator.DatasetGroup): return datasetSlot = self.topLevelOperator.DatasetGroup[laneIndex] # Create if necessary if datasetSlot not in self.volumeEditors.keys(): class DatasetViewer(LayerViewerGui): def moveToTop(self, roleIndex): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return datasetRoles = opLaneView.DatasetRoles.value if roleIndex >= len(datasetRoles): return roleName = datasetRoles[roleIndex] try: layerIndex = [l.name for l in self.layerstack].index(roleName) except ValueError: return else: self.layerstack.selectRow(layerIndex) self.layerstack.moveSelectedToTop() def setupLayers(self): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return [] layers = [] datasetRoles = opLaneView.DatasetRoles.value for roleIndex, slot in enumerate(opLaneView.ImageGroup): if slot.ready(): roleName = datasetRoles[roleIndex] layer = self.createStandardLayerFromSlot(slot) layer.name = roleName layers.append(layer) return layers opLaneView = self.topLevelOperator.getLane(laneIndex) layerViewer = DatasetViewer(self.parentApplet, opLaneView, crosshair=False) # Maximize the x-y view by default. layerViewer.volumeEditorWidget.quadview.ensureMaximized(2) self.volumeEditors[datasetSlot] = layerViewer self.viewerStack.addWidget(layerViewer) self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget()) # Show the right one viewer = self.volumeEditors[datasetSlot] displayedRole = self.fileInfoTabWidget.currentIndex() viewer.moveToTop(displayedRole) self.viewerStack.setCurrentWidget(viewer) self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget()) def handleReplaceFile(self, roleIndex, startingLane): self.addFiles(roleIndex, startingLane) def addFiles(self, roleIndex, startingLane=None): """ The user clicked the "Add File" button. Ask him to choose a file (or several) and add them to both the GUI table and the top-level operator inputs. """ # Find the directory of the most recently opened image file mostRecentImageFile = PreferencesManager().get('DataSelection', 'recent image') if mostRecentImageFile is not None: defaultDirectory = os.path.split(mostRecentImageFile)[0] else: defaultDirectory = os.path.expanduser('~') # Launch the "Open File" dialog fileNames = self.getImageFileNamesToOpen(self, defaultDirectory) # If the user didn't cancel if len(fileNames) > 0: PreferencesManager().set('DataSelection', 'recent image', fileNames[0]) try: self.addFileNames(fileNames, roleIndex, startingLane) except Exception as ex: log_exception(logger) QMessageBox.critical(self, "Error loading file", str(ex)) @classmethod def getImageFileNamesToOpen(cls, parent_window, defaultDirectory): """ Launch an "Open File" dialog to ask the user for one or more image files. """ extensions = OpDataSelection.SupportedExtensions filter_strs = ["*." + x for x in extensions] filters = ["{filt} ({filt})".format(filt=x) for x in filter_strs] filt_all_str = "Image files (" + ' '.join(filter_strs) + ')' fileNames = [] if ilastik_config.getboolean("ilastik", "debug"): # use Qt dialog in debug mode (more portable?) file_dialog = QFileDialog(parent_window, "Select Images") file_dialog.setOption(QFileDialog.DontUseNativeDialog, True) # do not display file types associated with a filter # the line for "Image files" is too long otherwise file_dialog.setFilters([filt_all_str] + filters) file_dialog.setNameFilterDetailsVisible(False) # select multiple files file_dialog.setFileMode(QFileDialog.ExistingFiles) file_dialog.setDirectory(defaultDirectory) if file_dialog.exec_(): fileNames = file_dialog.selectedFiles() else: # otherwise, use native dialog of the present platform fileNames = QFileDialog.getOpenFileNames(parent_window, "Select Images", defaultDirectory, filt_all_str) # Convert from QtString to python str fileNames = map(encode_from_qstring, fileNames) return fileNames def _findFirstEmptyLane(self, roleIndex): opTop = self.topLevelOperator # Determine the number of files this role already has # Search for the last valid value. firstNewLane = 0 for laneIndex, slot in reversed( zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)): if slot[roleIndex].ready(): firstNewLane = laneIndex + 1 break return firstNewLane def addFileNames(self, fileNames, roleIndex, startingLane=None, rois=None): """ Add the given filenames to both the GUI table and the top-level operator inputs. If startingLane is None, the filenames will be *appended* to the role's list of files. If rois is provided, it must be a list of (start,stop) tuples (one for each fileName) """ # What lanes will we touch? startingLane, endingLane = self._determineLaneRange( fileNames, roleIndex, startingLane) if startingLane is None: # Something went wrong. return # If we're only adding new lanes, NOT modifying existing lanes... adding_only = startingLane == len(self.topLevelOperator) # Create a list of DatasetInfos try: infos = self._createDatasetInfos(roleIndex, fileNames, rois) except DataSelectionGui.UserCancelledError: return # If no exception was thrown so far, set up the operator now loaded_all = self._configureOpWithInfos(roleIndex, startingLane, endingLane, infos) if loaded_all: # Now check the resulting slots. # If they should be copied to the project file, say so. self._reconfigureDatasetLocations(roleIndex, startingLane, endingLane) self._checkDataFormatWarnings(roleIndex, startingLane, endingLane) # If we succeeded in adding all images, show the first one. self.showDataset(startingLane, roleIndex) # Notify the workflow that we just added some new lanes. if adding_only: workflow = self.parentApplet.topLevelOperator.parent workflow.handleNewLanesAdded() # Notify the workflow that something that could affect applet readyness has occurred. self.parentApplet.appletStateUpdateRequested.emit() self.updateInternalPathVisiblity() def _determineLaneRange(self, fileNames, roleIndex, startingLane=None): """ Determine which lanes should be configured if the user wants to add the given fileNames to the specified role, starting at startingLane. If startingLane is None, assume the user wants to APPEND the files to the role's slots. """ if startingLane is None or startingLane == -1: startingLane = len(self.topLevelOperator.DatasetGroup) endingLane = startingLane + len(fileNames) - 1 else: assert startingLane < len(self.topLevelOperator.DatasetGroup) max_files = len(self.topLevelOperator.DatasetGroup) - \ startingLane if len(fileNames) > max_files: msg = "You selected {num_selected} files for {num_slots} "\ "slots. To add new files use the 'Add new...' option "\ "in the context menu or the button in the last row."\ .format(num_selected=len(fileNames), num_slots=max_files) QMessageBox.critical(self, "Too many files", msg) return (None, None) endingLane = min(startingLane + len(fileNames) - 1, len(self.topLevelOperator.DatasetGroup)) if self._max_lanes and endingLane >= self._max_lanes: msg = "You may not add more than {} file(s) to this workflow. Please try again.".format( self._max_lanes) QMessageBox.critical(self, "Too many files", msg) return (None, None) return (startingLane, endingLane) def _createDatasetInfos(self, roleIndex, filePaths, rois): """ Create a list of DatasetInfos for the given filePaths and rois rois may be None, in which case it is ignored. """ if rois is None: rois = [None] * len(filePaths) assert len(rois) == len(filePaths) infos = [] for filePath, roi in zip(filePaths, rois): info = self._createDatasetInfo(roleIndex, filePath, roi) infos.append(info) return infos def _createDatasetInfo(self, roleIndex, filePath, roi): """ Create a DatasetInfo object for the given filePath and roi. roi may be None, in which case it is ignored. """ cwd = self.topLevelOperator.WorkingDirectory.value datasetInfo = DatasetInfo(filePath, cwd=cwd) datasetInfo.subvolume_roi = roi # (might be None) absPath, relPath = getPathVariants(filePath, cwd) # If the file is in a totally different tree from the cwd, # then leave the path as absolute. Otherwise, override with the relative path. if relPath is not None and len(os.path.commonprefix([cwd, absPath ])) > 1: datasetInfo.filePath = relPath h5Exts = ['.ilp', '.h5', '.hdf5'] if os.path.splitext(datasetInfo.filePath)[1] in h5Exts: datasetNames = self.getPossibleInternalPaths(absPath) if len(datasetNames) == 0: raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath) elif len(datasetNames) == 1: datasetInfo.filePath += str(datasetNames[0]) else: # If exactly one of the file's datasets matches a user's previous choice, use it. if roleIndex not in self._default_h5_volumes: self._default_h5_volumes[roleIndex] = set() previous_selections = self._default_h5_volumes[roleIndex] possible_auto_selections = previous_selections.intersection( datasetNames) if len(possible_auto_selections) == 1: datasetInfo.filePath += str( list(possible_auto_selections)[0]) else: # Ask the user which dataset to choose dlg = H5VolumeSelectionDlg(datasetNames, self) if dlg.exec_() == QDialog.Accepted: selected_index = dlg.combo.currentIndex() selected_dataset = str(datasetNames[selected_index]) datasetInfo.filePath += selected_dataset self._default_h5_volumes[roleIndex].add( selected_dataset) else: raise DataSelectionGui.UserCancelledError() # Allow labels by default if this gui isn't being used for batch data. datasetInfo.allowLabels = (self.guiMode == GuiMode.Normal) return datasetInfo def _configureOpWithInfos(self, roleIndex, startingLane, endingLane, infos): """ Attempt to configure the specified role and lanes of the top-level operator with the given DatasetInfos. Returns True if all lanes were configured successfully, or False if something went wrong. """ opTop = self.topLevelOperator originalSize = len(opTop.DatasetGroup) # Resize the slot if necessary if len(opTop.DatasetGroup) < endingLane + 1: opTop.DatasetGroup.resize(endingLane + 1) # Configure each subslot for laneIndex, info in zip(range(startingLane, endingLane + 1), infos): try: self.topLevelOperator.DatasetGroup[laneIndex][ roleIndex].setValue(info) except DatasetConstraintError as ex: return_val = [False] # Give the user a chance to fix the problem self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex, return_val) if not return_val[0]: # Not successfully repaired. Roll back the changes and give up. opTop.DatasetGroup.resize(originalSize) return False except OpDataSelection.InvalidDimensionalityError as ex: opTop.DatasetGroup.resize(originalSize) QMessageBox.critical(self, "Dataset has different dimensionality", ex.message) return False except Exception as ex: msg = "Wasn't able to load your dataset into the workflow. See error log for details." log_exception(logger, msg) QMessageBox.critical(self, "Dataset Load Error", msg) opTop.DatasetGroup.resize(originalSize) return False return True def _reconfigureDatasetLocations(self, roleIndex, startingLane, endingLane): """ Check the metadata for the given slots. If the data is stored a format that is poorly optimized for 3D access, then configure it to be copied to the project file. Finally, save the project if we changed something. """ save_needed = False opTop = self.topLevelOperator for lane_index in range(startingLane, endingLane + 1): output_slot = opTop.ImageGroup[lane_index][roleIndex] if output_slot.meta.prefer_2d and 'z' in output_slot.meta.axistags: shape = numpy.array(output_slot.meta.shape) total_volume = numpy.prod(shape) # Only copy to the project file if the total volume is reasonably small if total_volume < 0.5e9: info_slot = opTop.DatasetGroup[lane_index][roleIndex] info = info_slot.value info.location = DatasetInfo.Location.ProjectInternal info_slot.setValue(info, check_changed=False) save_needed = True if save_needed: logger.info( "Some of your data cannot be accessed efficiently in 3D in its current format." " It will now be copied to the project file.") opWorkflow = self.topLevelOperator.parent opWorkflow.shell.onSaveProjectActionTriggered() def _checkDataFormatWarnings(self, roleIndex, startingLane, endingLane): warn_needed = False opTop = self.topLevelOperator for lane_index in range(startingLane, endingLane + 1): output_slot = opTop.ImageGroup[lane_index][roleIndex] if output_slot.meta.inefficient_format: warn_needed = True if warn_needed: QMessageBox.warning( self, "Inefficient Data Format", "Your data cannot be accessed efficiently in its current format. " "Check the console output for details.\n" "(For HDF5 files, be sure to enable chunking on your dataset.)" ) @threadRouted def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex, return_val=[False]): msg = "Can't use default properties for dataset:\n\n" + \ filename + "\n\n" + \ "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \ ex.message + "\n\n" + \ "Please enter valid dataset properties to continue." QMessageBox.warning(self, "Dataset Needs Correction", msg) # The success of this is 'returned' via our special out-param # (We can't return a value from this func because it is @threadRouted. successfully_repaired = self.repairDatasetInfo(info, roleIndex, laneIndex) return_val[0] = successfully_repaired def repairDatasetInfo(self, info, roleIndex, laneIndex): """Open the dataset properties editor and return True if the new properties are acceptable.""" defaultInfos = {} defaultInfos[laneIndex] = info editorDlg = DatasetInfoEditorWidget( self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos, show_axis_details=self.show_axis_details) dlg_state = editorDlg.exec_() return (dlg_state == QDialog.Accepted) @classmethod def getPossibleInternalPaths(cls, absPath, min_ndim=3, max_ndim=5): datasetNames = [] # Open the file as a read-only so we can get a list of the internal paths with h5py.File(absPath, 'r') as f: # Define a closure to collect all of the dataset names in the file. def accumulateDatasetPaths(name, val): if type(val) == h5py._hl.dataset.Dataset and min_ndim <= len( val.shape) <= max_ndim: datasetNames.append('/' + name) # Visit every group/dataset in the file f.visititems(accumulateDatasetPaths) return datasetNames def addStack(self, roleIndex, laneIndex): """ The user clicked the "Import Stack Files" button. """ stackDlg = StackFileSelectionWidget(self) stackDlg.exec_() if stackDlg.result() != QDialog.Accepted: return files = stackDlg.selectedFiles sequence_axis = stackDlg.sequence_axis if len(files) == 0: return cwd = self.topLevelOperator.WorkingDirectory.value info = DatasetInfo(os.path.pathsep.join(files), cwd=cwd) originalNumLanes = len(self.topLevelOperator.DatasetGroup) if laneIndex is None or laneIndex == -1: laneIndex = len(self.topLevelOperator.DatasetGroup) if len(self.topLevelOperator.DatasetGroup) < laneIndex + 1: self.topLevelOperator.DatasetGroup.resize(laneIndex + 1) def importStack(): self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() # Serializer will update the operator for us, which will propagate to the GUI. try: self.serializer.importStackAsLocalDataset(info, sequence_axis) try: self.topLevelOperator.DatasetGroup[laneIndex][ roleIndex].setValue(info) except DatasetConstraintError as ex: # Give the user a chance to repair the problem. filename = files[0] + "\n...\n" + files[-1] return_val = [False] self.handleDatasetConstraintError(info, filename, ex, roleIndex, laneIndex, return_val) if not return_val[0]: # Not successfully repaired. Roll back the changes and give up. self.topLevelOperator.DatasetGroup.resize( originalNumLanes) finally: self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() req = Request(importStack) req.notify_finished( lambda result: self.showDataset(laneIndex, roleIndex)) req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes)) req.submit() @threadRouted def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info): msg = "Failed to load stack due to the following error:\n{}".format( exc) msg += "\nAttempted stack files were:\n" msg += "\n".join(files) log_exception(logger, msg, exc_info) QMessageBox.critical(self, "Failed to load image stack", msg) self.topLevelOperator.DatasetGroup.resize(originalNumLanes) def handleClearDatasets(self, roleIndex, selectedRows): for row in selectedRows: self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect() # Remove all operators that no longer have any connected slots laneIndexes = range(len(self.topLevelOperator.DatasetGroup)) for laneIndex, multislot in reversed( zip(laneIndexes, self.topLevelOperator.DatasetGroup)): any_ready = False for slot in multislot: any_ready |= slot.ready() if not any_ready: self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup) - 1) # Notify the workflow that something that could affect applet readyness has occurred. self.parentApplet.appletStateUpdateRequested.emit() def editDatasetInfo(self, roleIndex, laneIndexes): editorDlg = DatasetInfoEditorWidget( self, self.topLevelOperator, roleIndex, laneIndexes, show_axis_details=self.show_axis_details) editorDlg.exec_() self.parentApplet.appletStateUpdateRequested.emit() def updateInternalPathVisiblity(self): for view in self._detailViewerWidgets: model = view.model() view.setColumnHidden(DatasetDetailedInfoColumn.InternalID, not model.hasInternalPaths()) def addDvidVolume(self, roleIndex, laneIndex): # TODO: Provide list of recently used dvid hosts, loaded from user preferences recent_hosts_pref = PreferencesManager.Setting("DataSelection", "Recent DVID Hosts") recent_hosts = recent_hosts_pref.get() if not recent_hosts: recent_hosts = ["localhost:8000"] recent_hosts = filter(lambda h: h, recent_hosts) from dvidDataSelectionBrowser import DvidDataSelectionBrowser browser = DvidDataSelectionBrowser(recent_hosts, parent=self) if browser.exec_() == DvidDataSelectionBrowser.Rejected: return if None in browser.get_selection(): QMessageBox.critical("Couldn't use your selection.") return rois = None hostname, dset_uuid, volume_name, uuid = browser.get_selection() dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format( **locals()) subvolume_roi = browser.get_subvolume_roi() # Relocate host to top of 'recent' list, and limit list to 10 items. try: i = recent_hosts.index(hostname) del recent_hosts[i] except ValueError: pass finally: recent_hosts.insert(0, hostname) recent_hosts = recent_hosts[:10] # Save pref recent_hosts_pref.set(recent_hosts) if subvolume_roi is None: self.addFileNames([dvid_url], roleIndex, laneIndex) else: # In ilastik, we display the dvid volume axes in C-order, despite the dvid convention of F-order # Transpose the subvolume roi to match # (see implementation of OpDvidVolume) start, stop = subvolume_roi start = tuple(reversed(start)) stop = tuple(reversed(stop)) self.addFileNames([dvid_url], roleIndex, laneIndex, [(start, stop)])
class SimulationPanel(QWidget): def __init__(self): QWidget.__init__(self) layout = QVBoxLayout() self._simulation_mode_combo = QComboBox() addHelpToWidget(self._simulation_mode_combo, "run/simulation_mode") self._simulation_mode_combo.currentIndexChanged.connect( self.toggleSimulationMode) simulation_mode_layout = QHBoxLayout() simulation_mode_layout.addSpacing(10) simulation_mode_layout.addWidget(QLabel("Simulation mode:"), 0, Qt.AlignVCenter) simulation_mode_layout.addWidget(self._simulation_mode_combo, 0, Qt.AlignVCenter) simulation_mode_layout.addSpacing(20) self.run_button = QToolButton() self.run_button.setIconSize(QSize(32, 32)) self.run_button.setText("Start Simulation") self.run_button.setIcon(resourceIcon("ide/gear_in_play")) self.run_button.clicked.connect(self.runSimulation) self.run_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) addHelpToWidget(self.run_button, "run/start_simulation") simulation_mode_layout.addWidget(self.run_button) simulation_mode_layout.addStretch(1) layout.addSpacing(5) layout.addLayout(simulation_mode_layout) layout.addSpacing(10) self._simulation_stack = QStackedWidget() self._simulation_stack.setLineWidth(1) self._simulation_stack.setFrameStyle(QFrame.StyledPanel) layout.addWidget(self._simulation_stack) self._simulation_widgets = {} """ :type: dict[BaseRunModel,SimulationConfigPanel]""" self.addSimulationConfigPanel(EnsembleExperimentPanel()) self.addSimulationConfigPanel(EnsembleSmootherPanel()) self.addSimulationConfigPanel(IteratedEnsembleSmootherPanel()) self.addSimulationConfigPanel(MultipleDataAssimilationPanel()) self.setLayout(layout) def addSimulationConfigPanel(self, panel): assert isinstance(panel, SimulationConfigPanel) panel.toggleAdvancedOptions(False) self._simulation_stack.addWidget(panel) simulation_model = panel.getSimulationModel() self._simulation_widgets[simulation_model] = panel self._simulation_mode_combo.addItem(str(simulation_model), simulation_model) panel.simulationConfigurationChanged.connect( self.validationStatusChanged) def getActions(self): return [] def toggleAdvancedMode(self, show_advanced): for panel in self._simulation_widgets.values(): panel.toggleAdvancedOptions(show_advanced) def getCurrentSimulationModel(self): data = self._simulation_mode_combo.itemData( self._simulation_mode_combo.currentIndex(), Qt.UserRole) return data.toPyObject() def getSimulationArguments(self): """ @rtype: dict[str,object]""" simulation_widget = self._simulation_widgets[ self.getCurrentSimulationModel()] return simulation_widget.getSimulationArguments() def runSimulation(self): case_name = getCurrentCaseName() message = "Are you sure you want to use case '%s' for initialization of the initial ensemble when running the simulations?" % case_name start_simulations = QMessageBox.question( self, "Start simulations?", message, QMessageBox.Yes | QMessageBox.No) if start_simulations == QMessageBox.Yes: run_model = self.getCurrentSimulationModel() arguments = self.getSimulationArguments() dialog = RunDialog(run_model, arguments, self) dialog.startSimulation() dialog.exec_() ERT.emitErtChange() # simulations may have added new cases. def toggleSimulationMode(self): widget = self._simulation_widgets[self.getCurrentSimulationModel()] self._simulation_stack.setCurrentWidget(widget) self.validationStatusChanged() def validationStatusChanged(self): widget = self._simulation_widgets[self.getCurrentSimulationModel()] self.run_button.setEnabled(widget.isConfigurationValid())
def __init__(self, parent, layer): ''' Constructor ''' QDialog.__init__(self, parent) self.resize(200, 200) self.rasterLayer = layer sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth()) self.setSizePolicy(sizePolicy) verticalLayout = QVBoxLayout(self) verticalLayout.setObjectName("verticalLayout") stackedWidget = QStackedWidget(self) stackedWidget.setObjectName("stackedWidget") pageRender = QWidget(stackedWidget) pageRender.setObjectName("pageRender") sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(pageRender.sizePolicy().hasHeightForWidth()) pageRender.setSizePolicy(sizePolicy) horizontalLayout = QHBoxLayout(pageRender) horizontalLayout.setObjectName("horizontalLayout") frameRender = QFrame(pageRender) frameRender.setObjectName("frameRender") frameRender.setFrameShape(QFrame.StyledPanel) frameRender.setFrameShadow(QFrame.Raised) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(frameRender.sizePolicy().hasHeightForWidth()) frameRender.setSizePolicy(sizePolicy) self.vLayoutFrameRender = QVBoxLayout(frameRender) self.vLayoutFrameRender.setObjectName("vLayoutFrameRender") horizontalLayout.addWidget(frameRender) self.cmbRendererType = ComboBoxPanel(frameRender) self.cmbRendererType.Caption = "Render Type" self.cmbRendererType.LabelWidth = 70 self.cmbRendererType.Items = ["Mutiband color", "Paletted", "Singleband gray", "Singleband pseudocolor"] self.connect(self.cmbRendererType, SIGNAL("Event_0"), self.cmbRendererType_currentIndexChanged) self.vLayoutFrameRender.addWidget(self.cmbRendererType) self.gbRenderer = GroupBox(frameRender) self.gbRenderer.Caption = self.cmbRendererType.SelectedItem self.vLayoutFrameRender.addWidget(self.gbRenderer) self.qgsMultiBandColorRendererWidget = QgsMultiBandColorRendererWidget(self.rasterLayer) self.qgsPalettedRendererWidget = QgsPalettedRendererWidget(self.rasterLayer) self.qgsSingleBandGrayRendererWidget = QgsSingleBandGrayRendererWidget(self.rasterLayer) self.qgsSingleBandPseudoColorRendererWidget = QgsSingleBandPseudoColorRendererWidget(self.rasterLayer) self.gbRenderer.Add = self.qgsMultiBandColorRendererWidget self.gbRenderer.Add = self.qgsPalettedRendererWidget self.gbRenderer.Add = self.qgsSingleBandGrayRendererWidget self.gbRenderer.Add = self.qgsSingleBandPseudoColorRendererWidget self.qgsPalettedRendererWidget.setVisible(False) self.qgsSingleBandGrayRendererWidget.setVisible(False) self.qgsSingleBandPseudoColorRendererWidget.setVisible(False) stackedWidget.addWidget(pageRender) # page_2 = QWidget() # page_2.setObjectName("page_2") # stackedWidget.addWidget(page_2) verticalLayout.addWidget(stackedWidget) buttonBox = QDialogButtonBox(self) buttonBox.setObjectName("buttonBox") buttonBox.setOrientation(Qt.Horizontal) buttonBox.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok | QDialogButtonBox.Apply) btnApply = buttonBox.button(QDialogButtonBox.Apply) btnApply.clicked.connect(self.btnApply_clicked) verticalLayout.addWidget(buttonBox) # retranslateUi(Dialog) buttonBox.accepted.connect(self.OK) buttonBox.rejected.connect(self.reject) if self.rasterLayer.renderer().bandCount() == 1: self.cmbRendererType.SelectedIndex = 2 elif self.rasterLayer.renderer().bandCount() > 1: self.cmbRendererType.SelectedIndex = 0
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.size == 0: self.error(_("Array is empty")) return False if data.ndim > 2: self.error(_("Arrays with more than 2 dimensions " "are not supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error(_("The 'xlabels' argument length " "do no match array column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error(_("The 'ylabels' argument length " "do no match array row number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('string') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(get_icon('arredit.png')) if title: title = unicode(title) # in case title is not a string else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) else: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() self.connect(self.stack, SIGNAL('currentChanged(int)'), self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not isinstance(title, basestring): title = repr(title) text += ' - '+title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] ra_combo = QComboBox(self) self.connect(ra_combo, SIGNAL('currentIndexChanged(int)'), self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel(_("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()")) self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()")) btn_layout.addWidget(bbox) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
class SimulationPanel(QWidget): def __init__(self): QWidget.__init__(self) layout = QVBoxLayout() simulation_mode_layout = QHBoxLayout() simulation_mode_layout.addSpacing(10) simulation_mode_model = SimulationModeModel() simulation_mode_model.observable().attach(SimulationModeModel.CURRENT_CHOICE_CHANGED_EVENT, self.toggleSimulationMode) simulation_mode_combo = ComboChoice(simulation_mode_model, "Simulation mode", "run/simulation_mode") simulation_mode_layout.addWidget(QLabel(simulation_mode_combo.getLabel()), 0, Qt.AlignVCenter) simulation_mode_layout.addWidget(simulation_mode_combo, 0, Qt.AlignVCenter) # simulation_mode_layout.addStretch() simulation_mode_layout.addSpacing(20) self.run_button = QToolButton() self.run_button.setIconSize(QSize(32, 32)) self.run_button.setText("Start Simulation") self.run_button.setIcon(util.resourceIcon("ide/gear_in_play")) self.run_button.clicked.connect(self.runSimulation) self.run_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) HelpedWidget.addHelpToWidget(self.run_button, "run/start_simulation") simulation_mode_layout.addWidget(self.run_button) simulation_mode_layout.addStretch(1) layout.addSpacing(5) layout.addLayout(simulation_mode_layout) layout.addSpacing(10) self.simulation_stack = QStackedWidget() self.simulation_stack.setLineWidth(1) self.simulation_stack.setFrameStyle(QFrame.StyledPanel) layout.addWidget(self.simulation_stack) self.simulation_widgets = {} self.addSimulationConfigPanel(EnsembleExperimentPanel()) self.addSimulationConfigPanel(EnsembleSmootherPanel()) self.addSimulationConfigPanel(MultipleDataAssimilationPanel()) self.addSimulationConfigPanel(IteratedEnsembleSmootherPanel()) self.setLayout(layout) def addSimulationConfigPanel(self, panel): assert isinstance(panel, SimulationConfigPanel) panel.toggleAdvancedOptions(False) self.simulation_stack.addWidget(panel) self.simulation_widgets[panel.getSimulationModel()] = panel panel.simulationConfigurationChanged.connect(self.validationStatusChanged) def getActions(self): return [] def toggleAdvancedMode(self, show_advanced): for panel in self.simulation_widgets.values(): panel.toggleAdvancedOptions(show_advanced) def getCurrentSimulationMode(self): return SimulationModeModel().getCurrentChoice() def runSimulation(self): case_name = CaseSelectorModel().getCurrentChoice() message = "Are you sure you want to use case '%s' for initialization of the initial ensemble when running the simulations?" % case_name start_simulations = QMessageBox.question(self, "Start simulations?", message, QMessageBox.Yes | QMessageBox.No ) if start_simulations == QMessageBox.Yes: run_model = self.getCurrentSimulationMode() dialog = RunDialog(run_model, self) dialog.startSimulation() dialog.exec_() CaseList().externalModificationNotification() # simulations may have added new cases. def toggleSimulationMode(self): widget = self.simulation_widgets[self.getCurrentSimulationMode()] self.simulation_stack.setCurrentWidget(widget) self.validationStatusChanged() def validationStatusChanged(self): widget = self.simulation_widgets[self.getCurrentSimulationMode()] self.run_button.setEnabled(widget.isConfigurationValid())
class SimulationPanel(QWidget): def __init__(self): QWidget.__init__(self) layout = QVBoxLayout() simulation_mode_layout = QHBoxLayout() simulation_mode_layout.addSpacing(10) simulation_mode_model = SimulationModeModel() simulation_mode_model.observable().attach(SimulationModeModel.CURRENT_CHOICE_CHANGED_EVENT, self.toggleSimulationMode) simulation_mode_combo = ComboChoice(simulation_mode_model, "Simulation mode", "run/simulation_mode") simulation_mode_layout.addWidget(QLabel(simulation_mode_combo.getLabel()), 0, Qt.AlignVCenter) simulation_mode_layout.addWidget(simulation_mode_combo, 0, Qt.AlignVCenter) # simulation_mode_layout.addStretch() simulation_mode_layout.addSpacing(20) self.run_button = QToolButton() self.run_button.setIconSize(QSize(32, 32)) self.run_button.setText("Start Simulation") self.run_button.setIcon(util.resourceIcon("ide/gear_in_play")) self.run_button.clicked.connect(self.runSimulation) self.run_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) HelpedWidget.addHelpToWidget(self.run_button, "run/start_simulation") simulation_mode_layout.addWidget(self.run_button) simulation_mode_layout.addStretch(1) layout.addSpacing(5) layout.addLayout(simulation_mode_layout) layout.addSpacing(10) self.simulation_stack = QStackedWidget() self.simulation_stack.setLineWidth(1) self.simulation_stack.setFrameStyle(QFrame.StyledPanel) layout.addWidget(self.simulation_stack) self.simulation_widgets = {} self.addSimulationConfigPanel(EnsembleExperimentPanel()) self.addSimulationConfigPanel(EnsembleSmootherPanel()) self.addSimulationConfigPanel(MultipleDataAssimilationPanel()) self.addSimulationConfigPanel(IteratedEnsembleSmootherPanel()) self.setLayout(layout) def addSimulationConfigPanel(self, panel): assert isinstance(panel, SimulationConfigPanel) panel.toggleAdvancedOptions(False) self.simulation_stack.addWidget(panel) self.simulation_widgets[panel.getSimulationModel()] = panel panel.simulationConfigurationChanged.connect(self.validationStatusChanged) def getActions(self): return [] def toggleAdvancedMode(self, show_advanced): for panel in self.simulation_widgets.values(): panel.toggleAdvancedOptions(show_advanced) def getCurrentSimulationMode(self): return SimulationModeModel().getCurrentChoice() def runSimulation(self): case_name = CaseSelectorModel().getCurrentChoice() message = "Are you sure you want to use case '%s' for initialization of the initial ensemble when running the simulations?" % case_name start_simulations = QMessageBox.question(self, "Start simulations?", message, QMessageBox.Yes | QMessageBox.No ) if start_simulations == QMessageBox.Yes: run_model = self.getCurrentSimulationMode() dialog = RunDialog(run_model) dialog.startSimulation() dialog.exec_() CaseList().externalModificationNotification() # simulations may have added new cases. def toggleSimulationMode(self): widget = self.simulation_widgets[self.getCurrentSimulationMode()] self.simulation_stack.setCurrentWidget(widget) self.validationStatusChanged() def validationStatusChanged(self): widget = self.simulation_widgets[self.getCurrentSimulationMode()] self.run_button.setEnabled(widget.isConfigurationValid())
class DataExportGui(QWidget): """ Manages all GUI elements in the data selection applet. This class itself is the central widget and also owns/manages the applet drawer widgets. """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def centralWidget( self ): return self def appletDrawer(self): return self.drawer def menus( self ): return [] def viewerControlWidget(self): return self._viewerControlWidgetStack def setImageIndex(self, index): pass def stopAndCleanUp(self): for editor in self.layerViewerGuis.values(): self.viewerStack.removeWidget( editor ) editor.stopAndCleanUp() self.layerViewerGuis.clear() def imageLaneAdded(self, laneIndex): pass def imageLaneRemoved(self, laneIndex, finalLength): pass def allowLaneSelectionChange(self): return False ########################################### ########################################### def __init__(self, parentApplet, topLevelOperator): super(DataExportGui, self).__init__() self.drawer = None self.topLevelOperator = topLevelOperator self.threadRouter = ThreadRouter(self) self._thunkEventHandler = ThunkEventHandler(self) self._initAppletDrawerUic() self.initCentralUic() self.initViewerControls() self.parentApplet = parentApplet self.progressSignal = parentApplet.progressSignal @threadRoutedWithRouter(self.threadRouter) def handleNewDataset( multislot, index ): # Make room in the GUI table self.batchOutputTableWidget.insertRow( index ) # Update the table row data when this slot has new data # We can't bind in the row here because the row may change in the meantime. multislot[index].notifyReady( bind( self.updateTableForSlot ) ) if multislot[index].ready(): self.updateTableForSlot( multislot[index] ) multislot[index].notifyUnready( self._updateExportButtons ) multislot[index].notifyReady( self._updateExportButtons ) self.topLevelOperator.ExportPath.notifyInserted( bind( handleNewDataset ) ) # For each dataset that already exists, update the GUI for i, subslot in enumerate(self.topLevelOperator.ExportPath): handleNewDataset( self.topLevelOperator.ExportPath, i ) if subslot.ready(): self.updateTableForSlot(subslot) @threadRoutedWithRouter(self.threadRouter) def handleLaneRemoved( multislot, index, finalLength ): if self.batchOutputTableWidget.rowCount() <= finalLength: return # Remove the row we don't need any more self.batchOutputTableWidget.removeRow( index ) # Remove the viewer for this dataset imageMultiSlot = self.topLevelOperator.Inputs[index] if imageMultiSlot in self.layerViewerGuis.keys(): layerViewerGui = self.layerViewerGuis[imageMultiSlot] self.viewerStack.removeWidget( layerViewerGui ) self._viewerControlWidgetStack.removeWidget( layerViewerGui.viewerControlWidget() ) layerViewerGui.stopAndCleanUp() self.topLevelOperator.Inputs.notifyRemove( bind( handleLaneRemoved ) ) def _initAppletDrawerUic(self, drawerPath=None): """ Load the ui file for the applet drawer, which we own. """ if drawerPath is None: localDir = os.path.split(__file__)[0] drawerPath = os.path.join( localDir, "dataExportDrawer.ui") self.drawer = uic.loadUi(drawerPath) self.drawer.settingsButton.clicked.connect( self._chooseSettings ) self.drawer.exportAllButton.clicked.connect( self.exportAllResults ) self.drawer.exportAllButton.setIcon( QIcon(ilastikIcons.Save) ) self.drawer.deleteAllButton.clicked.connect( self.deleteAllResults ) self.drawer.deleteAllButton.setIcon( QIcon(ilastikIcons.Clear) ) @threadRoutedWithRouter(self.threadRouter) def _handleNewSelectionNames( *args ): input_names = self.topLevelOperator.SelectionNames.value self.drawer.inputSelectionCombo.addItems( input_names ) self.topLevelOperator.SelectionNames.notifyDirty( _handleNewSelectionNames ) _handleNewSelectionNames() def _handleInputComboSelectionChanged( index ): assert index < len(self.topLevelOperator.SelectionNames.value) if self.drawer.inputSelectionCombo.currentText() == self.topLevelOperator.TableOnlyName.value: self.topLevelOperator.TableOnly.setValue(True) else: self.topLevelOperator.TableOnly.setValue(False) self.topLevelOperator.InputSelection.setValue( index ) self.drawer.inputSelectionCombo.currentIndexChanged.connect( _handleInputComboSelectionChanged ) def initCentralUic(self): """ Load the GUI from the ui file into this class and connect it with event handlers. """ # Load the ui file into this class (find it in our own directory) localDir = os.path.split(__file__)[0] uic.loadUi(localDir+"/dataExport.ui", self) self.batchOutputTableWidget.resizeRowsToContents() self.batchOutputTableWidget.resizeColumnsToContents() self.batchOutputTableWidget.setAlternatingRowColors(True) self.batchOutputTableWidget.setShowGrid(False) self.batchOutputTableWidget.horizontalHeader().setResizeMode(0, QHeaderView.Interactive) self.batchOutputTableWidget.horizontalHeader().resizeSection(Column.Dataset, 200) self.batchOutputTableWidget.horizontalHeader().resizeSection(Column.ExportLocation, 250) self.batchOutputTableWidget.horizontalHeader().resizeSection(Column.Action, 100) self.batchOutputTableWidget.verticalHeader().hide() # Set up handlers self.batchOutputTableWidget.itemSelectionChanged.connect(self.handleTableSelectionChange) # Set up the viewer area self.initViewerStack() self.splitter.setSizes([150, 850]) def initViewerStack(self): self.layerViewerGuis = {} self.viewerStack.addWidget( QWidget() ) def initViewerControls(self): self._viewerControlWidgetStack = QStackedWidget(parent=self) def showEvent(self, event): super( DataExportGui, self ).showEvent(event) self.showSelectedDataset() def hideEvent(self, event): super( DataExportGui, self ).hideEvent(event) # Make sure all 'on disk' layers are discarded so we aren't using those files any more. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() def _chooseSettings(self): opExportModelOp, opSubRegion = get_model_op( self.topLevelOperator ) if opExportModelOp is None: QMessageBox.information( self, "Image not ready for export", "Export isn't possible yet: No images are ready for export. " "Please configure upstream pipeline with valid settings, " "check that images were specified in the (batch) input applet and try again." ) return settingsDlg = DataExportOptionsDlg(self, opExportModelOp) if settingsDlg.exec_() == DataExportOptionsDlg.Accepted: # Copy the settings from our 'model op' into the real op setting_slots = [ opExportModelOp.RegionStart, opExportModelOp.RegionStop, opExportModelOp.InputMin, opExportModelOp.InputMax, opExportModelOp.ExportMin, opExportModelOp.ExportMax, opExportModelOp.ExportDtype, opExportModelOp.OutputAxisOrder, opExportModelOp.OutputFilenameFormat, opExportModelOp.OutputInternalPath, opExportModelOp.OutputFormat ] # Disconnect the special 'transaction' slot to prevent these # settings from triggering many calls to setupOutputs. self.topLevelOperator.TransactionSlot.disconnect() for model_slot in setting_slots: real_inslot = getattr(self.topLevelOperator, model_slot.name) if model_slot.ready(): real_inslot.setValue( model_slot.value ) else: real_inslot.disconnect() # Re-connect the 'transaction' slot to apply all settings at once. self.topLevelOperator.TransactionSlot.setValue(True) # Discard the temporary model op opExportModelOp.cleanUp() opSubRegion.cleanUp() # Update the gui with the new export paths for index, slot in enumerate(self.topLevelOperator.ExportPath): self.updateTableForSlot(slot) def getSlotIndex(self, multislot, subslot ): # Which index is this slot? for index, slot in enumerate(multislot): if slot == subslot: return index return -1 @threadRouted def updateTableForSlot(self, slot): """ Update the table row that corresponds to the given slot of the top-level operator (could be either input slot) """ row = self.getSlotIndex( self.topLevelOperator.ExportPath, slot ) assert row != -1, "Unknown input slot!" if not self.topLevelOperator.ExportPath[row].ready() or\ not self.topLevelOperator.RawDatasetInfo[row].ready(): return try: nickname = self.topLevelOperator.RawDatasetInfo[row].value.nickname exportPath = self.topLevelOperator.ExportPath[row].value except Slot.SlotNotReadyError: # Sadly, it is possible to get here even though we checked for .ready() immediately beforehand. # That's because the graph has a diamond-shaped DAG of connections, but the graph has no transaction mechanism # (It's therefore possible for RawDatasetInfo[row] to be ready() even though it's upstream partner is NOT ready. return self.batchOutputTableWidget.setItem( row, Column.Dataset, QTableWidgetItem( decode_to_qstring(nickname, 'utf-8') ) ) self.batchOutputTableWidget.setItem( row, Column.ExportLocation, QTableWidgetItem( decode_to_qstring(exportPath) ) ) exportNowButton = QPushButton("Export") exportNowButton.setToolTip("Generate individual batch output dataset.") exportNowButton.clicked.connect( bind(self.exportResultsForSlot, self.topLevelOperator[row] ) ) self.batchOutputTableWidget.setCellWidget( row, Column.Action, exportNowButton ) # Select a row if there isn't one already selected. selectedRanges = self.batchOutputTableWidget.selectedRanges() if len(selectedRanges) == 0: self.batchOutputTableWidget.selectRow(0) def setEnabledIfAlive(self, widget, enable): if not sip.isdeleted(widget): widget.setEnabled(enable) def _updateExportButtons(self, *args): """Called when at least one dataset became 'unready', so we have to disable the export button.""" all_ready = True # Enable/disable the appropriate export buttons in the table. # Use ThunkEvents to ensure that this happens in the Gui thread. for row, slot in enumerate( self.topLevelOperator.ImageToExport ): all_ready &= slot.ready() export_button = self.batchOutputTableWidget.cellWidget( row, Column.Action ) if export_button is not None: executable_event = ThunkEvent( partial(self.setEnabledIfAlive, export_button, slot.ready()) ) QApplication.instance().postEvent( self, executable_event ) # Disable the "Export all" button unless all slots are ready. executable_event = ThunkEvent( partial(self.setEnabledIfAlive, self.drawer.exportAllButton, all_ready) ) QApplication.instance().postEvent( self, executable_event ) def handleTableSelectionChange(self): """ Any time the user selects a new item, select the whole row. """ self.selectEntireRow() self.showSelectedDataset() def selectEntireRow(self): # FIXME: There is a better way to do this... # Figure out which row is selected selectedItemRows = set() selectedRanges = self.batchOutputTableWidget.selectedRanges() for rng in selectedRanges: for row in range(rng.topRow(), rng.bottomRow()+1): selectedItemRows.add(row) # Disconnect from selection change notifications while we do this self.batchOutputTableWidget.itemSelectionChanged.disconnect(self.handleTableSelectionChange) for row in selectedItemRows: self.batchOutputTableWidget.selectRow(row) # Reconnect now that we're finished self.batchOutputTableWidget.itemSelectionChanged.connect(self.handleTableSelectionChange) def exportSlots(self, laneViewList ): try: # Set the busy flag so the workflow knows not to allow # upstream changes or shell changes while we're exporting self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() # Disable our own gui QApplication.instance().postEvent( self, ThunkEvent( partial(self.setEnabledIfAlive, self.drawer, False) ) ) QApplication.instance().postEvent( self, ThunkEvent( partial(self.setEnabledIfAlive, self, False) ) ) # Start with 1% so the progress bar shows up self.progressSignal.emit(0) self.progressSignal.emit(1) def signalFileProgress(slotIndex, percent): self.progressSignal.emit( (100*slotIndex + percent) / len(laneViewList) ) # Client hook self.parentApplet.prepare_for_entire_export() for i, opLaneView in enumerate(laneViewList): lane_index = self.topLevelOperator.innerOperators.index(opLaneView) logger.debug("Exporting result {}".format(i)) # If the operator provides a progress signal, use it. slotProgressSignal = opLaneView.progressSignal slotProgressSignal.subscribe( partial(signalFileProgress, i) ) try: # Client hook self.parentApplet.prepare_lane_for_export(lane_index) # Export the image opLaneView.run_export() # Client hook self.parentApplet.post_process_lane_export(lane_index) except Exception as ex: if opLaneView.ExportPath.ready(): msg = "Failed to generate export file: \n" msg += opLaneView.ExportPath.value msg += "\n{}".format( ex ) else: msg = "Failed to generate export file." msg += "\n{}".format( ex ) log_exception( logger, msg ) self.showExportError(msg) # We're finished with this file. self.progressSignal.emit( 100*(i+1)/float(len(laneViewList)) ) # Client hook self.parentApplet.post_process_entire_export() # Ensure the shell knows we're really done. self.progressSignal.emit(100) except: # Cancel our progress. self.progressSignal.emit(0, True) raise finally: # We're not busy any more. Tell the workflow. self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() # Re-enable our own gui QApplication.instance().postEvent( self, ThunkEvent( partial(self.setEnabledIfAlive, self.drawer, True) ) ) QApplication.instance().postEvent( self, ThunkEvent( partial(self.setEnabledIfAlive, self, True) ) ) def postProcessLane(self, lane_index): """ Called immediately after the result for each lane is exported. Can be overridden by subclasses for post-processing purposes. """ pass @threadRouted def showExportError(self, msg): QMessageBox.critical(self, "Failed to export", msg ) def exportResultsForSlot(self, opLane): # Make sure all 'on disk' layers are discarded so we aren't using those files any more. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() # Do this in a separate thread so the UI remains responsive exportThread = threading.Thread(target=bind(self.exportSlots, [opLane]), name="DataExportThread") exportThread.start() def exportAllResults(self): # Make sure all 'on disk' layers are discarded so we aren't using those files any more. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() # Do this in a separate thread so the UI remains responsive exportThread = threading.Thread(target=bind(self.exportSlots, self.topLevelOperator), name="DataExportThread") exportThread.start() def deleteAllResults(self): for innerOp in self.topLevelOperator: operatorView = innerOp operatorView.cleanupOnDiskView() pathComp = PathComponents(operatorView.ExportPath.value, operatorView.WorkingDirectory.value) if os.path.exists(pathComp.externalPath): os.remove(pathComp.externalPath) operatorView.setupOnDiskView() # we need to toggle the dirts state in order to enforce a frech dirty signal operatorView.Dirty.setValue( False ) operatorView.Dirty.setValue( True ) def showSelectedDataset(self): """ Show the exported file in the viewer """ # Get the selected row and corresponding slot value selectedRanges = self.batchOutputTableWidget.selectedRanges() if len(selectedRanges) == 0: return row = selectedRanges[0].topRow() # Hide all layers that come from the disk. for opLaneView in self.topLevelOperator: opLaneView.cleanupOnDiskView() # Activate the 'on disk' layers for this lane (if possible) opLane = self.topLevelOperator.getLane(row) opLane.setupOnDiskView() # Create if necessary imageMultiSlot = self.topLevelOperator.Inputs[row] if imageMultiSlot not in self.layerViewerGuis.keys(): layerViewer = self.createLayerViewer(opLane) # Maximize the x-y view by default. layerViewer.volumeEditorWidget.quadview.ensureMaximized(2) self.layerViewerGuis[imageMultiSlot] = layerViewer self.viewerStack.addWidget( layerViewer ) self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() ) # Show the right one layerViewer = self.layerViewerGuis[imageMultiSlot] self.viewerStack.setCurrentWidget( layerViewer ) self._viewerControlWidgetStack.setCurrentWidget( layerViewer.viewerControlWidget() ) def createLayerViewer(self, opLane): """ This method provides an instance of LayerViewerGui for the given data lane. If this GUI class is subclassed, this method can be reimplemented to provide custom layer types for the exported layers. """ return DataExportLayerViewerGui(self.parentApplet, opLane)
class Preferencias(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent, Qt.Dialog) self.setWindowTitle(self.tr("Preferencias - EDIS")) self.setMinimumSize(700, 500) self.general = preferencias_general.ConfiguracionGeneral(self) self.editor = preferencias_editor.TabEditor() self.gui = preferencias_gui.ConfiguracionGUI(self) self._ejecucion = preferencias_ejecucion.ConfiguracionEjecucion(self) # valor: texto en combo, clave: instancia de widgets self.widgets = OrderedDict([ ('General', self.general), ('Editor', self.editor), ('GUI', self.gui), ('Ejecucion', self._ejecucion)]) #]) self.load_ui() # Conexiones self.connect(self.button_general, SIGNAL("clicked()"), lambda: self.cambiar_widget(0)) self.connect(self.button_editor, SIGNAL("clicked()"), lambda: self.cambiar_widget(1)) self.connect(self.button_gui, SIGNAL("clicked()"), lambda: self.cambiar_widget(2)) self.connect(self.button_compi, SIGNAL("clicked()"), lambda: self.cambiar_widget(3)) self.connect(self.btn_cancel, SIGNAL("clicked()"), self.close) self.connect(self.btn_guardar, SIGNAL("clicked()"), self._guardar) EDIS.cargar_componente("preferencias", self) def load_ui(self): box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) box.setSpacing(0) toolbar = QToolBar() toolbar.setIconSize(QSize(40, 40)) toolbar.setObjectName("preferencias") toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) self.button_general = ToolButton("General", paths.ICONOS['general']) self.button_editor = ToolButton("Editor", paths.ICONOS['edit']) self.button_gui = ToolButton("Interfáz", paths.ICONOS['gui']) self.button_compi = ToolButton("Ejecución", paths.ICONOS['build']) toolbar.addWidget(self.button_general) toolbar.addWidget(self.button_editor) toolbar.addWidget(self.button_gui) toolbar.addWidget(self.button_compi) box.addWidget(toolbar) self.stack = QStackedWidget() box.addWidget(self.stack) [self.stack.addWidget(widget) for widget in list(self.widgets.values())] box_buttons = QHBoxLayout() box_buttons.setMargin(10) box_buttons.setSpacing(10) box_buttons.addStretch(1) self.btn_cancel = QPushButton(self.tr("Cancelar")) self.btn_guardar = QPushButton(self.tr("Guardar")) box_buttons.addWidget(self.btn_cancel) box_buttons.addWidget(self.btn_guardar) box.addLayout(box_buttons) def mostrar(self): self.stack.setCurrentIndex(0) self.show() def cambiar_widget(self, index): if not self.isVisible(): self.show() self.stack.setCurrentIndex(index) def _guardar(self): [self.stack.widget(i).guardar() for i in range(self.stack.count())] self.close()
class DataSelectionGui(QWidget): """ Manages all GUI elements in the data selection applet. This class itself is the central widget and also owns/manages the applet drawer widgets. """ ########################################### ### AppletGuiInterface Concrete Methods ### ########################################### def centralWidget( self ): return self def appletDrawer( self ): return self._drawer def menus( self ): return [] def viewerControlWidget(self): return self._viewerControlWidgetStack def setImageIndex(self, imageIndex): if imageIndex is not None: self.laneSummaryTableView.selectRow(imageIndex) for detailWidget in self._detailViewerWidgets: detailWidget.selectRow(imageIndex) def stopAndCleanUp(self): for editor in self.volumeEditors.values(): self.viewerStack.removeWidget( editor ) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() ) editor.stopAndCleanUp() self.volumeEditors.clear() def imageLaneAdded(self, laneIndex): if len(self.laneSummaryTableView.selectedIndexes()) == 0: self.laneSummaryTableView.selectRow(laneIndex) # We don't have any real work to do because this gui initiated the lane addition in the first place if self.guiMode != GuiMode.Batch: if(len(self.topLevelOperator.DatasetGroup) != laneIndex+1): import warnings warnings.warn("DataSelectionGui.imageLaneAdded(): length of dataset multislot out of sync with laneindex [%s != %s + 1]" % (len(self.topLevelOperator.DatasetGroup), laneIndex)) def imageLaneRemoved(self, laneIndex, finalLength): # We assume that there's nothing to do here because THIS GUI initiated the lane removal if self.guiMode != GuiMode.Batch: assert len(self.topLevelOperator.DatasetGroup) == finalLength ########################################### ########################################### def __init__(self, parentApplet, dataSelectionOperator, serializer, instructionText, guiMode=GuiMode.Normal, max_lanes=None): """ Constructor. :param dataSelectionOperator: The top-level operator. Must be of type :py:class:`OpMultiLaneDataSelectionGroup`. :param serializer: The applet's serializer. Must be of type :py:class:`DataSelectionSerializer` :param instructionText: A string to display in the applet drawer. :param guiMode: Either ``GuiMode.Normal`` or ``GuiMode.Batch``. Currently, there is no difference between normal and batch mode. :param max_lanes: The maximum number of lanes that the user is permitted to add to this workflow. If ``None``, there is no maximum. """ super(DataSelectionGui, self).__init__() self.parentApplet = parentApplet self._max_lanes = max_lanes self._viewerControls = QWidget() self.topLevelOperator = dataSelectionOperator self.guiMode = guiMode self.serializer = serializer self.threadRouter = ThreadRouter(self) self._initCentralUic() self._initAppletDrawerUic(instructionText) self._viewerControlWidgetStack = QStackedWidget(self) def handleImageRemoved(multislot, index, finalLength): # Remove the viewer for this dataset imageSlot = self.topLevelOperator.Image[index] if imageSlot in self.volumeEditors.keys(): editor = self.volumeEditors[imageSlot] self.viewerStack.removeWidget( editor ) self._viewerControlWidgetStack.removeWidget( editor.viewerControlWidget() ) editor.stopAndCleanUp() self.topLevelOperator.Image.notifyRemove( bind( handleImageRemoved ) ) def _initCentralUic(self): """ Load the GUI from the ui file into this class and connect it with event handlers. """ # Load the ui file into this class (find it in our own directory) localDir = os.path.split(__file__)[0]+'/' uic.loadUi(localDir+"/dataSelection.ui", self) self._initTableViews() self._initViewerStack() self.splitter.setSizes( [150, 850] ) def _initAppletDrawerUic(self, instructionText): """ Load the ui file for the applet drawer, which we own. """ localDir = os.path.split(__file__)[0]+'/' self._drawer = uic.loadUi(localDir+"/dataSelectionDrawer.ui") self._drawer.instructionLabel.setText( instructionText ) def _initTableViews(self): self.fileInfoTabWidget.setTabText( 0, "Summary" ) self.laneSummaryTableView.setModel( DataLaneSummaryTableModel(self, self.topLevelOperator) ) self.laneSummaryTableView.dataLaneSelected.connect( self.showDataset ) self.laneSummaryTableView.addFilesRequested.connect( self.addFiles ) self.laneSummaryTableView.addStackRequested.connect( self.addStack ) self.laneSummaryTableView.removeLanesRequested.connect( self.handleRemoveLaneButtonClicked ) # These two helper functions enable/disable an 'add files' button for a given role # based on the the max lane index for that role and the overall permitted max_lanes def _update_button_status(viewer, role_index): if self._max_lanes: viewer.setEnabled( self._findFirstEmptyLane(role_index) < self._max_lanes ) def _handle_lane_added( button, role_index, slot, lane_index ): slot[lane_index][role_index].notifyReady( bind(_update_button_status, button, role_index) ) slot[lane_index][role_index].notifyUnready( bind(_update_button_status, button, role_index) ) self._retained = [] # Retain menus so they don't get deleted self._detailViewerWidgets = [] for roleIndex, role in enumerate(self.topLevelOperator.DatasetRoles.value): detailViewer = DatasetDetailedInfoTableView(self) detailViewer.setModel(DatasetDetailedInfoTableModel(self, self.topLevelOperator, roleIndex)) self._detailViewerWidgets.append( detailViewer ) # Button detailViewer.addFilesRequested.connect( partial(self.addFiles, roleIndex)) detailViewer.addStackRequested.connect( partial(self.addStack, roleIndex)) detailViewer.addRemoteVolumeRequested.connect( partial(self.addDvidVolume, roleIndex)) # Monitor changes to each lane so we can enable/disable the 'add lanes' button for each tab self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, detailViewer, roleIndex ) ) self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, detailViewer, roleIndex ) ) # While we're at it, do the same for the buttons in the summary table, too self.topLevelOperator.DatasetGroup.notifyInserted( bind( _handle_lane_added, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) ) self.topLevelOperator.DatasetGroup.notifyRemoved( bind( _update_button_status, self.laneSummaryTableView.addFilesButtons[roleIndex], roleIndex ) ) # Context menu detailViewer.replaceWithFileRequested.connect( partial(self.handleReplaceFile, roleIndex) ) detailViewer.replaceWithStackRequested.connect( partial(self.addStack, roleIndex) ) detailViewer.editRequested.connect( partial(self.editDatasetInfo, roleIndex) ) detailViewer.resetRequested.connect( partial(self.handleClearDatasets, roleIndex) ) # Drag-and-drop detailViewer.addFilesRequestedDrop.connect( partial( self.addFileNames, roleIndex=roleIndex ) ) # Selection handling def showFirstSelectedDataset( _roleIndex, lanes ): if lanes: self.showDataset( lanes[0], _roleIndex ) detailViewer.dataLaneSelected.connect( partial(showFirstSelectedDataset, roleIndex) ) self.fileInfoTabWidget.insertTab(roleIndex, detailViewer, role) self.fileInfoTabWidget.currentChanged.connect( self.handleSwitchTabs ) self.fileInfoTabWidget.setCurrentIndex(0) def handleSwitchTabs(self, tabIndex ): if tabIndex < len(self._detailViewerWidgets): roleIndex = tabIndex # If summary tab is moved to the front, change this line. detailViewer = self._detailViewerWidgets[roleIndex] selectedLanes = detailViewer.selectedLanes if selectedLanes: self.showDataset( selectedLanes[0], roleIndex ) def _initViewerStack(self): self.volumeEditors = {} self.viewerStack.addWidget( QWidget() ) def handleRemoveLaneButtonClicked(self): """ The user clicked the "Remove" button. Remove the currently selected row(s) from both the GUI and the top-level operator. """ # Figure out which lanes to remove selectedIndexes = self.laneSummaryTableView.selectedIndexes() rows = set() for modelIndex in selectedIndexes: rows.add( modelIndex.row() ) # Don't remove the last row, which is just buttons. rows.discard( self.laneSummaryTableView.model().rowCount()-1 ) # Remove in reverse order so row numbers remain consistent for row in reversed(sorted(rows)): # Remove from the GUI self.laneSummaryTableView.model().removeRow(row) # Remove from the operator finalSize = len(self.topLevelOperator.DatasetGroup) - 1 self.topLevelOperator.DatasetGroup.removeSlot(row, finalSize) # The gui and the operator should be in sync (model has one extra row for the button row) assert self.laneSummaryTableView.model().rowCount() == len(self.topLevelOperator.DatasetGroup)+1 @threadRouted def showDataset(self, laneIndex, roleIndex=None): if laneIndex == -1: self.viewerStack.setCurrentIndex(0) return assert threading.current_thread().name == "MainThread" imageSlot = self.topLevelOperator.Image[laneIndex] # Create if necessary if imageSlot not in self.volumeEditors.keys(): class DatasetViewer(LayerViewerGui): def moveToTop(self, roleIndex): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return datasetRoles = opLaneView.DatasetRoles.value if roleIndex >= len(datasetRoles): return roleName = datasetRoles[roleIndex] try: layerIndex = [l.name for l in self.layerstack].index(roleName) except ValueError: return else: self.layerstack.selectRow(layerIndex) self.layerstack.moveSelectedToTop() def setupLayers(self): opLaneView = self.topLevelOperatorView if not opLaneView.DatasetRoles.ready(): return [] layers = [] datasetRoles = opLaneView.DatasetRoles.value for roleIndex, slot in enumerate(opLaneView.ImageGroup): if slot.ready(): roleName = datasetRoles[roleIndex] layer = self.createStandardLayerFromSlot(slot) layer.name = roleName layers.append(layer) return layers opLaneView = self.topLevelOperator.getLane(laneIndex) layerViewer = DatasetViewer(self.parentApplet, opLaneView, crosshair=False) # Maximize the x-y view by default. layerViewer.volumeEditorWidget.quadview.ensureMaximized(2) self.volumeEditors[imageSlot] = layerViewer self.viewerStack.addWidget( layerViewer ) self._viewerControlWidgetStack.addWidget( layerViewer.viewerControlWidget() ) # Show the right one viewer = self.volumeEditors[imageSlot] displayedRole = self.fileInfoTabWidget.currentIndex() viewer.moveToTop(displayedRole) self.viewerStack.setCurrentWidget( viewer ) self._viewerControlWidgetStack.setCurrentWidget( viewer.viewerControlWidget() ) def handleReplaceFile(self, roleIndex, startingLane): self.addFiles(roleIndex, startingLane) def addFiles(self, roleIndex, startingLane=None): """ The user clicked the "Add File" button. Ask him to choose a file (or several) and add them to both the GUI table and the top-level operator inputs. """ # Find the directory of the most recently opened image file mostRecentImageFile = PreferencesManager().get( 'DataSelection', 'recent image' ) if mostRecentImageFile is not None: defaultDirectory = os.path.split(mostRecentImageFile)[0] else: defaultDirectory = os.path.expanduser('~') # Launch the "Open File" dialog fileNames = self.getImageFileNamesToOpen(defaultDirectory) # If the user didn't cancel if len(fileNames) > 0: PreferencesManager().set('DataSelection', 'recent image', fileNames[0]) try: self.addFileNames(fileNames, roleIndex, startingLane) except RuntimeError as e: QMessageBox.critical(self, "Error loading file", str(e)) def getImageFileNamesToOpen(self, defaultDirectory): """ Launch an "Open File" dialog to ask the user for one or more image files. """ file_dialog = QFileDialog(self, "Select Images") extensions = OpDataSelection.SupportedExtensions filter_strs = ["*." + x for x in extensions] filters = ["{filt} ({filt})".format(filt=x) for x in filter_strs] filt_all_str = "Image files (" + ' '.join(filter_strs) + ')' file_dialog.setFilters([filt_all_str] + filters) # do not display file types associated with a filter # the line for "Image files" is too long otherwise file_dialog.setNameFilterDetailsVisible(False) # select multiple files file_dialog.setFileMode(QFileDialog.ExistingFiles) if ilastik_config.getboolean("ilastik", "debug"): file_dialog.setOption(QFileDialog.DontUseNativeDialog, True) if file_dialog.exec_(): fileNames = file_dialog.selectedFiles() # Convert from QtString to python str fileNames = map(encode_from_qstring, fileNames) return fileNames return [] def _findFirstEmptyLane(self, roleIndex): opTop = self.topLevelOperator # Determine the number of files this role already has # Search for the last valid value. firstNewLane = 0 for laneIndex, slot in reversed(zip(range(len(opTop.DatasetGroup)), opTop.DatasetGroup)): if slot[roleIndex].ready(): firstNewLane = laneIndex+1 break return firstNewLane def addFileNames(self, fileNames, roleIndex, startingLane=None): """ Add the given filenames to both the GUI table and the top-level operator inputs. If startingLane is None, the filenames will be *appended* to the role's list of files. """ infos = [] if startingLane is None or startingLane == -1: startingLane = len(self.topLevelOperator.DatasetGroup) endingLane = startingLane+len(fileNames)-1 else: assert startingLane < len(self.topLevelOperator.DatasetGroup) max_files = len(self.topLevelOperator.DatasetGroup) - \ startingLane if len(fileNames) > max_files: msg = "You selected {num_selected} files for {num_slots} "\ "slots. To add new files use the 'Add new...' option "\ "in the context menu or the button in the last row."\ .format(num_selected=len(fileNames), num_slots=max_files) QMessageBox.critical( self, "Too many files", msg ) return endingLane = min(startingLane+len(fileNames)-1, len(self.topLevelOperator.DatasetGroup)) if self._max_lanes and endingLane >= self._max_lanes: msg = "You may not add more than {} file(s) to this workflow. Please try again.".format( self._max_lanes ) QMessageBox.critical( self, "Too many files", msg ) return # Assign values to the new inputs we just allocated. # The GUI will be updated by callbacks that are listening to slot changes for i, filePath in enumerate(fileNames): datasetInfo = DatasetInfo() cwd = self.topLevelOperator.WorkingDirectory.value absPath, relPath = getPathVariants(filePath, cwd) # Relative by default, unless the file is in a totally different tree from the working directory. if relPath is not None and len(os.path.commonprefix([cwd, absPath])) > 1: datasetInfo.filePath = relPath else: datasetInfo.filePath = absPath datasetInfo.nickname = PathComponents(absPath).filenameBase h5Exts = ['.ilp', '.h5', '.hdf5'] if os.path.splitext(datasetInfo.filePath)[1] in h5Exts: datasetNames = self.getPossibleInternalPaths( absPath ) if len(datasetNames) > 0: datasetInfo.filePath += str(datasetNames[0]) else: raise RuntimeError("HDF5 file %s has no image datasets" % datasetInfo.filePath) # Allow labels by default if this gui isn't being used for batch data. datasetInfo.allowLabels = ( self.guiMode == GuiMode.Normal ) infos.append(datasetInfo) # if no exception was thrown, set up the operator now opTop = self.topLevelOperator originalSize = len(opTop.DatasetGroup) if len( opTop.DatasetGroup ) < endingLane+1: opTop.DatasetGroup.resize( endingLane+1 ) for laneIndex, info in zip(range(startingLane, endingLane+1), infos): try: self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue( info ) except DatasetConstraintError as ex: return_val = [False] # Give the user a chance to fix the problem self.handleDatasetConstraintError(info, info.filePath, ex, roleIndex, laneIndex, return_val) if not return_val[0]: # Not successfully repaired. Roll back the changes and give up. opTop.DatasetGroup.resize( originalSize ) break except OpDataSelection.InvalidDimensionalityError as ex: opTop.DatasetGroup.resize( originalSize ) QMessageBox.critical( self, "Dataset has different dimensionality", ex.message ) break except: QMessageBox.critical( self, "Dataset Load Error", "Wasn't able to load your dataset into the workflow. See console for details." ) opTop.DatasetGroup.resize( originalSize ) raise # If we succeeded in adding all images, show the first one. if laneIndex == endingLane: self.showDataset(startingLane, roleIndex) # Notify the workflow that something that could affect applet readyness has occurred. self.parentApplet.appletStateUpdateRequested.emit() self.updateInternalPathVisiblity() @threadRouted def handleDatasetConstraintError(self, info, filename, ex, roleIndex, laneIndex, return_val=[False]): msg = "Can't use default properties for dataset:\n\n" + \ filename + "\n\n" + \ "because it violates a constraint of the {} applet.\n\n".format( ex.appletName ) + \ ex.message + "\n\n" + \ "Please enter valid dataset properties to continue." QMessageBox.warning( self, "Dataset Needs Correction", msg ) # The success of this is 'returned' via our special out-param # (We can't return a value from this func because it is @threadRouted. successfully_repaired = self.repairDatasetInfo( info, roleIndex, laneIndex ) return_val[0] = successfully_repaired def repairDatasetInfo(self, info, roleIndex, laneIndex): """Open the dataset properties editor and return True if the new properties are acceptable.""" defaultInfos = {} defaultInfos[laneIndex] = info editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, [laneIndex], defaultInfos) dlg_state = editorDlg.exec_() return ( dlg_state == QDialog.Accepted ) def getPossibleInternalPaths(self, absPath): datasetNames = [] # Open the file as a read-only so we can get a list of the internal paths with h5py.File(absPath, 'r') as f: # Define a closure to collect all of the dataset names in the file. def accumulateDatasetPaths(name, val): if type(val) == h5py._hl.dataset.Dataset and 3 <= len(val.shape) <= 5: datasetNames.append( '/' + name ) # Visit every group/dataset in the file f.visititems(accumulateDatasetPaths) return datasetNames def addStack(self, roleIndex, laneIndex): """ The user clicked the "Import Stack Files" button. """ stackDlg = StackFileSelectionWidget(self) stackDlg.exec_() if stackDlg.result() != QDialog.Accepted : return files = stackDlg.selectedFiles if len(files) == 0: return info = DatasetInfo() info.filePath = "//".join( files ) prefix = os.path.commonprefix(files) info.nickname = PathComponents(prefix).filenameBase # Add an underscore for each wildcard digit num_wildcards = len(files[-1]) - len(prefix) - len( os.path.splitext(files[-1])[1] ) info.nickname += "_"*num_wildcards # Allow labels by default if this gui isn't being used for batch data. info.allowLabels = ( self.guiMode == GuiMode.Normal ) info.fromstack = True originalNumLanes = len(self.topLevelOperator.DatasetGroup) if laneIndex is None or laneIndex == -1: laneIndex = len(self.topLevelOperator.DatasetGroup) if len(self.topLevelOperator.DatasetGroup) < laneIndex+1: self.topLevelOperator.DatasetGroup.resize(laneIndex+1) def importStack(): self.parentApplet.busy = True self.parentApplet.appletStateUpdateRequested.emit() # Serializer will update the operator for us, which will propagate to the GUI. try: self.serializer.importStackAsLocalDataset( info ) try: self.topLevelOperator.DatasetGroup[laneIndex][roleIndex].setValue(info) except DatasetConstraintError as ex: # Give the user a chance to repair the problem. filename = files[0] + "\n...\n" + files[-1] return_val = [False] self.handleDatasetConstraintError( info, filename, ex, roleIndex, laneIndex, return_val ) if not return_val[0]: # Not successfully repaired. Roll back the changes and give up. self.topLevelOperator.DatasetGroup.resize(originalNumLanes) finally: self.parentApplet.busy = False self.parentApplet.appletStateUpdateRequested.emit() req = Request( importStack ) req.notify_finished( lambda result: self.showDataset(laneIndex, roleIndex) ) req.notify_failed( partial(self.handleFailedStackLoad, files, originalNumLanes ) ) req.submit() @threadRouted def handleFailedStackLoad(self, files, originalNumLanes, exc, exc_info): import traceback traceback.print_tb(exc_info[2]) msg = "Failed to load stack due to the following error:\n{}".format( exc ) msg += "Attempted stack files were:" for f in files: msg += f + "\n" QMessageBox.critical(self, "Failed to load image stack", msg) self.topLevelOperator.DatasetGroup.resize(originalNumLanes) def handleClearDatasets(self, roleIndex, selectedRows): for row in selectedRows: self.topLevelOperator.DatasetGroup[row][roleIndex].disconnect() # Remove all operators that no longer have any connected slots last_valid = -1 laneIndexes = range( len(self.topLevelOperator.DatasetGroup) ) for laneIndex, multislot in reversed(zip(laneIndexes, self.topLevelOperator.DatasetGroup)): any_ready = False for slot in multislot: any_ready |= slot.ready() if not any_ready: self.topLevelOperator.DatasetGroup.removeSlot( laneIndex, len(self.topLevelOperator.DatasetGroup)-1 ) # Notify the workflow that something that could affect applet readyness has occurred. self.parentApplet.appletStateUpdateRequested.emit() def editDatasetInfo(self, roleIndex, laneIndexes): editorDlg = DatasetInfoEditorWidget(self, self.topLevelOperator, roleIndex, laneIndexes) editorDlg.exec_() def updateInternalPathVisiblity(self): for view in self._detailViewerWidgets: model = view.model() view.setColumnHidden(DatasetDetailedInfoColumn.InternalID, not model.hasInternalPaths()) def addDvidVolume(self, roleIndex, laneIndex): # TODO: Provide list of recently used dvid hosts, loaded from user preferences from dvidclient.gui.contents_browser import ContentsBrowser browser = ContentsBrowser(["localhost:8000"], parent=self) if browser.exec_() == ContentsBrowser.Rejected: return hostname, dset_index, volume_name, uuid = browser.get_selection() dvid_url = 'http://{hostname}/api/node/{uuid}/{volume_name}'.format( **locals() ) self.addFileNames([dvid_url], roleIndex, laneIndex)
class Container(QSplitter): def __init__(self, orientation=Qt.Vertical): super(Container, self).__init__(orientation) self.__last_open_folder = None self.__filename = "" self.__created = False self.__modified = False vbox = QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) # Stacked self.stacked = QStackedWidget() vbox.addWidget(self.stacked) # Table self.table_widget = table_widget.TableWidget() Pireal.load_service("container", self) def create_data_base(self, filename=''): """ This function opens or creates a database :param filename: Database filename """ if self.__created: QMessageBox.critical(self, self.tr("Error"), self.tr("Solo puede tener una base de datos " "abierta a la vez.")) return if not filename: db_name, ok = QInputDialog.getText(self, self.tr("Nueva DB"), self.tr("Nombre:")) if not ok: return else: # From file try: db_name, data = file_manager.open_database(filename) except Exception as reason: QMessageBox.critical(self, self.tr("Error!"), reason.__str__()) return self.table_widget.add_data_base(data) # Remove Start Page widget if isinstance(self.stacked.widget(0), start_page.StartPage): self.stacked.removeWidget(self.stacked.widget(0)) self.stacked.addWidget(self.table_widget) # Title pireal = Pireal.get_service("pireal") pireal.change_title(db_name) # Enable QAction's pireal.enable_disable_db_actions() self.__created = True def create_new_relation(self): dialog = new_relation_dialog.NewRelationDialog(self) dialog.show() def remove_relation(self): lateral = Pireal.get_service("lateral") rname = lateral.get_relation_name() if not rname: QMessageBox.critical(self, self.tr("Error"), self.tr("No se ha seleccionado ninguna " "relación.")) return r = QMessageBox.question(self, self.tr("Confirmación"), self.tr("Seguro que quieres eliminar la " "relación <b>{}</b>").format(rname), QMessageBox.Yes | QMessageBox.No) if r == QMessageBox.No: return index = lateral.current_index() # Remove table self.table_widget.remove_table(index) # Remove item from list widget lateral.remove_item(index) def new_query(self, filename=''): query_widget = Pireal.get_service("query_widget") self.addWidget(query_widget) if not query_widget.isVisible(): query_widget.show() pireal = Pireal.get_service("pireal") pireal.enable_disable_query_actions() query_widget.new_query(filename) self.connect(query_widget, SIGNAL("currentEditorSaved(QPlainTextEdit)"), self.save_query) @property def modified(self): return self.__modified def show_start_page(self): sp = start_page.StartPage() self.stacked.addWidget(sp) def close_db(self): """ Close data base """ widget = self.stacked.currentWidget() if isinstance(widget, table_widget.TableWidget): # Clear list of relations lateral = Pireal.get_service("lateral") lateral.clear_items() lateral.hide() # Close table widget self.stacked.removeWidget(widget) # Add start page self.show_start_page() self.__created = False def save_query(self, weditor=None): if weditor is None: query_widget = Pireal.get_service("query_widget") # Editor instance weditor = query_widget.get_active_editor() if weditor.rfile.is_new: return self.save_query_as(weditor) content = weditor.toPlainText() weditor.rfile.write(content) weditor.document().setModified(False) self.emit(SIGNAL("currentFileSaved(QString)"), self.tr("Archivo guardado: {}").format(weditor.filename)) def open_file(self): if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder filename = QFileDialog.getOpenFileName(self, self.tr("Abrir Archivo"), directory, settings.DBFILE, QFileDialog.DontUseNativeDialog) if not filename: return # Save folder self.__last_open_folder = file_manager.get_path(filename) ext = file_manager.get_extension(filename) if ext == '.pqf': # Query file self.new_query(filename) elif ext == '.rdb': self.load_rdb_database(file_manager.read_rdb_file(filename)) else: self.create_data_base(filename) def load_rdb_database(self, content): csv_content = "" for line in content.splitlines(): if line.startswith('@'): csv_content += '@' portion = line.split('(') name = portion[0][1:] csv_content += name + ':' for i in portion[1].split(','): if not i.startswith(' '): field = i.split('/')[0].strip() csv_content += field + ',' else: if not line: continue csv_content += line csv_content += '\n' self.table_widget.add_table_from_rdb_content(csv_content) def save_query_as(self, editor=None): if editor is None: query_widget = Pireal.get_service("query_widget") editor = query_widget.get_active_editor() directory = os.path.expanduser("~") filename = QFileDialog.getSaveFileName(self, self.tr("Guardar Archivo"), directory) if not filename: return content = editor.toPlainText() editor.rfile.write(content, filename) editor.document().setModified(False) def load_relation(self, filenames=[]): """ Load relation from file """ if not filenames: native_dialog = QFileDialog.DontUseNativeDialog if self.__last_open_folder is None: directory = os.path.expanduser("~") else: directory = self.__last_open_folder ffilter = settings.RFILES.split(';;')[-1] filenames = QFileDialog.getOpenFileNames(self, self.tr("Abrir Archivo"), directory, ffilter, native_dialog) if not filenames: return # Save folder self.__last_open_folder = file_manager.get_path(filenames[0]) self.__modified = True # Load tables self.table_widget.load_relation(filenames) def execute_queries(self): query_widget = Pireal.get_service("query_widget") query_widget.execute_queries() def undo_action(self): query_widget = Pireal.get_service("query_widget") query_widget.undo() def redo_action(self): query_widget = Pireal.get_service("query_widget") query_widget.redo() def cut_action(self): query_widget = Pireal.get_service("query_widget") query_widget.cut() def copy_action(self): query_widget = Pireal.get_service("query_widget") query_widget.copy() def paste_action(self): query_widget = Pireal.get_service("query_widget") query_widget.paste() def check_opened_query_files(self): query_widget = Pireal.get_service("query_widget") return query_widget.opened_files()