Análisis de datos científicos con LabPlot en Python: procesamiento de señales, ajuste de picos espectrales, visualización y automatización de lotes
TEMAS = { “NegroSobreBlanco”: dict(bg=”#ffffff”, fg=”#000000″, grid=”#c8c8c8″, ciclo=[“#3465a4”, “#cc0000”, “#4e9a06”, “#f57900”, “#75507b”, “#06989a”]), “Drácula”: dict(bg=”#282a36″, fg=”#f8f8f2″, grid=”#44475a”, ciclo=[“#8be9fd”, “#ff79c6”, “#50fa7b”, “#ffb86c”, “#bd93f9”, “#f1fa8c”]), “SolarizadoOscuro”: dict(bg=”#002b36″, fg=”#93a1a1″, grid=”#0f4b57″, ciclo=[“#268bd2”, “#dc322f”, “#859900”, “#b58900”, “#6c71c4”, “#2aa198″])} clase XYCurve(AbstractAspect): def __init__(self, nombre, x=Ninguno, y=Ninguno, lineStyle=”-“, lineWidth=1.6, symbolStyle=Ninguno, symbolSize=4., color=Ninguno, alpha=1., zorder=2): super().__init__(nombre) self.xColumn, self.yColumn, self.color, self.alpha = x, y, color, alfa self.lineStyle, self.lineWidth = lineStyle, lineWidth self.symbolStyle, self.symbolSize, self.zorder = symbolStyle, symbolSize, zorder self.yErrorColumn = self.fillBetween = Ninguno def setXColumn(self, c): self.xColumn = c; devolver autodefinición setYColumn(self, c): self.yColumn = c; return self @staticmethod def _v(c): devuelve c.values() si esinstance(c, Column) else np.asarray(c, float) def draw(self, ax, color): c = self.color or color; X, Y = self._v(self.xColumn), self._v(self.yColumn) si self.fillBetween no es Ninguno: ax.fill_between(X, *self.fillBetween, color=c, alpha=.2, lw=0, zorder=self.zorder-1) si self.yErrorColumn no es Ninguno: ax.errorbar(X, Y, yerr=self._v(self.yErrorColumn), fmt=”none”, ecolor=c, elinewidth=.8, capsize=2, alpha=.7, zorder=self.zorder) ax.plot(X, Y, linestyle=self.lineStyle o “none”, marcador=self.symbolStyle o “none”, Markersize=self.symbolSize, linewidth=self.lineWidth, color=c, alpha=self.alpha, label=self._name, zorder=self.zorder, Markeredgewidth=0) class Histograma(AbstractAspect): “””normalización: ‘Count’ | ‘Probability’ | ‘CountDensity’ | ‘ProbabilityDensity’.””” def __init__(self, name, dataColumn=None, bins=”auto”, normalization=”ProbabilityDensity”): super().__init__(nombre) self.dataColumn, self.bins, self.normalization = dataColumn, bins, normalización def draw(self, ax, color): d = (self.dataColumn.clean() if isinstance(self.dataColumn, Column) else np.asarray(self.dataColumn, float)) ax.hist(d, bins=self.bins, color=color, alfa=.55, edgecolor=color, lw=.8, etiqueta=self._name, zorder=1, densidad=”Densidad” en self.normalization o self.normalization == “Probabilidad”) clase CartesianPlot(AbstractAspect): clase Tipo(Enum): FourAxes = 0; TwoAxes = 1 def __init__(self, nombre, título=Ninguno, xLabel=”x”, yLabel=”y”, logX=False, logY=False): super().__init__(nombre); self.type = CartesianPlot.Type.FourAxes self.title, self.xLabel, self.yLabel = título o nombre, xLabel, yLabel self.logX, self.logY, self.legend = logX, logY, Ninguno self.xRange, self.yRange, self.labels = Ninguno, Ninguno, []
def setType(self, t): self.type = t; return self def addLegend(self, loc=”mejor”): self.legend = loc; devolver autodefinición setRange(self, x=Ninguno, y=Ninguno): self.xRange, self.yRange = x, y; devolver autodefinición addTextLabel(self, txt, x, y): self.labels.append((txt, x, y)); devolver autodefinición _render(self, ax, th): ax.set_facecolor(th[“bg”]) para i, ch en enumerar(self.children): ch.draw(ax, th[“cycle”][i % len(th[“cycle”])]) ax.set_title(self.title, color=ésimo[“fg”]tamaño de fuente=10.5, pad=7) ax.set_xlabel(self.xLabel, color=th[“fg”]tamaño de fuente=9.5) ax.set_ylabel(self.yLabel, color=th[“fg”]fontsize=9.5) para lg, sc, axis in ((self.logX, ax.set_xscale, ax.xaxis), (self.logY, ax.set_yscale, ax.yaxis)): sc(“log”) si lg else axis.set_minor_locator(AutoMinorLocator(2)) if self.xRange: ax.set_xlim(*self.xRange) if self.yRange: ax.set_ylim(*self.yRange) four = self.type es CartesianPlot.Type.FourAxes para s en (“arriba”, “derecha”): ax.spines[s].set_visible(cuatro) para s en ax.spines.values(): s.set_color(th[“fg”]); s.set_linewidth(.9) ax.tick_params(que = “ambos”, dirección = “en”, colores = th[“fg”]arriba=cuatro, derecha=cuatro, tamaño de etiqueta=8.5) ax.grid(Verdadero, color=ésimo[“grid”]lw=.6, alpha=.7, zorder=0) para t, x, y en self.labels: ax.annotate(t, (x, y), color=th[“fg”]fontsize=7.5, ha=”center”) si self.legend: para t en ax.legend(loc=self.legend, fontsize=8, framealpha=.85, facecolor=th[“bg”]color de borde = th[“grid”]).get_texts(): t.set_color(th[“fg”]) clase Hoja de trabajo (Aspecto abstracto): clase Formato de exportación (Enum): PDF = 0; SVG = 1; PNG = 2 def __init__(self, nombre, cols=Ninguno, figsize=(15, 8.5), dpi=110): super().__init__(nombre); self.themeName = “BlackOnWhite” self.cols, self.figsize, self.dpi, self._fig = cols, figsize, dpi, Ninguno def setTheme(self, n): si n no está en TEMAS: rise KeyError(f”themes: {list(THEMES)}”) self.themeName = n; return self def render(self): th = TEMAS[self.themeName]
pd = [c for c in self.children if isinstance(c, CartesianPlot)]
cols = self.cols o min(len(ps), 2) fig, axes = plt.subplots(math.ceil(len(ps)/cols), cols, figsize=self.figsize, dpi=self.dpi) fig.patch.set_facecolor(th[“bg”]); ejes = np.atleast_1d(ejes).ravel() para hacha, p en zip(ejes, ps): p._render(ax, th) para hacha en ejes[len(ps):]: ax.axis(“off”) fig.suptitle(self._name, color=th[“fg”]tamaño de fuente=13, y=.995) fig.tight_layout(rect=(0, 0, 1, .98)); self._fig = higo; return fig def show(self): (self.render() si self._fig es Ninguno más Ninguno); plt.show() def exportToFile(self, ruta, formato=None): si self._fig es Ninguno: self.render() fmt = (format.name.lower() if isinstance(format, Worksheet.ExportFormat) else formatee o os.path.splitext(ruta)[1].lstrip(“.”)) self._fig.savefig(ruta, formato=fmt, dpi=self.dpi, bbox_inches=”tight”, facecolor=self._fig.get_facecolor()); ruta de retorno def _reduce(x, y, tolerancia=Ninguna): i = nsl_geom.douglas_peucker(x, y, tolerancia si la tolerancia no es Ninguna más .02*np.ptp(y)) return x[i]y[i]{“in”: len(x), “out”: len(i), “compresión”: 1 – len(i)/len(x)} clase XYAnalysisCurve(XYCurve): OPS = { “smooth”: lambda x, y, puntos=11, orden=3: (x, nsl_smooth.savitzky_golay(y, puntos, orden), {}), “diferenciar”: lambda x, y, derivOrder=1, smoothPoints=0: (x, nsl_diff.derive(x, y, derivOrder, smoothPoints), {}), “integrar”: lambda x, y, método=”trapezoid”, absoluto=False: (lambda c: (x, c, {“total”: float(c[-1])}))(nsl_int.integrate(x, y, método, absoluto)), “dft”: lambda x, y, salida=”amplitud”, ventana=”rectangular”: nsl_dft.transform(x, y, salida, ventana) + ({},), “filtro”: lambda x, y, tipo=”lowpass”, form=”butterworth”, corte=.1, corte2=.3, orden=3: (x, nsl_filter.apply(x, y, tipo, formulario, corte, corte2, orden), {}), “hilbert”: lambda x, y, salida=”sobre”: (x, nsl_hilbert.transform(y, salida), {}), “reduce”: _reduce} def __init__(self, nombre, xData, yData, op, estilo=None, **opts): super().__init__(nombre, **(estilo o {})) self._xin, self._yin = XYCurve._v(xData), XYCurve._v(yData) self.op, self.opts, self.result = op, opts, Ninguno self.recalculate() def recalculate(self): self.xColumn, self.yColumn, self.result = \ XYAnalysisCurve.OPS[self.op](self._xin, self._yin, **self.opts) return self _mk = operación lambda: (nombre lambda, x, y, estilo=Ninguno, **kw: XYAnalysisCurve(nombre, x, y, op, estilo, **kw)) XYSmoothCurve, XYDifferentiationCurve = _mk(“smooth”), _mk(“diferenciar”) XYIntegrationCurve = _mk(“integrate”) XYFourierTransformCurve, XYFourierFilterCurve = _mk(“dft”), _mk(“filter”) XYHilbertTransformCurve, XYDataReductionCurve = _mk(“hilbert”), _mk(“reduce”) class XYFitCurve(XYCurve): “””Pieza central de LabPlot: ajuste no lineal con el completo tabla de estadísticas. self.paramNames = modelo, p0, paramNames self.yerr, self.bounds, self.npoints, self.fitResult = yerr, límites, npoints, Ninguno def recalcular(self, conf=.95, showConfidenceInterval=True): self.fitResult = nsl_fit.fit(self.model, self._xin, self._yin, self.p0, self.yerr, self.bounds, self.paramNames, conf) xf = np.linspace(self._xin.min(), self._xin.max(), self.npoints) yf = self.model(xf, *self.fitResult.values); self.xColumn, self.yColumn = xf, yf if showConfidenceInterval: d = nsl_fit.confidenceBand(self.model, xf, self.fitResult, conf) self.fillBetween = (yf – d, yf + d) return self class ProjectFile: MAGIC = ((b”\x1f\x8b”, gzip.decompress, “gzip”), (b”BZh”, bz2.decompress, “bzip2″), (b”\xfd7zXZ\x00”, lzma.decompress, “xz”)) @staticmethod def load(ruta): blob = open(ruta, “rb”).read(); kind = “simple” para magic, dec, nm en ProjectFile.MAGIC: if blob.startswith(magic): blob, kind = dec(blob), nm; break root = ET.fromstring(blob.decode(“utf-8”, “replace”)) root = root if root.tag == “project” else root.find(“.//project”) si root es Ninguno: elevar ValueError(“no se encontró ningún elemento del proyecto”) prj = Project(os.path.basename(path), root.get(“author”, “”)) prj.version = root.get(“version”, “?”) print(f” cargado .lml: compresión={kind} versión={prj.version} xmlVersion=” f”{root.get(‘xmlVersion’,’?’)}”) padres = {c: p para p en root.iter() para c en p} def hoja_of(n): n = padres.get(n) mientras n no es Ninguno y n.tag != “hoja de cálculo”: n = padres.get(n) devuelve n depósitos = {} para col en root.iter(“columna”): cubos.setdefault(id(hoja_de(col)), (hoja_de(col), []))[1].append(col) para el, cols en buckets.values(): sp = Spreadsheet(el.get(“name”, “spreadsheet”) si el no es Ninguno más “sheet”) para c en cols: sp.addChild(ProjectFile._column(c)) prj.addChild(sp) return prj @staticmethod def _column(el): name = el.get(“name”) o next( (el.find
else: nodo = next((el.find
para v en bruto: intente: vals.append(float(v)) excepto (TypeError, ValueError): vals.append(np.nan) intente: des = PlotDesignation(int(el.get(“designation”, 0))) excepto (ValueError, TypeError): des = PlotDesignation.NoDesignation return Columna(nombre, vals, designación=des) @staticmethod def guardar(proyecto, ruta, compresión=”gzip”): root = ET.Element(“proyecto”, { “versión”: proyecto.versión, “xmlVersion”: str(Project.XML_VERSION), “fileName”: os.path.basename(ruta), “autor”: proyecto.autor, “modificationTime”: time.strftime(“%Y-%m-%d %H:%M:%S”)}) ET.SubElement(raíz, “comentario”).text = proyecto.comment para sp en proyecto.spreadsheets(): e = ET.SubElement(raíz, “hoja de cálculo”, {“nombre”: sp.name()}) ET.SubElement(e, “general”, {“rowCount”: str(sp.rowCount()), “columnCount”: str(sp.columnCount())}) para col en sp.columns(): c = ET.SubElement(e, “columna”, { “nombre”: col.name(), “rows”: str(col.rowCount()), “designation”: str(col.plotDesignation.value), “mode”: str(col.columnMode.value)}) para i, v en enumerate(col.values()): ET.SubElement(c, “row”, {“index”: str(i)}).text = repr(float(v)) xml = (b’\n\n’ + ET.tostring(root, codificación=”utf-8″)) open(ruta, “wb”).write({“gzip”: gzip.compress, “bzip2”: bz2.compress, “xz”: lzma.compress, “none”: lambda b: b}[compression](xml)) ruta de retorno