Source code for est.resources.generate

import os
from glob import glob
from pathlib import Path
import logging
from typing import Dict, List, Tuple
import numpy
import h5py
from numpy.typing import ArrayLike

try:
    import PIL.Image
    import PIL.ImageDraw
    import PIL.ImageFont

    has_PIL = True
except ImportError:
    has_PIL = False

logger = logging.getLogger(__name__)

DataDict = Dict[str, ArrayLike]


[docs] def save_3d_exafs(infile: Path, outfile: Path, word="EXAMPLE") -> None: """Save a 1D EXAFS spectrum as 3D scan data (fullfield or fluoxas) :param Path infile: ASCII file containing EXAFS spectrum :param Path outfile: output hdf5 file """ lst = numpy.loadtxt(infile).T if len(lst) == 3: energy, mu, i0 = lst else: energy, mu = lst i0 = numpy.ones_like(mu) it = i0 * numpy.exp(-mu) data = {"mu": mu, "i0": i0, "it": it} generate_3d_exafs(str(infile), energy, data, outfile, word=word) return outfile
[docs] def generate_3d_exafs( filename: str, energy: ArrayLike, data: DataDict, outfile: Path, word="EXAMPLE" ) -> None: scannr = 0 info = {"source": filename} positioners = {"energy": energy} signals = list(data) axes = ["energy"] if outfile.exists(): outfile.unlink() info["comment"] = "Bliss energy scan with a 1D detector" scannr += 1 _save_scan( outfile, f"{scannr}.1", "exafs", positioners, data, signals, axes, "spectrum", info, ) im = _generate_test_image(word) data = { k: numpy.multiply.outer(v, im) for k, v in data.items() } # Axes: energy, y, x nenergy = len(energy) ny, nx = im.shape step_size = 1 xstart = -nx / 3 xstop = xstart + step_size * (nx - 1) x = numpy.linspace(xstart, xstop, nx) ystop = 10 ystart = ystop + step_size * (ny - 1) y = numpy.linspace(ystart, ystop, ny) positioners["x"] = x positioners["y"] = y for name, values in positioners.items(): info[f"n{name}"] = len(values) axes = ["energy", "y", "x"] dimensions = (axes.index("x"), axes.index("y"), axes.index("energy")) info["dimensions"] = dimensions info["comment"] = "Bliss energy scan with a 2D detector" tmp = next(iter(data.values())) assert tmp.shape[dimensions[0]] == nx assert tmp.shape[dimensions[1]] == ny assert tmp.shape[dimensions[2]] == nenergy scannr += 1 _save_scan( outfile, f"{scannr}.1", "fullfield exafs", positioners, data, signals, axes, None, info, ) axes = ["y", "x", "energy"] dimensions = (axes.index("x"), axes.index("y"), axes.index("energy")) prev_dimensions = info["dimensions"] info["dimensions"] = dimensions info["comment"] = ( "Bliss mesh scan (Y is the fast axis) at multiple energies with a 1D detector" ) data = {k: numpy.moveaxis(v, prev_dimensions, dimensions) for k, v in data.items()} tmp = next(iter(data.values())) assert tmp.shape[dimensions[0]] == nx assert tmp.shape[dimensions[1]] == ny assert tmp.shape[dimensions[2]] == nenergy scannr += 1 fpositioners, fdata = _flatten_mesh_scan(axes, positioners, data) title = f"fluoxas y {ystart} {ystop} {nx-1} x {xstart} {xstop} {nx-1}" _save_scan( outfile, f"{scannr}.1", title, fpositioners, fdata, list(), list(), None, info, ) axes = ["x", "y", "energy"] dimensions = (axes.index("x"), axes.index("y"), axes.index("energy")) prev_dimensions = info["dimensions"] info["dimensions"] = dimensions info["comment"] = ( "Bliss mesh scan (X is the fast axis) at multiple energies with a 1D detector" ) data = {k: numpy.moveaxis(v, prev_dimensions, dimensions) for k, v in data.items()} tmp = next(iter(data.values())) assert tmp.shape[dimensions[0]] == nx assert tmp.shape[dimensions[1]] == ny assert tmp.shape[dimensions[2]] == nenergy scannr += 1 fpositioners, fdata = _flatten_mesh_scan(axes, positioners, data) title = f"fluoxas x {xstart} {xstop} {nx-1} y {ystart} {ystop} {nx-1}" _save_scan( outfile, f"{scannr}.1", title, fpositioners, fdata, list(), list(), None, info, ) axes = ["energy", "x", "y"] dimensions = (axes.index("x"), axes.index("y"), axes.index("energy")) prev_dimensions = info["dimensions"] info["dimensions"] = dimensions info["comment"] = ( "Bliss energy scan at grid positions (X is the fast axis)with a 1D detector" ) data = {k: numpy.moveaxis(v, prev_dimensions, dimensions) for k, v in data.items()} tmp = next(iter(data.values())) assert tmp.shape[dimensions[0]] == nx assert tmp.shape[dimensions[1]] == ny assert tmp.shape[dimensions[2]] == nenergy scannr += 1 fpositioners, fdata = _flatten_mesh_scan(axes, positioners, data) title = f"exafsgrid x {xstart} {xstop} {nx-1} y {ystart} {ystop} {nx-1}" _save_scan( outfile, f"{scannr}.1", title, fpositioners, fdata, list(), list(), None, info, ) axes = ["energy", "y", "x"] dimensions = (axes.index("x"), axes.index("y"), axes.index("energy")) prev_dimensions = info["dimensions"] info["dimensions"] = dimensions info["comment"] = ( "Bliss energy scan at grid positions (Y is the fast axis) with a 1D detector" ) data = {k: numpy.moveaxis(v, prev_dimensions, dimensions) for k, v in data.items()} tmp = next(iter(data.values())) assert tmp.shape[dimensions[0]] == nx assert tmp.shape[dimensions[1]] == ny assert tmp.shape[dimensions[2]] == nenergy scannr += 1 fpositioners, fdata = _flatten_mesh_scan(axes, positioners, data) title = f"exafsgrid y {ystart} {ystop} {nx-1} x {xstart} {xstop} {nx-1}" _save_scan( outfile, f"{scannr}.1", title, fpositioners, fdata, list(), list(), None, info, ) with h5py.File(outfile, "a") as h5f: h5f.attrs["default"] = "1.1"
def _save_scan( filename: str, scan: str, title: str, positioners: DataDict, data: DataDict, plot_signals: List[str], plot_axes: List[str], interpretation: str, info: dict, ) -> None: with h5py.File(filename, "a") as h5f: h5f.attrs["NX_class"] = "NXroot" entry = h5f.create_group(scan) url = f"{filename}::{entry.name}" entry.attrs["NX_class"] = "NXentry" entry["title"] = title dinfo = entry.create_group("info") dinfo.attrs["NX_class"] = "NXnote" if info is not None: for k, v in info.items(): dinfo[k] = v if plot_signals: plotselect = entry.create_group("plotselect") plotselect.attrs["NX_class"] = "NXdata" instrument = entry.create_group("instrument") instrument.attrs["NX_class"] = "NXinstrument" measurement = entry.create_group("measurement") measurement.attrs["NX_class"] = "NXconnection" for name, values in positioners.items(): grp = instrument.create_group(name) grp.attrs["NX_class"] = "NXpositioner" grp["value"] = values if name == "energy": grp["value"].attrs["units"] = "eV" dest = grp["value"].name measurement[name] = h5py.SoftLink(dest) if name in plot_axes: plotselect[name] = h5py.SoftLink(dest) for name, values in data.items(): grp = instrument.create_group(name) grp.attrs["NX_class"] = "NXdetector" grp["data"] = values dest = grp["data"].name measurement[name] = h5py.SoftLink(dest) if name in plot_signals: plotselect[name] = h5py.SoftLink(dest) if plot_signals: h5f.attrs["default"] = scan entry.attrs["default"] = "plotselect" plotselect.attrs["signal"] = plot_signals[0] if len(plot_signals) > 1: plotselect.attrs["auxiliary_signals"] = plot_signals[1:] if plot_axes: plotselect.attrs["axes"] = plot_axes if interpretation: plotselect.attrs["interpretation"] = interpretation logger.info("SAVED %s (%s)", url, info["comment"]) def _generate_test_image(text: str) -> numpy.ndarray: if not has_PIL: return numpy.ones((15, 23)) fontsize = 28 if os.name == "nt": font = PIL.ImageFont.truetype("Arial.ttf", fontsize) else: files = glob("/usr/share/fonts/truetype/*/*.ttf") for filename in files: if "arial" in filename.lower(): break font = PIL.ImageFont.truetype(filename, fontsize) left, top, right, bottom = font.getbbox(text) width, height = right, bottom off = 10 canvas = PIL.Image.new("RGB", (width + off, height + off), "black") draw = PIL.ImageDraw.Draw(canvas) draw.text((off / 2, off / 2), text, fill="red", font=font) img = numpy.asarray(canvas, dtype=bool)[..., 0] return img def _flatten_mesh_scan( axes: List[str], positioners: DataDict, data: DataDict ) -> Tuple[DataDict, DataDict]: # Axes: fast axis, slow axis, energy axis # Data shape: fast axis, slow axis, energy axis fast = positioners[axes[0]] slow = positioners[axes[1]] energy = positioners[axes[2]] shape = (fast.size, slow.size, energy.size) fast = fast[:, None, None] slow = slow[None, :, None] energy = energy[None, None, :] fast = numpy.tile(fast, (1, shape[1], shape[2])) slow = numpy.tile(slow, (shape[0], 1, shape[2])) energy = numpy.tile(energy, (shape[0], shape[1], 1)) positioners = { axes[0]: fast.flatten(), axes[1]: slow.flatten(), axes[2]: energy.flatten(), } data = {k: v.flatten() for k, v in data.items()} return positioners, data