Source code for mcsas3.McData2D

import logging
from pathlib import Path
from typing import Optional

import numpy as np
import pandas

from .McData import McData


[docs]class McData2D(McData): """Subclass for managing 2D datasets. Copied from 1D dataset handler, not every functionality is enabled""" csvargs = { "sep": r"\s+", "header": None, "names": ["Q", "I", "ISigma"], } # default for 1D, overwritten in subclass dataRange = [0, np.inf] # min-max for data range to fit orthoQ1Range = [0, np.inf] # min-max for abs(Qx) in case of square masking orthoQ0Range = [0, np.inf] # min-max for abs(Qy) in case of square masking qNudge = [ 0, 0, ] # nudge in direction 0 and 1 in case of misaligned centers. Applied to measData
[docs] def __init__(self, df=None, loadFromFile=None, resultIndex: int = 1, **kwargs: dict) -> None: super().__init__(resultIndex=resultIndex, **kwargs) self.csvargs = ( {} ) # not sure you'd want to load 2D from a CSV.... though I've seen stranger things self.dataRange = [0, np.inf] # min-max for data range to fit self.orthoQ1Range = [0, np.inf] self.orthoQ0Range = [0, np.inf] self.qNudge = [0, 0] # nudge in case of misaligned centers. Applied to measData self.processKwargs(**kwargs) # load from dataframe if provided if df is not None: self.loader = "from_pandas" # TODO: need to handle this on restore state self.from_pandas(df) # TODO not sure why loadFromFile is not used.. elif self.filename is not None: # filename has been set self.from_file(self.filename)
# link measData to the requested value def linkMeasData(self, measDataLink: Optional[str] = None) -> None: if measDataLink is None: measDataLink = self.measDataLink assert measDataLink in [ "rawData", "clippedData", "binnedData", ], ( f"measDataLink value: {measDataLink} not valid. Must be one of 'rawData', 'clippedData'" " or 'binnedData'" ) measDataObj = getattr(self, measDataLink) self.measData = dict( Q=[ measDataObj["Q"][0] + self.qNudge[0], measDataObj["Q"][1] + self.qNudge[1], ], I=measDataObj["I"], ISigma=measDataObj["ISigma"], ) def from_pandas(self, df: pandas.DataFrame = None) -> None: assert False, "2D data from_pandas not implemented yet" pass def from_csv(self, filename: Path, csvargs: dict = {}) -> None: assert False, "2D data from_csv not implemented yet" pass def clip(self) -> None: # copied from a jupyter notebook: # test with directly imported data Int = self.rawData2D["I"] ISigma = self.rawData2D["ISigma"] Q1 = self.rawData2D["Qx"] Q0 = self.rawData2D["Qy"] if "mask" in self.rawData2D.keys(): mask = self.rawData2D["mask"] else: mask = np.zeros(Int.shape) newMask = mask.astype(bool) withinLimits = ( (np.abs(Q1) > self.orthoQ1Range[0]) & (np.abs(Q1) < self.orthoQ1Range[1]) & (np.abs(Q0) > self.orthoQ0Range[0]) & (np.abs(Q0) < self.orthoQ0Range[1]) & (np.sqrt(Q1**2 + Q0**2) > self.dataRange[0]) & (np.sqrt(Q1**2 + Q0**2) < self.dataRange[1]) ).astype(bool) * np.invert(newMask) # find crop envelope: Q0Lim = ( np.argwhere(withinLimits.sum(axis=1) > 0).min(), np.argwhere(withinLimits.sum(axis=1) > 0).max(), ) Q1Lim = ( np.argwhere(withinLimits.sum(axis=0) > 0).min(), np.argwhere(withinLimits.sum(axis=0) > 0).max(), ) assert Q0Lim[0] < Q0Lim[1], "Could not determine valid crop limits for axis 0 (y)" assert Q1Lim[0] < Q1Lim[1], "Could not determine valid crop limits for axis 1 (x)" # a0l, a0h, a1l, a1h = 200, 600, 300, 700 self.clippedData = dict() self.clippedData["I2D"] = Int[Q0Lim[0] : Q0Lim[1], Q1Lim[0] : Q1Lim[1]] self.clippedData["mask2D"] = newMask[Q0Lim[0] : Q0Lim[1], Q1Lim[0] : Q1Lim[1]] self.clippedData["ISigma2D"] = ISigma[Q0Lim[0] : Q0Lim[1], Q1Lim[0] : Q1Lim[1]] self.clippedData["Q0Crop2D"] = Q0[Q0Lim[0] : Q0Lim[1], Q1Lim[0] : Q1Lim[1]] self.clippedData["Q1Crop2D"] = Q1[Q0Lim[0] : Q0Lim[1], Q1Lim[0] : Q1Lim[1]] self.clippedData["kansas"] = self.clippedData["I2D"].shape # remove infinite intensities and zero-uncertainty datapoints as well (add to mask): bArr = np.invert((np.isinf(self.clippedData["I2D"]) | (self.clippedData["ISigma2D"] == 0))) self.clippedData["invMask"] = bArr * np.invert(self.clippedData["mask2D"]).astype(bool) self.clippedData["I"] = (self.clippedData["I2D"][self.clippedData["invMask"]]).flatten() self.clippedData["ISigma"] = ( self.clippedData["ISigma2D"][self.clippedData["invMask"]] ).flatten() self.clippedData["Q"] = [ self.clippedData["Q0Crop2D"][self.clippedData["invMask"]].flatten(), self.clippedData["Q1Crop2D"][self.clippedData["invMask"]].flatten(), ] self.clippedData["Qextent"] = [ (self.clippedData["Q"][0]).min(), (self.clippedData["Q"][0]).max(), (self.clippedData["Q"][1]).min(), (self.clippedData["Q"][1]).max(), ]
[docs] def omit(self) -> None: """This can skip/omit unwanted ranges of data (for example a data range with an unwanted XRD peak in it). Requires an "omitQRanges" list of [[qmin, qmax]]-data ranges to omit.""" logging.warning("Omitting ranges not implemented yet for 2D") pass
[docs] def reconstruct2D(self, modelI1D: np.ndarray) -> np.ndarray: """Reconstructs a masked 2D data array from the (1D) model intensity, skipping the masked and clipped pixels (left as NaN). This function can be used to plot the resulting model intensity and comparing it with self.clippedData["I2D"]. """ # RMI = reconstructedModelI RMI = np.full(self.clippedData["I2D"].shape, np.nan) RMI[np.where(self.clippedData["invMask"])] = modelI1D return RMI
def reBin(self, nbins: Optional[int] = None, IEMin: float = 0.01, QEMin: float = 0.01) -> None: print("2D data rebinning not implemented, binnedData = clippedData for now") self.binnedData = self.clippedData