diff --git a/eos/__main__.py b/eos/__main__.py index 5284351..b8a6e1b 100644 --- a/eos/__main__.py +++ b/eos/__main__.py @@ -12,6 +12,7 @@ from eos.options import ReflectivityConfig, ReaderConfig, ExperimentConfig, Refl from eos.command_line import commandLineArgs from eos.logconfig import setup_logging, update_loglevel + def main(): setup_logging() diff --git a/eos/e2h.py b/eos/e2h.py index 9d49646..5025433 100644 --- a/eos/e2h.py +++ b/eos/e2h.py @@ -1,10 +1,15 @@ +""" +events2histogram vizualising data from Amor@SINQ, PSI + +Author: Jochen Stahn (algorithms, python draft), + Artur Glavic (structuring and optimisation of code) +""" import logging # need to do absolute import here as pyinstaller requires it from eos.options import E2HConfig, ReaderConfig, ExperimentConfig, E2HReductionConfig from eos.command_line import commandLineArgs from eos.logconfig import setup_logging, update_loglevel -from eos.reduction_e2h import E2HReduction def main(): @@ -22,6 +27,7 @@ def main(): config = E2HConfig(reader_config, experiment_config, reduction_config) logging.warning('######## events2histogram - data vizualization for Amor ########') + from eos.reduction_e2h import E2HReduction # only import heavy module if sufficient command line parameters were provided from eos.reduction_reflectivity import ReflectivityReduction diff --git a/eos/options.py b/eos/options.py index 269121d..5d06d14 100644 --- a/eos/options.py +++ b/eos/options.py @@ -609,6 +609,7 @@ class E2HPlotArguments(StrEnum): @dataclass class E2HReductionConfig(ArgParsable): fileIdentifier: str = field( + default='0', metadata={ 'short': 'f', 'priority': 100, diff --git a/eos/path_handling.py b/eos/path_handling.py index 2783ddc..07acf0e 100644 --- a/eos/path_handling.py +++ b/eos/path_handling.py @@ -49,7 +49,8 @@ class PathResolver: if len(potential_file)>0: path = os.path.dirname(potential_file[0]) else: - raise FileNotFoundError(f'# ERROR: the file {fileName} can not be found in {self.rawPath}') + raise FileNotFoundError(f'# ERROR: the file {fileName} can not be found ' + f'in {self.rawPath+["/home/amor/data"]}') return os.path.join(path, fileName) def search_latest(self, number): @@ -72,4 +73,10 @@ class PathResolver: possible_files += glob(f'/home/amor/data/{self.year}/*/amor{self.year}n??????.hdf') possible_indices = list(set([int(os.path.basename(fi)[9:15]) for fi in possible_files])) possible_indices.sort() - return possible_indices[number-1] + try: + return possible_indices[number-1] + except IndexError: + raise FileNotFoundError(f'# Could not find suitable file for relative index {number} ' + f'in {self.rawPath+["/home/amor/data"]}, ' + f'possible indices {possible_indices}') + diff --git a/eos/projection.py b/eos/projection.py index 3244c17..fbe0b74 100644 --- a/eos/projection.py +++ b/eos/projection.py @@ -394,7 +394,10 @@ class YZProjection(ProjectionInterface): if not 'norm' in kwargs: kwargs['norm'] = LogNorm() - self._graph = plt.imshow(self.data.I[:, ::-1].T, **kwargs) + self._graph = plt.imshow(self.data.I.T, + extent=(float(self.y[0]), float(self.y[-1]), + float(self.z[0]), float(self.z[-1])), + **kwargs) if cmap: plt.colorbar(label='I / cpm') @@ -410,7 +413,7 @@ class YZProjection(ProjectionInterface): """ Inline update of previous plot by just updating the data. """ - self._graph.set_array(self.data.I[:, ::-1].T) + self._graph.set_array(self.data.I.T) def draw_yzcross(self, event): if event.inaxes is not self._graph_axis: @@ -426,3 +429,88 @@ class YZProjection(ProjectionInterface): for art in list(plt.gca().lines)+list(plt.gca().texts): art.remove() plt.draw() + +class TofZProjection(ProjectionInterface): + tof: np.ndarray + z: np.ndarray + + data: np.recarray + _dtype = np.dtype([ + ('cts', np.float64), + ('I', np.float64), + ('err', np.float64), + ]) + + def __init__(self, tau, foldback=False): + self.z = np.arange(Detector.nBlades*Detector.nWires+1)-0.5 + if foldback: + self.tof = np.arange(0, tau, 0.0005) + else: + self.tof = np.arange(0, 2*tau, 0.0005) + self.data = np.zeros((self.tof.shape[0]-1, self.z.shape[0]-1), dtype=self._dtype).view(np.recarray) + self.monitor = 0. + + def project(self, dataset: EventDatasetProtocol, monitor: float): + detYi, detZi, detX, delta = Detector.pixelLookUp[dataset.data.events.pixelID-1].T + + cts , *_ = np.histogram2d(dataset.data.events.tof, detZi, bins=(self.tof, self.z)) + self.data.cts += cts + self.monitor += monitor + + self.data.I = self.data.cts / self.monitor + self.data.err = np.sqrt(self.data.cts) / self.monitor + + def clear(self): + self.data[:] = 0 + self.monitor = 0. + + def plot(self, **kwargs): + from matplotlib import pyplot as plt + from matplotlib.colors import LogNorm + + if 'colorbar' in kwargs: + cmap=True + del(kwargs['colorbar']) + else: + cmap=False + if not 'aspect' in kwargs: + kwargs['aspect'] = 'auto' + + if not 'norm' in kwargs: + kwargs['norm'] = LogNorm() + + self._graph = plt.imshow(self.data.I.T, + extent=(float(self.tof[0])*1e3, float(self.tof[-1])*1e3, + float(self.z[0]), float(self.z[-1])), + **kwargs) + if cmap: + plt.colorbar(label='I / cpm') + + plt.xlabel('Time of Flight / ms') + plt.ylabel('Z') + plt.xlim(self.tof[0]*1e3, self.tof[-1]*1e3) + plt.ylim(self.z[0], self.z[-1]) + + self._graph_axis = plt.gca() + plt.connect('button_press_event', self.draw_tzcross) + + def update_plot(self): + """ + Inline update of previous plot by just updating the data. + """ + self._graph.set_array(self.data.I.T) + + def draw_tzcross(self, event): + if event.inaxes is not self._graph_axis: + return + from matplotlib import pyplot as plt + tbm = self._graph_axis.figure.canvas.manager.toolbar.mode + if event.button is plt.MouseButton.LEFT and tbm=='': + plt.plot([event.xdata, event.xdata], [self.z[0], self.z[-1]], '-', color='grey') + plt.plot([self.tof[0]*1e3, self.tof[-1]*1e3], [event.ydata, event.ydata], '-', color='grey') + plt.text(event.xdata, event.ydata, f'({event.xdata:.2f}, {event.ydata:.1f})', backgroundcolor='white') + plt.draw() + if event.button is plt.MouseButton.RIGHT and tbm=='': + for art in list(plt.gca().lines)+list(plt.gca().texts): + art.remove() + plt.draw() diff --git a/eos/reduction_e2h.py b/eos/reduction_e2h.py index cb176e9..75b8c76 100644 --- a/eos/reduction_e2h.py +++ b/eos/reduction_e2h.py @@ -16,10 +16,11 @@ from .header import Header from .instrument import LZGrid from .normalization import LZNormalisation from .options import E2HConfig, E2HPlotArguments, IncidentAngle, MonitorType, E2HPlotSelection -from . import event_handling as eh, event_analysis as ea +from . import event_handling as eh from .path_handling import PathResolver -from .projection import LZProjection, ProjectionInterface, YZProjection +from .projection import LZProjection, ProjectionInterface, TofZProjection, YZProjection +NEEDS_LAMDA = (E2HPlotSelection.All, E2HPlotSelection.LT, E2HPlotSelection.Q, E2HPlotSelection.L) class E2HReduction: config: E2HConfig @@ -50,6 +51,9 @@ class E2HReduction: # live update implies plotting self.config.reduction.show_plot = True + if not self.config.reduction.fast or self.config.reduction.plot in NEEDS_LAMDA: + from . import event_analysis as ea + # Actions on datasets not used for normalization self.event_actions = eh.ApplyPhaseOffset(self.config.experiment.chopperPhaseOffset) if not self.config.reduction.fast: @@ -64,9 +68,13 @@ class E2HReduction: self.event_actions |= eh.FilterMonitorThreshold(self.config.experiment.lowCurrentThreshold) if not self.config.reduction.fast: self.event_actions |= eh.FilterStrangeTimes() + if self.config.reduction.plot==E2HPlotSelection.TZ: + # perform time fold-back and corrections for tof if not fast mode + self.event_actions |= ea.MergeFrames() + self.event_actions |= ea.AnalyzePixelIDs(self.config.experiment.yRange) + self.event_actions |= eh.TofTimeCorrection(self.config.experiment.incidentAngle==IncidentAngle.alphaF) # select needed actions in depenence of plots - if self.config.reduction.plot in [E2HPlotSelection.All, E2HPlotSelection.LT, E2HPlotSelection.Q, - E2HPlotSelection.L]: + if self.config.reduction.plot in NEEDS_LAMDA: self.event_actions |= ea.MergeFrames() self.event_actions |= ea.AnalyzePixelIDs(self.config.experiment.yRange) self.event_actions |= eh.TofTimeCorrection(self.config.experiment.incidentAngle==IncidentAngle.alphaF) @@ -77,7 +85,7 @@ class E2HReduction: if self.config.reduction.plot in [E2HPlotSelection.All, E2HPlotSelection.LT, E2HPlotSelection.Q]: self.grid = LZGrid(0.01, [0.0, 0.25]) - if self.config.reduction.plot in [E2HPlotSelection.LT, E2HPlotSelection.YZ]: + if self.config.reduction.plot in [E2HPlotSelection.LT, E2HPlotSelection.YZ, E2HPlotSelection.TZ]: self.plot_kwds['colorbar'] = True self.plot_kwds['cmap'] = str(self.config.reduction.plot_colormap) @@ -119,6 +127,9 @@ class E2HReduction: if self.config.reduction.plot==E2HPlotSelection.YZ: self.projection = YZProjection() + if self.config.reduction.plot==E2HPlotSelection.TZ: + self.projection = TofZProjection(last_file_header.timing.tau, foldback=not self.config.reduction.fast) + def read_data(self): fileName = self.file_list[self.file_index] self.file_index += 1