Source code for autopew.io.laser.chromium

"""
Import and export functions for the Chromium Laser Ablation Navigation Software.
"""
import csv
import re
from pathlib import Path

import numpy as np
import pandas as pd


[docs]def split_config(s): """ Splits a config-formatted string. Parameters ---------- s : :class:`str` Returns --------- :class:`dict` See Also --------- :func:`get_scandata` Todo ----- Consider using :mod:`configparser` to read .lase files. """ x = re.split(r";", s) d = {k: v for (k, v) in [i.split("=") for i in x]} return d
[docs]def get_scandata(scandict): """ Process a dictionary of scan information into a :class:`~pandas.DataFrame`. Parameters ---------- scandict : :class:`dict` Dictionary of scan data. Returns --------- :class:`pandas.DataFrame` See Also --------- :class:`ScanData` """ headers = scandict["Header"].split(",") scannames = [i for i in scandict.keys() if not i == "Header"] no_scans = len(scannames) df = pd.DataFrame(columns=headers, index=scannames) for s in scannames: scandata = re.findall(r'".+?"|[\w-]+', scandict[s]) df.loc[s, headers[: len(scandata)]] = scandata for c in [ "Description", "Vertex List", "Preablation Settings", "Ablation Settings", ]: df[c] = df[c].str.replace('"', "") df["Vertex List"] = ( df["Vertex List"] .str.split(";") .apply(lambda x: np.array([i.split(",") for i in x])) ) df["Preablation Settings"] = df["Preablation Settings"].apply(split_config) df["Ablation Settings"] = df["Ablation Settings"].apply(split_config) df["Data"] = df["Data"].apply(split_config) return df
[docs]def read_lasefile(filepath, encoding="cp1252"): """ Read a .lase formatted file into a dictonary (one item per scan), and return this in the form of a :class:`~pandas.DataFrame`. Parameters ---------- filepath : :class:`str`, :class:`pathlib.Path` Path to the .lase file to import. encoding : :class:`str` File encoding of the .lase file. Returns --------- :class:`~pandas.DataFrame` Notes ------- `.lase` files have all spot information as well as gas flows and all control options of laser operation. These are configuration files structured by blocks demarkated with square brackets, along the lines of:: [Scans] Header=Scan Type,Description, ... Scan0=Spot,"Spot 1", ... [<other control blocks>] ... Some of the values in this table are string-encoded tuples (e.g. `"57950.00,37530.00,20734.50"`) and dictionaries (e.g. `"Dosage=1;DwellTime=1.00;LineSpacing=100.00;Laser.Output=10.00;..."`). See Also --------- :class:`ScanData` :func:`get_scandata` """ path = Path(filepath) if not path.suffix == ".lase": path = path.with_suffix(".lase") file = open(str(path), encoding=encoding).read() lines = [i for i in re.split("[\n\r]", file) if i] data = {} for l in lines: if re.match(r"^\[.*\]$", l): section = l.replace("[", "").replace("]", "") data[section] = {} else: var, value = re.split("=", l, maxsplit=1) data[section][var] = value df = get_scandata(data["Scans"]) return df
[docs]def read_scancsv(filepath, encoding="cp1252"): """ Read a .scancsv into a dictonary (one item per scan), and return this in the form of a :class:`~pandas.DataFrame`. Parameters ---------- filepath : :class:`str`, :class:`pathlib.Path` Path to the .scancsv file to import. encoding : :class:`str` File encoding of the .scancsv file. Returns --------- :class:`~pandas.DataFrame` Notes -------- `.scancsv` files only contain spot information and do not edit the laser and gas conditions. These are essentially comma-separated values files, with a structure along the lines of:: Scan Type,Description, ... Spot,"Spot 1", ... Some of the values in this table are string-encoded tuples (e.g. `"57950.00,37530.00,20734.50"`) and dictionaries (e.g. `"Dosage=1;DwellTime=1.00;LineSpacing=100.00;Laser.Output=10.00;..."`). See Also --------- :class:`ScanData` :func:`get_scandata` """ path = Path(filepath) if not path.suffix == ".scancsv": path = path.with_suffix(".scancsv") scanfile = open(str(path), encoding=encoding).read() # .readlines() scanfilelines = [i for i in re.split("[\n\r]", scanfile) if i] scanfiledict = {} scanfiledict["Header"] = scanfilelines[0] for ix, l in enumerate(scanfilelines[1:]): scanfiledict[ix] = l df = get_scandata(scanfiledict) df = df.reindex(columns=["name", "x", "y", "z"] + df.columns.tolist()) df[["x", "y", "z"]] = ( np.vstack(df["Vertex List"].map(np.array).values) .reshape(df.index.size, -1) .astype(float) ) # pd.DataFrame(verts, index=df.Description, columns=["x", "y", "z"]) return df
[docs]def write_scancsv( df, filepath=Path("./exportedpoints.scancsv"), spotnames=None, encoding="cp1252", z=20800, **kwargs ): """ Export an array of coordinates to a .scancsv file. Parameters ------------ df : :class:`pandas.DataFrame` Dataframe containing points to serialise. filepath : :class:`str` | :class:`pathlib.Path` Filepath for export. spotnames : :class:`str` | :class:`list` Name to prefix spot indicies or a list of spot names. encoding : :class:`str` Encoding for the output file. z : :class:`int` Optional specification of default focus value to use. Returns -------- :class:`pandas.DataFrame` Todo ------ * Take spot numbers/names from imported point list index * Take spot sizes from imported point list * Output encoding to match input scansv (it's not UTF-8, issues with spotsize) """ scancsv = pd.DataFrame( columns=( "Scan Type,Description,Selected,Lock Edit,Vertex Count," "Vertex List,Preablation Settings,Ablation Settings,Data" ).split(","), index=range(df.index.size), ) scancsv["Scan Type"] = "Spot" if spotnames is not None: if isinstance(spotnames, str): scancsv["Description"] = [ "{}_{:d}".format(spotnames, ix + 1) for ix in range(df.index.size) ] else: assert len(spotnames) == scancsv.index.size scancsv["Description"] = list(spotnames) else: scancsv["Description"] = ["{:d}".format(ix + 1) for ix in range(df.index.size)] scancsv.Selected = 1 scancsv["Lock Edit"] = 0 scancsv["Vertex Count"] = 1 scancsv["Vertex List"] = [ "{:.2f},{:.2f},{:.2f}".format(*df.loc[ix, ["x", "y"]].values, z) for ix in df.index ] preablate, ablate = ( "Dosage=1;DwellTime=1.00;LineSpacing=100.00;Laser.Output=10.00;LineScanMode=1;PassCount=1;Laser.RepRate=1.00;ScanSpeed=50.00;PassEnabled=0;ShotCount=1;SpotSpacing=100.00;Laser.SpotSize=150\xb5m Circle;Laser.SpotRotation=0.00", "Dosage=0;DwellTime=1.00;LineSpacing=0.00;Laser.Output=60.10;LineScanMode=1;PassCount=1;Laser.RepRate=9.00;ScanSpeed=0.00;PassEnabled=1;ShotCount=260;SpotSpacing=0.00;Laser.SpotSize=50\xb5m Circle;Laser.SpotRotation=0.00", ) scancsv["Data"] = "ExpectedLaseTime=28.9;ScanLength=0.0" scancsv["Preablation Settings"] = preablate scancsv["Ablation Settings"] = ablate output_filepath = Path(filepath).with_suffix(".scancsv") with open( str(output_filepath), "w", encoding=encoding ) as f: # str for Python 3.5 compatibility f.write(",".join(scancsv.columns.tolist()) + "\n") buffer = scancsv.to_csv( header=False, index=False, encoding=encoding, quoting=csv.QUOTE_NONNUMERIC ) f.write(buffer) return scancsv