class Plot: WIDTH = 800 HEIGHT = 600 def __init__(self, shifts, multiplicities, deviations): self.shifts = shifts self.multiplicities = multiplicities self.deviations = deviations xr = Range1d(start=220, end=-20) self.plot = figure(x_axis_label="ppm", x_range=xr, tools="save,reset", plot_width=self.WIDTH, plot_height=self.HEIGHT) self.lineSource = ColumnDataSource(data=dict(x=[0], y=[0])) self.plot.line('x', 'y', source=self.lineSource, line_width=2) # Remove grid from plot self.plot.xgrid.grid_line_color = None self.plot.ygrid.grid_line_color = None # Remove bokeh logo self.plot.toolbar.logo = None horizontalBoxZoomTool = HorizontalBoxZoomTool() self.plot.add_tools(horizontalBoxZoomTool) fixedZoomOutTool = FixedZoomOutTool(factor=0.4) self.plot.add_tools(fixedZoomOutTool) self.plot.extra_y_ranges['box'] = Range1d(start=0, end=0.1) self.selectionSource = ColumnDataSource(data=dict(left=[], right=[]), callback=CustomJS(code=""" if ('data' in this.selected) { window.top.location.href = "http://localhost:8080?" + this.selected.data.query + "style=plot"; } """)) self.selectionSource.on_change('selected', lambda attr, old, new: self.delete(new['1d']['indices'])) rect = HBar( left='left', right='right', y=0.5, height=1, line_alpha=0.2, fill_alpha=0.2, line_color="red", fill_color="red" ) renderer = self.plot.add_glyph(self.selectionSource, rect) renderer.y_range_name = "box" self.labelSource = ColumnDataSource(data=dict(x=[], y=[], text=[])) label = Text( x='x', y='y', text='text', text_align='center', text_font_size="10pt" ) renderer = self.plot.add_glyph(self.labelSource, label) renderer.y_range_name = "box" removeTool = CustomTapTool() self.plot.add_tools(removeTool) self.plot.toolbar.active_tap = None callback = CustomJS(args=dict(), code=""" /// get BoxSelectTool dimensions from cb_data parameter of Callback var geometry = cb_data['geometry']; var shift = (geometry['x0'] + geometry['x1']) / 2; var deviation = Math.abs(geometry['x1'] - shift); var query = cb_data['query'] + "shift%5B%5D=" + shift + '&multiplicity%5B%5D=any&deviation%5B%5D=' + deviation + '&style=plot'; window.top.location.href = "http://localhost:8080?" + query; """) selectTool = CustomBoxSelectTool( tool_name="Select Area", dimensions = "width", callback = callback, query=self.paramsToQuery() ) self.plot.add_tools(selectTool) self.initSelect() self.drawButton = CustomButton(id='myButton') self.drawButton.on_click(self.drawPlot) curdoc().add_root( row( column(row(self.plot)), column(CustomRow(column(self.drawButton), hide=True)) ) ) def paramsToQuery(self): query = "" for (shift, multiplicity, deviation) in zip(self.shifts, self.multiplicities, self.deviations): query += "shift%5B%5D={}&multiplicity%5B%5D={}&deviation%5B%5D={}&".format(shift, multiplicity, deviation) return query def initSelect(self): left, right = [], [] labelX, labelY, labelText = [], [], [] for (shift, multiplicity, deviation) in zip(self.shifts, self.multiplicities, self.deviations): left.append(shift - deviation) right.append(shift + deviation) labelX.append(shift) labelY.append(0.08) labelText.append(multiplicity) self.selectionSource.stream({ 'left': left, 'right': right }) self.labelSource.stream({ 'x': labelX, 'y': labelY, 'text': labelText }) def drawPlot(self, data): dic, _ = ng.bruker.read("../data/{}".format(data['id'])) _, pdata = ng.bruker.read_pdata("../data/{}/pdata/1/".format(data['id'])) udic = ng.bruker.guess_udic(dic, pdata) uc = ng.fileiobase.uc_from_udic(udic) ppmScale = uc.ppm_scale() self.plot.y_range = None self.lineSource.data = { 'x': ppmScale, 'y': pdata } def delete(self, ids): if ids: left = list(self.selectionSource.data['left']) right = list(self.selectionSource.data['right']) for i in ids: left.pop(i) right.pop(i) self.shifts.pop(i) self.multiplicities.pop(i) self.deviations.pop(i) self.selectionSource.data = { 'left': left, 'right': right } # Deselect all self.selectionSource.selected = { '0d': {'glyph': None, 'indices': []}, '1d': {'indices': []}, '2d': {'indices': {}}, 'data': {'query': self.paramsToQuery()} } def selectArea(self, dimensions): patch = { 'left': [dimensions['x0']], 'right': [dimensions['x1']] } self.selectionSource.stream(patch)
class Integration(Observer): def __init__(self, logger, spectrumId, pdata, dataSource, reference): Observer.__init__(self, logger) self.logger = logger self.id = spectrumId self.pdata = pdata self.dataSource = dataSource reference.addObserver(lambda n: referenceObserver(self, n)) self.sources = dict() self.sources['integration'] = ColumnDataSource( data=dict(x=[], y=[], width=[], height=[])) self.initIntegral = None def create(self): self.sources['table'] = ColumnDataSource( dict(xStart=[], xStop=[], top=[], bottom=[], integral=[])) columns = [ TableColumn(field="xStart", title="start", editor=NumberEditor(step=0.01), formatter=NumberFormatter(format="0.00")), TableColumn(field="xStop", title="stop", editor=NumberEditor(step=0.01), formatter=NumberFormatter(format="0.00")), TableColumn(field="integral", title="integral", editor=NumberEditor(step=0.01), formatter=NumberFormatter(format="0.00")) ] self.dataTable = DataTable(source=self.sources['table'], columns=columns, reorderable=False, width=500, editable=True) self.sources['table'].on_change( 'selected', lambda attr, old, new: self.rowSelect(new['1d']['indices'])) self.sources['table'].on_change( 'data', lambda attr, old, new: self.changeData(old, new)) self.manual = CustomButton( label="Manual Integration", button_type="primary", width=250, error="Please select area using the integration tool.") self.manual.on_click(self.manualIntegration) self.createDeselectButton() self.createDeleteButton() callback = CustomJS(args=dict(button=self.manual), code=""" /// get BoxSelectTool dimensions from cb_data parameter of Callback var geometry = cb_data['geometry']; button.data = { x0: geometry['x0'], x1: geometry['x1'], y0: geometry['y0'], y1: geometry['y1'] }; // Callback to the backend button.clicks++; """) self.tool = CustomBoxSelectTool(tool_name="Integration", icon="my_icon_integration", dimensions="width", callback=callback, id="integrationTool") def changeData(self, old, new): self.checkIntegral(old, new) self.checkRange(old, new, 'xStart') self.checkRange(old, new, 'xStop') def checkIntegral(self, old, new): diff = 1 if len(old['integral']) == len(new['integral']): for o, n in zip(old['integral'], new['integral']): if o != n: diff = n / o break if diff != 1: self.updateIntervals(diff, old) self.notifyObservers(diff) def updateIntervals(self, ratio, old): self.initIntegral /= ratio patch = { 'integral': [(i, old['integral'][i] * ratio) for i in xrange(len(old['integral']))] } self.sources['table'].patch(patch) def checkRange(self, old, new, key): if len(old[key]) == len(new[key]): for pos, o, n in zip(xrange(len(old[key])), old[key], new[key]): if o != n: points = [ point for (point, i) in zip(self.dataSource.data['data'], self.dataSource.data['ppm']) if i <= new['xStart'][pos] and i >= new['xStop'][pos] ] integral = np.trapz(points, axis=0) ratio = integral / self.initIntegral patch = {'integral': [(pos, ratio)]} self.sources['table'].patch(patch) self.rowSelect([pos]) def manualIntegration(self, dimensions): integral = self.calcIntegral(dimensions) # Update DataTable Values data = { 'xStart': [dimensions['x0']], 'xStop': [dimensions['x1']], 'top': [dimensions['y1']], 'bottom': [dimensions['y0']], 'integral': [integral] } self.sources['table'].stream(data) def calcIntegral(self, dimensions): points = [ point for (point, pos) in zip(self.dataSource.data['data'], self.dataSource.data['ppm']) if pos <= dimensions['x0'] and pos >= dimensions['x1'] ] integral = np.trapz(points, axis=0) ratio = 1.0 if self.initIntegral is None: self.initIntegral = integral else: ratio = integral / self.initIntegral return ratio def rowSelect(self, ids): maxBottom = min( max(self.sources['table'].data['bottom']) if self.sources['table'].data['bottom'] else 0, 0) minTop = max( min(self.sources['table'].data['top']) if self.sources['table'].data['top'] else 0, self.pdata.max()) tempHeight = minTop - maxBottom x, y, width, height = [], [], [], [] for i in ids: sx0 = self.sources['table'].data['xStart'][i] sx1 = self.sources['table'].data['xStop'][i] tempWidth = sx1 - sx0 x.append(sx0 + tempWidth / 2) y.append(maxBottom + tempHeight / 2) width.append(tempWidth) height.append(tempHeight) self.sources['integration'].data = { 'x': x, 'y': y, 'width': width, 'height': height } def createDeselectButton(self): self.deselectButton = Button(label="Deselect all integrals", button_type="default", width=250) self.deselectButton.on_click( lambda: deselectRows(self.sources['table'])) def createDeleteButton(self): self.deleteButton = Button(label="Delete selected integrals", button_type="danger", width=250) self.deleteButton.on_click(self.deleteIntegrals) def deleteIntegrals(self): xStart = list(self.sources['table'].data['xStart']) xStop = list(self.sources['table'].data['xStop']) top = list(self.sources['table'].data['top']) bottom = list(self.sources['table'].data['bottom']) integral = list(self.sources['table'].data['integral']) ids = self.sources['table'].selected['1d']['indices'] for i in sorted(ids, reverse=True): try: xStart.pop(i) xStop.pop(i) top.pop(i) bottom.pop(i) integral.pop(i) except IndexError: pass # Update DataTable Values self.sources['table'].data = { 'xStart': xStart, 'xStop': xStop, 'top': top, 'bottom': bottom, 'integral': integral } deselectRows(self.sources['table']) def draw(self, plot): rect = Rect(x="x", y="y", width='width', height='height', fill_alpha=0.3, fill_color="#de5eff") plot.add_glyph(self.sources['integration'], rect, selection_glyph=rect, nonselection_glyph=rect) plot.add_tools(self.tool)
class PeakPicking(Observer): def __init__(self, logger, spectrumId, dic, udic, pdata, dataSource, reference): Observer.__init__(self, logger) self.logger = logger self.id = spectrumId self.dic = dic self.udic = udic self.pdata = pdata self.mpdata = np.array(map(lambda x: -x, pdata)) self.dataSource = dataSource reference.addObserver(lambda n: referenceObserver(self, n)) self.sources = dict() self.sources['peaks'] = ColumnDataSource(data=dict(x=[], y=[])) def create(self): self.sources['table'] = ColumnDataSource(dict(x=[], y=[])) self.sources['background'] = ColumnDataSource(dict(x=[], y=[])) columns = [ TableColumn(field="x", title="ppm", formatter=NumberFormatter(format="0.00")), TableColumn(field="y", title="y", formatter=NumberFormatter(format="0.00")) ] self.dataTable = DataTable(source=self.sources['table'], columns=columns, reorderable=False, width=500) self.sources['table'].on_change('selected', lambda attr, old, new: self.rowSelect(new['1d']['indices'])) self.sources['table'].on_change('data', lambda attr, old, new: self.dataChanged(old, new)) self.manual = CustomButton(label="Manual Peaks", button_type="success", width=500, error="Please select area using the peak picking tool.") self.manual.on_click(self.manualPeakPicking) self.peak = CustomButton(label="Peak By Peak", button_type="primary", width=250, error="Please select area using the peak by peak tool.") self.peak.on_click(self.peakByPeakPicking) self.peakTool = CustomTapTool.Create(self.peak, tapTool=PeakByPeakTapTool, auto=True, id="peakByPeakTool") self.createManualTool() self.createDeselectButton() self.createDeleteButton() self.chemicalShiftReportTitle = Div(text="<strong>Chemical Shift Report</strong>" if getLabel(self.udic) == "13C" else "") self.chemicalShiftReport = Paragraph(text=self.getChemicalShiftReport(), width=500) def createManualTool(self): callback = CustomJS(args=dict(button=self.manual), code=""" /// get BoxSelectTool dimensions from cb_data parameter of Callback var geometry = cb_data['geometry']; button.data = { x0: geometry['x0'], x1: geometry['x1'], y: geometry['y'] }; // Callback to the backend button.clicks++; """) self.manualTool = BothDimensionsSelectTool( tool_name = "Peak Picking By Threshold", icon = "my_icon_peak_picking", callback = callback, id = "peakPickingByThresholdTool" ) def dataChanged(self, old, new): label = getLabel(self.udic) if label == "13C": added = [(peak, 'm') for peak in (set(new['x']) - set(old['x']))] removed = [(peak, 'm') for peak in (set(old['x']) - set(new['x']))] SpectrumDB.RemovePeaks(self.id, removed) SpectrumDB.AddPeaks(self.id, added) # Update Chemical Shift Report self.updateChemicalShiftReport() def updateChemicalShiftReport(self): self.chemicalShiftReport.text = self.getChemicalShiftReport() def getChemicalShiftReport(self): label = getLabel(self.udic) if label == "13C": return getMetadata(self.dic, self.udic) + " δ " + ", ".join("{:0.2f}".format(x) for x in [round(x, 2) for x in self.sources['table'].data['x']]) + "." else: return "" def createDeselectButton(self): self.deselectButton = Button(label="Deselect all peaks", button_type="default", width=250) self.deselectButton.on_click(lambda: deselectRows(self.sources['table'])) def createDeleteButton(self): self.ids = [] self.deleteButton = Button(label="Delete selected peaks", button_type="danger", width=250) self.deleteButton.on_click(self.deletePeaks) def deletePeaks(self): self.sources['peaks'].data = dict(x=[], y=[]) newX = list(self.sources['table'].data['x']) newY = list(self.sources['table'].data['y']) ids = self.sources['table'].selected['1d']['indices'] for i in sorted(ids, reverse=True): try: newX.pop(i) newY.pop(i) except IndexError: pass self.sources['table'].data = { 'x': list(newX), 'y': list(newY) } self.sources['background'].data = { 'x': list(newX), 'y': list(newY) } deselectRows(self.sources['table']) self.notifyObservers() def manualPeakPicking(self, dimensions, notify=True): # Positive Peaks self.peaksIndices = list(self.manualPeakPickingOnData(self.pdata, dimensions)) # Negative Peaks self.peaksIndices.extend(self.manualPeakPickingOnData(self.mpdata, dimensions)) # Sort Peaks self.peaksIndices = sorted(self.peaksIndices, reverse=True) if len(self.peaksIndices) > 0: self.updateDataValues({ 'x': [self.dataSource.data['ppm'][i] for i in self.peaksIndices], 'y': [self.pdata[i] for i in self.peaksIndices] }) if notify: self.notifyObservers() def manualPeakPickingOnData(self, data, dimensions): threshold = abs(dimensions['y']) if data.max() < threshold: return [] peaks = ng.peakpick.pick(data, abs(dimensions['y']), algorithm="downward") peaksIndices = [int(peak[0]) for peak in peaks] # Filter left peaksIndices = [i for i in peaksIndices if self.dataSource.data['ppm'][i] <= dimensions['x0']] # Filter right peaksIndices = [i for i in peaksIndices if self.dataSource.data['ppm'][i] >= dimensions['x1']] return peaksIndices def peakByPeakPicking(self, dimensions): self.updateDataValues({ 'x': [dimensions['x']], 'y': [dimensions['y']] }) self.notifyObservers() def updateDataValues(self, data): # Update DataTable Values newData = list(OrderedDict.fromkeys( zip( self.sources['table'].data['x'] + data['x'], self.sources['table'].data['y'] + data['y'] ) )) newX, newY = zip(*sorted(newData, reverse=True)) self.sources['table'].data = { 'x': list(newX), 'y': list(newY) } self.sources['background'].data = { 'x': list(newX), 'y': list(newY) } def selectByPPM(self, peaks): self.sources['table'].selected = { '0d': {'glyph': None, 'indices': []}, '1d': {'indices': [self.sources['table'].data['x'].index(peak) for peak in peaks]}, '2d': {'indices': {}} } def rowSelect(self, ids): self.sources['peaks'].data = { 'x': [self.sources['table'].data['x'][i] for i in ids], 'y': [self.sources['table'].data['y'][i] for i in ids] } def getPeaksInSpace(self, start, stop): return [y for x, y in zip(self.sources['table'].data['x'], self.sources['table'].data['y']) if x <= start and x >= stop] def getPPMInSpace(self, start, stop): return [x for x in self.sources['table'].data['x'] if x <= start and x >= stop] def draw(self, plot): peak = Circle( x="x", y="y", size=10, line_color="#C0C0C0", fill_color="#C0C0C0", line_width=1 ) plot.add_glyph(self.sources['background'], peak, selection_glyph=peak, nonselection_glyph=peak) selected = Circle( x="x", y="y", size=10, line_color="#ff0000", fill_color="#ff0000", line_width=1 ) plot.add_glyph(self.sources['peaks'], selected, selection_glyph=selected, nonselection_glyph=selected) self.manualTool.addToPlot(plot) plot.add_tools(self.peakTool)
class MultipletAnalysis: MULTIPLET_ERROR = 1000000 MULTIPLETS = { 's': { 'table': [1], 'sum': 1, 'j': [] }, 'd': { 'table': [1, 1], 'sum': 2, 'j': [[0, 1]] }, 't': { 'table': [1, 2, 1], 'sum': 3, 'j': [[0, 1]] }, 'q': { 'table': [1, 3, 3, 1], 'sum': 4, 'j': [[0, 1]] }, 'p': { 'table': [1, 4, 6, 4, 1], 'sum': 5, 'j': [[0, 1]] }, 'h': { 'table': [1, 5, 10, 10, 5, 1], 'sum': 6, 'j': [[0, 1]] }, 'hept': { 'table': [1, 6, 15, 20, 15, 6, 1], 'sum': 7, 'j': [[0, 1]] }, 'dd': { 'table': [[1, 1], [1, 1]], 'sum': 4, 'j': [[0, 1], [0, 2]] }, 'ddd': { 'table': [[1, 1], [1, 1], [1, 1], [1, 1]], 'sum': 8, 'j': [[0, 1], [0, 2], [0, 4]] }, 'dt': { 'table': [[1, 2, 1], [1, 2, 1]], 'sum': 6, 'j': [[0, 1], [0, 3]] }, 'td': { 'table': [1, 1, 2, 2, 1, 1], 'sum': 6, 'j': [[0, 1], [0, 2]] }, 'ddt': { 'table': [[1, 2, 1], [1, 2, 1], [1, 2, 1], [1, 2, 1]], 'sum': 12, 'j': [[0, 1], [0, 3], [0, 6]] } } def __init__(self, logger, spectrumId, dic, udic, pdata, dataSource, peakPicking, integration, reference): self.logger = logger self.id = spectrumId self.dic = dic self.udic = udic self.pdata = pdata self.dataSource = dataSource self.peakPicking = peakPicking peakPicking.addObserver(self.recalculateAllMultipletsForPeaks) self.integration = integration self.integration.addObserver(lambda ratio: self.updateIntervals( ratio, self.sources['table'].data['integral'])) reference.addObserver(lambda n: referenceObserver(self, n)) self.sources = dict() def create(self): self.oldData = dict(peaks=[], classes=[]) self.sources['table'] = ColumnDataSource( dict(xStart=[], xStop=[], name=[], classes=[], j=[], h=[], integral=[], peaks=[], top=[], bottom=[])) columns = [ TableColumn(field="xStart", title="start", formatter=NumberFormatter(format="0.00")), TableColumn(field="xStop", title="stop", formatter=NumberFormatter(format="0.00")), TableColumn(field="name", title="Name"), TableColumn(field="classes", title="Class"), TableColumn(field="j", title="J"), TableColumn(field="h", title="H", formatter=NumberFormatter(format="0")), TableColumn(field="integral", title="Integral", formatter=NumberFormatter(format="0.00")) ] self.dataTable = DataTable(source=self.sources['table'], columns=columns, reorderable=False, width=500) self.sources['table'].on_change( 'selected', lambda attr, old, new: self.rowSelect(new['1d']['indices'])) self.sources['table'].on_change( 'data', lambda attr, old, new: self.dataChanged(new)) self.manual = CustomButton( label="Multiplet Analysis", button_type="primary", width=250, error="Please select area using the multiplet analysis tool.") self.manual.on_click(self.manualMultipletAnalysis) self.createTool() self.title = Div(text="<strong>Edit Multiplet:</strong>", width=500) self.classes = Select(title="Class:", options=[ "m", "s", "d", "t", "q", "p", "h", "hept", "dd", "ddd", "dt", "td", "ddt" ], width=100, disabled=True) self.classes.on_change( 'value', lambda attr, old, new: self.manualChange('classes', new)) self.integral = TextInput(title="Integral:", value="", placeholder="Integral", width=175, disabled=True) self.integral.on_change( 'value', lambda attr, old, new: self.changeIntegral(new)) self.j = TextInput(title='J-list:', value="", width=175, disabled=True) self.j.on_change('value', lambda attr, old, new: self.manualChange('j', new)) self.delete = Button(label="Delete Multiplet", button_type="danger", width=500, disabled=True) self.delete.on_click(self.deleteMultiplet) self.reportTitle = Div(text="<strong>Multiplet Report:</strong>") self.report = Paragraph(width=500) def createTool(self): callback = CustomJS(args=dict(button=self.manual), code=""" /// get BoxSelectTool dimensions from cb_data parameter of Callback var geometry = cb_data['geometry']; button.data = { x0: geometry['x0'], x1: geometry['x1'], y: geometry['y'], y0: geometry['y0'], y1: geometry['y1'] }; // Callback to the backend button.clicks++; """) self.tool = BothDimensionsSelectTool(tool_name="Multiplet Analysis", icon="my_icon_multiplet_analysis", callback=callback, id="multipletAnalysisTool") def rowSelect(self, ids): if len(ids) == 1: self.selected = ids[0] # Enable options self.classes.disabled = False self.classes.value = self.sources['table'].data['classes'][ self.selected] self.integral.disabled = False self.integral.value = str( self.sources['table'].data['integral'][self.selected]) self.j.disabled = False self.j.value = self.sources['table'].data['j'][self.selected] self.delete.disabled = False self.peakPicking.selectByPPM( self.sources['table'].data['peaks'][self.selected]) else: deselectRows(self.sources['table']) def recalculateAllMultipletsForPeaks(self): data = self.sources['table'].data patch = dict(classes=[], j=[]) for pos, start, stop in zip(range(len(data['xStart'])), data['xStart'], data['xStop']): ppm = self.peakPicking.getPPMInSpace(start, stop) peaks = self.peakPicking.getPeaksInSpace(start, stop) multiplet = self.predictMultiplet(peaks) patch['classes'].append((pos, multiplet)) patch['j'].append((pos, self.calcJ(ppm, multiplet))) self.sources['table'].patch(patch) def manualMultipletAnalysis(self, dimensions): self.peakPicking.manualPeakPicking(dimensions, notify=False) # Check if empty if not self.peakPicking.peaksIndices: return integral = round(self.integration.calcIntegral(dimensions), 3) peaks = [self.pdata[i] for i in self.peakPicking.peaksIndices] multiplet = self.predictMultiplet(peaks) ppm = sorted([ self.dataSource.data['ppm'][i] for i in self.peakPicking.peaksIndices ]) data = { 'xStart': [dimensions['x0']], 'xStop': [dimensions['x1']], 'name': [ 'A' if not self.sources['table'].data['name'] else chr(ord(self.sources['table'].data['name'][-1]) + 1) ], 'classes': [multiplet], 'j': [self.calcJ(ppm, multiplet)], 'h': [round(integral)], 'integral': [integral], 'peaks': [ppm], 'top': [dimensions['y1']], 'bottom': [dimensions['y0']] } # Add to DataTable self.sources['table'].stream(data) # Select the multiplet in the table self.sources['table'].selected = { '0d': { 'glyph': None, 'indices': [] }, '1d': { 'indices': [len(self.sources['table'].data['xStart']) - 1] }, '2d': { 'indices': {} } } def calcJ(self, ppm, multiplet): if multiplet in self.MULTIPLETS: js = self.MULTIPLETS[multiplet]['j'] calc = sorted([ round(abs(ppm[j[0]] - ppm[j[1]]) * getFrequency(self.udic), 1) for j in js ], reverse=True) return ', '.join(str(j) for j in calc) + ' Hz' return "" def predictMultiplet(self, peaks): for key, value in self.MULTIPLETS.iteritems(): if len(peaks) == value['sum'] and self.checkMultiplet( value['table'], peaks): return key return "m" def checkMultiplet(self, multiplet, peaks): if not multiplet: return True # check list if isinstance(multiplet[0], list): return self.checkMultiplet(multiplet[0], peaks) and self.checkMultiplet( multiplet[1:], peaks) else: return self.checkMultiplicity(multiplet, peaks) def checkMultiplicity(self, multiplet, peaks): if not multiplet: return True for (m, peak) in zip(multiplet[1:], peaks[1:]): low = m * peaks[0] - self.MULTIPLET_ERROR high = m * peaks[0] + self.MULTIPLET_ERROR if peak < low or peak > high: return False return True def dataChanged(self, data): self.updateMultipletReport() label = getLabel(self.udic) if label == "1H": newData = [(np.median(peaks), c) for (peaks, c) in zip(data['peaks'], data['classes'])] oldData = [ (np.median(peaks), c) for (peaks, c) in zip(self.oldData['peaks'], self.oldData['classes']) ] added = list(set(newData) - set(oldData)) removed = list(set(oldData) - set(newData)) SpectrumDB.RemovePeaks(self.id, removed) SpectrumDB.AddPeaks(self.id, added) self.oldData = { 'peaks': [i[0] for i in newData], 'classes': [i[1] for i in newData] } def updateMultipletReport(self): label = getLabel(self.udic) text = "" if label == "1H": data = self.sources['table'].data text = getMetadata(self.dic, self.udic) + " δ = " + ", ".join( ("{:0.2f}".format(np.median(peaks)) if classes != 'm' else "{:0.2f}-{:0.2f}".format(peaks[-1], peaks[0])) + " ({}, ".format(classes) + ("J={}, ".format(j) if classes != 'm' and classes != 's' else "") + "{:d}H)".format(int(h)) for (peaks, classes, j, h) in sorted( zip(data['peaks'], data['classes'], data['j'], data['h']), reverse=True) if h > 0) + "." self.report.text = text def manualChange(self, key, new): if self.sources['table'].data[key][self.selected] != new: patch = {key: [(self.selected, new)]} self.sources['table'].patch(patch) def changeIntegral(self, new): try: new = float(new) data = self.sources['table'].data['integral'] old = data[self.selected] ratio = new / old self.updateIntervals(ratio, data) self.integration.updateIntervals( ratio, self.integration.sources['table'].data) except: pass def updateIntervals(self, ratio, data): h, integral = [], [] for pos, val in zip(xrange(len(data)), data): newIntegral = val * ratio h.append((pos, round(newIntegral))) integral.append((pos, newIntegral)) self.sources['table'].patch(dict(h=h, integral=integral)) def deleteMultiplet(self): xStart = list(self.sources['table'].data['xStart']) xStop = list(self.sources['table'].data['xStop']) name = list(self.sources['table'].data['name']) classes = list(self.sources['table'].data['classes']) j = list(self.sources['table'].data['j']) h = list(self.sources['table'].data['h']) integral = list(self.sources['table'].data['integral']) peaks = list(self.sources['table'].data['peaks']) top = list(self.sources['table'].data['top']) bottom = list(self.sources['table'].data['bottom']) xStart.pop(self.selected) xStop.pop(self.selected) name.pop(self.selected) classes.pop(self.selected) j.pop(self.selected) h.pop(self.selected) integral.pop(self.selected) peaks.pop(self.selected) top.pop(self.selected) bottom.pop(self.selected) self.sources['table'].data = { 'xStart': xStart, 'xStop': xStop, 'name': name, 'classes': classes, 'j': j, 'h': h, 'integral': integral, 'peaks': peaks, 'top': top, 'bottom': bottom } deselectRows(self.sources['table']) self.disableOptions() def disableOptions(self): self.classes.disabled = True self.integral.disabled = True self.j.disabled = True self.delete.disabled = True def draw(self, plot): self.tool.addToPlot(plot)