diff --git a/links/enzymeml.toml b/links/enzymeml.toml index a312726..4f2a90f 100644 --- a/links/enzymeml.toml +++ b/links/enzymeml.toml @@ -14,8 +14,8 @@ name = "EnzymeMLDocument.name" doi = "EnzymeMLDocument.doi" ["citation.authors"] -last_name = "EnzymeMLDocument.creators.given_name" -first_name = "EnzymeMLDocument.creators.family_name" +last_name = "EnzymeMLDocument.creators.family_name" +first_name = "EnzymeMLDocument.creators.given_name" email = "EnzymeMLDocument.creators.mail" ["citation.related_publications"] diff --git a/nmrpy/data_objects.py b/nmrpy/data_objects.py index 8401371..a0d72aa 100644 --- a/nmrpy/data_objects.py +++ b/nmrpy/data_objects.py @@ -737,6 +737,7 @@ def _phase_correct(cls, list_params): phased_data = Fid._ps( data, p0=mz.params["p0"].value, p1=mz.params["p1"].value ) + # data model if abs(phased_data.min()) > abs(phased_data.max()): phased_data *= -1 if sum(phased_data) < 0.0: @@ -1405,6 +1406,19 @@ def __init__(self): datetime_modified=_now, ) del _now + self._force_pyenzyme = False + + @property + def force_pyenzyme(self): + return self._force_pyenzyme + + @force_pyenzyme.setter + def force_pyenzyme(self): + raise PermissionError("Forbidden!") + + @force_pyenzyme.deleter + def force_pyenzyme(self): + raise PermissionError("Forbidden!") @property def data_model(self): @@ -2243,6 +2257,16 @@ def save_to_file(self, filename=None, overwrite=False): def save_data(self, file_format: str, filename=None, overwrite=False): print("~~~ Method under contruction ~~~") + if self.force_pyenzyme: + import pyenzyme as pe + + enzymeml = pe.EnzymeMLDocument( + name=self.data_mode.experiment.name + if hasattr(self.data_model.experiment, "name") + else "NMR experiment" + ) + ... + return 1 if file_format.lower() == ("enzymeml" or "nmrml"): # model = self.data_model.convert_to( # template=Path(__file__).parent.parent / "links/enzymeml.toml" diff --git a/nmrpy/datamodel/core/citation.py b/nmrpy/datamodel/core/citation.py index dc21126..799d198 100644 --- a/nmrpy/datamodel/core/citation.py +++ b/nmrpy/datamodel/core/citation.py @@ -5,15 +5,15 @@ from sdRDM.base.listplus import ListPlus from sdRDM.base.utils import forge_signature, IDGenerator -from typing import Any from pydantic import AnyUrl +from typing import Any from .term import Term -from .identifiertypes import IdentifierTypes from .person import Person -from .publication import Publication from .subjects import Subjects +from .publication import Publication from .publicationtypes import PublicationTypes +from .identifiertypes import IdentifierTypes @forge_signature diff --git a/nmrpy/datamodel/core/experiment.py b/nmrpy/datamodel/core/experiment.py index 1f78eca..3af5c83 100644 --- a/nmrpy/datamodel/core/experiment.py +++ b/nmrpy/datamodel/core/experiment.py @@ -7,8 +7,8 @@ from .fidarray import FIDArray -from .fid import FID from .parameters import Parameters +from .fid import FID @forge_signature diff --git a/nmrpy/datamodel/core/publication.py b/nmrpy/datamodel/core/publication.py index 42025c6..3d34597 100644 --- a/nmrpy/datamodel/core/publication.py +++ b/nmrpy/datamodel/core/publication.py @@ -7,9 +7,9 @@ from pydantic import AnyUrl -from .identifiertypes import IdentifierTypes from .person import Person from .publicationtypes import PublicationTypes +from .identifiertypes import IdentifierTypes @forge_signature diff --git a/nmrpy/plotting.py b/nmrpy/plotting.py index a30bd88..024af76 100644 --- a/nmrpy/plotting.py +++ b/nmrpy/plotting.py @@ -15,7 +15,8 @@ from IPython.display import display import asyncio -class Plot(): + +class Plot: """ Basic 'plot' class containing functions for various types of plots. """ @@ -24,7 +25,7 @@ class Plot(): def __init__(self): self._time = datetime.now() - self.id = 'plot_{}'.format(Plot._plot_id_num) + self.id = "plot_{}".format(Plot._plot_id_num) Plot._plot_id_num += 1 self.fig = None @@ -45,148 +46,182 @@ def fig(self, fig): if fig is None or isinstance(fig, Figure): self._fig = fig else: - raise TypeError('fig must be of type matplotlib.figure.Figure.') - - def _plot_ppm(self, fid, - upper_ppm=None, - lower_ppm=None, - color='k', - lw=1, - filename=None): + raise TypeError("fig must be of type matplotlib.figure.Figure.") + + def _plot_ppm( + self, + fid, + upper_ppm=None, + lower_ppm=None, + color="k", + lw=1, + filename=None, + ): data = fid.data params = fid._params - ft=fid._flags['ft'] - if not Plot._is_flat_iter(data): - raise AttributeError('data must be flat iterable.') + ft = fid._flags["ft"] + if not Plot._is_flat_iter(data): + raise AttributeError("data must be flat iterable.") if upper_ppm is not None and lower_ppm is not None: if upper_ppm == lower_ppm or upper_ppm < lower_ppm: - raise ValueError('ppm range specified is invalid.') - sw_left = params['sw_left'] - sw = params['sw'] + raise ValueError("ppm range specified is invalid.") + sw_left = params["sw_left"] + sw = params["sw"] if upper_ppm is None: upper_ppm = sw_left if lower_ppm is None: - lower_ppm = sw_left-sw + lower_ppm = sw_left - sw - ppm = numpy.linspace(sw_left-sw, sw_left, len(data))[::-1] + ppm = numpy.linspace(sw_left - sw, sw_left, len(data))[::-1] ppm_bool_index = (ppm < upper_ppm) * (ppm > lower_ppm) ppm = ppm[ppm_bool_index] data = data[ppm_bool_index] - self.fig = plt.figure(figsize=[9,5]) + self.fig = plt.figure(figsize=[9, 5]) ax = self.fig.add_subplot(111) if ft: ax.plot(ppm, data, color=color, lw=lw) ax.invert_xaxis() ax.set_xlim([upper_ppm, lower_ppm]) ax.grid() - ax.set_xlabel('PPM (%.2f MHz)'%(params['reffrq'])) + ax.set_xlabel("PPM (%.2f MHz)" % (params["reffrq"])) elif not ft: - at = params['at']*1000 # ms + at = params["at"] * 1000 # ms t = numpy.linspace(0, at, len(data)) ax.plot(t, data, color=color, lw=lw) ax.set_xlim([0, at]) ax.grid() - ax.set_xlabel('Time (ms)') - #self.fig.show() + ax.set_xlabel("Time (ms)") + # self.fig.show() if filename is not None: - self.fig.savefig(filename, format='pdf') - - def _deconv_generator(self, fid, - upper_ppm=None, - lower_ppm=None, - ): - + self.fig.savefig(filename, format="pdf") + + def _deconv_generator( + self, + fid, + upper_ppm=None, + lower_ppm=None, + ): data = fid.data params = fid._params - if not Plot._is_flat_iter(data): - raise AttributeError('data must be flat iterable.') + if not Plot._is_flat_iter(data): + raise AttributeError("data must be flat iterable.") - peakshapes = fid._f_pks_list(fid._deconvoluted_peaks, numpy.arange(len(data))) + peakshapes = fid._f_pks_list( + fid._deconvoluted_peaks, numpy.arange(len(data)) + ) - if not Plot._is_iter_of_iters(peakshapes): - raise AttributeError('data must be flat iterable.') + if not Plot._is_iter_of_iters(peakshapes): + raise AttributeError("data must be flat iterable.") if upper_ppm is not None and lower_ppm is not None: if upper_ppm == lower_ppm or upper_ppm < lower_ppm: - raise ValueError('ppm range specified is invalid.') - sw_left = params['sw_left'] - sw = params['sw'] + raise ValueError("ppm range specified is invalid.") + sw_left = params["sw_left"] + sw = params["sw"] if upper_ppm is None: upper_ppm = sw_left if lower_ppm is None: - lower_ppm = sw_left-sw + lower_ppm = sw_left - sw - ppm = numpy.linspace(sw_left-sw, sw_left, len(data))[::-1] + ppm = numpy.linspace(sw_left - sw, sw_left, len(data))[::-1] ppm_bool_index = (ppm <= upper_ppm) * (ppm >= lower_ppm) ppm = ppm[ppm_bool_index] data = data[ppm_bool_index] peakshapes = peakshapes[:, ppm_bool_index] summed_peaks = peakshapes.sum(0) - residual = data-summed_peaks - return ppm, data, peakshapes, summed_peaks, residual, upper_ppm, lower_ppm - - def _plot_deconv(self, fid, - upper_ppm=None, - lower_ppm=None, - colour='k', - peak_colour='b', - summed_peak_colour='r', - residual_colour='g', - lw=1): - - #validation takes place in self._deconv_generator - ppm, data, peakshapes, summed_peaks, residual, upper_ppm, \ - lower_ppm = self._deconv_generator(fid, - upper_ppm=upper_ppm, - lower_ppm=lower_ppm) - - self.fig = plt.figure(figsize=[9,5]) + residual = data - summed_peaks + return ( + ppm, + data, + peakshapes, + summed_peaks, + residual, + upper_ppm, + lower_ppm, + ) + + def _plot_deconv( + self, + fid, + upper_ppm=None, + lower_ppm=None, + colour="k", + peak_colour="b", + summed_peak_colour="r", + residual_colour="g", + lw=1, + ): + # validation takes place in self._deconv_generator + ( + ppm, + data, + peakshapes, + summed_peaks, + residual, + upper_ppm, + lower_ppm, + ) = self._deconv_generator( + fid, upper_ppm=upper_ppm, lower_ppm=lower_ppm + ) + + self.fig = plt.figure(figsize=[9, 5]) ax = self.fig.add_subplot(111) ax.plot(ppm, residual, color=residual_colour, lw=lw) ax.plot(ppm, data, color=colour, lw=lw) - ax.plot(ppm, summed_peaks, '--', color=summed_peak_colour, lw=lw) - label_pad = 0.02*peakshapes.max() + ax.plot(ppm, summed_peaks, "--", color=summed_peak_colour, lw=lw) + label_pad = 0.02 * peakshapes.max() for n in range(len(peakshapes)): peak = peakshapes[n] - ax.plot(ppm, peak, '-', color=peak_colour, lw=lw) - ax.text(ppm[numpy.argmax(peak)], label_pad+peak.max(), str(n), ha='center') + ax.plot(ppm, peak, "-", color=peak_colour, lw=lw) + ax.text( + ppm[numpy.argmax(peak)], + label_pad + peak.max(), + str(n), + ha="center", + ) ax.invert_xaxis() ax.set_xlim([upper_ppm, lower_ppm]) ax.grid() - ax.set_xlabel('PPM (%.2f MHz)'%(fid._params['reffrq'])) - - def _plot_deconv_array(self, fids, - upper_index=None, - lower_index=None, - upper_ppm=None, - lower_ppm=None, - data_colour='k', - summed_peak_colour='r', - residual_colour='g', - data_filled=False, - summed_peak_filled=True, - residual_filled=False, - figsize=[9, 6], - lw=0.3, - azim=-90, - elev=20, - filename=None): - + ax.set_xlabel("PPM (%.2f MHz)" % (fid._params["reffrq"])) + + def _plot_deconv_array( + self, + fids, + upper_index=None, + lower_index=None, + upper_ppm=None, + lower_ppm=None, + data_colour="k", + summed_peak_colour="r", + residual_colour="g", + data_filled=False, + summed_peak_filled=True, + residual_filled=False, + figsize=[9, 6], + lw=0.3, + azim=-90, + elev=20, + filename=None, + ): if lower_index is None: lower_index = 0 if upper_index is None: upper_index = len(fids) if lower_index >= upper_index: - raise ValueError('upper_index must exceed lower_index') - fids = fids[lower_index: upper_index] + raise ValueError("upper_index must exceed lower_index") + fids = fids[lower_index:upper_index] generated_deconvs = [] for fid in fids: - generated_deconvs.append(self._deconv_generator(fid, upper_ppm=upper_ppm, lower_ppm=lower_ppm)) - - params = fids[0]._params + generated_deconvs.append( + self._deconv_generator( + fid, upper_ppm=upper_ppm, lower_ppm=lower_ppm + ) + ) + + params = fids[0]._params ppm = generated_deconvs[0][0] data = [i[1] for i in generated_deconvs] peakshapes = [i[2] for i in generated_deconvs] @@ -195,83 +230,87 @@ def _plot_deconv_array(self, fids, upper_ppm = generated_deconvs[0][5] lower_ppm = generated_deconvs[0][6] - plot_data = numpy.array([ - residuals, - data, - summed_peaks, - ]) + plot_data = numpy.array( + [ + residuals, + data, + summed_peaks, + ] + ) colours_list = [ - [residual_colour]*len(residuals), - [data_colour]*len(data), - [summed_peak_colour]*len(summed_peaks), - ] + [residual_colour] * len(residuals), + [data_colour] * len(data), + [summed_peak_colour] * len(summed_peaks), + ] filled_list = [ - residual_filled, - data_filled, - summed_peak_filled, - ] - - xlabel = 'PPM (%.2f MHz)'%(params['reffrq']) - ylabel = 'min.' - acqtime = fids[0]._params['acqtime'] + residual_filled, + data_filled, + summed_peak_filled, + ] + + xlabel = "PPM (%.2f MHz)" % (params["reffrq"]) + ylabel = "min." + acqtime = fids[0]._params["acqtime"] minutes = acqtime[lower_index:upper_index] - self.fig = self._generic_array_plot(ppm, minutes, plot_data, - colours_list=colours_list, - filled_list=filled_list, - figsize=figsize, - xlabel=xlabel, - ylabel=ylabel, - lw=lw, - azim=azim, - elev=elev, - ) + self.fig = self._generic_array_plot( + ppm, + minutes, + plot_data, + colours_list=colours_list, + filled_list=filled_list, + figsize=figsize, + xlabel=xlabel, + ylabel=ylabel, + lw=lw, + azim=azim, + elev=elev, + ) if filename is not None: - self.fig.savefig(filename, format='pdf') + self.fig.savefig(filename, format="pdf") plt.show() - - - - def _plot_array(self, data, params, - upper_index=None, - lower_index=None, - upper_ppm=None, - lower_ppm=None, - figsize=(9, 6), - lw=0.3, - azim=-90, - elev=20, - filled=False, - show_zticks=False, - labels=None, - colour=True, - filename=None, - ): + def _plot_array( + self, + data, + params, + upper_index=None, + lower_index=None, + upper_ppm=None, + lower_ppm=None, + figsize=(9, 6), + lw=0.3, + azim=-90, + elev=20, + filled=False, + show_zticks=False, + labels=None, + colour=True, + filename=None, + ): if not Plot._is_iter_of_iters(data): - raise AttributeError('data must be 2D.') + raise AttributeError("data must be 2D.") if upper_ppm is not None and lower_ppm is not None: if upper_ppm == lower_ppm or upper_ppm < lower_ppm: - raise ValueError('ppm range specified is invalid.') + raise ValueError("ppm range specified is invalid.") if upper_index is not None and lower_index is not None: if upper_index == lower_index or upper_index < lower_index: - raise ValueError('index range specified is invalid.') + raise ValueError("index range specified is invalid.") - - sw_left = params['sw_left'] - sw = params['sw'] + sw_left = params["sw_left"] + sw = params["sw"] if upper_index is None: upper_index = len(data) if lower_index is None: lower_index = 0 - + if upper_ppm is None: upper_ppm = sw_left if lower_ppm is None: - lower_ppm = sw_left-sw + lower_ppm = sw_left - sw - acqtime = params['acqtime'] - ppm = numpy.linspace(sw_left-sw, sw_left, data.shape[1])[::-1] + acqtime = params["acqtime"] + ppm = numpy.linspace(sw_left - sw, sw_left, data.shape[1])[::-1] ppm_bool_index = (ppm < upper_ppm) * (ppm > lower_ppm) ppm = ppm[ppm_bool_index] if len(data) > 1: @@ -286,20 +325,23 @@ def _plot_array(self, data, params, else: colours_list = None - xlabel = 'PPM (%.2f MHz)'%(params['reffrq']) - ylabel = 'min.' - self.fig = self._generic_array_plot(ppm, minutes, [data], - colours_list=colours_list, - filled_list=[filled], - figsize=figsize, - xlabel=xlabel, - ylabel=ylabel, - lw=lw, - azim=azim, - elev=elev, - ) + xlabel = "PPM (%.2f MHz)" % (params["reffrq"]) + ylabel = "min." + self.fig = self._generic_array_plot( + ppm, + minutes, + [data], + colours_list=colours_list, + filled_list=[filled], + figsize=figsize, + xlabel=xlabel, + ylabel=ylabel, + lw=lw, + azim=azim, + elev=elev, + ) if filename is not None: - self.fig.savefig(filename, format='pdf') + self.fig.savefig(filename, format="pdf") plt.show() @staticmethod @@ -313,21 +355,25 @@ def _interleave_datasets(data): idata.append(data[x][y]) return idata - def _generic_array_plot(self, x, y, zlist, - colours_list=None, - filled_list=None, - upper_lim=None, - lower_lim=None, - lw=0.3, - azim=-90, - elev=20, - figsize=[5,5], - show_zticks=False, - labels=None, - xlabel=None, - ylabel=None, - filename=None, - ): + def _generic_array_plot( + self, + x, + y, + zlist, + colours_list=None, + filled_list=None, + upper_lim=None, + lower_lim=None, + lw=0.3, + azim=-90, + elev=20, + figsize=[5, 5], + show_zticks=False, + labels=None, + xlabel=None, + ylabel=None, + filename=None, + ): """ Generic function for plotting arrayed data on a set of 3D axes. x and y @@ -337,46 +383,44 @@ def _generic_array_plot(self, x, y, zlist, """ - - - if colours_list is None: - colours_list = [['k']*len(y)]*len(zlist) + colours_list = [["k"] * len(y)] * len(zlist) if filled_list is None: - filled_list = [False]*len(zlist) - + filled_list = [False] * len(zlist) fig = plt.figure(figsize=figsize) - ax = fig.add_subplot(111, projection='3d', azim=azim, elev=elev) + ax = fig.add_subplot(111, projection="3d", azim=azim, elev=elev) for data_n in range(len(zlist)): data = zlist[data_n] - bh = abs(data.min()) + bh = abs(data.min()) filled = filled_list[data_n] cl = colours_list[data_n] if not filled: - #spectra are plotted in reverse for zorder + # spectra are plotted in reverse for zorder for n in range(len(data))[::-1]: datum = data[n] clr = cl[n] - ax.plot(x, len(datum)*[y[n]], datum, color=clr, lw=lw) + ax.plot(x, len(datum) * [y[n]], datum, color=clr, lw=lw) if filled: verts = [] - plot_data = data+bh + plot_data = data + bh for datum in plot_data: datum[0], datum[-1] = 0, 0 verts.append(list(zip(x, datum))) - - fclr, eclr = ['w']*len(data), ['k']*len(data) + + fclr, eclr = ["w"] * len(data), ["k"] * len(data) fclr = cl - poly = PolyCollection(verts, + poly = PolyCollection( + verts, facecolors=fclr, edgecolors=eclr, - linewidths=[lw]*len(verts)) - ax.add_collection3d(poly, zs=y, zdir='y') - - ax.set_zlim([0, 1.1*max(numpy.array(zlist).flat)]) + linewidths=[lw] * len(verts), + ) + ax.add_collection3d(poly, zs=y, zdir="y") + + ax.set_zlim([0, 1.1 * max(numpy.array(zlist).flat)]) ax.invert_xaxis() if upper_lim is None: upper_lim = x[0] @@ -389,7 +433,6 @@ def _generic_array_plot(self, x, y, zlist, if not show_zticks: ax.set_zticklabels([]) return fig - @classmethod def _is_iter(cls, i): @@ -415,48 +458,58 @@ def _is_flat_iter(cls, i): return True return False + class Phaser: """Interactive phase-correction widget""" + def __init__(self, fid): - if not Plot._is_flat_iter(fid.data): - raise ValueError('data must be flat iterable.') + if not Plot._is_flat_iter(fid.data): + raise ValueError("data must be flat iterable.") if fid.data is [] or fid.data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") self.fid = fid self.fig = plt.figure(figsize=[9, 6]) self.phases = numpy.array([0.0, 0.0]) self.cum_phases = numpy.array([0.0, 0.0]) self.y = 0.0 self.ax = self.fig.add_subplot(111) - self.ax.plot(self.fid.data, color='k', linewidth=1.0) - self.ax.hlines(0, 0, len(self.fid.data)-1) + self.ax.plot(self.fid.data, color="k", linewidth=1.0) + self.ax.hlines(0, 0, len(self.fid.data) - 1) self.ax.set_xlim([0, len(self.fid.data)]) - xtcks = numpy.linspace(0,1,11)*len(self.fid.data) - xtcks[-1] = xtcks[-1]-1 + xtcks = numpy.linspace(0, 1, 11) * len(self.fid.data) + xtcks[-1] = xtcks[-1] - 1 self.ax.set_xticks(xtcks) - self.ax.set_xlabel('PPM (%.2f MHz)'%(self.fid._params['reffrq'])) - self.ax.set_xticklabels([numpy.round(self.fid._ppm[int(i)], 1) for i in xtcks]) - ylims = numpy.array([-1.6, 1.6])*max(abs(numpy.array(self.ax.get_ylim()))) + self.ax.set_xlabel("PPM (%.2f MHz)" % (self.fid._params["reffrq"])) + self.ax.set_xticklabels( + [numpy.round(self.fid._ppm[int(i)], 1) for i in xtcks] + ) + ylims = numpy.array([-1.6, 1.6]) * max( + abs(numpy.array(self.ax.get_ylim())) + ) self.ax.set_ylim(ylims) self.ax.grid() self.visible = True self.canvas = self.ax.figure.canvas - self.canvas.mpl_connect('motion_notify_event', self.onmove) - self.canvas.mpl_connect('button_press_event', self.press) - self.canvas.mpl_connect('button_release_event', self.release) + self.canvas.mpl_connect("motion_notify_event", self.onmove) + self.canvas.mpl_connect("button_press_event", self.press) + self.canvas.mpl_connect("button_release_event", self.release) self.pressv = None self.buttonDown = False self.prev = (0, 0) - self.ax.text(0.05 *self.ax.get_xlim()[1],0.7 *self.ax.get_ylim()[1],'phasing\nleft - zero-order\nright - first order') - cursor = Cursor(self.ax, useblit=True, color='k', linewidth=0.5) + self.ax.text( + 0.05 * self.ax.get_xlim()[1], + 0.7 * self.ax.get_ylim()[1], + "phasing\nleft - zero-order\nright - first order", + ) + cursor = Cursor(self.ax, useblit=True, color="k", linewidth=0.5) cursor.horizOn = False self.fig.subplots_adjust(bottom=0.13) - self.text1 = self.fig.text(0.12, 0.02, ' ', fontsize='large') + self.text1 = self.fig.text(0.12, 0.02, " ", fontsize="large") plt.show() def press(self, event): tb = plt.get_current_fig_manager().toolbar - if tb.mode == '': + if tb.mode == "": x, y = event.xdata, event.ydata if event.inaxes is not None: self.buttonDown = True @@ -464,7 +517,9 @@ def press(self, event): self.y = y def release(self, event): - self.text1.set_text('cumulative p0: {0:.1f} p1: {1:.1f}'.format(*self.cum_phases)) + self.text1.set_text( + "cumulative p0: {0:.1f} p1: {1:.1f}".format(*self.cum_phases) + ) self.buttonDown = False return False @@ -473,26 +528,27 @@ def onmove(self, event): return x = event.xdata y = event.ydata - dy = y-self.y + dy = y - self.y self.y = y if self.button == 1: - self.phases[0] = 50*dy/self.ax.get_ylim()[1] + self.phases[0] = 50 * dy / self.ax.get_ylim()[1] self.phases[1] = 0.0 if self.button == 3: - self.phases[1] = 50*dy/self.ax.get_ylim()[1] + self.phases[1] = 50 * dy / self.ax.get_ylim()[1] self.phases[0] = 0.0 self.fid.ps(p0=self.phases[0], p1=self.phases[1]) self.cum_phases += self.phases - self.ax.lines[0].set_data(numpy.array([numpy.arange(len(self.fid.data)), self.fid.data])) + self.ax.lines[0].set_data( + numpy.array([numpy.arange(len(self.fid.data)), self.fid.data]) + ) self.canvas.draw() # _idle() return False class BaseSelectorMixin: - def __init__(self): super().__init__() - + def press(self, event): pass @@ -508,17 +564,19 @@ def redraw(self): def change_visible(self): pass -class PolySelectorMixin(BaseSelectorMixin): +class PolySelectorMixin(BaseSelectorMixin): def __init__(self): super().__init__() + class Psm: pass + self.psm = Psm() self.psm.btn_add = 1 self.psm.btn_del = 1 self.psm.btn_cls = 3 - self.psm.key_mod = 'control' + self.psm.key_mod = "control" self.psm.xs = [] self.psm.ys = [] self.psm._xs = [] @@ -535,12 +593,12 @@ class Psm: self.psm._yline = None self.psm.lw = 1 self.blocking = False - if not hasattr(self, 'show_tracedata'): + if not hasattr(self, "show_tracedata"): self.show_tracedata = False def redraw(self): super().redraw() - if hasattr(self, 'psm'): + if hasattr(self, "psm"): for i in self.psm._visual_lines: self.ax.draw_artist(i) if self.psm.line is not None: @@ -550,20 +608,21 @@ def redraw(self): def change_visible(self): super().change_visible() - if hasattr(self, 'psm'): + if hasattr(self, "psm"): for i in self.psm._visual_lines: i.set_visible(not i.get_visible()) if self.psm.line is not None: self.psm.line.set_visible(not self.psm.line.get_visible()) - def makepoly(self, + def makepoly( + self, xs=None, ys=None, lw=1, - colour='r', - ms='+', - ls='-', - ): + colour="r", + ms="+", + ls="-", + ): if xs is not None and ys is not None: return self.ax.plot( xs, @@ -572,99 +631,113 @@ def makepoly(self, color=colour, marker=ms, ls=ls, - ) - + ) + def press(self, event): super().press(event) - if self.check_mode() != '': + if self.check_mode() != "": return if event.xdata is None or event.ydata is None: return if event.button == self.psm.btn_add and event.key != self.psm.key_mod: - self.psm.xs.append(event.xdata) - self.psm.ys.append(event.ydata) + self.psm.xs.append(event.xdata) + self.psm.ys.append(event.ydata) + if self.show_tracedata: + self.psm._xs, self.psm._ys = self.get_line_ydata( + self.psm.xs, self.psm.ys + ) + if self.psm.line is None: + (self.psm.line,) = self.makepoly( + self.psm.xs, + self.psm.ys, + lw=self.psm.lw, + ) + self.blocking = True if self.show_tracedata: - self.psm._xs, self.psm._ys = self.get_line_ydata(self.psm.xs, self.psm.ys) - if self.psm.line is None: - self.psm.line, = self.makepoly( - self.psm.xs, - self.psm.ys, + (self.psm._yline,) = self.makepoly( + self.psm._xs, + self.psm._ys, lw=self.psm.lw, - ) - self.blocking = True - if self.show_tracedata: - self.psm._yline, = self.makepoly( - self.psm._xs, - self.psm._ys, - lw=self.psm.lw, - ms='+', - ls='-', - colour='r', - ) - else: - self.psm.line.set_data(self.psm.xs, self.psm.ys) - if self.show_tracedata: - self.psm._yline.set_data(self.psm._xs, self.psm._ys) - elif event.button == self.psm.btn_del and event.key == self.psm.key_mod: + ms="+", + ls="-", + colour="r", + ) + else: + self.psm.line.set_data(self.psm.xs, self.psm.ys) + if self.show_tracedata: + self.psm._yline.set_data(self.psm._xs, self.psm._ys) + elif ( + event.button == self.psm.btn_del and event.key == self.psm.key_mod + ): if len(self.psm._visual_lines) > 0: x = event.xdata y = event.ydata - #trace_dist = [[i[0]-x, i[1]-y] for i in self.psm.lines] - trace_dist = [[i[0]-x] for i in self.psm.lines] - #delete_trace = numpy.argmin([min(numpy.sqrt(i[0]**2+i[1]**2)) - delete_trace = numpy.argmin([min(numpy.sqrt(i[0]**2)) for i in trace_dist]) + # trace_dist = [[i[0]-x, i[1]-y] for i in self.psm.lines] + trace_dist = [[i[0] - x] for i in self.psm.lines] + # delete_trace = numpy.argmin([min(numpy.sqrt(i[0]**2+i[1]**2)) + delete_trace = numpy.argmin( + [min(numpy.sqrt(i[0] ** 2)) for i in trace_dist] + ) self.psm.lines.pop(delete_trace) self.psm.data_lines.pop(delete_trace) trace = self.psm._visual_lines.pop(delete_trace) trace.remove() elif event.button == self.psm.btn_cls and self.psm.line is not None: if len(self.psm.xs) > 1: - self.psm._visual_lines.append(self.makepoly( - self.psm.xs, - self.psm.ys, + self.psm._visual_lines.append( + self.makepoly( + self.psm.xs, + self.psm.ys, lw=self.psm.lw, - colour='b', - )[0]) + colour="b", + )[0] + ) self.psm.lines.append(numpy.array([self.psm.xs, self.psm.ys])) self.psm.xs, self.psm.ys = [], [] self.psm.line.remove() self.psm.line = None self.psm._yline.remove() self.psm._yline = None - self.psm.data_lines.append(self.get_polygon_neighbours_data(self.psm.lines[-1])) - self.psm.index_lines.append(self.get_polygon_neighbours_indices(self.psm.lines[-1])) + self.psm.data_lines.append( + self.get_polygon_neighbours_data(self.psm.lines[-1]) + ) + self.psm.index_lines.append( + self.get_polygon_neighbours_indices(self.psm.lines[-1]) + ) self.blocking = False else: self.psm.xs, self.psm.ys = [], [] self.psm.line = None - #self.redraw() - + # self.redraw() + def onmove(self, event): super().onmove(event) self.psm._x = event.xdata self.psm._y = event.ydata if self.psm.line is not None: - xs = self.psm.xs+[self.psm._x] - ys = self.psm.ys+[self.psm._y] + xs = self.psm.xs + [self.psm._x] + ys = self.psm.ys + [self.psm._y] self.psm.line.set_data(xs, ys) if self.show_tracedata: current_x_ydata = self.get_line_ydata( - [self.psm.xs[-1]]+[self.psm._x], - [self.psm.ys[-1]]+[self.psm._y], - ) + [self.psm.xs[-1]] + [self.psm._x], + [self.psm.ys[-1]] + [self.psm._y], + ) self.psm._yline.set_data( - self.psm._xs+current_x_ydata[0], - self.psm._ys+current_x_ydata[1], - ) + self.psm._xs + current_x_ydata[0], + self.psm._ys + current_x_ydata[1], + ) def get_line_ydata(self, xs, ys): xdata = [] ydata = [] - for i in range(len(xs)-1): - current_xy_data = self.get_polygon_neighbours_data([ - xs[i:i+2], - ys[i:i+2], - ]) + for i in range(len(xs) - 1): + current_xy_data = self.get_polygon_neighbours_data( + [ + xs[i : i + 2], + ys[i : i + 2], + ] + ) xdata += current_xy_data[0] ydata += current_xy_data[1] return xdata, ydata @@ -676,12 +749,17 @@ def get_polygon_neighbours_data(self, line): """ line_xs = [] line_ys = [] - for i in range(len(line[0])-1): - x1, y1, x2, y2 = line[0][i], line[1][i], line[0][i+1], line[1][i+1] + for i in range(len(line[0]) - 1): + x1, y1, x2, y2 = ( + line[0][i], + line[1][i], + line[0][i + 1], + line[1][i + 1], + ) x, y, x_index, y_index = self.get_neighbours([x1, x2], [y1, y2]) if x is not None and y is not None: - line_xs = line_xs+list(x) - line_ys = line_ys+list(y) + line_xs = line_xs + list(x) + line_ys = line_ys + list(y) return [line_xs, line_ys] def get_polygon_neighbours_indices(self, line): @@ -691,14 +769,19 @@ def get_polygon_neighbours_indices(self, line): """ line_xs = [] line_ys = [] - for i in range(len(line[0])-1): - x1, y1, x2, y2 = line[0][i], line[1][i], line[0][i+1], line[1][i+1] + for i in range(len(line[0]) - 1): + x1, y1, x2, y2 = ( + line[0][i], + line[1][i], + line[0][i + 1], + line[1][i + 1], + ) x, y, x_index, y_index = self.get_neighbours([x1, x2], [y1, y2]) if x_index is not None and y_index is not None: - line_xs = line_xs+list(x_index) - line_ys = line_ys+list(y_index) + line_xs = line_xs + list(x_index) + line_ys = line_ys + list(y_index) return [line_xs, line_ys] - + def get_neighbours(self, xs, ys): """ For a pair of coordinates (xs = [x1, x2], ys = [y1, y2]), return the @@ -710,7 +793,7 @@ def get_neighbours(self, xs, ys): if True not in ymask: return None, None, None, None y_lo = ymask.index(True) - y_hi = len(ymask)-ymask[::-1].index(True) + y_hi = len(ymask) - ymask[::-1].index(True) x_neighbours = [] y_neighbours = [] y_indices = [i for i in range(y_lo, y_hi)] @@ -718,13 +801,13 @@ def get_neighbours(self, xs, ys): y_indices = y_indices[::-1] x_indices = [] for i in y_indices: - x = [self.ppm[0], self.ppm[-1], xs[0], xs[1]] - y = [self.y_indices[i], self.y_indices[i], ys[0], ys[1]] + x = [self.ppm[0], self.ppm[-1], xs[0], xs[1]] + y = [self.y_indices[i], self.y_indices[i], ys[0], ys[1]] x, y = self.get_intersection(x, y) - x = numpy.argmin(abs(self.ppm[::-1]-x)) + x = numpy.argmin(abs(self.ppm[::-1] - x)) x_indices.append(x) x_neighbours.append(self.ppm[::-1][x]) - y_neighbours.append(self.data[i][x]+self.y_indices[i]) + y_neighbours.append(self.data[i][x] + self.y_indices[i]) return x_neighbours, y_neighbours, x_indices, y_indices @staticmethod @@ -736,46 +819,54 @@ def get_intersection(x, y): and [x4, y4] represent the other. See https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line """ - px = (((x[0]*y[1]-y[0]*x[1])*(x[2]-x[3])-(x[0]-x[1])*(x[2]*y[3]-y[2]*x[3]))/((x[0]-x[1])*(y[2]-y[3])-(y[0]-y[1])*(x[2]-x[3]))) - py = (((x[0]*y[1]-y[0]*x[1])*(y[2]-y[3])-(y[0]-y[1])*(x[2]*y[3]-y[2]*x[3]))/((x[0]-x[1])*(y[2]-y[3])-(y[0]-y[1])*(x[2]-x[3]))) + px = ( + (x[0] * y[1] - y[0] * x[1]) * (x[2] - x[3]) + - (x[0] - x[1]) * (x[2] * y[3] - y[2] * x[3]) + ) / ((x[0] - x[1]) * (y[2] - y[3]) - (y[0] - y[1]) * (x[2] - x[3])) + py = ( + (x[0] * y[1] - y[0] * x[1]) * (y[2] - y[3]) + - (y[0] - y[1]) * (x[2] * y[3] - y[2] * x[3]) + ) / ((x[0] - x[1]) * (y[2] - y[3]) - (y[0] - y[1]) * (x[2] - x[3])) return px, py -class LineSelectorMixin(BaseSelectorMixin): +class LineSelectorMixin(BaseSelectorMixin): def __init__(self): super().__init__() + class Lsm: pass + self.lsm = Lsm() self.lsm.btn_add = 1 self.lsm.btn_del = 1 - self.lsm.key_mod = 'control' + self.lsm.key_mod = "control" self.lsm.peaklines = {} self.lsm.peaks = [] for x in self.peaks: self.lsm.peaks.append(x) self.lsm.peaklines[x] = self.makeline(x) - #self.ax.draw_artist(self.lsm.peaklines[x]) + # self.ax.draw_artist(self.lsm.peaklines[x]) self.lsm.peaks = sorted(self.lsm.peaks)[::-1] - + def makeline(self, x): return self.ax.plot( - [x, x], + [x, x], self.ylims, - color='#CC0000', + color="#CC0000", lw=1, - #animated=True - )[0] + # animated=True + )[0] def redraw(self): super().redraw() - if hasattr(self, 'lsm'): + if hasattr(self, "lsm"): for i, j in self.lsm.peaklines.items(): self.ax.draw_artist(j) def change_visible(self): super().change_visible() - if hasattr(self, 'lsm'): + if hasattr(self, "lsm"): for i, j in self.lsm.peaklines.items(): j.set_visible(True) j.set_visible(not j.get_visible()) @@ -784,30 +875,37 @@ def press(self, event): super().press(event) x = numpy.round(event.xdata, 2) # left - if event.button == self.lsm.btn_add and \ - event.key != self.lsm.key_mod and \ - (x >= self.xlims[1]) and (x <= self.xlims[0]): + if ( + event.button == self.lsm.btn_add + and event.key != self.lsm.key_mod + and (x >= self.xlims[1]) + and (x <= self.xlims[0]) + ): with self.out: - print('peak {}'.format(x)) + print("peak {}".format(x)) if x not in self.lsm.peaks: self.lsm.peaks.append(x) self.lsm.peaklines[x] = self.makeline(x) self.lsm.peaks = sorted(self.lsm.peaks)[::-1] - #self.ax.draw_artist(self.lsm.peaklines[x]) - #Ctrl+left - elif event.button == self.lsm.btn_del and event.key == self.lsm.key_mod: - #find and delete nearest peakline + # self.ax.draw_artist(self.lsm.peaklines[x]) + # Ctrl+left + elif ( + event.button == self.lsm.btn_del and event.key == self.lsm.key_mod + ): + # find and delete nearest peakline if len(self.lsm.peaks) > 0: - delete_peak = numpy.argmin([abs(i-x) for i in self.lsm.peaks]) + delete_peak = numpy.argmin( + [abs(i - x) for i in self.lsm.peaks] + ) old_peak = self.lsm.peaks.pop(delete_peak) - try: + try: peakline = self.lsm.peaklines.pop(old_peak) peakline.remove() except: with self.out: - print('Could not remove peakline') + print("Could not remove peakline") self.canvas.draw() - #self.redraw() + # self.redraw() def release(self, event): super().release(event) @@ -817,59 +915,62 @@ def onmove(self, event): class SpanSelectorMixin(BaseSelectorMixin): - def __init__(self): super().__init__() + class Ssm: pass + self.ssm = Ssm() self.ssm.btn_add = 3 self.ssm.btn_del = 3 - self.ssm.key_mod = 'control' + self.ssm.key_mod = "control" self.ssm.minspan = 0 self.ssm.rect = None self.ssm.rangespans = [] - self.ssm.rectprops = dict(facecolor='0.5', alpha=0.2) + self.ssm.rectprops = dict(facecolor="0.5", alpha=0.2) self.ssm.ranges = self.ranges for rng in self.ssm.ranges: - self.ssm.rangespans.append(self.makespan(rng[1], rng[0]-rng[1])) + self.ssm.rangespans.append(self.makespan(rng[1], rng[0] - rng[1])) self.redraw() - trans = blended_transform_factory( - self.ax.transData, - self.ax.transAxes) + trans = blended_transform_factory(self.ax.transData, self.ax.transAxes) w, h = 0, 1 - self.ssm.rect = Rectangle([0, 0], w, h, - transform=trans, - visible=False, - animated=True, - **self.ssm.rectprops - ) + self.ssm.rect = Rectangle( + [0, 0], + w, + h, + transform=trans, + visible=False, + animated=True, + **self.ssm.rectprops + ) self.ax.add_patch(self.ssm.rect) def makespan(self, left, width): - trans = blended_transform_factory( - self.ax.transData, - self.ax.transAxes) + trans = blended_transform_factory(self.ax.transData, self.ax.transAxes) bottom, top = self.ylims - height = top-bottom - rect = Rectangle([left, bottom], width, height, - transform=trans, - visible=True, - #animated=True, - **self.ssm.rectprops - ) + height = top - bottom + rect = Rectangle( + [left, bottom], + width, + height, + transform=trans, + visible=True, + # animated=True, + **self.ssm.rectprops + ) self.ax.add_patch(rect) return rect def redraw(self): super().redraw() - if hasattr(self, 'ssm'): + if hasattr(self, "ssm"): for i in self.ssm.rangespans: self.ax.draw_artist(i) def change_visible(self): super().change_visible() - if hasattr(self, 'ssm'): + if hasattr(self, "ssm"): for i in self.ssm.rangespans: i.set_visible(not i.get_visible()) @@ -880,14 +981,19 @@ def press(self, event): if event.button == self.ssm.btn_add and event.key != self.ssm.key_mod: self.buttonDown = True self.pressv = event.xdata - elif event.button == self.ssm.btn_add and event.key == self.ssm.key_mod: - #find and delete range + elif ( + event.button == self.ssm.btn_add and event.key == self.ssm.key_mod + ): + # find and delete range if len(self.ssm.ranges) > 0: x = event.xdata rng = 0 while rng < len(self.ssm.ranges): - if x >= (self.ssm.ranges[rng])[1] and x <= (self.ssm.ranges[rng])[0]: - self.ssm.ranges.pop(rng) + if ( + x >= (self.ssm.ranges[rng])[1] + and x <= (self.ssm.ranges[rng])[0] + ): + self.ssm.ranges.pop(rng) rangespan = self.ssm.rangespans.pop(rng) rangespan.remove() break @@ -904,20 +1010,21 @@ def release(self, event): span = vmax - vmin self.pressv = None spantest = False - #if len(self.ssm.ranges) > 0: + # if len(self.ssm.ranges) > 0: # for i in self.ssm.ranges: # if (vmin >= i[1]) and (vmin <= i[0]): # spantest = True # if (vmax >= i[1]) and (vmax <= i[0]): # spantest = True if span > self.ssm.minspan and spantest is False: - self.ssm.ranges.append([numpy.round(vmin, 2), numpy.round(vmax, 2)]) + self.ssm.ranges.append( + [numpy.round(vmin, 2), numpy.round(vmax, 2)] + ) self.ssm.rangespans.append(self.makespan(vmin, span)) with self.out: - print('range {} -> {}'.format(vmax, vmin)) + print("range {} -> {}".format(vmax, vmin)) self.ssm.ranges = [numpy.sort(i)[::-1] for i in self.ssm.ranges] - def onmove(self, event): super().onmove(event) if self.pressv is None or self.buttonDown is False: @@ -927,40 +1034,46 @@ def onmove(self, event): v = x minv, maxv = v, self.pressv if minv > maxv: - minv, maxv = maxv, minv + minv, maxv = maxv, minv vmin = self.pressv vmax = event.xdata # or self.prev[0] if vmin > vmax: - vmin, vmax = vmax, vmin + vmin, vmax = vmax, vmin self.ssm.rect.set_visible(self.visible) self.ssm.rect.set_xy([minv, self.ssm.rect.xy[1]]) - self.ssm.rect.set_width(maxv-minv) + self.ssm.rect.set_width(maxv - minv) self.ax.draw_artist(self.ssm.rect) -class PeakSelectorMixin(BaseSelectorMixin): +class PeakSelectorMixin(BaseSelectorMixin): def __init__(self): super().__init__() + class Psm: pass + self.psm = Psm() self.psm.btn_add = 1 self.psm.peak = None self.psm.newx = None - + def makeline(self, x): return self.ax.plot( - [x, x], + [x, x], self.ylims, - color='#CC0000', + color="#CC0000", lw=1, - )[0] + )[0] def press(self, event): super().press(event) x = numpy.round(event.xdata, 2) # left - if event.button == self.psm.btn_add and (x >= self.xlims[1]) and (x <= self.xlims[0]): + if ( + event.button == self.psm.btn_add + and (x >= self.xlims[1]) + and (x <= self.xlims[0]) + ): self.psm.peak = x self.makeline(x) self.process() @@ -970,55 +1083,60 @@ def release(self, event): def onmove(self, event): super().onmove(event) - + def process(self): pass - -class AssignMixin(BaseSelectorMixin): + +class AssignMixin(BaseSelectorMixin): def __init__(self): super().__init__() + class Am: pass + self.am = Am() self.am.btn_assign = 3 - self.am.key_mod1 = 'ctrl+alt' - self.am.key_mod2 = 'alt+control' + self.am.key_mod1 = "ctrl+alt" + self.am.key_mod2 = "alt+control" def press(self, event): super().press(event) - if event.button == self.am.btn_assign and (event.key == self.am.key_mod1 \ - or event.key == self.am.key_mod2): + if event.button == self.am.btn_assign and ( + event.key == self.am.key_mod1 or event.key == self.am.key_mod2 + ): with self.out: - print('assigned peaks and ranges') - self.assign() + print("assigned peaks and ranges") + self.assign() def assign(self): pass - -class DataSelector(): + + +class DataSelector: """ Interactive selector widget. can inherit from various mixins for functionality: Line selection: :class:`~nmrpy.plotting.LineSelectorMixin` Span selection: :class:`~nmrpy.plotting.SpanSelectorMixin` Poly selection: :class:`~nmrpy.plotting.PolySelectorMixin` - + This class is not intended to be used without inheriting at least one mixin. """ - def __init__(self, - data, - params, - extra_data=None, - extra_data_colour='k', - peaks=None, - ranges=None, - title=None, - voff=0.001, - label=None, - ): + def __init__( + self, + data, + params, + extra_data=None, + extra_data_colour="k", + peaks=None, + ranges=None, + title=None, + voff=0.001, + label=None, + ): if not Plot._is_iter(data): - raise AttributeError('data must be iterable.') + raise AttributeError("data must be iterable.") self.data = numpy.array(data) self.extra_data = extra_data self.extra_data_colour = extra_data_colour @@ -1041,19 +1159,25 @@ def __init__(self, self.pressv = None self.buttonDown = False self.prev = (0, 0) - self.blocking = False - #self.canvas.restore_region(self.background) - super().__init__() #calling parent init - #self.canvas.blit(self.ax.bbox) - - self.cidmotion = self.canvas.mpl_connect('motion_notify_event', self.onmove) - self.cidpress = self.canvas.mpl_connect('button_press_event', self.press) - self.cidrelease = self.canvas.mpl_connect('button_release_event', self.release) - self.ciddraw = self.canvas.mpl_connect('draw_event', self.on_draw) - #cursor = Cursor(self.ax, useblit=True, color='k', linewidth=0.5) - #cursor.horizOn = False + self.blocking = False + # self.canvas.restore_region(self.background) + super().__init__() # calling parent init + # self.canvas.blit(self.ax.bbox) + + self.cidmotion = self.canvas.mpl_connect( + "motion_notify_event", self.onmove + ) + self.cidpress = self.canvas.mpl_connect( + "button_press_event", self.press + ) + self.cidrelease = self.canvas.mpl_connect( + "button_release_event", self.release + ) + self.ciddraw = self.canvas.mpl_connect("draw_event", self.on_draw) + # cursor = Cursor(self.ax, useblit=True, color='k', linewidth=0.5) + # cursor.horizOn = False # self.canvas.draw() - #self.redraw() + # self.redraw() # plt.show() def disconnect(self): @@ -1065,9 +1189,9 @@ def disconnect(self): def _isnotebook(self): try: shell = get_ipython().__class__.__name__ - if shell == 'ZMQInteractiveShell': + if shell == "ZMQInteractiveShell": return True # Jupyter notebook or qtconsole - elif shell == 'TerminalInteractiveShell': + elif shell == "TerminalInteractiveShell": return False # Terminal running IPython else: return False # Other type (?) @@ -1080,16 +1204,21 @@ def _make_basic_fig(self, *args, **kwargs): self.ax = self.fig.add_subplot(111) if len(self.data.shape) == 1: self.ppm = numpy.mgrid[ - self.params['sw_left'] - - self.params['sw'] : self.params['sw_left'] : complex(self.data.shape[0]) + self.params["sw_left"] + - self.params["sw"] : self.params["sw_left"] : complex( + self.data.shape[0] + ) ] # extra_data if self.extra_data is not None: self.ax.plot( - self.ppm[::-1], self.extra_data, color=self.extra_data_colour, lw=1 + self.ppm[::-1], + self.extra_data, + color=self.extra_data_colour, + lw=1, ) # data - self.ax.plot(self.ppm[::-1], self.data, color='k', lw=1) + self.ax.plot(self.ppm[::-1], self.data, color="k", lw=1) elif len(self.data.shape) == 2: cl = dict( zip( @@ -1098,14 +1227,20 @@ def _make_basic_fig(self, *args, **kwargs): ) ) self.ppm = numpy.mgrid[ - self.params['sw_left'] - - self.params['sw'] : self.params['sw_left'] : complex(self.data.shape[1]) + self.params["sw_left"] + - self.params["sw"] : self.params["sw_left"] : complex( + self.data.shape[1] + ) ] - self.y_indices = numpy.arange(len(self.data)) * self.voff * self.data.max() + self.y_indices = ( + numpy.arange(len(self.data)) * self.voff * self.data.max() + ) # this is reversed for zorder # extra_data if self.extra_data is not None: - for i, j in zip(range(len(self.extra_data))[::-1], self.extra_data[::-1]): + for i, j in zip( + range(len(self.extra_data))[::-1], self.extra_data[::-1] + ): self.ax.plot( self.ppm[::-1], j + self.y_indices[i], @@ -1114,8 +1249,10 @@ def _make_basic_fig(self, *args, **kwargs): ) # data for i, j in zip(range(len(self.data))[::-1], self.data[::-1]): - self.ax.plot(self.ppm[::-1], j + self.y_indices[i], color=cl[i], lw=1) - self.ax.set_xlabel('ppm') + self.ax.plot( + self.ppm[::-1], j + self.y_indices[i], color=cl[i], lw=1 + ) + self.ax.set_xlabel("ppm") self.ylims = numpy.array(self.ax.get_ylim()) # numpy.array([self.ax.get_ylim()[0], self.data.max() + abs(self.ax.get_ylim()[0])]) # self.ax.set_ylim(self.ylims)#self.ax.get_ylim()[0], self.data.max()*1.1]) @@ -1123,7 +1260,11 @@ def _make_basic_fig(self, *args, **kwargs): self.xlims = [self.ppm[-1], self.ppm[0]] self.ax.set_xlim(self.xlims) self.fig.suptitle(self.title, size=20) - self.ax.text(0.95 * self.ax.get_xlim()[0], 0.7 * self.ax.get_ylim()[1], self.label) + self.ax.text( + 0.95 * self.ax.get_xlim()[0], + 0.7 * self.ax.get_ylim()[1], + self.label, + ) self.ax.set_ylim(self.ylims) self.canvas = self.ax.figure.canvas # self.canvas.draw() @@ -1151,7 +1292,7 @@ def on_zoom(self, event): def press(self, event): tb = plt.get_current_fig_manager().toolbar - if tb.mode == '' and event.xdata is not None: + if tb.mode == "" and event.xdata is not None: x = numpy.round(event.xdata, 2) self.canvas.restore_region(self.background) try: @@ -1159,7 +1300,7 @@ def press(self, event): except Exception as e: logging.error(traceback.format_exc()) self.redraw() - self.canvas.blit(self.ax.bbox) + self.canvas.blit(self.ax.bbox) def release(self, event): if self.pressv is None or not self.buttonDown: @@ -1171,7 +1312,7 @@ def release(self, event): except Exception as e: logging.error(traceback.format_exc()) self.redraw() - self.canvas.blit(self.ax.bbox) + self.canvas.blit(self.ax.bbox) def onmove(self, event): if event.inaxes is None: @@ -1184,87 +1325,105 @@ def onmove(self, event): except Exception as e: logging.error(traceback.format_exc()) self.redraw() - self.canvas.blit(self.ax.bbox) + self.canvas.blit(self.ax.bbox) def make_invisible(self): try: - super().make_invisible() + super().make_invisible() except Exception as e: logging.error(traceback.format_exc()) def make_visible(self): try: - super().make_visible() + super().make_visible() except Exception as e: logging.error(traceback.format_exc()) def redraw(self): try: - super().redraw() + super().redraw() except Exception as e: logging.error(traceback.format_exc()) - + def change_visible(self): try: - super().change_visible() + super().change_visible() except Exception as e: logging.error(traceback.format_exc()) + class IntegralDataSelector(DataSelector, PolySelectorMixin, AssignMixin): show_tracedata = True -class PeakTraceDataSelector(DataSelector, PolySelectorMixin, SpanSelectorMixin, AssignMixin): + +class PeakTraceDataSelector( + DataSelector, PolySelectorMixin, SpanSelectorMixin, AssignMixin +): show_tracedata = True -class LineSpanDataSelector(DataSelector, LineSelectorMixin, SpanSelectorMixin, AssignMixin): + +class LineSpanDataSelector( + DataSelector, LineSelectorMixin, SpanSelectorMixin, AssignMixin +): pass + class PeakDataSelector(DataSelector, PeakSelectorMixin): pass - + + class SpanDataSelector(DataSelector, SpanSelectorMixin, AssignMixin): pass + class DataTraceSelector: """ Interactive data-selection widget with traces and ranges. Traces are saved - as self.data_traces (WRT data) and self.index_traces (WRT index). + as self.data_traces (WRT data) and self.index_traces (WRT index). """ - def __init__(self, fid_array, - extra_data=None, - extra_data_colour='b', - voff=1e-3, - lw=1, - label=None, - ): + + def __init__( + self, + fid_array, + extra_data=None, + extra_data_colour="b", + voff=1e-3, + lw=1, + label=None, + ): self.fid_array = fid_array if fid_array.data is [] or fid_array.data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") data = fid_array.data params = fid_array._params - sw_left = params['sw_left'] - sw = params['sw'] + sw_left = params["sw_left"] + sw = params["sw"] + + ppm = numpy.linspace(sw_left - sw, sw_left, data.shape[1])[::-1] - ppm = numpy.linspace(sw_left-sw, sw_left, data.shape[1])[::-1] - self.integral_selector = IntegralDataSelector( - extra_data, - params, - extra_data=data, - extra_data_colour=extra_data_colour, - peaks=None, - ranges=None, - title='Integral trace selector', - voff=voff, - label=label) + extra_data, + params, + extra_data=data, + extra_data_colour=extra_data_colour, + peaks=None, + ranges=None, + title="Integral trace selector", + voff=voff, + label=label, + ) self.integral_selector.assign = self.assign - + def assign(self): data_traces = self.integral_selector.psm.data_lines index_traces = self.integral_selector.psm.index_lines - - self.fid_array._data_traces = [dict(zip(i[1], i[0])) for i in data_traces] - self.fid_array._index_traces = [dict(zip(i[1], i[0])) for i in index_traces] + + self.fid_array._data_traces = [ + dict(zip(i[1], i[0])) for i in data_traces + ] + self.fid_array._index_traces = [ + dict(zip(i[1], i[0])) for i in index_traces + ] decon_peaks = [] for i in self.fid_array._deconvoluted_peaks: @@ -1279,11 +1438,11 @@ def assign(self): integrals = {} for fid, indx in trace.items(): try: - integrals[fid] = numpy.argmin(abs(decon_peaks[fid]-indx)) + integrals[fid] = numpy.argmin(abs(decon_peaks[fid] - indx)) except: integrals[fid] = None trace_dict[t] = integrals - last_fid = (len(self.fid_array.get_fids())-1) + last_fid = len(self.fid_array.get_fids()) - 1 for i in trace_dict: tmin = min(trace_dict[i]) tminval = trace_dict[i][tmin] @@ -1293,97 +1452,113 @@ def assign(self): tmax = max(trace_dict[i]) tmaxval = trace_dict[i][tmax] if tmax < last_fid: - for j in range(tmax, last_fid+1): + for j in range(tmax, last_fid + 1): trace_dict[i][j] = tmaxval self.fid_array.integral_traces = trace_dict plt.close(self.integral_selector.fig) + class DataTraceRangeSelector: """ Interactive data-selection widget with traces and ranges. Traces are saved as self.data_traces (WRT data) and self.index_traces (WRT index). Spans are saves as self.spans. """ - def __init__(self, fid_array, - peaks=None, - ranges=None, - voff=1e-3, - lw=1, - label=None, - ): + + def __init__( + self, + fid_array, + peaks=None, + ranges=None, + voff=1e-3, + lw=1, + label=None, + ): self.fid_array = fid_array if fid_array.data is [] or fid_array.data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") data = fid_array.data params = fid_array._params - sw_left = params['sw_left'] - sw = params['sw'] + sw_left = params["sw_left"] + sw = params["sw"] + + ppm = numpy.linspace(sw_left - sw, sw_left, data.shape[1])[::-1] - ppm = numpy.linspace(sw_left-sw, sw_left, data.shape[1])[::-1] - self.peak_selector = PeakTraceDataSelector( - data, - params, - peaks=peaks, - ranges=ranges, - title='Peak and range trace selector', - voff=voff, - label=label) + data, + params, + peaks=peaks, + ranges=ranges, + title="Peak and range trace selector", + voff=voff, + label=label, + ) self.peak_selector.assign = self.assign def assign(self): data_traces = self.peak_selector.psm.data_lines index_traces = self.peak_selector.psm.index_lines spans = self.peak_selector.ssm.ranges - - traces = [[i[0], j[1]] for i, j in zip(data_traces, index_traces)] + + traces = [[i[0], j[1]] for i, j in zip(data_traces, index_traces)] self.fid_array.traces = traces - self.fid_array._trace_mask = self.fid_array._generate_trace_mask(traces) + self.fid_array._trace_mask = self.fid_array._generate_trace_mask( + traces + ) self.fid_array._set_all_peaks_ranges_from_traces_and_spans( - traces, spans) + traces, spans + ) plt.close(self.peak_selector.fig) + class DataPeakSelector: """ Interactive data-selection widget with lines and ranges for a single Fid. Lines and spans are saved as self.peaks, self.ranges. """ - def __init__(self, fid, - peaks=None, - ranges=None, - voff=1e-3, - lw=1, - label=None, - title=None, - ): + + def __init__( + self, + fid, + peaks=None, + ranges=None, + voff=1e-3, + lw=1, + label=None, + title=None, + ): self.fid = fid if fid.data is [] or fid.data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") data = fid.data params = fid._params - sw_left = params['sw_left'] - sw = params['sw'] - ppm = numpy.linspace(sw_left-sw, sw_left, len(data))[::-1] + sw_left = params["sw_left"] + sw = params["sw"] + ppm = numpy.linspace(sw_left - sw, sw_left, len(data))[::-1] if fid.peaks is not None: peaks = list(fid.peaks) if fid.ranges is not None: - ranges = list(fid.ranges) - + ranges = list(fid.ranges) + self.peak_selector = LineSpanDataSelector( - data, - params, - peaks=peaks, - ranges=ranges, - title=title, - voff=voff, - label=label) + data, + params, + peaks=peaks, + ranges=ranges, + title=title, + voff=voff, + label=label, + ) self.peak_selector.assign = self.assign - + def assign(self): - if len(self.peak_selector.ssm.ranges) > 0 and len(self.peak_selector.lsm.peaks) > 0: + if ( + len(self.peak_selector.ssm.ranges) > 0 + and len(self.peak_selector.lsm.peaks) > 0 + ): self.fid.ranges = self.peak_selector.ssm.ranges peaks = [] for peak in self.peak_selector.lsm.peaks: @@ -1396,17 +1571,21 @@ def assign(self): self.fid.ranges = None plt.close(self.peak_selector.fig) + class DataPeakRangeSelector: """Interactive data-selection widget with lines and ranges. Lines and spans are saved as self.peaks, self.ranges.""" - def __init__(self, fid_array, - peaks=None, - ranges=None, - y_indices=None, - aoti=True, - voff=1e-3, - lw=1, - label=None, - ): + + def __init__( + self, + fid_array, + peaks=None, + ranges=None, + y_indices=None, + aoti=True, + voff=1e-3, + lw=1, + label=None, + ): self.fid_array = fid_array self.fids = fid_array.get_fids() self.assign_only_to_index = aoti @@ -1417,30 +1596,31 @@ def __init__(self, fid_array, else: self.fid_number = range(len(self.fids)) if fid_array.data is [] or fid_array.data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") data = fid_array.data if y_indices is not None: data = fid_array.data[numpy.array(self.fid_number)] params = fid_array._params - sw_left = params['sw_left'] - sw = params['sw'] + sw_left = params["sw_left"] + sw = params["sw"] + + ppm = numpy.linspace(sw_left - sw, sw_left, data.shape[1])[::-1] - ppm = numpy.linspace(sw_left-sw, sw_left, data.shape[1])[::-1] - self.peak_selector = LineSpanDataSelector( - data, - params, - peaks=peaks, - ranges=ranges, - title='Peak and range selector', - voff=voff, - label=label) + data, + params, + peaks=peaks, + ranges=ranges, + title="Peak and range selector", + voff=voff, + label=label, + ) self.peak_selector.assign = self.assign - + def assign(self): self.peaks = self.peak_selector.lsm.peaks self.ranges = self.peak_selector.ssm.ranges - + if len(self.ranges) > 0 and len(self.peaks) > 0: ranges = self.ranges peaks = [] @@ -1456,80 +1636,96 @@ def assign(self): for fid in [self.fids[i] for i in self.fid_number]: fid.peaks = peaks fid.ranges = ranges - else: + else: for fid in self.fids: fid.peaks = peaks fid.ranges = ranges plt.close(self.peak_selector.fig) - + + class Calibrator: """ Interactive data-selection widget for calibrating PPM of a spectrum. """ - def __init__(self, fid, - lw=1, - label=None, - title=None, - ): + + def __init__( + self, + fid, + lw=1, + label=None, + title=None, + ): self.fid = fid if fid.data is [] or fid.data is None: - raise ValueError('data must exist.') - if not fid._flags['ft']: - raise ValueError('Only Fourier-transformed data can be calibrated.') + raise ValueError("data must exist.") + if not fid._flags["ft"]: + raise ValueError( + "Only Fourier-transformed data can be calibrated." + ) data = fid.data params = fid._params - sw_left = params['sw_left'] + sw_left = params["sw_left"] self.sw_left = sw_left - sw = params['sw'] - ppm = numpy.linspace(sw_left-sw, sw_left, len(data))[::-1] + sw = params["sw"] + ppm = numpy.linspace(sw_left - sw, sw_left, len(data))[::-1] self.peak_selector = PeakDataSelector( - data, - params, - title=title, - label=label) + data, params, title=title, label=label + ) self.peak_selector.process = self.process - - self.textinput = FloatText(value=0.0, description='New PPM:', - disabled=False, continuous_update=False) - + + self.textinput = FloatText( + value=0.0, + description="New PPM:", + disabled=False, + continuous_update=False, + ) + def _wait_for_change(self, widget, value): future = asyncio.Future() + def getvalue(change): # make the new value available future.set_result(change.new) widget.unobserve(getvalue, value) + widget.observe(getvalue, value) return future - + def process(self): peak = self.peak_selector.psm.peak self.peak_selector.out.clear_output() with self.peak_selector.out: - print('current peak ppm: {}'.format(peak)) + print("current peak ppm: {}".format(peak)) display(self.textinput) + async def f(): - newx = await self._wait_for_change(self.textinput, 'value') + newx = await self._wait_for_change(self.textinput, "value") offset = newx - peak - self.fid._params['sw_left'] = self.sw_left + offset + self.fid._params["sw_left"] = self.sw_left + offset with self.peak_selector.out: - print('calibration done.') + print("calibration done.") plt.close(self.peak_selector.fig) + asyncio.ensure_future(f()) + class RangeCalibrator: """ Interactive data-selection widget for calibrating PPM of an array of spectra. """ - def __init__(self, fid_array, - y_indices=None, - aoti=True, - voff=1e-3, - lw=1, - label=None, - ): + + def __init__( + self, + fid_array, + y_indices=None, + aoti=True, + voff=1e-3, + lw=1, + label=None, + ): self.fid_array = fid_array self.fids = fid_array.get_fids() self.assign_only_to_index = aoti @@ -1540,94 +1736,100 @@ def __init__(self, fid_array, else: self.fid_number = range(len(self.fids)) if fid_array.data is [] or fid_array.data is None: - raise ValueError('data must exist.') - if any (not fid._flags['ft'] for fid in self.fids): - raise ValueError('Only Fourier-transformed data can be calibrated.') + raise ValueError("data must exist.") + if any(not fid._flags["ft"] for fid in self.fids): + raise ValueError( + "Only Fourier-transformed data can be calibrated." + ) data = fid_array.data if y_indices is not None: data = fid_array.data[numpy.array(self.fid_number)] params = fid_array._params - sw_left = params['sw_left'] + sw_left = params["sw_left"] self.sw_left = sw_left - sw = params['sw'] - ppm = numpy.linspace(sw_left-sw, sw_left, data.shape[1])[::-1] + sw = params["sw"] + ppm = numpy.linspace(sw_left - sw, sw_left, data.shape[1])[::-1] self.peak_selector = PeakDataSelector( - data, - params, - title='FidArray calibration', - voff = voff, - label=label) + data, params, title="FidArray calibration", voff=voff, label=label + ) self.peak_selector.process = self.process - - self.textinput = FloatText(value=0.0, description='New PPM:', - disabled=False, continuous_update=False) - + + self.textinput = FloatText( + value=0.0, + description="New PPM:", + disabled=False, + continuous_update=False, + ) + def _wait_for_change(self, widget, value): future = asyncio.Future() + def getvalue(change): # make the new value available future.set_result(change.new) widget.unobserve(getvalue, value) + widget.observe(getvalue, value) return future - + def process(self): peak = self.peak_selector.psm.peak self.peak_selector.out.clear_output() with self.peak_selector.out: - print('current peak ppm: {}'.format(peak)) + print("current peak ppm: {}".format(peak)) display(self.textinput) + async def f(): - newx = await self._wait_for_change(self.textinput, 'value') + newx = await self._wait_for_change(self.textinput, "value") offset = newx - peak self._applycalibration(offset) with self.peak_selector.out: - print('calibration done.') + print("calibration done.") plt.close(self.peak_selector.fig) + asyncio.ensure_future(f()) def _applycalibration(self, offset): - self.fid_array._params['sw_left'] = self.sw_left + offset - + self.fid_array._params["sw_left"] = self.sw_left + offset + if self.assign_only_to_index: for fid in [self.fids[i] for i in self.fid_number]: - fid._params['sw_left'] = self.sw_left + offset - else: + fid._params["sw_left"] = self.sw_left + offset + else: for fid in self.fids: - fid._params['sw_left'] = self.sw_left + offset + fid._params["sw_left"] = self.sw_left + offset + class FidArrayRangeSelector: """Interactive data-selection widget with ranges. Spans are saved as self.ranges.""" - def __init__(self, - fid_array, - ranges=None, - y_indices=None, - voff=1e-3, - lw=1, - title=None, - label=None, - ): + + def __init__( + self, + fid_array, + ranges=None, + y_indices=None, + voff=1e-3, + lw=1, + title=None, + label=None, + ): self.fid_array = fid_array self.fids = fid_array.get_fids() data = fid_array.data params = fid_array._params if data is [] or data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") if y_indices is not None: data = data[numpy.array(y_indices)] - sw_left = params['sw_left'] - sw = params['sw'] + sw_left = params["sw_left"] + sw = params["sw"] + + ppm = numpy.linspace(sw_left - sw, sw_left, data.shape[1])[::-1] - ppm = numpy.linspace(sw_left-sw, sw_left, data.shape[1])[::-1] - self.span_selector = SpanDataSelector( - data, - params, - ranges=ranges, - title=title, - voff=voff, - label=label) + data, params, ranges=ranges, title=title, voff=voff, label=label + ) self.span_selector.assign = self.assign def assign(self): @@ -1642,36 +1844,35 @@ def assign(self): fid._bl_ppm = bl_ppm plt.close(self.span_selector.fig) + class FidRangeSelector: """Interactive data-selection widget with ranges. Spans are saved as self.ranges.""" - def __init__(self, - fid, - title=None, - ranges=None, - y_indices=None, - voff=1e-3, - lw=1, - label=None, - ): - self.fid=fid + + def __init__( + self, + fid, + title=None, + ranges=None, + y_indices=None, + voff=1e-3, + lw=1, + label=None, + ): + self.fid = fid data = fid.data params = fid._params if data is [] or data is None: - raise ValueError('data must exist.') + raise ValueError("data must exist.") if y_indices is not None: data = data[numpy.array(y_indices)] - sw_left = params['sw_left'] - sw = params['sw'] + sw_left = params["sw_left"] + sw = params["sw"] + + self.ppm = numpy.linspace(sw_left - sw, sw_left, len(data))[::-1] - self.ppm = numpy.linspace(sw_left-sw, sw_left, len(data))[::-1] - self.span_selector = SpanDataSelector( - data, - params, - ranges=ranges, - title=title, - voff=voff, - label=label) + data, params, ranges=ranges, title=title, voff=voff, label=label + ) self.span_selector.assign = self.assign def assign(self): @@ -1685,5 +1886,6 @@ def assign(self): self.fid._bl_ppm = bl_ppm plt.close(self.span_selector.fig) -if __name__ == '__main__': + +if __name__ == "__main__": pass diff --git a/specifications/nmrpy.md b/specifications/nmrpy.md index dd45c34..97d15e9 100644 --- a/specifications/nmrpy.md +++ b/specifications/nmrpy.md @@ -94,7 +94,7 @@ Container for relevant NMR parameters. ### FIDArray -Container for processing of multiple spectra. Must reference the respective `FID` objects by `id`. {Add reference back.} +Container for processing of multiple spectra. Must reference the respective `FID` objects by `id`. {Add reference back. Setup time for experiment, Default 0.5} - __fids__ - Type: string