From e05295d6fdacb9353fd0a84bcaa77905b23854be Mon Sep 17 00:00:00 2001 From: Torsten Giess Date: Tue, 5 Sep 2023 10:08:53 +0200 Subject: [PATCH] Implement IdentityAssigner for one FID object --- nmrpy/data_objects.py | 36 +++++++++++++++ nmrpy/plotting.py | 105 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 138 insertions(+), 3 deletions(-) diff --git a/nmrpy/data_objects.py b/nmrpy/data_objects.py index a0d72aa..a0eb6bf 100644 --- a/nmrpy/data_objects.py +++ b/nmrpy/data_objects.py @@ -260,6 +260,7 @@ def __init__(self, *args, **kwargs): self.data = kwargs.get("data", []) self.peaks = None self.ranges = None + self.identities = None self._deconvoluted_peaks = None self._flags = { "ft": False, @@ -350,6 +351,24 @@ def ranges(self, ranges): raise AttributeError("ranges must be numbers") self._ranges = ranges + @property + def identities(self): + """ + Assigned identities corresponding to the various ranges in :attr:`~nmrpy.data_objects.Fid.ranges`. + """ + return self._identitites + + @identities.setter + def identities(self, identities): + if identities is not None: + if not Fid._is_flat_iter(identities): + raise AttributeError("identitites must be a flat iterable") + if not all(isinstance(i, str) for i in identities): + raise AttributeError("identities must be strings") + self._identitites = numpy.array(identities) + else: + self._identities = identities + @property def _bl_ppm(self): return self.__bl_ppm @@ -1382,6 +1401,23 @@ def plot_deconv(self, **kwargs): setattr(self, plt.id, plt) pyplot.show() + def assign_identities(self): + """ + Instantiate a identity-assignment GUI widget. Select a range from dropdown menu containing + :attr:`~nmrpy.data_objects.Fid.ranges`. Select a species from second dropdown menu + containing species defined in EnzymeML. When satisfied with assignment, press Assign button + to apply. + """ + + widget_title = "Assign identitiy for {}".format(self.id) + self._assigner_widget = IdentityAssigner(fid=self, title=widget_title) + + def clear_identities(self): + """ + Clear assigned identities stored in :attr:`~nmrpy.data_objects.Fid.identities`. + """ + self.identities = None + class FidArray(Base): """ diff --git a/nmrpy/plotting.py b/nmrpy/plotting.py index 024af76..1ba0bb1 100644 --- a/nmrpy/plotting.py +++ b/nmrpy/plotting.py @@ -11,7 +11,7 @@ from matplotlib.widgets import Cursor from matplotlib.backend_bases import NavigationToolbar2, Event -from ipywidgets import FloatText, Output, VBox +from ipywidgets import FloatText, Output, VBox, Dropdown, Label, Button from IPython.display import display import asyncio @@ -942,7 +942,7 @@ class Ssm: transform=trans, visible=False, animated=True, - **self.ssm.rectprops + **self.ssm.rectprops, ) self.ax.add_patch(self.ssm.rect) @@ -957,7 +957,7 @@ def makespan(self, left, width): transform=trans, visible=True, # animated=True, - **self.ssm.rectprops + **self.ssm.rectprops, ) self.ax.add_patch(rect) return rect @@ -1376,6 +1376,105 @@ class SpanDataSelector(DataSelector, SpanSelectorMixin, AssignMixin): pass +class IdentityAssigner: + def __init__(self, fid, title): + self.fid = fid + self.title = title + self.selected_values = {} + if fid.data is [] or fid.data is None: + raise ValueError("data must exist.") + if fid.peaks is [] or fid.peaks is None: + raise RuntimeError( + f"`fid.peaks` are required but still empty. Please either assign them manually or using the `peakpicker` method." + ) + + # Create the label widget for the title + title_label = Label(value=title) + + # Create the dropdown widget for the peaks + peak_dropdown = Dropdown( + options=[str(peak) for peak in fid.peaks], + description="Select a peak:", + layout={"width": "max-content"}, + style={"description_width": "initial"}, + ) + + # Create the dropdown widget for the species + species_dropdown = Dropdown( + options=[], + description="Select a species:", + layout={"width": "max-content"}, + style={"description_width": "initial"}, + disabled=True, + ) + + # Create the button to save selection to dict + save_button = Button( + description="Save selection", icon="file-arrow-down", disabled=True + ) + + # Create an output widget to display the selection + selection_output = Output() + + # Define a method to handle the peak dropdown's change event + def on_peak_dropdown_change(event): + if event["type"] == "change" and event["name"] == "value": + selected_option = event["new"] + if selected_option != "": + species_dropdown.options = [ + "3PG", + "2PG", + "Phosphate", + "TEP", + "PEP", + ] + species_dropdown.disabled = False + save_button.disabled = False + + # Attach the function to the dropdown's change event + peak_dropdown.observe(on_peak_dropdown_change) + + # Define a method to handle the species dropdown's change event + def on_species_dropdown_change(event): + if event["type"] == "change" and event["name"] == "value": + selected_option = event["new"] + if selected_option != "": + new_key = peak_dropdown.value + self.selected_values[new_key] = selected_option + + # Attach the function to the second dropdown's change event + species_dropdown.observe(on_species_dropdown_change) + + # Define a function to handle the save button click event + def on_save_button_click(b): + with selection_output: + selection_output.clear_output(wait=True) + print("\nSaved selections:") + for key, value in self.selected_values.items(): + print(f"{key}: {value}") + + # Attach the function to the save button's click event + save_button.on_click(on_save_button_click) + + # Create a container for both the title and the dropdown + container = VBox( + [ + title_label, + peak_dropdown, + species_dropdown, + save_button, + selection_output, + ] + ) + + # Display the container + display(container) + + +class IdentityRangeAssigner: + ... + + class DataTraceSelector: """ Interactive data-selection widget with traces and ranges. Traces are saved