Source code for spike.Interactive.INTER

#!/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
Intermediate version MAD october 2019
Improvements MAD spring - summer - automn  2021 - winter 2022
"""

"""
The code contains several parts

first a bunch of general small gadgets used to improve a jupyter notebook (hidecode, jsalert, injectcss, etc...)

Then some NMR specific tools

The most important is the interactive tools (hence the name):

Show1D
    from which inherits
Show1Dplus
baseline1D
Phaser
NMRintegrate
NMRpeakpicker

all tools are not at the same level on completion, this is an evoluating code...

Naming Conventions are used
For interactive tools (phaser, show, baseline)
draw() creates static matplotlib "artists"
    should probably called once at creation 
    eventually is things change a lot
disp() 
    takes care of redisplaying after some modifications (x or y limits, eventually xdata / ydata ...)
    should note create new artists

"""


import asyncio
from spike.plugins import Peaks
from . import __path__
from spike.plugins.NMR.Integrate import Integrals, Integralitem
from .ipyfilechooser import FileChooser as FileChooser_code
from ..File.BrukerNMR import Import_1D
from ..NMR import NMRData
from .. import NMR
from .. import version
from numpy.core.overrides import array_function_dispatch
import numpy as np
from IPython.display import display, HTML, Javascript, Markdown, Image
import ipywidgets as widgets
from ipywidgets import fixed, Layout, HBox, VBox, Label, Output, Button, Tab
from matplotlib.widgets import MultiCursor
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import matplotlib as mpl
from datetime import datetime
import re
import warnings
import time
import math as m
import glob
import unittest
import sys
import os
import os.path as op
from pathlib import Path
if version.revision < "538":
    warnmsg = """
There is version missmatch between the core program and the interactive tools
You may experiment some difficulties or halting with this notebook
"""
    warnings.warn(warnmsg)


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

__version__ = "1.3.0"

# REACTIVE modify callback behaviour
# True is good for inline mode / False is better for notebook mode
REACTIVE = True
HEAVY = False
# Activate_Wheel: scale with wheel control in the graphic cells
Activate_Wheel = True
# reverse_scroll : if True, reverses direction of mouse scroll
reverse_scroll = True

# default color set
Colors = ('black', 'red', 'blue', 'green', 'orange',
          'blueviolet', 'crimson', 'turquoise', 'indigo',
          'magenta', 'gold', 'pink', 'purple', 'salmon', 'darkblue', 'sienna')
Cursor = dict(linestyle='--', color='darkblue',
              linewidth=0.8)   # used for running cursors
Contrast = dict(color="crimson")  # used for contrasting spectra
##############################
# First General Utilities
##############################


[docs]def initialize(verb=1): "initialize notebook interface for Spike" if verb > 0: print("\nInteractive module version,", __version__) if verb > 1: hidecode() hidedoc() else: hidecode(message="") hidedoc(message="") initCSS() Logo()
[docs]def initCSS(): "adapt some CSS" display(HTML(''' <style> hr {height: 2px; border: 0;border-top: 1px solid #ccc;margin: 1em 0;padding: 0; } .container {width:100% !important; } </style> '''))
[docs]def hidecode(initial='show', message="<i>useful to show/print a clean screen when processing is finished</i>"): """ this func adds a button to hide/show the code on a jupyter page initial is either 'show' or 'hide' inspired from: https://stackoverflow.com/questions/27934885/how-to-hide-code-from-cells-in-ipython-notebook-visualized-with-nbviewer/28073228#28073228 """ if initial == 'show': init = 'false' elif initial == 'hide': init = 'true' display(HTML(''' <script> code_show=%s; function code_toggle() { if (code_show) { $('div.input').hide(); $('div.prompt').hide(); $('#but').val("show python code"); } else { $('div.input').show(); $('div.prompt').show(); $('#but').val("hide python code"); } code_show = !code_show } $(document).ready(code_toggle); </script> <form action="javascript:code_toggle()"> <input id="but" type="submit" style="border:1px solid black; background-color:#DDD"> %s </form>''' % (init, message)))
[docs]def hidedoc(initial='show', message="<i>useful to show/print a clean screen when processing is finished</i>"): """ this func adds a button to hide/show the doc on a jupyter page initial is either 'show' or 'hide' inspired from: https://stackoverflow.com/questions/27934885/how-to-hide-code-from-cells-in-ipython-notebook-visualized-with-nbviewer/28073228#28073228 """ if initial == 'show': init = 'false' elif initial == 'hide': init = 'true' display(HTML(''' <script> doc_show=%s; function doc_toggle() { if (doc_show) { $('div.text_cell').hide(); $('#butdoc').val("show documentation"); } else { $('div.text_cell').show(); $('#butdoc').val("hide documentation"); } doc_show = !doc_show } $(document).ready(doc_toggle); </script> <form action="javascript:doc_toggle()"> <input id="butdoc" type="submit" style="border:1px solid black; background-color:#DDD"> %s </form>''' % (init, message)))
[docs]def UserLogofile(): "return the address on disk of the User Logo file - located in $HOME/Spike/Logo.png" ufile = Path.home()/'.config'/'Spike'/'Logo.png' if ufile.exists(): return ufile else: return Logofile()
[docs]def Logofile(): "return the address on disk of the Logo file" return op.join(__path__[0], 'Logo.png')
[docs]def jsalert(msg="Alert text"): "send a javascript alert" # use \\n if you want to add several lines display(Javascript("alert('%s')" % msg))
def _jsalert(msg="Alert text", title='Alert'): display(Javascript(""" require( ["base/js/dialog"], function(dialog) { dialog.modal({ title: '%s', body: '%s', buttons: { 'Ok': {} } }); } ); """ % (title, msg)))
[docs]def space(width="80px"): "defines a layout of width, width should be a string in CSS syntax" return widgets.Layout(width=width)
[docs]def spacer(width="80px"): "defines a spacer to be used in widgets.Box - width should be a string in CSS syntax" return widgets.HTML("&nbsp;", layout=space(width))
class _FileChooser: """a simple file chooser for Jupyter - obsolete - not used any more""" def __init__(self, base=None, filetype=['fid', 'ser'], mode='r', show=True): if base is None: self.curdir = "/" else: self.curdir = base self.filetype = filetype self.mode = mode self.wfile = widgets.Text(layout=space( '70%'), description='File to load') self.ldir = widgets.Label(value="Chosen dir: "+self.curdir) self.wdir = widgets.Select( options=self.dirlist(), description='Choose Dir', layout=space('70%')) if mode == 'r': self.wchooser = widgets.Select( options=self.filelist(), description='Choose File', layout=Layout(width='70%')) self.wchooser.observe(self.wob) self.wfile.disabled = True # not mofiable in read mode elif mode == "w": self.wfile.description = 'File to create' self.wfile.disabled = False self.wchooser = widgets.Select( options=self.filelist(), description='Files', layout=space('70%')) else: raise Exception('Only "r" and "w" modes supported') self.wsetdir = Button(description='❯', layout=space('20%'), button_style='success', # 'success', 'info', 'warning', 'danger' or '' tooltip='descend in directory') self.wup = Button(description='❮', layout=space('20%'), button_style='success', # 'success', 'info', 'warning', 'danger' or '' tooltip='up to previous directory') self.wsetdir.on_click(self.setdir) self.wup.on_click(self.updir) if show: self.show() def filelist(self): fl = [] if self.mode == 'r': if type(self.filetype) is str: fl = glob.glob(op.join(self.curdir, self.filetype)) elif type(self.filetype) in (tuple, list): for f in self.filetype: fl += glob.glob(op.join(self.curdir, f)) else: raise Exception( 'TypeError, filetype should be either a string or a list') else: # 'w' fl = [f for f in glob.glob( op.join(self.curdir, '*')) if op.isfile(f)] self.wfile.value = op.join(self.curdir, self.filetype) if fl == []: fl = [" "] return fl def dirlist(self): base = self.curdir return [d for d in glob.glob(op.join(base, '*')) if (op.isdir(d) and not d.endswith('__pycache__'))] def wob(self, e): self.wfile.value = self.wchooser.value def updir(self, e): self.curdir = op.dirname(self.curdir) self.ldir.value = "Chosen dir: "+self.curdir self.wdir.options = self.dirlist() self.wchooser.options = self.filelist() def setdir(self, e): self.curdir = self.wdir.value self.ldir.value = "Chosen dir: "+self.curdir self.wdir.options = self.dirlist() self.wchooser.options = self.filelist() def show(self): display(VBox( [self.ldir, HBox([self.wdir, VBox([self.wup, self.wsetdir])]), self.wchooser, self.wfile ]) ) return self @property def file(self): "the chosen complete filename" return self.wfile.value @property def dirname(self): "the final dirname containing the chosen file" if op.isdir(self.wfile.value): return op.basename(self.wfile.value) else: return op.basename(op.dirname(self.wfile.value)) @property def nmrname(self): "the final dirname containing the chosen file for TopSpin files" if op.isdir(self.wfile.value): return op.join( op.basename(op.dirname(self.wfile.value)), self.dirname) else: return op.join( op.basename(op.dirname(op.dirname(self.wfile.value))), self.dirname) @property def basename(self): "the basename of the chosen file" return op.basename(self.wfile.value) # the following set of utilities is to display a nice summary of a SPIKE/Bruker dataset # first parse parameters in dictionary derived from acqus or procs # for the program to recognize arrays, the arrayname should be given below ( | separated, no space) arraynames = 'D|P|PL|PCPD|SP'
[docs]def readplist(paramtoadd, paramdict): "parse lists from acqus files - only that ones defined in arraynames" m = (re.match('(%s)([0-9]+)' % (arraynames,), paramtoadd)) if m: # arrays are special ! i = int(m.group(2)) pp = paramdict['$'+m.group(1)] val = pp[i] else: val = paramdict['$%s' % paramtoadd] return val
ParamList = ['PULPROG', 'SFO1', 'NS', 'TE', 'TD', 'RG', 'SW', 'O1', 'D1', 'P1']
[docs]def param_line(acqus, paramlist=None, output='string'): """ given a acqus dictionary, and a list of parameters to extract, this functions return either a plain text or a HTML table depending on output value either "string" or "HTML" ) """ head = [] lines = [] if paramlist == None: paramlist = ParamList for k in paramlist: val = str(readplist(k, acqus)) if val.startswith('<'): val = val[1:-1] lines.append(val) if k.startswith("$"): head.append(k[1:]) else: head.append(k) if output == "string": res = "\t".join(head) + "\n" + "\t".join(lines) elif output == "HTML": Head = '<table class="dataframe" border="1"><thead><tr>' Head += '<th style="text-align: center;">' + \ '</th><th style="text-align: center;">'.join(head) + '</th>' Head += '</tr></thead>' Lines = '<tbody><tr><td>' + \ '</td><td>'.join(lines) + '</tr></tbody></table>' res = HTML(Head + Lines) return res
[docs]def param_table(data, output='string'): """ return the complete parameter table held into data output is either "string" or "HTML """ lines = [] acqus = data.params['acqu'] if output == 'string': for k in acqus.keys(): lines.append("%s : %s" % (k, acqus[k])) res = "\n".join(lines) if output == 'HTML': st = '<table class="dataframe" border="1"><thead><tr><th><b>Parameter</b></th><th style="text-align: left;">value</th></tr>' st += '</thead><tbody>\n' for k in acqus.keys(): val = str(acqus[k]).replace("'", "") \ .replace("&", '&amp;') \ .replace("[", '') \ .replace("]", '') \ .replace("<", '&lsaquo;') \ .replace(">", '&rsaquo;') \ .replace('"', '&quot;') lines.append( '<tr><td>%s</td><td style="text-align: left;">%s</td></tr>' % (k, val)) st += ('\n'.join(lines) + '\n</tbody></table>\n') res = st return res
[docs]def summary(data, param=True, output='string'): """ produces a summary of the data set if param is True, a short list of 1D parameters is given, using the globally defined Paramlist output is either "string" or "HTML" """ if output == 'string': res = data.params['acqu']['title'] if param: res += r"\n" + param_line(data.params['acqu'], output='string') elif output == 'HTML': res = HTML('<b>' + data.params['acqu']['title'] + '</b>') if param: res2 = param_line(data.params['acqu'], output='HTML') res = HTML(res.data + res2.data) return res
# ²
[docs]class Timer: def __init__(self, timeout, callback): self._timeout = timeout self._callback = callback async def _job(self): await asyncio.sleep(self._timeout) self._callback()
[docs] def start(self): self._task = asyncio.ensure_future(self._job())
[docs] def cancel(self): self._task.cancel()
[docs]def debounce(wait): """ Decorator that will postpone a function's execution until after `wait` seconds have elapsed since the last time it was invoked. """ def decorator(fn): timer = None def debounced(*args, **kwargs): nonlocal timer def call_it(): fn(*args, **kwargs) if timer is not None: timer.cancel() timer = Timer(wait, call_it) timer.start() return debounced return decorator
############################## # Then 1D NMR Tools ############################## """ There is a hierarchic structure of the tools All these tools are Jupyter widgets which can be mixed with other regulat ipywidgets - Show1D creates the basic environement it sets 2 functions called back by all actions - draw() which build the whole picture - disp() which refresh only the bounding box (in x and y), and eventually modify simple objects - Reset which redraw and reset to default display parameter - on-done() which freezes the current display as a static - Phaser1D, NMRPeaker1D, NMRIntegrate, baseline1D are surcharging Show1d, with new methods, and eventually surcharging draw() and disp() - Show1Dplus() which presents all the options """
[docs]class Show1D(HBox): """ An interactive display, 1D NMR Show1D(spectrum) """ def __init__(self, data, title=None, figsize=None, show=True, create_children=True, yratio=21.0): """ data : to be displayed title : text for window figsize : size in inches (x,y) yratio : inverse minimum neg display extension, 1=centered, 21:all positive """ super().__init__() self.yratio0 = yratio # self.yratio = self.yratio0 self.data = data self.drspectrum = None # will store matplotlib artist try: (_, self.noise) = data.robust_stats() # noise will be handy ! except AttributeError: self.noise = 0 self.title = title # graphic set-up details self.yaxis_visible = False self.ax_spines = False self.canvas_header_visible = False if figsize is None: figsize = mpl.rcParams['figure.figsize'] # (10,5) if reverse_scroll: self.reverse_scroll = -1 else: self.reverse_scroll = 1 self.blay = space('80px') # default layout for buttons self.blank = widgets.HTML("&nbsp;", layout=self.blay) self.done = Button(description="Done", button_style='success', layout=self.blay, tooltip="Stop interactivity and store figure") self.done.on_click(self.on_done) self.reset = Button(description="Reset", button_style='success', layout=self.blay, tooltip="Reset to default display") self.savepdf = widgets.Button(description="Save figure", button_style='', layout=self.blay, tooltip='Save the current figure as pdf') def onsave(e): name = self.fig.get_axes()[0].get_title() datedext = datetime.strftime( datetime.now(), " %y-%m-%d_%H:%M:%S.pdf") name = name.replace('/', '_') + datedext if name.startswith('.'): name = 'Figure'+name self.fig.savefig(name) print('figure saved as: ', name) self.savepdf.on_click(onsave) self.reset.on_click(self.on_reset) self.scale = widgets.FloatSlider(description='scale:', value=1.0, min=0.1, max=200, step=0.1, layout=Layout(width='80px', height=str(0.8*2.54*figsize[1])+'cm'), continuous_update=REACTIVE, orientation='vertical') for widg in (self.scale,): widg.observe(self.obdisp) if create_children: plt.ioff() fi, ax = plt.subplots(figsize=figsize) fi.set_tight_layout(True) plt.ion() self.ax = ax # def update_box(axes): # self.xb = self.ax.get_xbound() # self.ax.set_ybound(self.yb0/self.scale.value) # self.ax.add_callback(self.update_box) self.fig = fi # self.xb = self.ax.get_xbound() self.on_reset() self.children = [ VBox([self.reset, self.scale, self.savepdf, self.done]), self.fig.canvas] if show: self.draw() # call backs
[docs] def on_done(self, b): self.close() display(self.fig) # shows spectrum
[docs] def on_reset(self, b=None): self.scale.value = 1.0 try: self.xb0 = np.array([self.data.axis1.itoc(0), self.data.axis1.itoc(self.data.size1)]) # full box in x self.xb = self.xb0 # current x box self.yratio = self.yratio0 # inverse minimum neg display extension self.ymax = self.data.absmax*1.1 # highest position self.yb0 = np.array( [-self.ymax/self.yratio, self.ymax] ) # full box in y self.disp() except AttributeError: # if self.data not defined pass
[docs] def ob(self, event): "observe events and redraw" if event['name'] == 'value': self.draw()
[docs] def obdisp(self, event): "observe events and display" if event['name'] == 'value': self.disp()
[docs] @debounce(0.005) def scale_up(self, step): # 1.1892 is 4th root of 2.0 self.scale.value *= 1.1892**(self.reverse_scroll*step)
[docs] def set_on_redraw(self): "set-up mouse scroll control and click" @debounce(0.005) def on_draw(event): self.xb = self.ax.get_xbound() # follow x and y box yb = self.ax.get_ybound() # [-ymax/(yratio*scale), ymax/scale = [yb0, yb1] # ymax/scale = yb1 => scale = ymax/yb1 # -ymax/(yratio*scale) = yb0 => -ymax/(scale*yb0) = yratio ratio = -yb[1]/yb[0] if abs(ratio-self.yratio)/ratio > 0.1: # more than 10% variation self.scale.value = self.ymax/yb[1] self.yratio = -self.ymax/(yb[0]*self.scale.value) self.yb0 = np.array( [-self.ymax/self.yratio, self.ymax] ) self.cids_disp = self.fig.canvas.mpl_connect('draw_event', on_draw) def on_scroll(event): self.scale_up(event.step) if Activate_Wheel: self.fig.canvas.capture_scroll = True self.cids_scroll = self.fig.canvas.mpl_connect('scroll_event', on_scroll)
[docs] def draw(self): "builds and display the picture" self.ax.clear() self.data.display(new_fig=False, figure=self.ax, title=self.title) self.drspectrum = self.data.disp_artist self.ax.set_xbound(self.xb) self.ax.set_ybound(self.yb0/self.scale.value) self.fig.canvas.header_visible = False for s in ["left", "top", "right"]: self.ax.spines[s].set_visible(False) self.ax.yaxis.set_visible(False) visible_ticks = {"top": False, "right": False} self.ax.tick_params(axis="x", which="both", **visible_ticks) self.set_on_redraw()
[docs] def disp(self): # scale = self.ax.get_ybound() # self.scale.value = self.yb0/scale # self.xb = self.ax.get_xbound() self.ax.set_ybound(self.yb0/self.scale.value) self.ax.set_xbound(self.xb)
[docs]class baseline1D(Show1D): """ An interactive baseline for 1D NMR baseline1D(spectrum, ...) """ def __init__(self, data, figsize=None, show=True, create_children=True, yratio=10): try: self.BslPoints = data.BslPoints # recover from previous correction except: self.BslPoints = [] # initialize with empty list super().__init__(data, figsize=figsize, show=False, create_children=create_children,yratio=yratio) self.data.real() a, b = self.itoc3(0.0), self.itoc3(self.data.size1) # spectral borders self.select = widgets.FloatSlider(description='Manually:', min=min(a, b), max=max(a, b), value=self.itoc3(0.5*self.data.size1), continuous_update=REACTIVE, layout=space('40%'), tooltip='position of the selected point in %s' % (data.unit[0])) self.smooth = widgets.IntSlider(description='smooth:', min=0, max=20, layout=space('40%'), tooltip='apply a local smoothing for pivot points', value=1, readout=True, continuous_update=REACTIVE) self.toshow = widgets.Dropdown( options=['baseline', 'corrected', 'points'], description='Display:') self.bsl_points = self.BslPoints self.baseline = np.zeros_like(self.data.buffer) self.cancel = widgets.Button(description="Exit", button_style='warning', layout=self.blay, tooltip='Exit without corrections') self.auto = widgets.Button(description="Auto", button_style='success', layout=self.blay, tooltip='Automatic set of points') self.set = widgets.Button(description="Add", button_style='success', layout=self.blay, tooltip='Add a baseline point at the selector position') self.unset = widgets.Button(description="Rem", button_style='warning', layout=self.blay, tooltip='Remove the baseline point closest to the selector') self.nbautopoints = widgets.IntText(value=8, layout=space('4em')) if create_children: orig = self.children self.children = [VBox([ HBox([Label('Baseline points'), self.nbautopoints, Label("points"), self.auto, self.blank, self.select, self.set, self.unset]), HBox([self.cancel, self.toshow, self.smooth]), HBox(orig)])] # add interaction # done button is activated by base class self.cancel.on_click(self.on_cancel) self.auto.on_click(self.on_auto) self.set.on_click(self.on_set) self.unset.on_click(self.on_unset) for w in [self.select, self.smooth, self.toshow]: w.observe(self.ob) def on_press(event): if event.button == 3: # right-click v = event.xdata self.select.value = round(v, 3) self.disp() self.cids_press = self.fig.canvas.mpl_connect('button_press_event', on_press) # finalize if show: self.draw()
[docs] def disconnect_press(self): "should be called before close()" self.fig.canvas.mpl_disconnect(self.cids_press)
[docs] def itoc3(self, value): return round(self.data.axis1.itoc(value), 3)
[docs] def on_cancel(self, e): self.disconnect_press() self.close() print('no baseline correction')
[docs] def on_done(self, e): if self.bsl_points == []: jsalert( 'Please define control points before applying baseline correction') return printlist = [round(i, 3) for i in sorted(self.bsl_points)] print("Applied correction:\ndata.bcorr(xpoints={0}, nsmooth={1})".format( printlist, self.smooth.value)) sp = self.data # short name for convenience sp.set_buffer(sp.get_buffer() - self.correction()) self.drspectrum[0].set_xdata( sp.axis1.itoc( sp.axis1.points_axis() ) ) # I got once a shorten x display axis !!! so restore here self.drspectrum[0].set_ydata(sp.get_buffer()) self.clear() # remove additional drawings self.drpivot.set_visible(True) # but pivot points # self.ax.clear() # self.data.display(new_fig=False, figure=self.ax, title=self.title) self.drpivot.set_xdata(self.bsl_points) y = np.zeros(len(self.bsl_points)) self.drpivot.set_ydata(y) # store for latter use self.data.BslPoints = self.bsl_points self.disconnect_press() self.close() display(self.fig) # shows spectrum
[docs] def on_auto(self, e): "automatically set baseline points" self.bsl_points = [self.data.axis1.itoc(x) for x in bcorr.autopoints( self.data, Npoints=self.nbautopoints.value)] self.disp()
[docs] def on_set(self, e): "add baseline points at selector" self.bsl_points.append(self.select.value) self.disp()
[docs] def on_unset(self, e): "remove baseline points closest from selector" if len(self.bsl_points) == 0: return here = self.select.value # find distclose = np.inf pclose = np.NaN for i, p in enumerate(self.bsl_points): if abs(p-here) < distclose: pclose = p ipclose = i distclose = abs(p-here) self.bsl_points.remove(pclose) # remove and clean display self.disp()
[docs] def correction(self): "returns the correction to apply as a numpy array" ibsl_points = self.data.axis1.ptoi( np.array(self.bsl_points)).astype(int) x = np.arange(self.data.size1) yy = self.data.get_buffer() if len(self.bsl_points) == 0: return 0.0 elif len(self.bsl_points) == 1: value = self.data[ibsl_points[0]] * np.ones(self.data.size1) elif len(self.bsl_points) < 4: corr = bcorr._linear_interpolate( yy, ibsl_points, nsmooth=self.smooth.value) value = corr(x) else: corr = bcorr._spline_interpolate( yy, ibsl_points, nsmooth=self.smooth.value) value = corr(x) return value
[docs] def corrected(self): value = self.data.copy() value.set_buffer(value.get_buffer() - self.correction()) return value
[docs] def clear(self): "remove artists created by draw()" # self.drspectrum[0].set_visible(False) self.selector.set_visible(False) self.drhoriz.set_visible(False) self.drbaseline.set_visible(False) self.drpivot.set_visible(False)
[docs] def draw(self): "used to create the view with additional artists" super().draw() # baseline self.drbaseline = self.ax.plot( self.data.axis1.unit_axis(), self.baseline, **Contrast)[0] # corrected spectrum corrected = self.data.get_buffer()-self.correction() # yoffset is used to make fig clearer yoffset = 0.2*self.ax.get_ybound()[1] self.drcorrected = self.ax.plot( self.data.axis1.unit_axis(), corrected+yoffset, alpha=0.5, **Contrast) # horizontal line at yoffset self.drhoriz = self.ax.axhline(yoffset, **Cursor) # selector ppos = self.select.value self.selector = self.ax.axvline(ppos, **Cursor) # pivots if len(self.bsl_points) > 0: y = bcorr.get_ypoints(self.data.get_buffer(), self.data.axis1.ptoi( np.array(self.bsl_points)), nsmooth=self.smooth.value) self.drpivot = self.ax.plot( self.bsl_points, y, marker='o', linestyle="None", **Contrast)[0] # , markersize=12) else: self.drpivot = self.ax.plot( [], [], marker='o', linestyle="None", **Contrast)[0] # , markersize=12) self.disp()
[docs] def disp(self): "used to refresh view" # graphic objects: (drspectrum) drbaseline drpivot drcorrected drhoriz selector super().disp() if len(self.bsl_points) > 0: if self.toshow.value == 'baseline': self.drcorrected[0].set_visible(False) self.drhoriz.set_ydata(0.0) self.drhoriz.set_visible(True) self.drbaseline.set_ydata(self.correction()) self.drbaseline.set_visible(True) elif self.toshow.value == 'corrected': self.drbaseline.set_visible(False) yoffset = 0.2*self.ax.get_ybound()[1] self.drcorrected[0].set_ydata( self.data.get_buffer() - self.correction() + yoffset) self.drhoriz.set_ydata(yoffset) self.drhoriz.set_visible(True) self.drcorrected[0].set_visible(True) elif self.toshow.value == 'points': self.drbaseline.set_visible(False) self.drcorrected[0].set_visible(False) self.drhoriz.set_visible(False) else: self.drbaseline.set_visible(False) self.drcorrected[0].set_visible(False) self.drhoriz.set_visible(False) # selector ppos = self.select.value self.selector.set_xdata([ppos, ppos]) # pivots # pivot points y = bcorr.get_ypoints(self.data.get_buffer(), self.data.axis1.ptoi(np.array(self.bsl_points)), nsmooth=self.smooth.value) #self.drpivot = self.ax.scatter(self.bsl_points, y, c='r', marker='o') # self.drpivot = self.ax.plot(self.bsl_points, y, marker='o',linestyle="None", **Contrast)[0]#, markersize=12) self.drpivot.set_xdata(self.bsl_points) self.drpivot.set_ydata(y)
# set zoom
[docs]class SpforSuper(): "a holder for one spectrum to SuperImpose" def __init__(self, i, filename='None', axref=False, ref=None): """ filename is the dataset to show (None or Self possible) axref is the axes of the caller, where to display ref is a dataset in case filename is Self """ self.axref = axref self.ref = ref self.drartist = None self.filename = widgets.Text( value=filename, layout=Layout(width='30em')) self.filename.observe(self.nactivate) j = i % len(Colors) self.color = widgets.Dropdown( options=["steelblue"]+list(Colors), value=Colors[j], layout=space('90px')) self.direct = widgets.Dropdown( options=['up', 'down', 'off'], value='off', layout=space('60px')) self.direct.observe(self.activate) self.zmleft = widgets.FloatText( value=1000, step=0.1, layout=space('70px')) self.zmright = widgets.FloatText( value=-1000, step=0.1, layout=space('70px')) self.label = widgets.Checkbox( value=False, indent=False, layout=space('40px')) self.scale = widgets.FloatText(value=1.0, step=0.1, layout=space( '70px'), tooltip="relative intensity scale") self.stretch = widgets.FloatText( value=1.0, step=0.1, layout=space('70px'), tooltip="stretching along x") self.xoffset = widgets.FloatText(value=0.0, step=0.1, layout=space( '60px'), tooltip="x offset in spec unit") self.yoffset = widgets.FloatText( value=0.0, step=1, layout=space('60px'), tooltip="y offset in %") self.splw = widgets.FloatText( value=1.0, step=0.1, layout=space('50px')) # for w in (self.color, self.direct, self.zmleft, self.zmright, self.label, self.scale, self.offset, self.splw): # w.observe(self.ob) # control bar self.me = HBox([self.B(str(i)), self.filename, self.scale, self.stretch, self.direct, self.xoffset, self.yoffset, self.zmleft, self.zmright, self.splw, self.color, self.label, ]) self. header = HBox([self.B('Id', '40px'), self.B('file name', '5em'), self.I('or "Self" for spectrum excerpts', '26em'), self.B('scale', '60px'), self.B('stretching', '60px'), self.B('direction', '60px'), self.B('xoffset', '50px'), self.B('yoffset(%)', '50px'), self.B(' ', '50px'), self.B('Zoom', '90px'), self.B('Linewidth', '80px'), self.B('color', '70px'), self.B('label') ]) self.nactivate(None)
[docs] def ob(self, event): "observe events and display" if event['name'] == 'value': self.draw()
[docs] def injectobserve(self, method): "used to to observe form outer object for draw" for w in (self.color, self.zmleft, self.zmright, self.label, self.splw, self.direct, self.scale, self.stretch, self.xoffset, self.yoffset): w.observe(method)
[docs] def nactivate(self, e): "called to check name validity, when name is entered" fileexists = (self.filename.value == 'Self' or (self.filename.value not in ('None', '') and Path(self.filename.value).exists())) if fileexists: self.direct.value = 'up' # will call self.activate() self.direct.disabled = False else: self.direct.value = 'off' # will call self.activate() self.direct.disabled = True
[docs] def activate(self, e): "called when state changes" for w in (self.color, self.zmleft, self.zmright, self.label, self.scale, self.stretch, self.xoffset, self.yoffset, self.splw): if self.direct.value == 'off': w.disabled = True else: w.disabled = False
[docs] def B(self, text, size='auto'): "bold HTML widget" return widgets.HTML(value="<b>%s</b>" % text, layout=widgets.Layout(width=size))
[docs] def I(self, text, size='auto'): "Italic HTML widget" return widgets.HTML(value="<i>%s</i>" % text, layout=widgets.Layout(width=size))
[docs] def draw(self, unit='ppm'): if self.filename.value == 'None' or self.filename.value == '' or self.direct.value == 'off': return # nothing to do # else if self.drartist is not None: # remove previous ones del(self.drartist) # label if self.label.value: m = self.mult if not (abs(m) == 1 or m == 0): lb = "%s (x%.1f)" % (self.nmrname,m) else: lb = self.nmrname else: lb = None # load if self.filename.value == 'Self': d = self.ref.copy().set_unit(unit) else: try: d = NMRData(name=self.filename.value).set_unit(unit) except: self.direct.value = 'off' jsalert('%s : cannot open File' % self.filename.value) return # draw zml = min(self.zmleft.value, d.axis1.itoc(0)) zmr = max(self.zmright.value, d.axis1.itoc(d.size1)) d.display( scale=1, figure=self.axref, zoom=(zml, zmr), color=self.color.value, linewidth=self.splw.value, label=lb) self.drartist = d.disp_artist[0] self.ydata = self.drartist.get_ydata() # store spectral data self.xdata = self.drartist.get_xdata() # adapt to scale and direction self.drartist.set_ydata(self.move_ydata()) self.drartist.set_xdata(self.move_xdata()) # text # will be changed by disp() self.text = self.axref.text( zmr, self.ydata[-1], ' ', color=self.color.value) self.disp()
[docs] def move_xdata(self): "adapt self.xdata to offset and stretching, then return the value" # get starting value start = self.xdata[0] # affine transform return self.xdata[0] + self.stretch.value*(self.xdata - start) + self.xoffset.value
[docs] def move_ydata(self): "adapt self.ydata to direct, scale, and offset and return the value" # add offset # relative to screen in spectral unit off = self.axref.get_ybound()[1] * self.yoffset.value/100 # and multiply return self.mult*self.ydata + off
[docs] def disp(self): " refresh " if self.filename.value == 'None' or self.direct.value == 'off': return # nothing to do yd = self.move_ydata() xd = self.move_xdata() self.drartist.set_ydata(yd) self.drartist.set_xdata(xd) self.text.set_text(" ") # empty text if not self.label.value: # unless... there is a scale != 1, and no label m = self.mult if not (abs(m) == 1 or m == 0): tt = "x%.1f" % (m,) self.text.set_text(tt) self.text.set_y(yd[-1]) self.text.set_x(xd[-1])
@property def mult(self): if self.direct.value == 'up': mult = self.scale.value elif self.direct.value == 'down': mult = -self.scale.value else: mult = 1 return mult @property def nmrname(self): fnm = self.filename.value if fnm not in ('None', 'Self'): return op.join(op.basename(op.dirname(fnm)), op.basename(fnm)) elif fnm == 'Self': return 'excerpt' else: return None
[docs] def simpledisplay(self): "a simplified widget " lb = widgets.Label(self.nmrname) return HBox([lb, self.scale])
[docs]class Show1Dplus(Show1D): def __init__(self, data, base='/DATA', N=9, **kw): show = kw.get('show', True) kw['show'] = False super().__init__(data, **kw) self.NbMaxPeaks = Peaks.NbMaxDisplayPeaks # spectrum control widgets self.sptitle = widgets.Text(description='Title', value=self.title, layout=space('40%')) self.spcolor = widgets.Dropdown(description='Color', options=["steelblue"]+list(Colors), value='steelblue', layout=space('24%')) self.splw = widgets.FloatText(description='Linewidth', value=1.0, step=0.1, layout=space('24%')) self.showlogo = widgets.Checkbox(description="Logo", value=True, layout=widgets.Layout(left='200px')) self.axlogo = self.fig.add_axes([.92, .84, .08, .16], visible=True) self.axlogo.imshow(plt.imread(UserLogofile(), 'rb')) self.axlogo.set_axis_off() def switchlogo(e): if self.showlogo.value: self.axlogo.set_visible(True) else: self.axlogo.set_visible(False) self.showlogo.observe(switchlogo) SP = VBox([HBox([self.sptitle, self.showlogo]), HBox([self.spcolor, self.splw])]) # integral control widgets self.integ = widgets.Checkbox(description='Show', value=False) self.scaleint = widgets.FloatSlider(description='Scale', value=0.5, min=0.1, max=10, step=0.05, layout=Layout(width='40%'), continuous_update=REACTIVE) self.offset = widgets.FloatSlider(description='Offset', value=0.4, min=0.0, max=1.0, step=0.01, layout=Layout(width='45%'), continuous_update=REACTIVE) self.intlw = widgets.FloatText(description='Linewidth', value=1.5, step=0.1, layout=Layout(width='24%')) self.intcolor = widgets.Dropdown(description='Color', options=Colors, value='red', layout=Layout(width='45%')) self.intlabelcolor = widgets.Dropdown(description='Label', options=Colors, value='crimson', layout=Layout(width='40%')) self.labelx = widgets.FloatText(description='Label X pos', value=1, step=0.1, layout=Layout(width='24%')) self.labely = widgets.FloatText(description='Y pos', value=-1, step=0.1, layout=Layout(width='24%')) self.labelfont = widgets.BoundedFloatText(description='Size', value=8, mini=1, maxi=128, step=1, layout=Layout(width='24%')) self.labelrot = widgets.BoundedFloatText(description='Angle', value=0, mini=-90, maxi=90, step=1, layout=Layout(width='24%')) INT = VBox([HBox([widgets.HTML('<b>Integrals</b>'), self.integ]), HBox([self.scaleint, self.offset, self.intlw]), HBox([self.intcolor, self.intlabelcolor]), HBox([self.labelx, self.labely, self.labelfont, self.labelrot]) ], layout=Layout(width='60%')) # peaks control widgets self.peaks = widgets.Checkbox(description='Show',value=False, tooltip="show/hide peaks on the spectrum") markers = ['None', 'x', 'X', 'd', 'D', 'v', '^', '<', '>', '|'] self.marker = widgets.Dropdown(description='Marker', options=markers, value="x", layout=Layout(width='20%')) self.pkcolor = widgets.Dropdown(description='Color', options=Colors, value='darkblue', layout=Layout(width='40%')) self.pkvalues = widgets.Checkbox(description='Values', value=False, layout=Layout(width='30%')) self.pkrotation = widgets.BoundedFloatText(description='Angle', value=45, mini=-90, maxi=90, step=1, layout=Layout(width='20%')) self.pkfont = widgets.BoundedFloatText(description='Size', value=8, mini=1, maxi=128, step=1, layout=Layout(width='20%')) PK = VBox([HBox([widgets.HTML('<b>Peaks</b>'), self.peaks]), HBox([self.marker, self.pkcolor]), HBox([self.pkvalues, self.pkfont, self.pkrotation]) ], layout=Layout(width='60%')) # Superposition control widgets # (base=base, filetype="*.gs1", mode='r', show=False) self.Chooser = FileChooser_code(path='/DATA/', filename='.gs1') self.bsel = widgets.Button(description='Copy', layout=self.blay, button_style='info', # 'success', 'info', 'warning', 'danger' or '' tooltip='copy selected data-set to entry below') self.to = widgets.IntText( value=1, min=1, max=N, layout=Layout(width='10%')) self.bsel.on_click(self.copy) self.DataList = [SpforSuper( i+1, axref=self.ax, ref=self.data) for i in range(N)] for s in self.DataList: s.injectobserve(self.ob) self.DataList[0].color.value = 'red' for widg in (self.sptitle, self.spcolor, self.splw, self.peaks, self.integ, self.scaleint, self.offset, self.intlw, self.intcolor, self.intlabelcolor, self.labelx, self.labely, self.labelfont, self.labelrot, self.pkvalues, self.marker, self.pkcolor, self.pkrotation, self.pkfont): widg.observe(self.ob) orig = self.children controls = [SP] try: self.data.peaks controls.append(PK) except: pass try: self.data.integrals controls.append(INT) except: pass self.tabs = Tab() self.tabs.children = [ VBox([VBox(controls), HBox(orig), ]), VBox([Label("Choose spectra to superimpose - first Select, then Copy to the numbered slot"), HBox([self.Chooser, self.bsel, self.to]), self.DataList[0].header] + [sp.me for sp in self.DataList] ) ] self.tabs.set_title(0, 'Spectrum') self.tabs.set_title(1, 'Superimpose') self.children = [self.tabs] if show: self.draw()
[docs] def copy(self, event): if self.to.value < 1 or self.to.value > len(self.DataList): jsalert('Destination is out of range !') else: entry = self.DataList[self.to.value-1] entry.filename.value = self.Chooser.selected entry.direct.value = 'up' for w in entry.me.children: w.observe(self.ob) self.to.value = min(self.to.value, len(self.DataList)) + 1
[docs] def on_done(self, e): self.close() self.disp() display(self.fig)
[docs] def draw(self, zoom=False): "refresh display - if zoom is True, display only in xbound" super().draw() self.xb = self.ax.get_xbound() if zoom: zoom = self.xb else: zoom = None # self.data.display(new_fig=False, figure=self.ax, color=self.spcolor.value, # title=self.sptitle.value, linewidth=self.splw.value, zoom=zoom) self.drspectrum[0].set_color(self.spcolor.value) self.drspectrum[0].set_linewidth(self.splw.value) self.ax.set_title(self.sptitle.value) if self.integ.value: if hasattr(self.data,'integrals') and self.data.integrals != []: if self.labely.value == -1: labely = None else: labely = self.labely.value self.data.display_integral(label=True, integscale=self.scaleint.value, integoff=self.offset.value, labelxposition=self.labelx.value, labelyposition=labely, curvedict={ 'color': self.intcolor.value, 'linewidth': self.intlw.value}, labeldict={ 'color': self.intlabelcolor.value, 'fontsize': self.labelfont.value, 'rotation': self.labelrot.value}, figure=self.ax, zoom=zoom) else: # print('no or wrong integrals (have you clicked on "Done" in the Integration tool ?)') pass if self.peaks.value: if hasattr(self.data,'peaks') and self.data.peaks != []: self.data.display_peaks(peak_label=self.pkvalues.value, color=self.pkcolor.value, markersize=self.pkfont.value, NbMaxPeaks=self.NbMaxPeaks, markerdict={ 'marker': self.marker.value}, labeldict={'rotation': self.pkrotation.value, 'fontsize': self.pkfont.value}, figure=self.ax, scale=1.0, zoom=zoom) else: # print('no or wrong peaklist (have you clicked on "Done" in the Peak-Picker tool ?)') pass # superimposition for s in self.DataList: s.draw() self.ax.set_xbound(self.xb) # def disp(self, zoom=False): # self.xb = self.ax.get_xbound() # if zoom: # zoom = self.xb # else: # zoom = None super().disp() for s in self.DataList: s.disp()
[docs]class Phaser1D(Show1D): """ An interactive phaser in 1D NMR Phaser1D(spectrum, ...) """ def __init__(self, data, figsize=None, title=None, maxfirstorder=360, show=True, warning=True, create_children=True, yratio=10): data.check1D() if data.itype == 0: if warning: jsalert('Data is Real - the Imaginary part is being reconstructed') data.real2cpx() # we'll work on a copy of the data super().__init__(data, figsize=figsize, title=title, show=False, create_children=create_children, yratio=yratio) self.ydata = data.get_buffer() # store (complex) buffer # change done button and create an Apply one self.done.description = 'Apply' self.p0 = widgets.FloatSlider(description='P0:', min=-200, max=200, step=0.1, layout=Layout(width='100%'), continuous_update=REACTIVE) self.p1 = widgets.FloatSlider(description='P1:', min=-maxfirstorder, max=maxfirstorder, step=1.0, layout=Layout(width='100%'), continuous_update=REACTIVE) # self.pivot = widgets.FloatSlider(description='pivot:', # min=0.0, max=self.data.size1, # step=1, layout=Layout(width='80%'), # value=0.5*self.data.size1, readout=False, continuous_update=REACTIVE) pivl = self.data.axis1.itoc(self.data.size1) pivr = self.data.axis1.itoc(0) self.pivot = widgets.BoundedFloatText(description='Pivot', value=round(self.data.axis1.itoc( 0.5*self.data.size1), 2), min=min(pivr, pivl), max=max(pivr, pivl), format='%.3f', step=0.1, layout=Layout(width='20%')) self.cancel = widgets.Button( description="Exit", button_style='warning', tooltip='Exit without corrections') # draw HBox if create_children: orig = self.children self.children = [VBox([ HBox([self.cancel, self.pivot, widgets.HTML( '<i>set with right-click on spectrum</i>')]), self.p0, self.p1, HBox(orig)])] # add interaction self.cancel.on_click(self.on_cancel) self.done.on_click(self.on_Apply) for w in [self.p0, self.p1]: w.observe(self.ob) self.pivot.observe(self.on_movepivot) # add click event on spectral window def on_press(event): if event.button == 3: # right-click v = event.xdata self.pivot.value = round(v, 4) self.disp() self.cids_press = self.fig.canvas.mpl_connect('button_press_event', on_press) # finalize self.lp0, self.lp1 = self.ppivot() if show: self.draw()
[docs] def disconnect_press(self): "should be called before close()" self.fig.canvas.mpl_disconnect(self.cids_press)
[docs] def on_cancel(self, b): self.disconnect_press() self.close() print("no applied phase correction")
[docs] def on_Apply(self, b): self.drpivot.set_visible(False) lp0, lp1 = self.ppivot() # get centered values self.data.phase(lp0, lp1) self.disconnect_press() self.close() # display(self.fig) # would show twice - no idea why ... print("Applied: data.phase(%.1f, %.1f)" % (lp0, lp1))
[docs] def ppivot(self): "converts from pivot values to centered ones" pp = 1.0-(self.data.axis1.ctoi(self.pivot.value)/self.data.size1) return (self.p0.value + (pp-0.5)*self.p1.value, self.p1.value)
[docs] def ctopivot(self, p0, p1): "convert from centered to pivot values" pp = 1.0-(self.data.axis1.ctoi(self.pivot.value)/self.data.size1) p0p = p0 - (pp-0.5)*p1 while p0p > 180: p0p -= 360 while p0p < -180: p0p += 360 return p0p, p1
[docs] def on_movepivot(self, event): if event['name'] == 'value': if self.p1.value != 0: # in that case values do not change self.p0.value, self.p1.value = self.ctopivot( self.lp0, self.lp1) else: self.disp()
[docs] @debounce(1.0) def rescale_p1(self): "shifts by +/- 180° if P1 hits the border" if self.p1.value == self.p1.max: self.p1.max += 180 self.p1.min += 180 if self.p1.value == self.p1.min: self.p1.max -= 180 self.p1.min -= 180
[docs] def ob(self, event): "observe changes and start phasing" if event['name'] == 'value': self.rescale_p1() self.phase_n_disp()
[docs] def draw(self): "copied from super() as it does not display the spectrum !" # self.ax.clear() # self.drspectrum = self.ax.plot(self.data.axis1.unit_axis()[::2] , self.ydata.real, lw=1 )[0] super().draw() self.phase() ppos = self.pivot.value self.drpivot = self.ax.axvline(ppos, **Cursor)
[docs] def phase(self): self.lp0, self.lp1 = self.ppivot() # get centered values size = len(self.ydata) # compute correction in e if self.lp0 == 0: # we can optimize a little e = np.exp(1J*m.radians(self.lp0)) * \ np.ones(size, dtype=complex) # e = exp(j ph0) else: le = m.radians(self.lp0) + (m.radians(self.lp1)) * \ np.linspace(-0.5, 0.5, size) e = np.cos(le) + 1J*np.sin(le) # then apply y = (e*self.ydata).real self.drspectrum[0].set_ydata(y) # multiply and keep only real values
[docs] def phase_n_disp(self): "apply phase and disp" self.phase() self.disp()
[docs] def disp(self): "update display and display pivot" super().disp() ppos = self.pivot.value self.drpivot.set_xdata([ppos, ppos]) self.drpivot.set_ydata(self.ax.get_ybound())
[docs]class AvProc1D: "Detailed 1D NMR Processing" def __init__(self, filename=""): print('WARNING this tool is not functional/tested yet') self.wfile = widgets.Text( description='File to process', layout=Layout(width='80%'), value=filename) self.wapod = widgets.Dropdown( options=['None', 'apod_sin (sine bell)', 'apod_em (Exponential)', 'apod_gm (Gaussian)', 'gaussenh (Gaussian Enhacement)', 'kaiser'], value='apod_sin (sine bell)', description='Apodisation') self.wpapod_Hz = widgets.FloatText( value=1.0, min=0, # max exponent of base max=30, # min exponent of base description='Width in Hz', layout=Layout(width='15%'), disabled=True) self.wpapod_enh = widgets.FloatText( value=2.0, min=0.0, # max exponent of base max=5.0, # min exponent of base description='strength', layout=Layout(width='15%'), step=1, disabled=True) self.wpapod_sin = widgets.FloatText( value=0.0, min=0, # max exponent of base max=0.5, # min exponent of base description='bell shape', layout=Layout(width='15%'), step=0.01, tooltip='value is the maximum of the bell, 0 is pure cosine, 0.5 is pure sine', disabled=False) self.wzf = widgets.Dropdown( options=[0, 1, 2, 4, 8], value=1, description='Zero-Filling') self.wphase0 = widgets.FloatText( value=0, description='Phase : P0', layout=Layout(width='20%'), disabled=True) self.wphase1 = widgets.FloatText( value=0, description='P1', layout=Layout(width='20%'), disabled=True) self.wapmin = widgets.Checkbox( value=True, description='AutoPhasing', tooltip='Perform AutoPhasing') self.wapmin.observe(self.apmin_select) self.wbcorr = widgets.Checkbox( value=False, description='Baseline Correction', tooltip='Perform Baseline Correction') self.wapod.observe(self.apod_select) self.bapod = widgets.Button(description='Show effect on FID') self.bapod.on_click(self.show_apod) self.bdoit = widgets.Button(description='Process') self.bdoit.on_click(self.process) self.show() fi, ax = plt.subplots() self.ax = ax if os.path.exists(filename): self.load() # self.data.set_unit('sec') self.display()
[docs] def apod_select(self, e): test = self.wapod.value.split()[0] self.wpapod_sin.disabled = True self.wpapod_Hz.disabled = True self.wpapod_enh.disabled = True if test == "apod_sin": self.wpapod_sin.disabled = False if test in ('apod_em', 'apod_gm', 'gaussenh'): self.wpapod_Hz.disabled = False if test == 'gaussenh': self.wpapod_enh.disabled = False
[docs] def apmin_select(self, e): for w in self.wphase0, self.wphase1: w.disabled = self.wapmin.value
[docs] def load(self): self.data = Import_1D(self.wfile.value)
[docs] def apod(self): func = self.wapod.value.split()[0] todo = None if func == 'apod_sin': todo = 'self.data.apod_sin(%f)' % (self.wpapod_sin.value,) elif func in ('apod_em', 'apod_gm'): todo = 'self.data.%s(%f)' % (func, self.wpapod_Hz.value) elif func == 'gaussenh': todo = 'self.data.gaussenh(%f,enhancement=%f)' % ( self.wpapod_Hz.value, self.wpapod_enh.value) if todo is not None: eval(todo) return self.data
[docs] def show_apod(self, e): self.load() self.apod() self.display()
[docs] def process(self, e): self.load() self.apod().zf(self.wzf.value).ft_sim().bk_corr().set_unit('ppm') if self.wapmin.value: self.data.apmin() self.wphase0.value = round(self.data.axis1.P0, 1) self.wphase1.value = self.data.axis1.P1 else: self.data.phase(self.wphase0.value, self.wphase1.value) self.display()
[docs] def display(self): self.ax.clear() self.data.display(figure=self.ax)
[docs] def show(self): display( VBox([self.wfile, HBox([self.wapod, self.wpapod_sin, self.wpapod_Hz, self.wpapod_enh, self.bapod]), self.wzf, HBox([self.wapmin, self.wphase0, self.wphase1]), # self.wbcorr, self.bdoit]))
[docs]class NMRPeaker1D(Show1D): """ a peak-picker for NMR experiments """ # self.peaks : the defined peaklist, copyied in and out of data # self.temppk : the last computed pklist def __init__(self, data, figsize=None, show=True): self.data = data.real() try: if self.data.peaks is None: # might happen self.peaks = Peaks.Peak1DList( source=self.data) # empty peak list else: self.peaks = self.data.peaks except AttributeError: self.peaks = Peaks.Peak1DList(source=self.data) # empty peak list self.temppk = Peaks.Peak1DList(source=self.data) # empty peak list self.thresh = widgets.FloatLogSlider(value=20.0, min=-1, max=2.0, base=10, step=0.01, layout=Layout(width='30%'), continuous_update=True, readout=True, readout_format='.2f') try: self.thresh.value = 100*self.data.peaks.threshold / \ self.data.absmax # if already peak picked except: self.thresh.value = 20.0 self.thresh.observe(self.pickpeak) self.peak_mode = widgets.Dropdown( options=['marker', 'bar'], value='marker', description='show as') self.peak_mode.observe(self.ob) self.out = Output(layout={'border': '1px solid red'}) # self.done = widgets.Button(description="Done", button_style='success') # self.done.on_click(self.on_done) self.badd = widgets.Button( description="Add", button_style='success', layout=space('80px')) self.badd.on_click(self.on_add) self.brem = widgets.Button( description="Rem", button_style='warning', layout=space('80px')) self.brem.on_click(self.on_rem) self.cancel = widgets.Button( description="Exit", button_style='warning', layout=space('80px'), tooltip='Exit without corrections') self.cancel.on_click(self.on_cancel) self.selval = widgets.FloatText( value=0.0, description='', layout=Layout(width='10%'), step=0.001, disabled=True) self.newval = widgets.FloatText( value=0.0, description='calibration', layout=Layout(width='20%'), step=0.001, disabled=True) self.setcalib = widgets.Button(description="Set", layout=Layout(width='10%'), button_style='success', tooltip='Set spectrum calibration') self.setcalib.on_click(self.on_setcalib) super().__init__(data, figsize=figsize, show=False) def on_press(event): if event.button == 3: # right-click v = event.xdata # store position in index (peaks are internally in index) iv = self.data.axis1.ptoi(v) distclose = np.inf # search closest peak pclose = 0.0 for p in self.data.peaks: if abs(p.pos-iv) < distclose: pclose = p.pos distclose = abs(p.pos-iv) self.selval.value = self.data.axis1.itop(pclose) # back to ppm for w in (self.selval, self.newval): w.disabled = False self.cids_press = self.fig.canvas.mpl_connect('button_press_event', on_press) # redefine Box orig = self.children self.tabs = Tab() self.tabs.children = [ VBox([ HBox([Label('threshold - % largest signal'), self.thresh, self.badd, self.brem, self.peak_mode]), HBox( [VBox([self.blank, self.reset, self.scale, self.done]), self.fig.canvas]) ]), VBox([ HBox([Label('Select a peak with mouse (or value) and set calibrated values',layout=Layout(width="40%")), Label('selection (in ppm)'),self.selval, self.newval, self.setcalib]), HBox( [VBox([self.blank, self.reset, self.scale, self.done]), self.fig.canvas]) ]), self.out] self.tabs.set_title(0, 'Peak Picker') self.tabs.set_title(1, 'calibration') self.tabs.set_title(2, 'Peak Table') self.children = [VBox([HBox([self.cancel]), self.tabs])] self.pp() self.draw()
[docs] def on_add(self, b): self.peaks.pkadd(self.temppk) self.peaks = Peaks.peak_aggreg(self.peaks, distance=1.0) self.peaks.source = self.data self.temppk = Peaks.Peak1DList(source=self.data) self.draw()
[docs] def on_rem(self, b): (up, down) = self.ax.get_xbound() iup = self.data.axis1.ptoi(up) idown = self.data.axis1.ptoi(down) iup, idown = (max(iup, idown), min(iup, idown)) to_rem = [] for pk in self.peaks: if pk.pos < iup and pk.pos > idown: to_rem.append(pk) for pk in to_rem: self.peaks.remove(pk) self.draw()
[docs] def on_cancel(self, b): self.close() del self.data.peaks print("no Peak-Picking done")
[docs] def on_reset(self, b=None): self.thresh.value = 20.0 super().on_reset()
[docs] def on_done(self, b): self.temppk = Peaks.Peak1DList() # clear temp peaks self.close() # new figure self.draw() # and display display(self.fig) display(self.out) self.data.peaks = self.peaks # and copy
[docs] def on_setcalib(self, e): off = self.selval.value-self.newval.value # off is in ppm, axis1.offset is in Hz self.data.axis1.offset -= off*self.data.axis1.frequency self.selval.value = self.newval.value # self.pp() self.peaks.pos2label() self.temppk.pos2label() self.draw()
[docs] def pkprint(self, event): self.out.clear_output(wait=True) with self.out: if len(self.temppk) > 0: display(HTML("<p style=color:red> Transient peak list </p>")) self.data.peaks = self.temppk display(HTML(self.data.pk2pandas().to_html())) if len(self.peaks) > 0: display(HTML("<p style=color:blue> Defined peak list </p>")) self.data.peaks = self.peaks display(HTML(self.data.pk2pandas().to_html())) else: display(HTML("<p style=color:blue> Defined peak list Empty</p>"))
def _pkprint(self, event): self.out.clear_output(wait=True) with self.out: print(self.pklist())
[docs] def pklist(self): "creates peaklist for printing or exporting" text = ["ppm\tInt.(%)\twidth (Hz)"] data = self.data intmax = max(data.peaks.intens)/100 for pk in data.peaks: ppm = data.axis1.itop(pk.pos) width = 2*pk.width*data.axis1.specwidth/data.size1 l = "%.3f\t%.1f\t%.2f" % (ppm, pk.intens/intmax, width) text.append(l) return "\n".join(text)
[docs] def ob(self, event): if event['name'] == 'value': self.disp()
[docs] def pickpeak(self, event): "interactive wrapper to pp" if event['name'] == 'value': self.disp() self.pp()
[docs] @debounce(1.0) def pp(self): "do the peak-picking calling pp().centroid() within the current zoom" th = self.data.absmax*self.thresh.value/100 zm = self.ax.get_xbound() self.data.set_unit('ppm').peakpick( threshold=th, verbose=False, zoom=zm).centroid() self.temppk = self.data.peaks self.data.peaks = None self.draw() self.ax.annotate('%d peaks detected' % len( self.data.peaks), (0.05, 0.95), xycoords='figure fraction') self.pkprint({'name': 'value'})
[docs] def draw(self): "interactive wrapper to peakpick" super().draw() self.xb = self.ax.get_xbound() x = [self.data.axis1.itoc(z) for z in (0, self.data.size1)] y = [self.data.absmax*self.thresh.value/100]*2 self.threshline = self.ax.plot(x, y, ':r')[0] if True: # try: self.temppk.display(peak_label=False, peak_mode=self.peak_mode.value, f=self.data.axis1.itoc, figure=self.ax, color='red') self.peaks.display(peak_label=False, peak_mode=self.peak_mode.value, f=self.data.axis1.itoc, color='blue', figure=self.ax) else: # except: rrr("problem") self.temppk.display( peak_label=True, peak_mode=self.peak_mode.value, color='red', figure=self.ax) self.ax.set_xbound(self.xb) # self.ax.set_ylim(ymax=self.data.absmax/self.scale.value) # send pseudo event to display peak table self.pkprint({'name': 'value'}) self.disp()
[docs] def disp(self): y = [self.data.absmax*self.thresh.value/100]*2 self.threshline.set_ydata(y) super().disp()
[docs]class NMRIntegrate(Show1D): "an integrator for NMR experiments" def __init__(self, data, figsize=None, show=True): try: self.Integ = data.integrals except: # initialize with empty list self.Integ = Integrals(data, compute=False) try: self.peaks_reserved = data.peaks except AttributeError: #print('no peaks') self.peaks_reserved = None self.idisp = 0 self.idraw = 0 super().__init__(data, figsize=figsize, show=show) self.thresh = widgets.FloatLogSlider(description='sensitivity', value=10.0, min=-1, max=2.0, base=10, step=0.01, layout=Layout(width='30%'), continuous_update=HEAVY, readout=True, readout_format='.1f', tooltip='sensitivity to weak signals') self.bias = widgets.FloatSlider( description='bias', layout=Layout(width='20%'), value=0.0, min=-10.0, max=10.0, step=0.1, continuous_update=HEAVY, readout=True, readout_format='.1f') self.sep = widgets.FloatSlider( description='separation', layout=Layout(width='30%'), value=3.0, min=0.0, max=20.0, step=0.1, continuous_update=HEAVY, readout=True, readout_format='.1f') self.wings = widgets.FloatSlider( description='extension', layout=Layout(width='30%'), value=5.0, min=0.5, max=20.0, step=0.1, continuous_update=HEAVY, readout=True, readout_format='.1f') for w in (self.bias, self.sep, self.wings): w.observe(self.integrate) self.thresh.observe(self.peak_and_integrate) self.cancel = widgets.Button( description="Exit", button_style='warning', layout=self.blay, tooltip='Exit without corrections') self.cancel.on_click(self.on_cancel) self.badd = widgets.Button(description="Add", layout=self.blay, button_style='success', tooltip='Add an entry from current zoom') self.badd.on_click(self.on_add) self.brem = widgets.Button(description="Rem", layout=self.blay, button_style='warning', tooltip='Remove all entries in current zoom') self.brem.on_click(self.on_rem) self.bauto = widgets.Button(description="Compute", layout=self.blay, button_style='success', tooltip='Automatic definition of integrals') self.bauto.on_click(self.peak_and_integrate) self.entry = widgets.IntText(value=0, description='Entry', min=0, layout=Layout(width='25%'), tooltip='Index of the calibrating integral') self.value = widgets.FloatText(value=100, description='Value', layout=Layout(width='25%'), tooltip='Value of the calibrating integral') self.set = widgets.Button( description="Set", button_style='success', layout=Layout(width='10%')) self.set.on_click(self.set_value) self.out = Output(layout={'border': '1px solid red'}) # redefine children orig = self.children self.tabs = Tab() self.tabs.children = [ VBox([ HBox([widgets.HTML("Use buttons to add and remove integrals in the current zoom window&nbsp;&nbsp;"), self.badd, self.brem]), HBox(orig), ]), VBox([HBox([widgets.HTML("Define integral shapes using the sliders below (erases the current integrals)&nbsp;&nbsp;"), self.bauto]), HBox([self.thresh, self.sep, self.wings]), HBox(orig), ]), VBox([HBox([Label('Choose an integral for calibration'), self.entry, self.value, self.set]), self.out]) ] self.tabs.set_title(0, 'Manual integration') self.tabs.set_title(1, 'Automatic') self.tabs.set_title(2, 'Integral Table & Calibration') self.children = [VBox([self.cancel, self.tabs])] self.draw() self.print(None)
[docs] def on_cancel(self, b): self.close() print("No integration")
[docs] def on_done(self, b): self.close() self.draw() display(self.fig) display(self.out) self.data.integrals = self.Integ # copy integrals if self.peaks_reserved: # restore peaks self.data.peaks = self.peaks_reserved else: try: del(self.data.peaks) except AttributeError: # if no peaks pass
[docs] def on_add(self, b): start, end = self.ax.get_xbound() self.Integ.append(Integralitem(self.data.axis1.ptoi( start), self.data.axis1.ptoi(end), [], 0.0)) self.Integ.zonestocurves() self.draw() self.print(None)
[docs] def on_rem(self, b): start, end = self.ax.get_xbound() start, end = self.data.axis1.ptoi(start), self.data.axis1.ptoi(end) # -1,+1 needed sometimes... start, end = (min(start, end)-1, max(start, end)+1) to_rem = [] for ii in self.Integ: if ii.start > start and ii.end < end: to_rem.append(ii) for ii in to_rem: self.Integ.remove(ii) self.draw() self.print(None)
[docs] def set_value(self, b): self.Integ.recalibrate(self.entry.value, self.value.value) self.draw() self.print(None)
[docs] def print(self, event): self.out.clear_output() self.Integ.sort(key=lambda x: x.start) # sort integrals with self.out: display(HTML(self.Integ.to_pandas().to_html()))
[docs] def peak_and_integrate(self, event): self.data.pp(threshold=self.data.absmax*self.thresh.value/100, verbose=False, zoom=self.ax.get_xbound()).centroid() if len(self.data.peaks) > 0: self.int()
[docs] def integrate(self, event): #"integrate from event" # if event['name']=='value': self.int()
[docs] def int(self): "do the automatic integration from peaks and parameters" self.on_rem(None) try: calib = self.Integ.calibration except: calib = None self.data.set_unit('ppm') try: self.data.peaks except: self.data.pp(threshold=self.data.absmax*self.thresh.value/100, verbose=False, zoom=self.ax.get_xbound()).centroid() # appending to the list, self and Integrals are equivalent to list - self.Integ += Integrals(self.data, separation=self.sep.value, wings=self.wings.value, bias=self.data.absmax*self.bias.value/100) self.Integ.calibrate(calibration=calib) self.print(None) self.draw()
[docs] def ob(self, event): if event['name'] == 'value': self.draw()
[docs] def disp(self): self.ax.set_ybound(self.yb0/self.scale.value) self.idisp += 1
[docs] def draw(self): "refresh display from event - if zoom is True, display only in xbound" # self.yb = self.ax.get_ybound() super().draw() self.idraw += 1 self.Integ.display(label=True, figure=self.ax, labelyposition=None, regions=False, zoom=None) self.ax.set_xbound(self.xb)
# self.ax.set_ybound(self.yb) # if __name__ == '__main__': # unittest.main()