spike.Interactive.INTER_2D — spike 0.99.23 documentation

# Source code for spike.Interactive.INTER_2D

    #!/usr/bin/env python
    # encoding: utf-8

    """
    A set of utilities to use spike in NMR or FTMS within jupyter

    First version MAD june 2017
    definitive ? version MAD october 2019

    """

    from __future__ import print_function, division
    import unittest
    import sys
    import os
    import os.path as op
    import glob

    import matplotlib.pyplot as plt
    import matplotlib.gridspec as gridspec
    from matplotlib.widgets import MultiCursor
    from ipywidgets import fixed, Layout, HBox, VBox, Label, Output, Button, Tab
    import ipywidgets as widgets
    from IPython.display import display, HTML, Javascript
    import numpy as np

    from ..File.BrukerNMR import Import_1D
    from .. import NPKData
    from ..NMR import NMRData
    from ..Interactive.INTER import Show1D, Colors, baseline1D, debounce
    from spike.util.signal_tools import findnoiselevel_2D

    try:
        import spike.plugins.bcorr as bcorr
    except:
        print('Baseline correction plugins not installed !')

    # REACTIVE modify callback behaviour
    # True is good for inline mode / False is better for notebook mode
    REACTIVE = True
    HEAVY = False

    class baseline2D_F2(baseline1D):
        def __init__(self, data, figsize=None):
            print('WARNING this tool is not functional/tested yet')
            self.data2D = data
            super().__init__( self.data2D.projF2, figsize=figsize)
        def on_done(self, e):
            super().on_done(e)
            ibsl_points = [int(self.data2D.axis2.ptoi(x)) for x in self.bsl_points]
            self.data2D.bcorr(method='spline', xpoints=ibsl_points)

    class Show2D(Show1D):
        """
        A display for 2D NMR with a scale cursor
        Show2D(spectrum) where spectrum is a NPKData object
        - special display for DOSY.
        """
        def __init__(self, data, title=None, figsize=None, **kw):
            super().__init__(data, title=title, create_children=False, **kw)
            self.isDOSY =  isinstance(data.axis1, NPKData.LaplaceAxis)
            try:
                self.proj2 = data.projF2
            except:
                self.proj2 = data.proj(axis=2).real()
            try:
                self.proj1 = data.projF1
            except:
                self.proj1 = data.proj(axis=1).real()
            # Controls
            self.scale.min = 0.2
            if data.noise == 0.0:
                data.noise = findnoiselevel_2D(data.get_buffer().real)
    #            mean, std = data.robust_stats()
            self.scale.max = data.absmax*0.05/data.noise   # we do not want levels to plunge into noise
            self.posview = widgets.Checkbox(value=True,description='Positive', tooltip='Display Positive levels', layout=Layout(width='20%'))
            self.negview = widgets.Checkbox(value=False,description='Negative', tooltip='Display Negative levels', layout=Layout(width='20%'))
            self.cursors = widgets.Checkbox(value=False,description='Cursors', tooltip='show cursors (cpu intensive !)', layout=Layout(width='20%'))
            # self.showlogo = widgets.Checkbox(description="Logo", value = True)
            # def switchlogo(e):
            #     if self.showlogo.value:
            #         self.axlogo.set_visible(True)
            #     else:
            #         self.axlogo.set_visible(False)
            # self.showlogo.observe(switchlogo)
            for w in (self.scale, self.posview, self.negview, self.cursors):
                w.observe(self.disp, names='value')
            # Grid
            grid = {'height_ratios':[1,4],'hspace':0,'wspace':0}
            if self.isDOSY:
                if figsize is None:
                    fsize = (10,5)
                else:
                    fsize = figsize
                grid['width_ratios']=[7,1]
            else:
                if figsize is None:
                    fsize = (8,8)
                else:
                    fsize = figsize
                grid['width_ratios']=[4,1]
    #        fig, self.axarr = plt.subplots(2, 1, sharex=True, figsize=fsize, gridspec_kw=grid)
            # Figure
            plt.ioff()
            self.fig = plt.figure(figsize=fsize, constrained_layout=False, tight_layout=True)
            plt.ion()
            self.fig.canvas.toolbar_position = 'left'
            spec2 = gridspec.GridSpec(ncols=2, nrows=2, figure=self.fig, **grid)
            axarr = np.empty((2,2), dtype=object)
            axarr[0,0] = self.fig.add_subplot(spec2[0, 0])
            axarr[1,0] = self.fig.add_subplot(spec2[1, 0],sharex=axarr[0, 0])
            axarr[1,1] = self.fig.add_subplot(spec2[1, 1],sharey=axarr[1, 0])
            axarr[0,1] = self.fig.add_subplot(spec2[0, 1])
            self.top_ax = axarr[0,0]
            self.spec_ax = axarr[1,0]
            self.side_ax = axarr[1,1]
            self.axlogo = axarr[0,1]
            self.axlogo.set_visible(False)
            self.multitop = None
            self.multiside = None
            self.ax = self.spec_ax
            # Children
            self.topbar = HBox( [self.posview, self.negview, self.cursors])
            self.controlbar = VBox([self.reset, self.scale, self.savepdf, self.done])
            self.middlebar = HBox( [  self.controlbar, self.fig.canvas ] )

            self.children = [  VBox([self.topbar, self.middlebar ]) ]
            self.set_on_redraw()
            self.draw(new=True)
            self.on_reset()
        def on_reset(self, e=None):
            self.scale.value = 1.0
            self.ax.set_ybound( (self.data.axis1.itoc(0),self.data.axis1.itoc(self.data.size1)) )
            self.ax.set_xbound( (self.data.axis2.itoc(0),self.data.axis2.itoc(self.data.size2)) )
        @debounce(0.2)
        def draw(self,new=True):
            if new:
                self.proj2.display(figure=self.top_ax, title=self.title)
                xb = self.top_ax.get_xbound()
                sidedisplay(self.proj1, self.side_ax)
                yb = self.side_ax.get_ybound()
            else:
                yb = self.side_ax.get_ybound()
                xb = self.top_ax.get_xbound()
                self.spec_ax.clear()
            if self.cursors.value:
                self.multitop = MultiCursor(self.fig.canvas, (self.spec_ax, self.top_ax), color='r', lw=1, horizOn=False, vertOn=True)
                self.multiside = MultiCursor(self.fig.canvas, (self.spec_ax, self.side_ax), color='r', lw=1, horizOn=True, vertOn=False)
            else:
                self.multitop = None
                self.multiside = None
            if self.posview.value:
                self.data.display(scale=self.scale.value, new_fig=False, figure=self.spec_ax, mpldic={'cmap':'winter'})
            if self.negview.value:
                self.data.display(scale=-self.scale.value, new_fig=False, figure=self.spec_ax, mpldic={'cmap':'YlOrRd'})
            self.spec_ax.set_xbound(xb)
            self.spec_ax.set_ybound(yb)
            self.fig.canvas.header_visible = False
            for s in ["left", "top", "right"]:
                self.top_ax.spines[s].set_visible(False)
            self.top_ax.yaxis.set_visible(False)
            for s in [ "top", "right", "bottom"]:
                self.side_ax.spines[s].set_visible(False)
            self.side_ax.xaxis.set_visible(False)
        @debounce(0.1)
        def disp(self, e=None):
            yb = self.side_ax.get_ybound()
            xb = self.top_ax.get_xbound()
            self.spec_ax.clear()
            if self.posview.value:
                self.data.display(scale=self.scale.value, new_fig=False, figure=self.spec_ax, mpldic={'cmap':'winter'})
            if self.negview.value:
                self.data.display(scale=-self.scale.value, new_fig=False, figure=self.spec_ax, mpldic={'cmap':'YlOrRd'})
            self.spec_ax.set_xbound(xb)
            self.spec_ax.set_ybound(yb)

    def sidedisplay(dt1d, ax):
        step = dt1d.axis1.itype+1
        dataxis = dt1d.axis1.itoc( dt1d.axis1.points_axis() )
        ax.plot(dt1d.buffer[::step],dataxis[::step])

    class Phaser2D(Show2D):
        """
        An interactive phaser in 2D NMR

            Phaser2D(spec)

        """
        def __init__(self, data, **kw):
            if data.itype != 3:
                raise('Dataset should be complex along both axes, Phasing is not possible')
            super().__init__(data, **kw)
            self.data_ref = data
            # print('WARNING this tool is not functional/tested yet')
            # create additional widgets
            slidersize = Layout(width='500px')
            self.F1p0 = widgets.FloatSlider(min=-180, max=180, step=1.0, description='P0',continuous_update=HEAVY, layout=slidersize)
            self.F1p1 = widgets.FloatSlider(min=-250, max=250, step=1.0, description='P1',continuous_update=HEAVY, layout=slidersize)
            self.F2p0 = widgets.FloatSlider(min=-180, max=180, step=1.0, description='P0',continuous_update=HEAVY, layout=slidersize)
            self.F2p1 = widgets.FloatSlider(min=-250, max=250, step=1.0, description='P1',continuous_update=HEAVY, layout=slidersize)
            pivotsize = Layout(width='200px')
            self.pivotF1 = widgets.BoundedFloatText(description='Pivot',
                    value=round(self.data.axis1.itoc(0.5*self.data.size1),2),
                    min=self.data.axis1.itoc(self.data.size1),
                    max=self.data.axis1.itoc(0),
                    format='%.2f',
                    layout=pivotsize,
                    step=0.1)
            self.pivotF2 = widgets.BoundedFloatText(description='Pivot',
                    value=round(self.data.axis2.itoc(0.5*self.data.size2),2),
                    min=self.data.axis2.itoc(self.data.size2),
                    max=self.data.axis2.itoc(0),
                    format='%.2f',
                    layout=pivotsize,
                    step=0.1)
            # modify defaults
            self.negview.value = True
            self.done.description = 'Apply'
            for w in [self.F1p0, self.F1p1, self.F2p0, self.F2p1, self.pivotF1, self.pivotF2]:
                w.observe(self.ob)
            self.cancel = widgets.Button(description="Cancel",button_style='warning', layout=self.blay)
            self.cancel.on_click(self.on_cancel)

            stcenter = "<b>F%d</b>"
            box_layout = widgets.Layout(display='flex',
                    flex_flow='column',
                    align_items='center',
                    grid_gap="100px",
                    width='100%')
            grid_layout = widgets.Layout(grid_template_columns="40% 40%",
                        justify_items='center')
            self.phasebar = \
                widgets.GridBox( [widgets.HTML(stcenter%1),     widgets.HTML(stcenter%2),
                                 self.F1p0,                             self.F2p0,
                                 self.F1p1,                             self.F2p1,
                                 self.pivotF1,                          self.pivotF2],
                                 layout=grid_layout)
            doc = widgets.HTML("""
                <p>Use the sliders to adjust the phase parameters, &nbsp; the pivot can be set with a right click on the spectrum<br>
                Top and Side spectra are taken at the pivot level.<br>
                </p>
                """)

            self.topbar = HBox( [self.posview, self.negview])
            self.controlbar = VBox([self.reset, self.scale, self.savepdf, self.done, self.cancel])
            self.middlebar = HBox( [  self.controlbar, self.fig.canvas ] )

            self.children = [  VBox([self.phasebar, doc, self.topbar, self.middlebar ]) ]
            self.pivotF1.observe(self.on_movepivot)
            self.pivotF2.observe(self.on_movepivot)
            # add right-click event on spectral window
            def on_press(event):
                if event.button == 3:
                    self.pivotF1.value = round(event.ydata,4)
                    self.pivotF2.value = round(event.xdata,4)
            cids = self.fig.canvas.mpl_connect('button_press_event', on_press)
        # pivot handling
        def ppivot(self):
            "converts from pivot values to centered ones"
            pp1 = 1.0-(self.data.axis1.ctoi(self.pivotF1.value)/self.data.size1)
            pp2 = 1.0-(self.data.axis2.ctoi(self.pivotF2.value)/self.data.size2)
            return (self.F1p0.value + (pp1-0.5)*self.F1p1.value, self.F1p1.value, \
                    self.F2p0.value + (pp2-0.5)*self.F2p1.value, self.F2p1.value)
        def ctopivot(self, F1p0, F1p1, F2p0, F2p1):
            "convert from centered to pivot values"
            pp1 = 1.0-(self.data.axis1.ctoi(self.pivotF1.value)/self.data.size1)
            pp2 = 1.0-(self.data.axis2.ctoi(self.pivotF2.value)/self.data.size2)
            return F1p0- (pp1-0.5)*F1p1, F1p1, F2p0- (pp2-0.5)*F2p1, F2p1
        def on_movepivot(self, event):
            if event['name']=='value':
                self.F1p0.value, self.F1p1.value, self.F2p0.value, self.F2p1.value = \
                    self.ctopivot(self.lF1p0, self.lF1p1, self.lF2p0, self.lF2p1)
                self.phase()
                #self.disp()

        def ob(self, event):
            "observe changes and start phasing"
            if event['name']=='value':
                self.phase()
        # def close(self):
        #     for w in [self.F1p0, self.F1p1, self.F2p0, self.F2p1, self.scale, self.done, self.cancel]:
        #         w.close()
        def on_cancel(self, b):
            print("No action")
            self.ax.clear()
            self.data.display(figure=self.ax,scale=self.scale.value)
            self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
            self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
            self.close()
        def on_Apply(self, b):
            self.lF1p0, self.lF1p1, self.lF2p0, self.lF2p1 = self.ppivot()         # get centered values
            self.data.phase(self.lF2p0, self.lF2p1, axis='F2').phase(self.lF1p0, self.lF1p1, axis='F1')
            self.data.display(figure=self.ax,scale=self.scale.value)
            self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
            self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
            self.close()
            print("Applied: phase(%.1f,%.1f,axis='F1').phase(%.1f,%.1f,axis='F2')"%(self.lF1p0, self.lF1p1, self.lF2p0, self.lF2p1))

        def draw(self,todisplay=None, new=True):
            "display either the current data or the one provided - red and blue"
            if not todisplay:
                todisplay = self.data
            if not new:
                yb = self.spec_ax.get_ybound()
                xb = self.spec_ax.get_xbound()
                for axx in (self.spec_ax, self.top_ax, self.side_ax):
                    axx.clear()
                icol = todisplay.axis2.ctoi(self.pivotF2.value)
                irow = todisplay.axis1.ctoi(self.pivotF1.value)
                todisplay.row(irow).display(figure=self.top_ax, title=self.title)
                sidedisplay( todisplay.col(icol), self.side_ax)
            if self.posview.value:
                todisplay.display(scale=self.scale.value, new_fig=False, figure=self.spec_ax,color='blue')
            if self.negview.value:
                todisplay.display(scale=-self.scale.value, new_fig=False, figure=self.spec_ax, color='red')
            if new:
                yb = self.spec_ax.get_ybound()
                xb = self.spec_ax.get_xbound()
            else:
                self.spec_ax.set_xbound(xb)
                self.spec_ax.set_ybound(yb)
                self.spec_ax.scatter(self.pivotF2.value, self.pivotF1.value, s=200, c='r', alpha=0.5)
                self.spec_ax.plot([self.pivotF2.value, self.pivotF2.value], self.spec_ax.get_ybound(), 'r--', alpha=0.5)
                self.spec_ax.plot(self.spec_ax.get_xbound(), [self.pivotF1.value, self.pivotF1.value], 'r--', alpha=0.5)
            self.fig.canvas.header_visible = False
            self.fig.canvas.header_visible = False
            for s in ["left", "top", "right"]:
                self.top_ax.spines[s].set_visible(False)
            self.top_ax.yaxis.set_visible(False)
            for s in [ "top", "right", "bottom"]:
                self.side_ax.spines[s].set_visible(False)
            self.side_ax.xaxis.set_visible(False)
            # self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
            # self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))

        def phdisp(self, todisplay=None):
            "display either the current data or the one provided - red and blue"
            if not todisplay:
                todisplay = self.data

                yb = self.spec_ax.get_ybound()
                xb = self.spec_ax.get_xbound()
                for axx in (self.spec_ax, self.top_ax, self.side_ax):
                    axx.clear()
                icol = todisplay.axis2.ctoi(self.pivotF2.value)
                irow = todisplay.axis1.ctoi(self.pivotF1.value)
                todisplay.row(irow).display(figure=self.top_ax, title=self.title)
                sidedisplay( todisplay.col(icol), self.side_ax)
            if self.posview.value:
                todisplay.display(scale=self.scale.value, new_fig=False, figure=self.spec_ax,color='blue')
            if self.negview.value:
                todisplay.display(scale=-self.scale.value, new_fig=False, figure=self.spec_ax, color='red')
            if new:
                yb = self.spec_ax.get_ybound()
                xb = self.spec_ax.get_xbound()
            else:
                self.spec_ax.set_xbound(xb)
                self.spec_ax.set_ybound(yb)
                self.spec_ax.scatter(self.pivotF2.value, self.pivotF1.value, s=200, c='r', alpha=0.5)
                self.spec_ax.plot([self.pivotF2.value, self.pivotF2.value], self.spec_ax.get_ybound(), 'r--', alpha=0.5)
                self.spec_ax.plot(self.spec_ax.get_xbound(), [self.pivotF1.value, self.pivotF1.value], 'r--', alpha=0.5)
            self.fig.canvas.header_visible = False
            self.fig.canvas.header_visible = False
            for s in ["left", "top", "right"]:
                self.top_ax.spines[s].set_visible(False)
            self.top_ax.yaxis.set_visible(False)
            for s in [ "top", "right", "bottom"]:
                self.side_ax.spines[s].set_visible(False)
            self.side_ax.xaxis.set_visible(False)
            # self.ax.set_xlim(xmin=self.data.axis2.itop(0), xmax=self.data.axis2.itop(self.data.size2))
            # self.ax.set_ylim(ymin=self.data.axis1.itop(0), ymax=self.data.axis1.itop(self.data.size1))
        def phase(self):
            "compute phase and display"
            self.lF1p0, self.lF1p1, self.lF2p0, self.lF2p1 = self.ppivot()         # get centered values
            dp = self.data.copy().phase(self.lF2p0, self.lF2p1, axis='F2').phase(self.lF1p0, self.lF1p1, axis='F1')
            self.phdisp(dp)
        # def phase(self, scale, F1p0, F1p1, F2p0, F2p1):
        #     self.data.copy().phase(F1p0,F1p1,axis='F1').phase(F2p0,F2p1,axis='F2').display(scale=scale);

    # if __name__ == '__main__':
    #    unittest.main()