diff --git a/Fonts/DroidSansMono.ttf b/Fonts/DroidSansMono.ttf new file mode 100644 index 0000000..a007071 Binary files /dev/null and b/Fonts/DroidSansMono.ttf differ diff --git a/MTPy b/MTPy index bae1dfa..477ca5c 160000 --- a/MTPy +++ b/MTPy @@ -1 +1 @@ -Subproject commit bae1dfa1340a5537d9e30bf4dd353400ef9f31ca +Subproject commit 477ca5c4037b1aadc80365b10f540e181202220f diff --git a/Main/Main_Desktop.py b/Main/Main_Desktop.py new file mode 100644 index 0000000..9a6f5d8 --- /dev/null +++ b/Main/Main_Desktop.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +# Kivy module imports +from kivy.lang.builder import Builder +from kivy.properties import ObjectProperty +from kivy.uix.screenmanager import Screen +# Other python module imports +from common.MTPy_Modified import MT_Modded as MeltpoolTomography +from common.threading_decorators import run_in_thread +from types import SimpleNamespace +import operator as op +from ast import literal_eval +from contextlib import redirect_stdout + +# Load kv files +Builder.load_file("Templates/melter_desktop.kv") + + +# This class contains the main window code +class Main(Screen): + # Declare variables to be usable in kivy script + mtpy = ObjectProperty(MeltpoolTomography()) + cache = ObjectProperty(SimpleNamespace()) + + def __init__(self, *args, **kwargs): + super(Main, self).__init__(*args, **kwargs) + # Then, initialize an MTPy object for data processing + self.mtpy = MeltpoolTomography(quiet=True) + # list of shared choosers to keep the same between tabs + shared_io_choosers = ["io_chooser_dataloading", + "io_chooser_buildplate", + "io_chooser_sampledetection", + "io_chooser_persample"] + shared_io_choosers = [self.ids[x] for x in shared_io_choosers] + # Link progress bars in document to their associated functions + self.mtpy.progress_bars["read_layers"] = self.ids.read_layers_progbar + self.mtpy.progress_bars["apply_calibration_curve"] = self.ids.cal_curve_progbar # noqa + self.mtpy.progress_bars["_layers_to_figures"] = self.ids.layers_to_figures_progbar # noqa + self.mtpy.progress_bars["_layers_to_3dplot"] = self.ids.layers_to_3dplot_progbar # noqa + self.mtpy.progress_bars["_layers_to_3dplot_interactive"] = self.ids.layers_to_3dplot_interactive_progbar # noqa + self.mtpy.progress_bars["samples_to_figures"] = self.ids.samples_to_figures_progbar # noqa + self.mtpy.progress_bars["samples_to_3dplot"] = self.ids.samples_to_3dplot_progbar # noqa + self.mtpy.progress_bars["samples_to_3dplot_interactive"] = self.ids.samples_to_3dplot_interactive_progbar # noqa + self.mtpy.progress_bars["separate_samples"] = self.ids.kmeans_separate_samples_progbar # noqa + # self.mtpy.progress_bars["threshold_all_layers"] = self.ids.avgspeed_threshold_progbar # noqa + # self.mtpy.progress_bars["threshold_all_layers"] = self.ids.avgtemp_threshold_progbar # noqa + # Starting items in cache + starting_cache = {"shared_io_choosers": shared_io_choosers, + "in_path": "~", # path to input data + "out_path": "~", # path to output data + "last_loaded_path": False, # path to last loaded + "calibration_curve": False, # last cal curve used + "static_fileformats": # Allowed static formats + ("png", "pdf", "ps", "eps", "svg"), + "thresh_functions": # Threshold functions available + { + ">": op.gt, + "≥": op.ge, + "=": op.eq, + "≠": op.ne, + "≤": op.le, + "<": op.lt, + }, + "progress_bars": self.mtpy.progress_bars} + + self.cache = SimpleNamespace(**starting_cache) + # Make sure each shared io chooser is aware of others and parent app + for chooser in self.cache.shared_io_choosers: + chooser.cache.shared_io_choosers = \ + [x for x in self.cache.shared_io_choosers if x != chooser] + chooser.cache.parent_app = self + # Next, populate dropdowns + # First, the dropdowns for matplotlib filetype options + self.ids.layers_to_figures_filetype_dropdown.populate_dropdown( + self.cache.static_fileformats) + self.ids.avgtemp_thresh_function_dropdown.populate_dropdown( + self.cache.thresh_functions.keys()) + + # Property returns a string summarising the status of data processing + @property + def data_status(self): + # if data_dict is present, generate string for data_dict info + if hasattr(self.mtpy, "data_dict"): + data_dict = self.mtpy.data_dict + if len(data_dict) > 0: + if "layers" not in locals(): + layers = len(data_dict) + points_per_layer = round(sum((points.shape[1] for layer, points + in data_dict.items())) + / layers) + layers_string = f"Layers: {layers}\nAverage Points Per Layer: {points_per_layer}" # noqa + else: + return "No data loaded!" + # if sample_dict is present, generate string for it + else: + layers_string = "Layer data not loaded..." + + if hasattr(self.mtpy, "sample_dict"): + sample_dict = self.mtpy.sample_dict + if "layers" not in locals(): + layers = len(sample_dict[sample_dict.keys()[0]]) + if layers_string == "Layer data not loaded...": + layers_string += f"Layers: {layers}" + num_samples = len(sample_dict) + points_per_sample = round(sum((sum(len(points) + for layer, points in + layer_data.items()) / layers + for sample, layer_data in + sample_dict.items())) + / num_samples) + samples_string = f"Number of Samples: {num_samples}\nAverage Points Per Sample: {points_per_sample}" # noqa + else: + samples_string = "Samples not separated..." + + # Combine to form overall status string + outstring = f"{layers_string}\n{samples_string}" + # and add additional info at the end if present + if self.cache.calibration_curve: + outstring += f"\nCalibration Curve: {self.cache.calibration_curve}" # noqa + return outstring + + # Updates data status displayed in data loading tab + def update_data_status(self): + self.ids.dataloading_display.text = self.data_status + + # Parses text field inputs into **kwargs + def parse_kwargs(self, paramstring: str) -> dict: + if paramstring == "": + return dict() + parsed = [] + neststring = "" # this string keeps track of level and type of nesting + prev_split = 0 # keeps track of previous split point + # This loop splits string at un-nested commas + for i, c in enumerate(paramstring): + if c == "," and neststring == "": + parsed.append(paramstring[prev_split:i]) + prev_split = i + 1 + elif c in ("'", '"'): + if len(neststring) > 0: + if c == neststring[-1]: + neststring = neststring[:-1] + else: + neststring += c + else: + neststring += c + elif c in ("(", "{", "["): + neststring += c + elif c in (")", "}", "]"): + if (c == ")" and neststring[-1] == "(" or + c == "}" and neststring[-1] == "{" or + c == "]" and neststring[-1] == "["): + neststring = neststring[:-1] + parsed.append(paramstring[prev_split:]) + + # parse into pairs of keywords and objects + parsed = (str.strip(x) for x in parsed) + parsed = (x.split("=") for x in parsed) + parsed = ((str.strip(y) for y in x) for x in parsed) + # Finally, interpret objects in the loop below + parsed = {kw: literal_eval(val) for kw, val in parsed} + + return parsed + + # This function loads input data only if not already loaded + @run_in_thread + def load_data(self): + if self.cache.in_path != self.cache.last_loaded_path: + self.mtpy.data_path = self.cache.in_path + self.cache.last_loaded_path = self.cache.in_path + self.mtpy.read_layers() + self.update_data_status() + + # applies calibration curve if has changed + # NOTE: relies on eval! Function may be dangerous + @run_in_thread + def apply_calibration_curve(self): + equation = self.ids.calibration_curve.text + equation = equation.replace(" ", "") + if ((equation != self.cache.calibration_curve) and + (equation != "y=x") and + (equation[:2] == "y=")): + def func(x): + return eval(equation[2:]) + self.mtpy.apply_calibration_curve(func) + self.cache.calibration_curve = equation + self.update_data_status() + + # A wrapper function translating application state into a call to the + # mtpy function layers_to_figures + @run_in_thread + def layers_to_figures(self): + # get filetype and if not allowed replace with default (png) + filetype = self.ids.layers_to_figures_filetype_dropdown.text + if filetype not in self.cache.static_fileformats: + filetype = "png" + # get checkbox parameters + plot_w = self.ids.layers_to_figures_plot_w.active + colorbar = self.ids.layers_to_figures_colorbar.active + # then parse kwarg params + figureparams = self.parse_kwargs( + self.ids.layers_to_figures_figureparams.text) + scatterparams = self.parse_kwargs( + self.ids.layers_to_figures_plotparams.text) + self.mtpy.layers_to_figures(self.cache.out_path, + filetype=filetype, + plot_w=plot_w, + colorbar=colorbar, + figureparams=figureparams, + scatterparams=scatterparams) + + # A wrapper function translating application state into a call to the + # mtpy function layers_to_3dplot + @run_in_thread + def layers_to_3dplot(self): + # get filetype and if not allowed replace with default (png) + filetype = self.ids.layers_to_3dplot_filetype_dropdown.text + if filetype not in self.cache.static_fileformats: + filetype = "png" + # get checkbox parameters + plot_w = self.ids.layers_to_3dplot_plot_w.active + colorbar = self.ids.layers_to_3dplot_colorbar.active + # then parse kwarg params + figureparams = self.parse_kwargs( + self.ids.layers_to_3dplot_figureparams.text) + plotparams = self.parse_kwargs( + self.ids.layers_to_3dplot_plotparams.text) + self.mtpy.layers_to_3dplot(self.cache.out_path, + filetype=filetype, + plot_w=plot_w, + colorbar=colorbar, + figureparams=figureparams, + plotparams=plotparams) + + # A wrapper function translating application state into a call to the + # mtpy function layers_to_3dplot_interactive + @run_in_thread + def layers_to_3dplot_interactive(self): + # get checkbox parameters + plot_w = self.ids.layers_to_3dplot_interactive_plot_w.active + sliceable = self.ids.layers_to_3dplot_interactive_sliceable.active + downsampling = self.ids.layers_to_3dplot_interactive_downsampling.text + if downsampling == "": + downsampling = 1 + else: + downsampling = int(downsampling) + # then parse kwarg params + plotparams = self.parse_kwargs(self.ids.layers_to_3dplot_interactive_plotparams.text) # noqa + self.mtpy.layers_to_3dplot_interactive(self.cache.out_path, + plot_w=plot_w, + sliceable=sliceable, + downsampling=downsampling, + plotparams=plotparams) + + # A wrapper function translating application state into a call to the + # mtpy function samples_to_figures + @run_in_thread + def samples_to_figures(self): + # get filetype and if not allowed replace with default (png) + filetype = self.ids.samples_to_figures_filetype_dropdown.text + if filetype not in self.cache.static_fileformats: + filetype = "png" + # get checkbox parameters + plot_w = self.ids.samples_to_figures_plot_w.active + colorbar = self.ids.samples_to_figures_colorbar.active + # then parse kwarg params + figureparams = self.parse_kwargs( + self.ids.samples_to_figures_figureparams.text) + scatterparams = self.parse_kwargs( + self.ids.samples_to_figures_plotparams.text) + self.mtpy.samples_to_figures(self.cache.out_path, + filetype=filetype, + plot_w=plot_w, + colorbar=colorbar, + figureparams=figureparams, + scatterparams=scatterparams) + + # A wrapper function translating application state into a call to the + # mtpy function samples_to_3dplot + @run_in_thread + def samples_to_3dplot(self): + # get filetype and if not allowed replace with default (png) + filetype = self.ids.samples_to_3dplot_filetype_dropdown.text + if filetype not in self.cache.static_fileformats: + filetype = "png" + # get checkbox parameters + plot_w = self.ids.samples_to_3dplot_plot_w.active + colorbar = self.ids.samples_to_3dplot_colorbar.active + # then parse kwarg params + figureparams = self.parse_kwargs( + self.ids.samples_to_3dplot_figureparams.text) + plotparams = self.parse_kwargs( + self.ids.samples_to_3dplot_plotparams.text) + self.mtpy.samples_to_3dplot(self.cache.out_path, + filetype=filetype, + plot_w=plot_w, + colorbar=colorbar, + figureparams=figureparams, + plotparams=plotparams) + + # A wrapper function translating application state into a call to the + # mtpy function layers_to_3dplot_interactive + @run_in_thread + def samples_to_3dplot_interactive(self): + # get checkbox parameters + plot_w = self.ids.samples_to_3dplot_interactive_plot_w.active + sliceable = self.ids.samples_to_3dplot_interactive_sliceable.active + downsampling = self.ids.samples_to_3dplot_interactive_downsampling.text + if downsampling == "": + downsampling = 1 + else: + downsampling = int(downsampling) + # then parse kwarg params + plotparams = self.parse_kwargs(self.ids.samples_to_3dplot_interactive_plotparams.text) # noqa + self.mtpy.samples_to_3dplot_interactive(self.cache.out_path, + plot_w=plot_w, + sliceable=sliceable, + downsampling=downsampling, + plotparams=plotparams) + + # A wrapper function translating application state into a call to the + # mtpy module to threshold all layers based on speed + @run_in_thread + def avgspeed_threshold(self): + # get input parameters + thresh_percent = float(self.ids.avgspeed_thresh_thresh_percent.text) + avgof = int(self.ids.avgspeed_thresh_avgof.text) + # Link to progress bar (at time of call since this bar is shared) + self.mtpy.progress_bars["threshold_all_layers"] = self.ids.avgspeed_threshold_progbar # noqa + # then call the function + self.mtpy.threshold_all_layers( + self.mtpy.avgspeed_threshold, + { + "threshold_percent": thresh_percent, + "avgof": avgof + } + ) + + # A wrapper function translating application state into a call to the + # mtpy module to threshold all layers based on temperature + @run_in_thread + def avgtemp_threshold(self): + # get filetype and if not allowed replace with default (png) + thresh_function = self.ids.avgtemp_thresh_function_dropdown.text + if thresh_function not in self.cache.thresh_functions.keys(): + thresh_function = ">" + # get threshold percentage + thresh_percent = float(self.ids.avgtemp_thresh_thresh_percent.text) + # Link to progress bar (at time of call since this bar is shared) + self.mtpy.progress_bars["threshold_all_layers"] = self.ids.avgtemp_threshold_progbar # noqa + # then call the function + self.mtpy.threshold_all_layers( + self.mtpy.avgw_threshold, + { + "threshold_percent": thresh_percent, + "comparison_func": self.cache.thresh_functions[thresh_function] + } + ) + + @run_in_thread + def separate_samples(self): + # get input parameters + nsamples = int(self.ids.kmeans_nsamples.text) + # if only 0 or 1 samples, no need to separate + if nsamples == 0 or nsamples == 1: + return + console_io_buffer = self.ids.kmeans_separate_console_output.io_buffer + # Temporarily unmute mtpy for console output + self.mtpy.quiet = False + with redirect_stdout(console_io_buffer): + self.mtpy.detect_samples(nsamples) + print("\nSample detection complete!\n(Separation progress on bar above)") # noqa + # Then, remute once finished + self.mtpy.quiet = True + # Separate samples. Should use progbar so no need for teminal + self.mtpy.separate_samples() + # Finally, update the status string + self.update_data_status() diff --git a/Main/__init__.py b/Main/__init__.py new file mode 100644 index 0000000..0987460 --- /dev/null +++ b/Main/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +""" +This internal "Main" module of the "Melter" project contains a collection of +main files for booting Melter GUIs in different environments +""" diff --git a/Melter.py b/Melter.py new file mode 100755 index 0000000..c3e4b35 --- /dev/null +++ b/Melter.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +from kivy.app import App +# For polymorphism (e.g. phone app) use conditional import below +# The code below currently is kind of pointless and "Main_Phone" does not exist +# but it demonstrates the plan for polymorphism +mode = "desktop" +if mode == "desktop": + from Main.Main_Desktop import Main +elif mode == "phone": + from Main.Main_Phone import Main + + +# Create application class +class Melter(App): + + def build(self): + return Main() + + +dev_mode = False + + +if (__name__ == "__main__") and dev_mode: + # DEBUG + test = Melter() + test.run() + breakpoint() +elif __name__ == "__main__": + Melter().run() diff --git a/Templates/console_output.py b/Templates/console_output.py new file mode 100644 index 0000000..fb2b75c --- /dev/null +++ b/Templates/console_output.py @@ -0,0 +1,50 @@ +from kivy.event import EventDispatcher +from kivy.properties import BooleanProperty +from kivy.uix.textinput import TextInput +from io import StringIO + + +# This variation of StringIO communicates back to parent observer +class ObservableStringIO(StringIO): + + def __init__(self, *args, **kwargs): + if "observer" in kwargs: + self.observer = kwargs.pop("observer") + super(ObservableStringIO, self).__init__(*args, **kwargs) + + def write(self, *args, **kwargs): + # Just need to flip trigger on write. Specific value doesnt matter + self.observer.trigger = not self.observer.trigger + super(ObservableStringIO, self).write(*args, **kwargs) + + +# This StringIO wrapper object outputs string from io to target on every write +class StringIO_toString_Observer(EventDispatcher): + trigger = BooleanProperty() + + def __init__(self, target, **kwargs): + self.io_buffer = ObservableStringIO(observer=self) + self.trigger = False # <- val doesnt matter as long a bool + self.target = target + self.target.text = str(self.io_buffer.getvalue()) + super(StringIO_toString_Observer, self).__init__(**kwargs) + + def on_trigger(self, instance, value): + self.target.text = str(self.io_buffer.getvalue()) + + +# This console output widget can output io streams if redirected to its +# io_buffer property +class ConsoleOutput(TextInput): + def __init__(self, *args, **kwargs): + # Define and apply default kwargs + defaultkwargs = {"readonly": True, + "background_color": (0, 0, 0, 1), + "foreground_color": (1, 1, 1, 1)} + kwargs = {k: (v if k not in kwargs else kwargs[k]) + for k, v in defaultkwargs.items()} + # Then call super + super(ConsoleOutput, self).__init__(*args, **kwargs) + # Then add observer and ref to io_buffer for ease-of-use + self.observer = StringIO_toString_Observer(self) + self.io_buffer = self.observer.io_buffer diff --git a/Templates/dropdown_button.py b/Templates/dropdown_button.py new file mode 100644 index 0000000..c9e7951 --- /dev/null +++ b/Templates/dropdown_button.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +# Kivy module imports +from kivy.uix.button import Button +from kivy.uix.dropdown import DropDown + + +class DropdownButton(Button): + def __init__(self, option_list=None, **kwargs): + # ensure "text" kwarg isnt present + if "test" in kwargs: + kwargs.pop("test") + + # Add default args if they're not specifically assigned + self.defaultkwargs = \ + {"background_color": [x*0.75 for x in self.background_color], + } + + for keyword, arg in self.defaultkwargs.items(): + if keyword not in kwargs: + kwargs[keyword] = arg + + self.kwargs = kwargs + super(DropdownButton, self).__init__(**self.kwargs) + + # Create lambdas for callbacks + self.__bind_button = lambda btn: self.dropdown_list.select(btn.text) + self.__update_label = lambda instance, x: setattr(self, "text", x) + + if option_list is not None: + self.populate_dropdown(option_list) + + def populate_dropdown(self, option_list): + kwargs = self.kwargs.copy() + kwargs["size_hint_y"] = None + if "height" not in kwargs: + kwargs["height"] = 50 + if "__no_builder" in kwargs: + kwargs.pop("__no_builder") + + self.dropdown_list = None + self.dropdown_list = DropDown() + + for x in option_list: + button = Button(text=x, **kwargs) + # button = Button(text=x, size_hint_y=None, height=50) + button.bind(on_release=self.__bind_button) + self.dropdown_list.add_widget(button) + + self.bind(on_release=self.dropdown_list.open) + self.dropdown_list.bind(on_select=self.__update_label) diff --git a/Templates/file_chooser_popup.kv b/Templates/file_chooser_popup.kv new file mode 100644 index 0000000..acc83df --- /dev/null +++ b/Templates/file_chooser_popup.kv @@ -0,0 +1,29 @@ +# file_chooser_popup.kv +#:kivy 2.0 +#:import path os.path.expanduser + +: + title: "Choose a data folder" + size_hint: .9, .9 + auto_dismiss: False + + BoxLayout: + orientation: "vertical" + FileChooser: + id: filechooser + path: path("~") + dirselect: True + FileChooserIconLayout + + BoxLayout: + size_hint: (1, 0.1) + pos_hint: {'center_x': .5, 'center_y': .5} + spacing: 20 + Button: + text: "Cancel" + on_release: root.dismiss() + Button: + text: "Load" + on_release: root.load(filechooser.selection) + id: ldbtn + disabled: True if filechooser.selection==[] else False diff --git a/Templates/input_output_chooser.kv b/Templates/input_output_chooser.kv new file mode 100644 index 0000000..33fa00c --- /dev/null +++ b/Templates/input_output_chooser.kv @@ -0,0 +1,63 @@ +# input_output_chooser.kv +#:kivy 2.0 + +# Widget: +: + BoxLayout: + orientation: "vertical" + # First item is the input file directory chooser in stacked layout + StackLayout: + orientation: "lr-tb" + size_hint_y: None + size_hint_x: 1. + height: 30 + spacing: 5 + + BoxLayout: + size_hint_x: 1. + # Containing a description of what the field points to + Label: + halign: "right" + text: "Layer data directory:" + size_hint_x: None + width: 160 + # A textbox for filepath entry + TextInput: + id: in_path + readonly: True + hint_text: "Read pyrometry data from..." + # A button that opens the file chooser popup + Button: + text: "Choose Folder" + size_hint_x: None + width: 120 + on_press: root.open_chooser("in_path") + + # Second item is the output file directory chooser in stacked layout + StackLayout: + orientation: "lr-tb" + size_hint_y: None + size_hint_x: 1. + height: 30 + spacing: 5 + + BoxLayout: + size_hint_x: 1. + # Containing a description of what the field points to + Label: + halign: "right" + text: "Output directory:" + size_hint_x: None + width: 160 + # A textbox for filepath entry + TextInput: + id: out_path + readonly: True + hint_text: "Output figures to..." + # A button that opens the file chooser popup + Button: + text: "Choose Folder" + size_hint_x: None + width: 120 + on_press: + root.open_chooser("out_path") diff --git a/Templates/input_output_chooser.py b/Templates/input_output_chooser.py new file mode 100644 index 0000000..4c24f09 --- /dev/null +++ b/Templates/input_output_chooser.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +# Kivy module imports +from kivy.lang.builder import Builder +from kivy.uix.popup import Popup +from kivy.properties import ObjectProperty +from kivy.uix.boxlayout import BoxLayout +# Other python module imports +from types import SimpleNamespace + +Builder.load_file("Templates/file_chooser_popup.kv") + + +# Create classes for loaded kv files +# This class contains the popup for choosing files +class FileChooserPopup(Popup): + load = ObjectProperty() + + +class InputOutputChooser(BoxLayout): + load = ObjectProperty() + + def __init__(self, *args, **kwargs): + super(InputOutputChooser, self).__init__(*args, **kwargs) + starting_cache = {"popups": {}, # A dict to contain all popup objects + "shared_io_choosers": False, + "parent_app": False} + self.cache = SimpleNamespace(**starting_cache) + + # The functions "open" and "load" are used to load the file chooser popup + def open_chooser(self, pathattr: str): + # Wrapper function to allow for multiple different choosers + def load_chooser_wrapper(selection): + return self.load_chooser(pathattr, selection) + + self.cache.popups[pathattr] = \ + FileChooserPopup(load=load_chooser_wrapper) + self.cache.popups[pathattr].open() + + def load_chooser(self, pathattr: str, selection): + path_string = str(selection[0]) + setattr(self, pathattr, path_string) + self.cache.popups[pathattr].dismiss() + + # check for non-empty list i.e. file selected + if pathattr in self.__dict__: + # set own details based on selection + id = getattr(self.ids, pathattr) + id.text = getattr(self, pathattr) + # set parameters for shared and parent if present + if self.cache.shared_io_choosers: + for chooser in self.cache.shared_io_choosers: + id = getattr(chooser.ids, pathattr) + id.text = getattr(self, pathattr) + if self.cache.parent_app: + setattr(self.cache.parent_app.cache, pathattr, path_string) diff --git a/Templates/melter_desktop.kv b/Templates/melter_desktop.kv new file mode 100644 index 0000000..c1ad2ae --- /dev/null +++ b/Templates/melter_desktop.kv @@ -0,0 +1,735 @@ +# melter_desktop.kv +#:kivy 2.0 +#:include Templates/input_output_chooser.kv +#:import InputOutputChooser Templates.input_output_chooser.InputOutputChooser +#:import DropdownButton Templates.dropdown_button.DropdownButton +#:import ConsoleOutput Templates.console_output.ConsoleOutput + + +
: + name: "main_screen" + id: main_screen + TabbedPanel: + id: test + title: "Melter" + do_default_tab: False + # First tab is for loading data + TabbedPanelItem: + id: loading_tab + text: "Data Loading" + # UI made up of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_dataloading + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for loading data + GridLayout: + orientation: "tb-lr" + size_hint_x: 0.9 + size_hint_y: 0.8 + pos_hint: {"x": 0.05, "y": 0.05} + cols: 1 + # This is the button and progress bar for loading data + StackLayout: + size_hint_y: 0.15 + orientation: "lr-tb" + Button: + size_hint_x: 0.25 + text: "Load Pyrometry Data" + on_press: root.load_data() + ProgressBar: + id: read_layers_progbar + size_hint_x: 0.75 + value: 0 + # A button that applies the calibration curve + Button: + text: "Apply Calibration Curve" + size_hint_x: 0.25 + width: 120 + on_press: root.apply_calibration_curve() + StackLayout: + size_hint_x: 0.25 + size_hint_y: 0.5 + cols: 1 + rows: 2 + # Containing a description of what the field points to + Label: + halign: "right" + text: "Calibration Curve" + width: 160 + # A textbox for cal curve equation entry + TextInput: + halign: "center" + valign: "center" + id: calibration_curve + readonly: False + hint_text: "y = x" + ProgressBar: + id: cal_curve_progbar + size_hint_x: 0.5 + value: 0 + # This label displays current status of data processing + Label: + id: dataloading_display + text: "No data loaded!" + halign: "center" + valign: "center" + + # Second tab is for detecting & separating samples + TabbedPanelItem: + id: detection_tab + text: "Sample\nDetection" + + # Items are stacked from bottom to top. UI made of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_sampledetection + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for per sample data + GridLayout: + size_hint_x: 0.9 + size_hint_y: 0.75 + pos_hint: {"x": 0.05, "y": 0.05} + cols: 1 + # Below is a tabbed panel for different thresholding methods + TabbedPanel: + size_hint_x: 1.0 + size_hint_y: 0.25 + do_default_tab: False + tab_pos: "left_top" + tab_width: self.height + # This is a messy solution, but uses a nested TabbedPanel + # to label the threshold functions. Would prefer better + # method. Maybe some kind of tooltip? + TabbedPanelItem: + text: "Thresholding\nTechniques" + TabbedPanel: + size_hint_x: 1.0 + size_hint_y: 1.0 + do_default_tab: False + tab_pos: "left_top" + tab_width: self.height / 2 + # First tab is options for speed based thresholding + TabbedPanelItem: + text: "Speed" + id: avgspeed_thresh_panel + # Create a panel to hold items + StackLayout: + size_hint_x: 1.0 + size_hint_y: 1.0 + orientation: "lr-tb" + # Top panel contains buttons + #:set avgspeed_toprowheight 30. + # This label is a spacer + Label: + size_hint_x: 1.0 + size_hint_y: None + height: ((self.parent.height / 2) - avgspeed_toprowheight) / 2 + # Here is the main options, held in center by spacers + Label: + text: "Thresholding percent:" + size_hint_x: 0.25 + size_hint_y: None + height: avgspeed_toprowheight + text_size: self.size + halign: "right" + valign: "middle" + TextInput: + id: avgspeed_thresh_thresh_percent + hint_text: "x < (% of max speed)" + size_hint_y: None + height: avgspeed_toprowheight + size_hint_x: 0.25 + Label: + text: "Rolling average:" + size_hint_x: 0.25 + size_hint_y: None + height: avgspeed_toprowheight + text_size: self.size + halign: "right" + valign: "middle" + TextInput: + id: avgspeed_thresh_avgof + hint_text: "n" + size_hint_y: None + height: avgspeed_toprowheight + size_hint_x: 0.25 + # This label is a spacer + Label: + size_hint_x: 1.0 + size_hint_y: None + height: ((self.parent.height / 2) - avgspeed_toprowheight) / 2 + # This button and progress bar trigger and track the + # thresholding of data + Button: + text: "Threshold by rolling\naverage of speed" + size_hint_x: 0.25 + size_hint_y: 0.5 + on_press: root.avgspeed_threshold() + ProgressBar: + id: avgspeed_threshold_progbar + size_hint_x: 0.75 + size_hint_y: 0.5 + value: 0 + + # Second tab is for tepmerature based thresholding + TabbedPanelItem: + text: "Temp" + id: avgtemp_thresh_panel + StackLayout: + size_hint_x: 1.0 + size_hint_y: 1.0 + orientation: "lr-tb" + # Top panel contains buttons + #:set avgtemp_toprowheight 30. + # This label is a spacer + Label: + size_hint_x: 1.0 + size_hint_y: None + height: ((self.parent.height / 2) - avgtemp_toprowheight) / 2 + # Here is the main options, held in center by spacers + Label: + text: "Keep points where: Temperature " + size_hint_x: 0.5 + size_hint_y: None + height: avgtemp_toprowheight + text_size: self.size + halign: "right" + valign: "middle" + DropdownButton: + id: avgtemp_thresh_function_dropdown + text: ">" + size_hint_x: 0.1 + size_hint_y: None + height: avgtemp_toprowheight + TextInput: + id: avgtemp_thresh_thresh_percent + hint_text: "x" + size_hint_y: None + height: avgtemp_toprowheight + size_hint_x: 0.1 + Label: + text: "% of maximum" + size_hint_x: 0.3 + size_hint_y: None + height: avgtemp_toprowheight + text_size: self.size + halign: "left" + valign: "middle" + # This label is a spacer + Label: + size_hint_x: 1.0 + size_hint_y: None + height: ((self.parent.height / 2) - avgtemp_toprowheight) / 2 + # This button and progress bar trigger and track the + # thresholding of data + Button: + text: "Threshold by\naverage temperature" + size_hint_x: 0.25 + size_hint_y: 0.5 + on_press: root.avgtemp_threshold() + ProgressBar: + id: avgtemp_threshold_progbar + size_hint_x: 0.75 + size_hint_y: 0.5 + value: 0 + # The next piece contains controls for KMeans sample separation + StackLayout: + #:set kmeans_toprowheight 30. + id: kmeans_panel + size_hint_x: 1.0 + size_hint_y: 0.25 + # This label is a spacer + Label: + size_hint_x: 1.0 + size_hint_y: None + height: ((self.parent.height / 2) - kmeans_toprowheight) / 2 + Label: + text: "Number of Samples: " + size_hint_x: 0.25 + size_hint_y: None + height: kmeans_toprowheight + text_size: self.size + halign: "right" + valign: "middle" + TextInput: + id: kmeans_nsamples + hint_text: "n" + size_hint_x: 0.25 + size_hint_y: None + height: kmeans_toprowheight + # This label is a spacer + Label: + size_hint_x: 1.0 + size_hint_y: None + height: ((self.parent.height / 2) - kmeans_toprowheight) / 2 + # This button and progress bar trigger and track the + # generation of figures + Button: + text: "Separate Samples" + size_hint_x: 0.25 + size_hint_y: 0.5 + on_press: root.separate_samples() + ProgressBar: + id: kmeans_separate_samples_progbar + size_hint_x: 0.75 + size_hint_y: 0.5 + value: 0 + + # The box below will display console output from kmeans + ScrollView: + size_hint_x: 1.0 + size_hint_y: 0.5 + ConsoleOutput: + id: kmeans_separate_console_output + size_hint_x: 1.0 + + # Third tab is for generating whole buildplate figures + TabbedPanelItem: + id: plate_vis_tab + text: "Buildplate\nVisualization" + + # Items are stacked from bottom to top. UI made of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_buildplate + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for buildplate plotting + StackLayout: + size_hint_x: 0.9 + size_hint_y: 0.8 + pos_hint: {"x": 0.05, "y": 0.05} + orientation: "lr-tb" + #:set row_y_hint 0.15 + #:set num_functions 3 + #:set spacer_y_hint ((1-(row_y_hint*num_functions*2))/(num_functions-1)) + + # First function on this panel (layers_to_figures) begins here + # This button will be a dropdown + DropdownButton: + id: layers_to_figures_filetype_dropdown + text: "select file type\n(png)" + size_hint_x: 0.25 + size_hint_y: row_y_hint + # Right of the dropdown will be a suite of options + StackLayout: + orientation: "tb-lr" + size_hint_x: 0.75 + size_hint_y: row_y_hint + # Checkboxes for toggling plot colouring and colourbars + CheckBox: + id: layers_to_figures_plot_w + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + CheckBox: + id: layers_to_figures_colorbar + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + Label: + text: "Display colourbar" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + # And text input for other figure parameters + TextInput: + id: layers_to_figures_figureparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Figure parameters..." + TextInput: + id: layers_to_figures_plotparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate static 2d\nbuidplate figures" + size_hint_x: 0.25 + size_hint_y: row_y_hint + on_press: root.layers_to_figures() + ProgressBar: + id: layers_to_figures_progbar + size_hint_x: 0.75 + size_hint_y: row_y_hint + value: 0 + + # This spacer clearly separates panels for functions + Label: + size_hint_x: 1.0 + size_hint_y: spacer_y_hint + + # Second function on this panel (layers_to_3dplot) begins here + # This button will be a dropdown + DropdownButton: + id: layers_to_3dplot_filetype_dropdown + text: "select file type\n(png)" + size_hint_x: 0.25 + size_hint_y: row_y_hint + # Right of the dropdown will be a suite of options + StackLayout: + orientation: "tb-lr" + size_hint_x: 0.75 + size_hint_y: row_y_hint + # Checboxes for toggling plot colouring and colourbars + CheckBox: + id: layers_to_3dplot_plot_w + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + CheckBox: + id: layers_to_3dplot_colorbar + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + Label: + text: "Display colourbar" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + # And text input for other figure parameters + TextInput: + id: layers_to_3dplot_figureparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Figure parameters..." + TextInput: + id: layers_to_3dplot_plotparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate static 3d\nbuidplate figures" + size_hint_x: 0.25 + size_hint_y: row_y_hint + on_press: root.layers_to_3dplot() + ProgressBar: + id: layers_to_3dplot_progbar + size_hint_x: 0.75 + size_hint_y: row_y_hint + value: 0 + + # This spacer clearly separates panels for functions + Label: + size_hint_x: 1.0 + size_hint_y: spacer_y_hint + + # Second function on this panel (layers_to_3dplot) begins here + # First part of this panel will be a suite of options + StackLayout: + orientation: "lr-tb" + size_hint_x: 1.00 + size_hint_y: row_y_hint + # Checkboxes for toggling plot colouring and colourbars + # and downsampling factor input + #:set one_third 1/3 + Label: + text: "Downsampling: " + size_hint_x: 0.75 * one_third + size_hint_y: 0.5 + text_size: self.size + halign: "right" + valign: "middle" + TextInput: + id: layers_to_3dplot_interactive_downsampling + hint_text: "1" + size_hint_x: 0.25 * one_third + size_hint_y: 0.5 + CheckBox: + id: layers_to_3dplot_interactive_plot_w + active: True + size_hint_x: 0.25 * one_third + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.75 * one_third + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + CheckBox: + id: layers_to_3dplot_interactive_sliceable + active: True + size_hint_x: 0.25 * one_third + size_hint_y: 0.5 + Label: + text: "Sliceable model" + size_hint_x: 0.75 * one_third + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + TextInput: + id: layers_to_3dplot_interactive_plotparams + size_hint_x: 1. + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate interactive 3d\nbuidplate figures" + size_hint_x: 0.25 + size_hint_y: row_y_hint + on_press: root.layers_to_3dplot_interactive() + ProgressBar: + id: layers_to_3dplot_interactive_progbar + size_hint_x: 0.75 + size_hint_y: row_y_hint + value: 0 + + # Fourth tab is for generating figures + TabbedPanelItem: + id: sample_vis_tab + text: "Per Sample\nVisualization" + + # Items are stacked from bottom to top. UI made of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_persample + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for buildplate plotting + StackLayout: + size_hint_x: 0.9 + size_hint_y: 0.8 + pos_hint: {"x": 0.05, "y": 0.05} + orientation: "lr-tb" + #:set row_y_hint 0.15 + #:set num_functions 3 + #:set spacer_y_hint ((1-(row_y_hint*num_functions*2))/(num_functions-1)) + + # First function on this panel (layers_to_figures) begins here + # This button will be a dropdown + DropdownButton: + id: samples_to_figures_filetype_dropdown + text: "select file type\n(png)" + size_hint_x: 0.25 + size_hint_y: row_y_hint + # Right of the dropdown will be a suite of options + StackLayout: + orientation: "tb-lr" + size_hint_x: 0.75 + size_hint_y: row_y_hint + # Checkboxes for toggling plot colouring and colourbars + CheckBox: + id: samples_to_figures_plot_w + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + CheckBox: + id: samples_to_figures_colorbar + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + Label: + text: "Display colourbar" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + # And text input for other figure parameters + TextInput: + id: samples_to_figures_figureparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Figure parameters..." + TextInput: + id: samples_to_figures_plotparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate static 2d\nsample figures" + size_hint_x: 0.25 + size_hint_y: row_y_hint + on_press: root.samples_to_figures() + ProgressBar: + id: samples_to_figures_progbar + size_hint_x: 0.75 + size_hint_y: row_y_hint + value: 0 + + # This spacer clearly separates panels for functions + Label: + size_hint_x: 1.0 + size_hint_y: spacer_y_hint + + # Second function on this panel (layers_to_3dplot) begins here + # This button will be a dropdown + DropdownButton: + id: samples_to_3dplot_filetype_dropdown + text: "select file type\n(png)" + size_hint_x: 0.25 + size_hint_y: row_y_hint + # Right of the dropdown will be a suite of options + StackLayout: + orientation: "tb-lr" + size_hint_x: 0.75 + size_hint_y: row_y_hint + # Checboxes for toggling plot colouring and colourbars + CheckBox: + id: samples_to_3dplot_plot_w + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + CheckBox: + id: samples_to_3dplot_colorbar + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + Label: + text: "Display colourbar" + size_hint_x: 0.4 + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + # And text input for other figure parameters + TextInput: + id: samples_to_3dplot_figureparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Figure parameters..." + TextInput: + id: samples_to_3dplot_plotparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate static 3d\nsample figures" + size_hint_x: 0.25 + size_hint_y: row_y_hint + on_press: root.samples_to_3dplot() + ProgressBar: + id: samples_to_3dplot_progbar + size_hint_x: 0.75 + size_hint_y: row_y_hint + value: 0 + + # This spacer clearly separates panels for functions + Label: + size_hint_x: 1.0 + size_hint_y: spacer_y_hint + + # Second function on this panel (layers_to_3dplot) begins here + # First part of this panel will be a suite of options + StackLayout: + orientation: "lr-tb" + size_hint_x: 1.00 + size_hint_y: row_y_hint + # Checkboxes for toggling plot colouring and colourbars + # and downsampling factor input + #:set one_third 1/3 + Label: + text: "Downsampling: " + size_hint_x: 0.75 * one_third + size_hint_y: 0.5 + text_size: self.size + halign: "right" + valign: "middle" + TextInput: + id: samples_to_3dplot_interactive_downsampling + hint_text: "1" + size_hint_x: 0.25 * one_third + size_hint_y: 0.5 + CheckBox: + id: samples_to_3dplot_interactive_plot_w + active: True + size_hint_x: 0.25 * one_third + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.75 * one_third + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + CheckBox: + id: samples_to_3dplot_interactive_sliceable + active: True + size_hint_x: 0.25 * one_third + size_hint_y: 0.5 + Label: + text: "Sliceable model" + size_hint_x: 0.75 * one_third + size_hint_y: 0.5 + text_size: self.size + halign: "left" + valign: "middle" + TextInput: + id: samples_to_3dplot_interactive_plotparams + size_hint_x: 1. + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate interactive 3d\nsample figures" + size_hint_x: 0.25 + size_hint_y: row_y_hint + on_press: root.samples_to_3dplot_interactive() + ProgressBar: + id: samples_to_3dplot_interactive_progbar + size_hint_x: 0.75 + size_hint_y: row_y_hint + value: 0 diff --git a/Templates/melter_desktop_backup.kv b/Templates/melter_desktop_backup.kv new file mode 100644 index 0000000..6b9bca4 --- /dev/null +++ b/Templates/melter_desktop_backup.kv @@ -0,0 +1,284 @@ +# melter_desktop.kv +#:kivy 2.0 +#:include Templates/input_output_chooser.kv +#:import InputOutputChooser Templates.input_output_chooser.InputOutputChooser +#:import DropdownButton Templates.dropdown_button.DropdownButton + + +
: + name: "main_screen" + id: main_screen + TabbedPanel: + id: test + title: "Melter" + do_default_tab: False + + # First tab is for loading data + TabbedPanelItem: + id: loading_tab + text: "Data Loading" + + # UI made up of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_dataloading + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for loading data + GridLayout: + orientation: "tb-lr" + size_hint_x: 0.9 + size_hint_y: 0.8 + pos_hint: {"x": 0.05, "y": 0.05} + # spacing: 2. + cols: 1 + # This is the button and progress bar for loading data + StackLayout: + size_hint_y: 0.15 + orientation: "lr-tb" + Button: + size_hint_x: 0.25 + text: "Load Pyrometry Data" + on_press: root.load_data() + ProgressBar: + id: read_layers_progbar + size_hint_x: 0.75 + value: 0. + # A button that applies the calibration curve + Button: + text: "Apply Calibration Curve" + size_hint_x: 0.25 + width: 120 + on_press: root.apply_calibration_curve() + StackLayout: + size_hint_x: 0.25 + size_hint_y: 0.5 + cols: 1 + rows: 2 + # Containing a description of what the field points to + Label: + halign: "right" + text: "Calibration Curve" + width: 160 + # A textbox for cal curve equation entry + TextInput: + halign: "center" + valign: "center" + id: calibration_curve + readonly: False + hint_text: "y = x" + ProgressBar: + id: cal_curve_progbar + size_hint_x: 0.5 + value: 0. + # This label displays current status of data processing + Label: + id: dataloading_display + text: "No data loaded!" + halign: "center" + valign: "center" + + # Second tab is for detecting & separating samples + TabbedPanelItem: + id: detection_tab + text: "Sample\nDetection" + + # Items are stacked from bottom to top. UI made of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_sampledetection + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for per sample data + GridLayout: + size_hint_x: 0.9 + size_hint_y: 0.75 + pos_hint: {"x": 0.05, "y": 0.05} + # spacing: 2. + cols: 2 + Button: + text: "Test 5" + Button: + text: "Test 6" + Button: + text: "Test 7" + Button: + text: "Test 8" + + # Third tab is for generating whole buildplate figures + TabbedPanelItem: + id: plate_vis_tab + text: "Buildplate\nVisualization" + + # Items are stacked from bottom to top. UI made of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_buildplate + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for buildplate plotting + GridLayout: + orientation: "tb-lr" + size_hint_x: 0.9 + size_hint_y: 0.8 # These the buttons and progress bar for the layers_to_figures function + pos_hint: {"x": 0.05, "y": 0.05} + cols: 1 + # These the buttons and progress bar for the layers_to_figures function + StackLayout: + size_hint_x: 1.0 + size_hint_y: 1.0 + orientation: "lr-tb" + # This button will be a dropdown + DropdownButton: + id: layers_to_figures_filetype_dropdown + text: "select file type\n(png)" + size_hint_x: 0.25 + size_hint_y: 0.15 + # Right of the dropdown will be a suite of options + StackLayout: + orientation: "tb-lr" + size_hint_x: 0.75 + size_hint_y: 0.15 + # Checboxes for toggling plot colouring and colourbars + CheckBox: + id: layers_to_figures_plot_w + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + CheckBox: + id: layers_to_figures_colorbar + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.4 + size_hint_y: 0.5 + Label: + text: "Display colourbar" + size_hint_x: 0.4 + size_hint_y: 0.5 + # And text input for other figure parameters + TextInput: + id: layers_to_figures_figureparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Figure parameters..." + TextInput: + id: layers_to_figures_plotparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate static 2d\nbuidplate figures" + size_hint_x: 0.25 + size_hint_y: 0.15 + on_press: root.layers_to_figures() + ProgressBar: + id: layers_to_figures_progbar + size_hint_x: 0.75 + size_hint_y: 0.15 + value: 0. + # These the buttons and progress bar for the layers_to_figures function + StackLayout: + size_hint_x: 1.0 + size_hint_y: 0.15 + orientation: "lr-tb" + # This button will be a dropdown + DropdownButton: + id: layers_to_3dplot_filetype_dropdown + text: "select file type\n(png)" + size_hint_x: 0.25 + size_hint_y: 0.15 + # Right of the dropdown will be a suite of options + StackLayout: + orientation: "tb-lr" + size_hint_x: 0.75 + size_hint_y: 0.15 + # Checboxes for toggling plot colouring and colourbars + CheckBox: + id: layers_to_3dplot_plot_w + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + CheckBox: + id: layers_to_3dplot_colorbar + active: True + size_hint_x: 0.1 + size_hint_y: 0.5 + Label: + text: "Colour by temperature" + size_hint_x: 0.4 + size_hint_y: 0.5 + Label: + text: "Display colourbar" + size_hint_x: 0.4 + size_hint_y: 0.5 + # And text input for other figure parameters + TextInput: + id: layers_to_3dplot_figureparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Figure parameters..." + TextInput: + id: layers_to_3dplot_plotparams + size_hint_x: 0.5 + size_hint_y: 0.5 + hint_text: "Plot parameters..." + # This button an progress bar trigger and track the + # generation of figures + Button: + text: "Generate static 3d\nbuidplate figures" + size_hint_x: 0.25 + size_hint_y: 0.15 + on_press: root.layers_to_plot() + ProgressBar: + id: layers_to_3dplot_progbar + size_hint_x: 0.75 + size_hint_y: 0.15 + value: 0. + + # Fourth tab is for generating figures + TabbedPanelItem: + id: sample_vis_tab + text: "Per Sample\nVisualization" + + # Items are stacked from bottom to top. UI made of floating sub-layouts + FloatLayout: + + # First item is an InputOutputChooser + InputOutputChooser: + id: io_chooser_persample + size_hint_x: 1.0 + pos_hint: {"x": 0., "y": 0.875} + + # Second item is a grid layout filled with buttons + # These buttons denote available functions for per sample data + GridLayout: + size_hint_x: 0.9 + size_hint_y: 0.75 + pos_hint: {"x": 0.05, "y": 0.05} + # spacing: 2. + cols: 2 + Button: + text: "Test 9" + Button: + text: "Test 10" + Button: + text: "Test 11" + Button: + text: "Test 12" diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..6d9156d --- /dev/null +++ b/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +""" +Melter is a GUI wrapper for the MTPy meltpool tomography and selective laser +melting pyrometric analysis library +""" + +__version__ = "0.1.0" +__license__ = "MIT" +__status__ = "Prototype" diff --git a/common/MTPy_Modified.py b/common/MTPy_Modified.py new file mode 100644 index 0000000..b0bdcc5 --- /dev/null +++ b/common/MTPy_Modified.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +from MTPy.mtpy import MeltpoolTomography +import inspect + + +# A class for wrapping generators and adding the ability to increment +# a progress bar +class ProgBar_Wrapper(): + + def __init__(self, generator, progress_bar, + start=0, end=100, step=1): + # If the item isnt a generator make it into one + if not hasattr(generator, "__next__"): + generator = (x for x in generator) + self.generator = generator + self.progress_bar = progress_bar + self.progress_bar.value = start + self.progress_bar.max = end + self.step = step + + def __next__(self): + output = next(self.generator) + self.progress_bar.value += self.step + return output + + def __iter__(self): + return self + + +# This class is basically MTPy modified to replace tqdm progress bars with +# Kivy GUI ones +class MT_Modded(MeltpoolTomography): + + # __init__ includes a dict containing progress bars and + # sets progressbars to be controlled by a method of the object + def __init__(self, *args, **kwargs): + kwargs["progressbar"] = self._progressbar + self.progress_bars = dict() + super(MT_Modded, self).__init__(*args, **kwargs) + + # This function is intended to replace tqdm in the mtpy module + def _progressbar(self, *args, **kwargs): + # get name of calling function for keeping track + current_func = inspect.getframeinfo( + inspect.currentframe().f_back).function + # if progbar has an entry + if current_func in self.progress_bars: + current_bar = self.progress_bars[current_func] + # Then, wrap the generator to increment value while iterating + wrapped_generator = ProgBar_Wrapper(args[0], current_bar, + start=0, end=kwargs["total"]) + return wrapped_generator + else: + return args[0] # if not top-level, return unmodified generator diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..3f2c14c --- /dev/null +++ b/common/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +""" +Common, internal objects for shared use in the "Melter" program +""" diff --git a/common/threading_decorators.py b/common/threading_decorators.py new file mode 100644 index 0000000..7d22985 --- /dev/null +++ b/common/threading_decorators.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# *_* coding: utf-8 *_* + +from threading import Thread +from functools import wraps + + +# Wrapper for running a function in a parallel thread +def run_in_thread(func): + @wraps(func) + def wrapper(*args, **kwargs): + thread = Thread(target=func, args=args, kwargs=kwargs) + thread.start() + return wrapper