#!/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
[docs]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)
[docs] 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)
[docs]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()
[docs] 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)
[docs]def sidedisplay(dt1d, ax):
step = dt1d.axis1.itype+1
dataxis = dt1d.axis1.itoc( dt1d.axis1.points_axis() )
ax.plot(dt1d.buffer[::step],dataxis[::step])
[docs]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, 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
[docs] 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)
[docs] 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
[docs] 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()
[docs] 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()
[docs] 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()
[docs] 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))
[docs] 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))
[docs] 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))
[docs] 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()