Windows and Dialogs

Bases: QMainWindow

Main GUI window for interactive, automated plotting of experimental data.

Responsibilities

  • Load the main QtDesigner-generated UI file and dynamically attach device- specific option panels to the central QStackedWidget.
  • Manage the currently loaded DataSet:
    • Creating, loading, saving, and autosaving datasets.
    • Displaying raw JSON content and console history in helper dialogs.
    • Updating header fields (set name, device type) when datasets change.
  • Integrate logging with a QTextEdit-based console for time-stamped messages.
  • Provide a thin controller layer for:
    • Launching the plotting pipeline via plot_manager.
    • Handling progress updates and console appends.
    • Adding notes and console history back into the dataset.

On construction, the window: - Discovers available devices from implementations.devices. - Loads and registers per-device widgets and their plot functions. - Wires menu actions and buttons to dataset, plotting, and utility actions. - Optionally auto-opens a demo dataset if a file name is supplied.

The class is intended to be the central hub of the GUI application, with device-specific logic pushed into worker classes and implementations.

Source code in gui\windows\MainWindow.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
class UiMainWindow(QtWidgets.QMainWindow):
    """
    Main GUI window for interactive, automated plotting of experimental data.

    Responsibilities
    ----------------
    - Load the main QtDesigner-generated UI file and dynamically attach device-
      specific option panels to the central `QStackedWidget`.
    - Manage the currently loaded `DataSet`:
        * Creating, loading, saving, and autosaving datasets.
        * Displaying raw JSON content and console history in helper dialogs.
        * Updating header fields (set name, device type) when datasets change.
    - Integrate logging with a QTextEdit-based console for time-stamped messages.
    - Provide a thin controller layer for:
        * Launching the plotting pipeline via `plot_manager`.
        * Handling progress updates and console appends.
        * Adding notes and console history back into the dataset.

    On construction, the window:
    - Discovers available devices from `implementations.devices`.
    - Loads and registers per-device widgets and their plot functions.
    - Wires menu actions and buttons to dataset, plotting, and utility actions.
    - Optionally auto-opens a demo dataset if a file name is supplied.

    The class is intended to be the central hub of the GUI application, with
    device-specific logic pushed into worker classes and implementations.
    """
    def __init__(self, demo_file_name: str = None):
        super(UiMainWindow, self).__init__()

        self.thread = None
        self.device_worker = None
        self.dataset = None
        self.dataset_location = None

        # Load the UI, Note that loadUI adds objects to 'self' using objectName
        self.dataWindow = None
        uic.loadUi(constant_paths.WINDOW_PATH, self)

        # Read the config file
        self.config = read_config(constant_paths.CONFIG_PATH)

        # Create/Get a logger with the desired settings
        self.logger = logging.getLogger(constants.LOG_NAME)
        self.consoleTextEdit.setFormatter(
            logging.Formatter(
                "%(asctime)s [%(levelname)8.8s] %(message)s",
                datefmt=f'{constants.DATETIME_FORMAT}: '
            )
        )
        self.logger.addHandler(self.consoleTextEdit)
        self.logger.setLevel(self.config['log_level'])

        self.plot_functions = {}
        self.devices = {}

        # Get list of devices as defined manually in the.devices __init__.py file
        for entry in devices.__all__:
            # Find and load the widget for any given device and add it to the stackedWidget
            entry_ui_file = entry.lower() + ".ui"
            entry_widget = uic.loadUi(constant_paths.WIDGET_PATH + entry_ui_file)
            entry_index = self.stackedWidget.addWidget(entry_widget)
            self.devices[entry] = entry_index

            # Import the corresponding module and get the class methods to set the plot_functions combobox when needed
            module = importlib.import_module(f"{devices.workers.__name__}.{entry.lower()}")
            entry_cls = getattr(module, entry)
            self.plot_functions[entry] = get_class_methods(entry_cls, ignore=["run"])

        # Reset stacked widget to empty page
        self.stackedWidget.setCurrentWidget(self.stackedWidget.widget(0))

        # Define menubar actions
        self.actionCreate_Set.triggered.connect(partial(create_dataset, self))
        self.actionSave_Set.triggered.connect(partial(save_dataset, self))
        self.actionLoad_Set.triggered.connect(partial(open_dataset_file, self))
        self.actionPreferences.triggered.connect(self.not_implemented)
        self.actionQuit.triggered.connect(self.quit)

        self.actionSave_format.triggered.connect(self.not_implemented)
        self.actionColour_scheme.triggered.connect(self.not_implemented)
        self.actionLine_width.triggered.connect(self.not_implemented)

        self.actionDocumentation.triggered.connect(self.navigate_to_docs)
        self.actionAbout.triggered.connect(self.show_about)

        # Define gui button actions
        self.showDataBtn.clicked.connect(self.display_data)
        self.showHistoryBtn.clicked.connect(self.display_history)
        self.addNotesBtn.clicked.connect(self.add_notes)

        self.appendBtn.clicked.connect(self.append_console_to_set)
        self.clearBtn.clicked.connect(partial(clear_data, window=self))
        self.clearAllBtn.clicked.connect(partial(clear_all, window=self))
        self.quitBtn.clicked.connect(self.quit)

        # Define stackedWidget widget actions
        self.plotBtn.clicked.connect(partial(plot_manager, self))

        # Make sure the progress bar is cleared
        self.progressBar.setValue(0)

        # Show the app
        self.show()
        self.console_print("Program Started")

    # Getters
    def get_plot_functions(self, device='Generic') -> list:
        return self.plot_functions[device]

    def get_current_plot_function(self) -> str:
        return self.plotTypeCombo.currentText()

    def get_current_device(self) -> str:
        return self.dataset.get_device()

    def get_dataset(self):
        return self.dataset

    def get_dataset_name(self) -> str:
        if self.dataset is None:
            return None
        return self.dataset.get_name()

    def get_dataset_window(self) -> QtWidgets.QDialog:
        return self.dataWindow

    # Setters
    def set_dataset_window(self, dataset_window: QtWidgets.QDialog):
        self.dataWindow = dataset_window

    def set_dataset(self, dataset: dataset_manager.dataset.DataSet):
        self.dataset = dataset

    # FUNCTIONALITY
    def autosave(self):
        file_name = self.dataset.get_location()
        if file_name is None:
            return self.console_print("Cannot autosave, no file location known. Open or create dataset first")

        with open(file_name, "w") as json_file:
            json.dump(self.dataset, json_file, cls=dataset_manager.DataSetJSONEncoder)
        json_file.close()

        return self.console_print(f"Saved dataset to {file_name}")

    def display_data(self):
        # Abort if no dataset was loaded
        if self.dataset is None:
            return self.console_print("Err: Must first load DataSet", level="warning")

        # Pretty print the dataset in a simple dialog
        pretty_json = json.dumps(
            self.dataset,
            indent=4,
            separators=(',', ': '),
            cls=dataset_manager.DataSetJSONEncoder
        )
        dialog_print(window=self, title=f"DataSet RAW: {self.dataset.get_name()}", contents=pretty_json)

        return None

    def display_history(self):
        if self.dataset is None:
            return self.console_print("Err: Must first load DataSet", level="warning")

        # Prints only the console history to a simple dialog
        pretty_history = ""
        for k, v in sorted(self.dataset.get_console().items()):
            line = f"{v}\n"
            pretty_history += line

        dialog_print(window=self, title=f"DataSet History: {self.dataset.get_name()}", contents=pretty_history)

        return None

    def add_notes(self):
        if self.dataset is None:
            return self.console_print("Err: Must first load DataSet", level="warning")

        # Add any notes to the dataset_manager with a trailing new line
        self.dataset.add_notes(self.notesPlainText.toPlainText() + "\n")
        self.console_print("Notes added to dataset_manager")
        self.autosave()

        return None

    def update_header(self):
        # Header should reflect opened dataset
        self.currSetNameLineEdit.setText(self.dataset.get_name())
        self.currDeviceLineEdit.setText(self.dataset.get_device())

        # Stacked widget should show the correct widget for the opened dataset
        new_page = self.stackedWidget.widget(self.devices[self.dataset.get_device()])
        self.stackedWidget.setCurrentWidget(new_page)

    def report_progress(self, progress: int):
        if not (isinstance(progress, int) and 0 <= progress <= 100):
            raise ValueError("Progress must be an integer between 0 and 100")

        self.progressBar.setValue(progress)

    def on_plot_thread_finished(self):
        # Reset UI elements
        self.progressBar.setValue(0)

        # Free button and log to console
        self.plotBtn.setEnabled(True)
        self.console_print(f"(run {self.device_worker.identifier}) finished")

        # Drop strong references so GC can do its thing
        self.device_worker = None
        self.thread = None

    def save_to_file(self, plaintext: str):
        file_dialog = QtWidgets.QFileDialog.getSaveFileName(self, "Save File", "", "Text Files (*.txt);;All Files (*)")
        if file_dialog[0]:  # Check if a file was selected
            file_path = file_dialog[0]
            with open(file_path, 'w') as file:
                file.write(plaintext)

    def console_print(self, fstring, level="normal"):
        # Print a message to the gui console
        now = datetime.datetime.now()
        fstring_to_print = now.strftime(f"{constants.DATETIME_FORMAT}: ") + fstring

        c = ConsoleColours()

        self.consoleTextEdit.setTextColor(c.get_colour(level))
        self.consoleTextEdit.append(fstring_to_print)
        self.consoleTextEdit.setTextColor(c.get_colour("normal"))

    def append_console_to_set(self):
        if self.dataset is None:
            return self.console_print("Err: Must first load DataSet", level="warning")

        # Append console contents to the dataset_manager
        console_text = self.consoleTextEdit.toPlainText()
        now = datetime.datetime.now()
        self.dataset.add_console(now.strftime(constants.DATETIME_FORMAT), console_text)
        self.console_print("Added console contents to set")
        self.autosave()

        return None

    def show_about(self):
        """
            Shows a simple window with licence, authorship and build information
        """
        # Grab the "about" info from about.txt
        with open(constant_paths.ABOUT_PATH) as about_file:
            about_contents = about_file.read()

        about_dialog = generate_about_dialog(about_contents, self.centralWidget(), constant_paths.LOGO_PATH)

        # Show the about dialog
        about_dialog.exec_()

    def navigate_to_docs(self):
        """
            Opens the default web browser and navigates to the documentation URL.
        """
        import webbrowser
        webbrowser.open(constant_paths.DOCS_URL)

    def not_implemented(self):
        """
            Shows the user a message that the current feature is planned but not yet implemented.
        """
        self.console_print("Feature not implemented", level='warning')

    # ESC now triggers a program exit
    def keyPressEvent(self, event) -> None:
        if event.key() == QtCore.Qt.Key.Key_Escape:
            self.quit()
        else:
            super(UiMainWindow, self).keyPressEvent(event)

    # CHECK: Program exit is not safe
    @staticmethod
    def quit():
        # Terminate the application
        sys.exit()

navigate_to_docs()

Opens the default web browser and navigates to the documentation URL.

Source code in gui\windows\MainWindow.py
315
316
317
318
319
320
def navigate_to_docs(self):
    """
        Opens the default web browser and navigates to the documentation URL.
    """
    import webbrowser
    webbrowser.open(constant_paths.DOCS_URL)

not_implemented()

Shows the user a message that the current feature is planned but not yet implemented.

Source code in gui\windows\MainWindow.py
322
323
324
325
326
def not_implemented(self):
    """
        Shows the user a message that the current feature is planned but not yet implemented.
    """
    self.console_print("Feature not implemented", level='warning')

show_about()

Shows a simple window with licence, authorship and build information

Source code in gui\windows\MainWindow.py
302
303
304
305
306
307
308
309
310
311
312
313
def show_about(self):
    """
        Shows a simple window with licence, authorship and build information
    """
    # Grab the "about" info from about.txt
    with open(constant_paths.ABOUT_PATH) as about_file:
        about_contents = about_file.read()

    about_dialog = generate_about_dialog(about_contents, self.centralWidget(), constant_paths.LOGO_PATH)

    # Show the about dialog
    about_dialog.exec_()

Bases: QDialog

Dialog for interactively creating new DataSet instances.

Overview

This window guides the user through constructing a dataset by: - Selecting a device type from a combobox. - Adding individual files with custom labels, or - Auto-generating filepaths from a directory according to a chosen structure. - Setting the experiment name and experiment date/time.

Behaviour

  • Maintains an internal DataSet object that is updated as the user adds files or generates sets from directories.
  • Displays the current file mapping as formatted JSON in a plain-text widget.
  • Validates that both a name and at least one file are present before enabling the “Done” button.
  • On completion (finish), writes name, device type, and experiment datetime into the dataset and closes with an accepted result.

Parameters

devices : list[str], optional List of available device names to present in the device selection combo box. Defaults to a single "N/A" entry when not specified.

Source code in gui\windows\DataSetCreatorWindow.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
class UiDataCreatorWindow(QtWidgets.QDialog):
    """
       Dialog for interactively creating new `DataSet` instances.

       Overview
       --------
       This window guides the user through constructing a dataset by:
       - Selecting a device type from a combobox.
       - Adding individual files with custom labels, or
       - Auto-generating filepaths from a directory according to a chosen structure.
       - Setting the experiment name and experiment date/time.

       Behaviour
       ---------
       - Maintains an internal `DataSet` object that is updated as the user adds
         files or generates sets from directories.
       - Displays the current file mapping as formatted JSON in a plain-text widget.
       - Validates that both a name and at least one file are present before
         enabling the “Done” button.
       - On completion (`finish`), writes name, device type, and experiment
         datetime into the dataset and closes with an accepted result.

       Parameters
       ----------
       devices : list[str], optional
           List of available device names to present in the device selection combo
           box. Defaults to a single `"N/A"` entry when not specified.
       """
    def __init__(self, devices: list[str] = ["N/A"]):
        super(UiDataCreatorWindow, self).__init__()

        # Load the UI,
        # Note that loadUI adds objects to 'self' using objectName
        uic.loadUi("gui/windows/DataSetCreatorWindow.ui", self)

        self.dataset = dataset_manager.DataSet(datetime.datetime.now().strftime("%Y.%m.%d_%H.%M.%S"))

        # Add the correct devices to the experiment combo box
        self.dataTypeCombo.addItems(devices)

        # Set date to today by default
        self.dateTimeEdit.setDateTime(datetime.datetime.now())

        # Set starting tab to manual dataset creation
        self.tabWidget.setCurrentIndex(0)

        # Define widget action
        self.browseFilesBtn.clicked.connect(self.browse_files)
        self.browseDirBtn.clicked.connect(self.browse_dir)
        self.addLabelBtn.clicked.connect(self.add_file_to_set)
        self.generateBtn.clicked.connect(self.generate_set)
        self.resetBtn.clicked.connect(self.reset)
        self.doneBtn.clicked.connect(self.finish)

        # Enable button when all is filled
        self.showSetPlainText.textChanged.connect(self.finish_button_state)
        self.nameEdit.textChanged.connect(self.finish_button_state)
        self.browseFilesText.textChanged.connect(self.label_button_state)
        self.labelEdit.textChanged.connect(self.label_button_state)

        # Show the app
        self.show()

    def get_dataset(self) -> dataset_manager.dataset.DataSet:
        return self.dataset

    def browse_files(self):
        """
        # Open file selection dialog to get a file path and update gui when confirmed
        """
        file_name = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
        self.browseFilesText.setPlainText(file_name[0])

    def browse_dir(self):
        """
        # Open directory selection dialog to get a path and update gui when confirmed
        """
        dir_name = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Directory')
        self.browseDirText.setPlainText(dir_name)

    def add_file_to_set(self):
        """
        # Gets the path and label and adds it to the current DataSet instance while updating GUI
        """
        # Read name and legend label from gui
        file_name = self.browseFilesText.toPlainText()
        file_label = self.labelEdit.text()

        # Must add a label and file
        if not (file_name and file_label):
            self.show_message(
                title="Label/Path Undefined",
                message="No label or path were defined, cannot proceed"
            )
            return None

        # Check for duplicate label
        if file_label in self.dataset.get_labels():
            self.show_message(
                title="Duplicate Label",
                message="""This label has already been used. Choose another label and try again."""
            )
        else:
            # Add the file to the dataset and update the gui
            self.dataset.set_structure_type("flat")
            self.dataset.add_filepath(file_name, file_label)
            self.showSetPlainText.setPlainText(
                json.dumps(
                    self.dataset.get_filepaths(),
                    indent=4,
                    separators=(',', ': ')
                )
            )
            self.browseFilesText.clear()

        # Empty label widget
        self.labelEdit.clear()

    def generate_set(self):
        """
        Automatically generate a set of filepaths based on a directory path. Will create nested structure if desired
        """
        path = self.browseDirText.toPlainText()

        # If path is not selected, show message and return None
        if not path:
            self.show_message(title="No directory selected", message="""No directory was selected, please select directory and try again""")
            return None

        # Construct the filepaths for this dataset
        active_button = split_camel_case(search_for_first_active_radio_button(self).objectName())[0]
        errors = self.dataset.construct_filepaths(root_dir=path, type=active_button)

        # Show the directories/files that were ignored to the user
        if errors != "":
            self.show_message(title="Files were ignored", message=errors)

        # Show the files in the gui
        self.showSetPlainText.setPlainText(
            json.dumps(
                self.dataset.get_filepaths(),
                indent=4,
                separators=(',', ': ')
            )
        )
        self.dataset.set_structure_type(active_button)

    def label_button_state(self):
        """Only enable the add button once a label and a path were selected """
        # Read name and legend label from gui
        file_name = self.browseFilesText.toPlainText()
        file_label = self.labelEdit.text()

        if (file_name != "") and (file_label != ""):
            self.addLabelBtn.setEnabled(True)
        else:
            self.addLabelBtn.setEnabled(False)


    def finish_button_state(self):
        """ Only enable closing when some data was included """
        # TODO: hmmmmmmmmmmmm, should I be able to close the window if I mistakenly opened it?
        nameTxt = self.nameEdit.text()
        files = self.showSetPlainText.toPlainText()
        if (files != "") and (nameTxt != ""):
            self.doneBtn.setEnabled(True)
        else:
            self.doneBtn.setEnabled(False)

    @staticmethod
    def show_message(title, message):
        msg = QtWidgets.QMessageBox()
        msg.setWindowTitle(title)
        msg.setText(message)
        x = msg.exec_()

    def reset(self):
        """ Completely reset this UI by clearing all elements """
        self.nameEdit.clear()
        self.labelEdit.clear()
        self.browseDirText.clear()
        self.browseFilesText.clear()
        self.showSetPlainText.clear()
        self.finish_button_state()

    def finish(self):
        """ Add name, device type,  and date and time dataset before exiting """
        self.dataset.set_name(self.nameEdit.text())
        self.dataset.set_device(self.dataTypeCombo.currentText())
        experiment_date_time = self.dateTimeEdit.dateTime().toPyDateTime().strftime("%Y.%m.%d_%H.%M.%S")
        self.dataset.set_experiment_date(experiment_date_time)

        self.done(1)

add_file_to_set()

Gets the path and label and adds it to the current DataSet instance while updating GUI

Source code in gui\windows\DataSetCreatorWindow.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def add_file_to_set(self):
    """
    # Gets the path and label and adds it to the current DataSet instance while updating GUI
    """
    # Read name and legend label from gui
    file_name = self.browseFilesText.toPlainText()
    file_label = self.labelEdit.text()

    # Must add a label and file
    if not (file_name and file_label):
        self.show_message(
            title="Label/Path Undefined",
            message="No label or path were defined, cannot proceed"
        )
        return None

    # Check for duplicate label
    if file_label in self.dataset.get_labels():
        self.show_message(
            title="Duplicate Label",
            message="""This label has already been used. Choose another label and try again."""
        )
    else:
        # Add the file to the dataset and update the gui
        self.dataset.set_structure_type("flat")
        self.dataset.add_filepath(file_name, file_label)
        self.showSetPlainText.setPlainText(
            json.dumps(
                self.dataset.get_filepaths(),
                indent=4,
                separators=(',', ': ')
            )
        )
        self.browseFilesText.clear()

    # Empty label widget
    self.labelEdit.clear()

browse_dir()

Open directory selection dialog to get a path and update gui when confirmed

Source code in gui\windows\DataSetCreatorWindow.py
84
85
86
87
88
89
def browse_dir(self):
    """
    # Open directory selection dialog to get a path and update gui when confirmed
    """
    dir_name = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Directory')
    self.browseDirText.setPlainText(dir_name)

browse_files()

Open file selection dialog to get a file path and update gui when confirmed

Source code in gui\windows\DataSetCreatorWindow.py
77
78
79
80
81
82
def browse_files(self):
    """
    # Open file selection dialog to get a file path and update gui when confirmed
    """
    file_name = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
    self.browseFilesText.setPlainText(file_name[0])

finish()

Add name, device type, and date and time dataset before exiting

Source code in gui\windows\DataSetCreatorWindow.py
196
197
198
199
200
201
202
203
def finish(self):
    """ Add name, device type,  and date and time dataset before exiting """
    self.dataset.set_name(self.nameEdit.text())
    self.dataset.set_device(self.dataTypeCombo.currentText())
    experiment_date_time = self.dateTimeEdit.dateTime().toPyDateTime().strftime("%Y.%m.%d_%H.%M.%S")
    self.dataset.set_experiment_date(experiment_date_time)

    self.done(1)

finish_button_state()

Only enable closing when some data was included

Source code in gui\windows\DataSetCreatorWindow.py
170
171
172
173
174
175
176
177
178
def finish_button_state(self):
    """ Only enable closing when some data was included """
    # TODO: hmmmmmmmmmmmm, should I be able to close the window if I mistakenly opened it?
    nameTxt = self.nameEdit.text()
    files = self.showSetPlainText.toPlainText()
    if (files != "") and (nameTxt != ""):
        self.doneBtn.setEnabled(True)
    else:
        self.doneBtn.setEnabled(False)

generate_set()

Automatically generate a set of filepaths based on a directory path. Will create nested structure if desired

Source code in gui\windows\DataSetCreatorWindow.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def generate_set(self):
    """
    Automatically generate a set of filepaths based on a directory path. Will create nested structure if desired
    """
    path = self.browseDirText.toPlainText()

    # If path is not selected, show message and return None
    if not path:
        self.show_message(title="No directory selected", message="""No directory was selected, please select directory and try again""")
        return None

    # Construct the filepaths for this dataset
    active_button = split_camel_case(search_for_first_active_radio_button(self).objectName())[0]
    errors = self.dataset.construct_filepaths(root_dir=path, type=active_button)

    # Show the directories/files that were ignored to the user
    if errors != "":
        self.show_message(title="Files were ignored", message=errors)

    # Show the files in the gui
    self.showSetPlainText.setPlainText(
        json.dumps(
            self.dataset.get_filepaths(),
            indent=4,
            separators=(',', ': ')
        )
    )
    self.dataset.set_structure_type(active_button)

label_button_state()

Only enable the add button once a label and a path were selected

Source code in gui\windows\DataSetCreatorWindow.py
158
159
160
161
162
163
164
165
166
167
def label_button_state(self):
    """Only enable the add button once a label and a path were selected """
    # Read name and legend label from gui
    file_name = self.browseFilesText.toPlainText()
    file_label = self.labelEdit.text()

    if (file_name != "") and (file_label != ""):
        self.addLabelBtn.setEnabled(True)
    else:
        self.addLabelBtn.setEnabled(False)

reset()

Completely reset this UI by clearing all elements

Source code in gui\windows\DataSetCreatorWindow.py
187
188
189
190
191
192
193
194
def reset(self):
    """ Completely reset this UI by clearing all elements """
    self.nameEdit.clear()
    self.labelEdit.clear()
    self.browseDirText.clear()
    self.browseFilesText.clear()
    self.showSetPlainText.clear()
    self.finish_button_state()

Bases: Handler, QTextEdit

QTextEdit-based logging console widget for the GUI.

This class bridges the logging module with a Qt text widget by: - Subclassing both logging.Handler and QTextEdit. - Emitting formatted log messages through a dedicated Qt signal (appendTextEdit), which is connected to the widget's append slot. - Keeping the text area read-only so it behaves like a console.

Typical usage

  • Create an instance and add it as a handler to a logging.Logger.
  • Configure a formatter for the handler.
  • Logged messages will appear in the GUI with the configured format.

Parameters

parent : QWidget Parent widget that will own this console.

Source code in gui\windows\qtexteditconsole.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class QTextEditConsole(logging.Handler, QtWidgets.QTextEdit):
    """
    QTextEdit-based logging console widget for the GUI.

    This class bridges the `logging` module with a Qt text widget by:
    - Subclassing both `logging.Handler` and `QTextEdit`.
    - Emitting formatted log messages through a dedicated Qt signal
      (`appendTextEdit`), which is connected to the widget's `append` slot.
    - Keeping the text area read-only so it behaves like a console.

    Typical usage
    -------------
    - Create an instance and add it as a handler to a `logging.Logger`.
    - Configure a formatter for the handler.
    - Logged messages will appear in the GUI with the configured format.

    Parameters
    ----------
    parent : QWidget
        Parent widget that will own this console.
    """
    appendTextEdit = QtCore.pyqtSignal(str)

    def __init__(self, parent):
        logging.Handler.__init__(self)
        super(QtWidgets.QTextEdit, self).__init__(parent)

        self.setReadOnly(True)
        self.appendTextEdit.connect(self.append)

    def emit(self, record):
        msg = self.format(record)
        self.appendTextEdit.emit(msg)

Display text content inside a modal dialog with optional saving.

The dialog contains: - A read-only text editor showing contents. - “OK” to close the dialog. - “SAVE” to delegate saving via window.save_to_file.

Parameters

window : QMainWindow Parent window providing the save callback. title : str Dialog title bar text. contents : str Text content to display.

Source code in gui\windows\dialogs\dialog_print.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def dialog_print(window: QtWidgets.QMainWindow, title, contents):
    """
    Display text content inside a modal dialog with optional saving.

    The dialog contains:
    - A read-only text editor showing `contents`.
    - “OK” to close the dialog.
    - “SAVE” to delegate saving via `window.save_to_file`.

    Parameters
    ----------
    window : QMainWindow
        Parent window providing the save callback.
    title : str
        Dialog title bar text.
    contents : str
        Text content to display.
    """
    # Prepare a text edit widget to host the contents
    history_text_edit = QtWidgets.QTextEdit(window)
    history_text_edit.setPlainText(contents)

    # Initialise the window
    dialog = QtWidgets.QDialog(window)
    dialog.setWindowTitle(title)

    # Set a default width and minimum height for the dialog
    dialog.resize(600, 400)

    # Create a QVBoxLayout for the dialog
    layout = QtWidgets.QVBoxLayout(dialog)

    # Add the QTextEdit widget to the layout
    layout.addWidget(history_text_edit)

    # Create a QHBoxLayout and host buttons
    button_layout = QtWidgets.QHBoxLayout()
    ok_button = QtWidgets.QPushButton("OK")
    save_button = QtWidgets.QPushButton("SAVE")
    button_layout.addWidget(ok_button)
    button_layout.addWidget(save_button)

    # Add the button layout to the main layout
    layout.addLayout(button_layout)

    # Connect the "OK" button to close the dialog
    ok_button.clicked.connect(dialog.accept)
    save_button.clicked.connect(
        lambda: window.save_to_file(history_text_edit.toPlainText())
    )

    # Show the dialog
    dialog.exec_()

Build and return an 'About' information dialog containing a logo and text.

Features: - Displays an application logo loaded via QPixmap. - Shows about text with HTML formatting support. - Uses a fixed-size vertical layout.

Parameters

about_contents : str HTML/markdown-like text describing the application. centralwidget : QWidget Parent widget for modal behavior. logo_path : str Directory path to the logo image file.

Returns

QDialog Configured dialog ready to be shown.

Source code in gui\windows\dialogs\generate_about_dialog.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def generate_about_dialog(about_contents, centralwidget, logo_path):
    """
    Build and return an 'About' information dialog containing a logo and text.

    Features:
    - Displays an application logo loaded via QPixmap.
    - Shows about text with HTML formatting support.
    - Uses a fixed-size vertical layout.

    Parameters
    ----------
    about_contents : str
        HTML/markdown-like text describing the application.
    centralwidget : QWidget
        Parent widget for modal behavior.
    logo_path : str
        Directory path to the logo image file.

    Returns
    -------
    QDialog
        Configured dialog ready to be shown.
    """
    # Create a custom QDialog for the about information
    about_dialog = QtWidgets.QDialog(centralwidget)
    about_dialog.setWindowTitle("About")

    # Set the fixed size of the dialog
    about_dialog.setFixedSize(650, 700)  # Adjust the dimensions as needed

    # Load and set the image using QPixmap (make sure the path is correct)
    pixmap = QtGui.QPixmap(logo_path + "X_logo_x-lab_baseline_KL.png")
    pixmap = pixmap.scaled(600, 200, QtCore.Qt.KeepAspectRatio)
    image_label = QtWidgets.QLabel(about_dialog)
    image_label.setPixmap(pixmap)

    # Create a QLabel for the text (using HTML formatting)
    text_label = QtWidgets.QLabel(about_dialog)
    text_label.setWordWrap(True)
    text_label.setText(about_contents)

    # Create a QVBoxLayout for the dialog and add the image and text labels
    layout = QtWidgets.QVBoxLayout(about_dialog)
    layout.addWidget(image_label)
    layout.addWidget(text_label)

    about_dialog.setLayout(layout)

    return about_dialog