From a504c0915639886c71fd7733fb45e6cfae2e75f5 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 11:10:59 +0200 Subject: [PATCH 01/29] Hackathon 2025 reporting WIP --- ptypy/core/ptycho.py | 112 ++++++++++++++++++------- ptypy/utils/__init__.py | 1 + ptypy/utils/reporting.py | 20 +++++ templates/misc/moonflower_reporting.py | 60 +++++++++++++ 4 files changed, 163 insertions(+), 30 deletions(-) create mode 100644 ptypy/utils/reporting.py create mode 100644 templates/misc/moonflower_reporting.py diff --git a/ptypy/core/ptycho.py b/ptypy/core/ptycho.py index 61dc1ccd6..4490b9862 100644 --- a/ptypy/core/ptycho.py +++ b/ptypy/core/ptycho.py @@ -267,7 +267,32 @@ class Ptycho(Base): doc = Switch to request the production of a movie from the dumped plots at the end of the reconstruction. - [io.benchmark] + [io.report] + default = None + type = Param + help = Final report parameters + doc = Container for the final report parameters. + userlevel = 2 + + [io.report.metrics] + default = 'all' + type = str + help = Compute metrics for the report + doc = Choose which metrics to compute for the final report. + [``'all'``] + choices = ['all'] + userlevel = 2 + + [io.report.maps] + default = 'all' + type = str + help = Compute maps for the report + doc = Choose which maps to compute for the final report. + [``'all'``] + choices = ['all'] + userlevel = 2 + + [io.report.benchmark] default = None type = str help = Produce timings for benchmarking the performance of data loaders and engines @@ -323,8 +348,8 @@ def __init__(self, pars=None, level=2, **kwargs): see :py:meth:`init_communication` - 4 : also initializes reconstruction engines, see :py:meth:`init_engine` - - >= 4 : also and starts reconstruction - see :py:meth:`run` + - >=5 : also and starts reconstruction + see :py:meth:`run` """ super(Ptycho, self).__init__(None, 'Ptycho') @@ -381,18 +406,19 @@ def __init__(self, pars=None, level=2, **kwargs): ) if level >= 1: - logger.info('\n' + headerline('Ptycho init level 1', 'l')) + logger.info('\n' + headerline('Ptycho init level 1 (init_structures)', 'l')) self.init_structures() if level >= 2: - logger.info('\n' + headerline('Ptycho init level 2', 'l')) + logger.info('\n' + headerline('Ptycho init level 2 (init_data)', 'l')) self.init_data() if level >= 3: - logger.info('\n' + headerline('Ptycho init level 3', 'l')) + logger.info('\n' + headerline('Ptycho init level 3 (init_communication)', 'l')) self.init_communication() if level >= 4: - logger.info('\n' + headerline('Ptycho init level 4', 'l')) + logger.info('\n' + headerline('Ptycho init level 4 (init_engine)', 'l')) self.init_engine() if level >= 5: + logger.info('\n' + headerline('Ptycho init level 5 (run)', 'l')) self.run() self.finalize() @@ -446,7 +472,7 @@ def _configure(self): self.runtime.run = self.paths.run(p.run) # Benchmark - if self.p.io.benchmark == 'all': + if self.p.io.report.benchmark == 'all': self.benchmark = u.Param() self.benchmark.data_load = 0 self.benchmark.engine_init = 0 @@ -535,10 +561,10 @@ def init_data(self, print_stats=True): """ # Load the data. This call creates automatically the scan managers, # which create the views and the PODs. Sets self.new_data - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: while not self.new_data: self.new_data = self.model.new_data() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.data_load += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.data_load += t.duration # Print stats parallel.barrier() @@ -658,16 +684,16 @@ def run(self, label=None, epars=None, engine=None): # Prepare the engine ilog_message('%s: initializing engine' %engine.p.name) - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: engine.initialize() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.engine_init += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.engine_init += t.duration # One .prepare() is always executed, as Ptycho may hold data ilog_message('%s: preparing engine' %engine.p.name) self.new_data = [(d.label, d) for d in self.diff.S.values()] - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: engine.prepare() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.engine_prepare += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.engine_prepare += t.duration # Start the iteration loop ilog_streamer('%s: starting engine' %engine.p.name) @@ -679,16 +705,16 @@ def run(self, label=None, epars=None, engine=None): parallel.barrier() # Check for new data - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: self.new_data = self.model.new_data() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.data_load += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.data_load += t.duration # Last minute preparation before a contiguous block of # iterations if self.new_data: - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: engine.prepare() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.engine_prepare += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.engine_prepare += t.duration # Keep loading data, unless we have reached minimum nr. of frames or end of scan if (len(self.diff.V) < self.p.min_frames_for_recon) and not self.model.end_of_scan: @@ -709,9 +735,9 @@ def run(self, label=None, epars=None, engine=None): engine.numiter += engine.p.numiter_contiguous # One iteration - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: engine.iterate() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.engine_iterate += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.engine_iterate += t.duration # Display runtime information and do saving if parallel.master: @@ -740,9 +766,17 @@ def run(self, label=None, epars=None, engine=None): ilog_newline() # Done. Let the engine finish up - with LogTime(self.p.io.benchmark == 'all') as t: + with LogTime(self.p.io.report.benchmark == 'all') as t: engine.finalize() - if (self.p.io.benchmark == 'all') and parallel.master: self.benchmark.engine_finalize += t.duration + if (self.p.io.report.benchmark == 'all') and parallel.master: self.benchmark.engine_finalize += t.duration + + # Create report if requested + if self.p.io.report is not None: + self.create_report() + + # Append benchmark info to report + if (self.p.io.report.benchmark == 'all') and parallel.master: + self.report['benchmark'] = self.benchmark # Save if self.p.io.rfile: @@ -752,14 +786,6 @@ def run(self, label=None, epars=None, engine=None): # Time the initialization self.runtime.stop = time.asctime() - # Save benchmarks to json file - if (self.p.io.benchmark == 'all') and parallel.master: - try: - with open(self.paths.home + "/benchmark.json", "w") as json_file: - json.dump(self.benchmark, json_file) - logger.info("Benchmarks have been written to %s" %self.paths.home + "/benchmark.json") - except Exception as e: - logger.warning("Failed to write benchmarks to file: %s" %e) elif epars is not None: # A fresh set of engine parameters arrived. @@ -1039,6 +1065,9 @@ def save_run(self, alt_file=None, kind='minimal', force_overwrite=True): for ID, S in self.obj.storages.items(): content.positions[ID] = np.array([v.coord for v in S.views if v.pod.pr_view.layer==0]) + if self.report is not None: + content.report = self.report + h5opt = io.h5options['UNSUPPORTED'] io.h5options['UNSUPPORTED'] = 'ignore' logger.info('Saving to %s' % dest_file) @@ -1159,6 +1188,29 @@ def restore_state(self, name="baseline", reformat_exit=True): for scan in self.model.scans.values(): scan._initialize_exit(list(self.pods.values())) + def create_report(self): + """ + Create a final report at the end of a reconstruction. + + For now: just print out info. + + WIP: Some metrics that are worth knowing about the reconstruction: + *what was the probe size (probes are messy, so a metric or various metrics would need to be found and reported) + *what was the average step size + *using those numbers, what was the (linear / area) overlap in the sample plane + *using those numbers, what was the oversampling in the detector plane + *what do these photon error metrics mean + *maps of which pixels are covered by how many views (how often do they get updated) + *fluence map - how much of the total intensity ended up in each pixel of the object + *just again saying there were X diffraction patterns of size N * M + """ + logger.info(headerline('Ptycho final report', 'l', '=')) + self.report = {} + if self.p.io.report.metrics == 'all': + self.report['metrics'] = u.reporting.calculate_metrics(self) + if self.p.io.report.maps == 'all': + self.report['maps'] = u.reporting.calculate_maps(self) + def _redistribute_data(self, div = 'rect', obj_storage=None): """ This function redistributes data among nodes, so that each diff --git a/ptypy/utils/__init__.py b/ptypy/utils/__init__.py index 37df5cf85..74ed265c6 100644 --- a/ptypy/utils/__init__.py +++ b/ptypy/utils/__init__.py @@ -16,6 +16,7 @@ from .citations import * from . import descriptor from . import parallel +from . import reporting from .. import __has_matplotlib__ as hmpl if hmpl: from .plot_utils import * diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py new file mode 100644 index 000000000..3ce331a21 --- /dev/null +++ b/ptypy/utils/reporting.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +Reporting functions + +This file is part of the PTYPY package. + + :copyright: Copyright 2014 by the PTYPY team, see AUTHORS. + :license: see LICENSE for details. +""" + +def calculate_metrics(ptycho): + print('metrics') + ptycho.print_stats() + return {} + + +def calculate_maps(ptycho): + print('maps') + ptycho.print_stats() + return {} \ No newline at end of file diff --git a/templates/misc/moonflower_reporting.py b/templates/misc/moonflower_reporting.py new file mode 100644 index 000000000..ee6ec07a3 --- /dev/null +++ b/templates/misc/moonflower_reporting.py @@ -0,0 +1,60 @@ +""" +This script is a test for ptychographic reconstruction in the absence +of actual data. It uses the test Scan class +`ptypy.core.data.MoonFlowerScan` to provide "data". + +This tests the reporting functionality. +""" +from ptypy.core import Ptycho +from ptypy import utils as u + +import tempfile +tmpdir = tempfile.gettempdir() + +p = u.Param() + +# for verbose output +p.verbose_level = "info" + +# set home path +p.io = u.Param() +p.io.home = "/".join([tmpdir, "ptypy"]) + +# saving intermediate results +p.io.autosave = u.Param(active=False) + +# opens plotting GUI if interaction set to active) +p.io.autoplot = u.Param(active=True) +p.io.interaction = u.Param(active=True) + +p.io.report = u.Param() +p.io.report.benchmark = 'all' + +# max 200 frames (128x128px) of diffraction data +p.scans = u.Param() +p.scans.MF = u.Param() +# now you have to specify which ScanModel to use with scans.XX.name, +# just as you have to give 'name' for engines and PtyScan subclasses. +p.scans.MF.name = 'BlockVanilla' # or 'BlockFull' +p.scans.MF.data= u.Param() +p.scans.MF.data.name = 'MoonFlowerScan' +p.scans.MF.data.shape = 128 +p.scans.MF.data.num_frames = 200 +p.scans.MF.data.save = None + +# position distance in fraction of illumination frame +p.scans.MF.data.density = 0.2 +# total number of photon in empty beam +p.scans.MF.data.photons = 1e8 +# Gaussian FWHM of possible detector blurring +p.scans.MF.data.psf = 0. + +# attach a reconstrucion engine +p.engines = u.Param() +p.engines.engine00 = u.Param() +p.engines.engine00.name = 'DM' +p.engines.engine00.numiter = 80 + +# prepare and run +if __name__ == "__main__": + P = Ptycho(p,level=5) \ No newline at end of file From 33306beccc66f0a538963625f1b09fa8fc3ed537 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 12:04:32 +0200 Subject: [PATCH 02/29] Bug fix for inexistant reports --- ptypy/core/ptycho.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ptypy/core/ptycho.py b/ptypy/core/ptycho.py index 4490b9862..f15f59707 100644 --- a/ptypy/core/ptycho.py +++ b/ptypy/core/ptycho.py @@ -1065,7 +1065,7 @@ def save_run(self, alt_file=None, kind='minimal', force_overwrite=True): for ID, S in self.obj.storages.items(): content.positions[ID] = np.array([v.coord for v in S.views if v.pod.pr_view.layer==0]) - if self.report is not None: + if self.p.io.report is not None: content.report = self.report h5opt = io.h5options['UNSUPPORTED'] From c3e0a750b98005bbe3efac27186cd7e158119bc2 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 13:47:05 +0200 Subject: [PATCH 03/29] fluence map function - untested --- ptypy/utils/reporting.py | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 3ce331a21..de7732dce 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Reporting functions @@ -7,6 +6,8 @@ :copyright: Copyright 2014 by the PTYPY team, see AUTHORS. :license: see LICENSE for details. """ +from .. import abs2 +import numpy as np def calculate_metrics(ptycho): print('metrics') @@ -17,4 +18,38 @@ def calculate_metrics(ptycho): def calculate_maps(ptycho): print('maps') ptycho.print_stats() - return {} \ No newline at end of file + return {} + + +def map(ptycho, ID='fluence'): + """ + Compute fluence or transmission map(s) for all object storages and modes + + Parameters + ---------- + ptycho : The Ptycho object instance to draw information from. + ID : one of 'fluence' (default), 'transmission' or 'coverage' + """ + if ID not in ['fluence', 'transmission', 'coverage']: + raise NotImplementedError(f'Unknown map {ID}') + + # Delete existing copy if it exists + if f'C{ID}' in [c.ID for c in ptycho.obj.copies]: + del ptycho.obj.owner._pool['C'][f'C{ID}'] + + # Create copy of object container + fmap = ptycho.obj.copy(ID=ID, fill=0., dtype='real') + + # Loop through storages and add the probe intensities + for sname, sobj in fmap.S.items(): + for v in sobj.views: + if not v.active: + continue + for pid, pod in v.pods.items(): + if ID == 'transmission': + sobj[v] += abs2(pod.probe*pod.obj) + elif ID == 'fluence': + sobj[v] += abs2(pod.probe) + elif ID == 'coverage': + sobj[v] += np.ones_like(pod.probe.real) + return fmap \ No newline at end of file From c6b2ac6f2c968ef9c827a10804650da6fc37d8f2 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 14:00:35 +0200 Subject: [PATCH 04/29] bugfix for reporting --- ptypy/core/ptycho.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ptypy/core/ptycho.py b/ptypy/core/ptycho.py index f15f59707..a14db0950 100644 --- a/ptypy/core/ptycho.py +++ b/ptypy/core/ptycho.py @@ -471,14 +471,19 @@ def _configure(self): # Find run name self.runtime.run = self.paths.run(p.run) - # Benchmark - if self.p.io.report.benchmark == 'all': - self.benchmark = u.Param() - self.benchmark.data_load = 0 - self.benchmark.engine_init = 0 - self.benchmark.engine_prepare = 0 - self.benchmark.engine_iterate = 0 - self.benchmark.engine_finalize = 0 + # Reporting + if self.p.io.report: + # Initialize report dict + self.report = {} + + # Benchmark + if self.p.io.report.benchmark == 'all': + self.benchmark = u.Param() + self.benchmark.data_load = 0 + self.benchmark.engine_init = 0 + self.benchmark.engine_prepare = 0 + self.benchmark.engine_iterate = 0 + self.benchmark.engine_finalize = 0 def init_communication(self): """ @@ -1205,7 +1210,6 @@ def create_report(self): *just again saying there were X diffraction patterns of size N * M """ logger.info(headerline('Ptycho final report', 'l', '=')) - self.report = {} if self.p.io.report.metrics == 'all': self.report['metrics'] = u.reporting.calculate_metrics(self) if self.p.io.report.maps == 'all': From ad8372936458aa7bd8703bb9f260179562f26676 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Mon, 15 Sep 2025 14:51:45 +0200 Subject: [PATCH 05/29] added simple probe size calculaton via FWHM --- ptypy/utils/reporting.py | 51 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index de7732dce..0f21d1dfb 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -6,13 +6,18 @@ :copyright: Copyright 2014 by the PTYPY team, see AUTHORS. :license: see LICENSE for details. """ -from .. import abs2 +from .math_utils import abs2 import numpy as np +from scipy.interpolate import UnivariateSpline def calculate_metrics(ptycho): print('metrics') ptycho.print_stats() - return {} + + metrics = {} + metrics['probe_size'] = measure_probe_size(ptycho) + + return metrics def calculate_maps(ptycho): @@ -52,4 +57,44 @@ def map(ptycho, ID='fluence'): sobj[v] += abs2(pod.probe) elif ID == 'coverage': sobj[v] += np.ones_like(pod.probe.real) - return fmap \ No newline at end of file + return fmap + + +def measure_probe_size(ptycho): + """ + Measuring the size of the reconstructed probe + """ + + results = {} + + # for each storage do: + for sname, sprobe in ptycho.probe.S.items(): + probes = sprobe.data + pixel_size = sprobe._psize + results[sname] = {} + + probe_intensity = np.sum(abs2(probes), axis=0) + fwhm_x,fwhm_y = size_estimate_FWHM(probe_intensity) + results[sname]['FWHM_px'] = (fwhm_y, fwhm_x) + results[sname]['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) + + return results + +def size_estimate_FWHM(probe_intensity): + """ + estimate the probe size via a simple FWHM measurment + of the projected intensty profiles + """ + projection_x = np.sum(probe_intensity, axis=0) + projection_y = np.sum(probe_intensity, axis=1) + Ny, Nx = np.shape(probe_intensity) + + # spline interpolate the profiles and find all the I_max/2 crossings + edges_x = UnivariateSpline(np.linspace(0, Nx-1, Nx), projection_x-projection_x.max()/2).roots() + edges_y = UnivariateSpline(np.linspace(0, Ny-1, Ny), projection_y-projection_y.max()/2).roots() + + # the distance between the first and the last I_max/2 crossing is the FWHM + fwhm_x = abs(edges_x[0] - edges_x[-1]) # measured in pixels + fwhm_y = abs(edges_y[0] - edges_y[-1]) # measured in pixels + + return fwhm_x,fwhm_y \ No newline at end of file From 540614922781b1dbe7afeeafa0cb71450b18fee8 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 14:51:52 +0200 Subject: [PATCH 06/29] maps reporting --- ptypy/utils/reporting.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 0f21d1dfb..fa6766dd3 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -21,9 +21,16 @@ def calculate_metrics(ptycho): def calculate_maps(ptycho): - print('maps') - ptycho.print_stats() - return {} + # Fluence map + fluence = map(ptycho, ID='fluence') + # Transmission map + transmission = map(ptycho, ID='transmission') + # Coverage map + coverage = map(ptycho, ID='coverage') + + return {'fluence': fluence, + 'transmission': transmission, + 'coverage': coverage} def map(ptycho, ID='fluence'): @@ -52,13 +59,13 @@ def map(ptycho, ID='fluence'): continue for pid, pod in v.pods.items(): if ID == 'transmission': - sobj[v] += abs2(pod.probe*pod.obj) + sobj[v] += abs2(pod.probe*pod.object) elif ID == 'fluence': sobj[v] += abs2(pod.probe) elif ID == 'coverage': sobj[v] += np.ones_like(pod.probe.real) - return fmap + return {sname: sobj.data[0] for sname, sobj in fmap.S} def measure_probe_size(ptycho): """ From 60a31527a6ba417a52f97ccff72371f17caf8f7f Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 14:53:54 +0200 Subject: [PATCH 07/29] useless file removed --- .idea/codeStyleSettings.xml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .idea/codeStyleSettings.xml diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 7fb8ed00d..000000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file From 12d9aa3e9072561135e2bd0e53c7649e3ba6a725 Mon Sep 17 00:00:00 2001 From: Benedikt Daurer Date: Mon, 15 Sep 2025 13:58:22 +0100 Subject: [PATCH 08/29] Added function to calculate average step size --- ptypy/utils/reporting.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index fa6766dd3..f7d84ef1c 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -10,6 +10,21 @@ import numpy as np from scipy.interpolate import UnivariateSpline +def calculate_average_step_size(ptycho, number_of_neighbours=3): + """ + Calculates the average step size + """ + ass = {} + for sname, sobj in ptycho.obj.S.items(): + allcoords = np.array([v.coord for v in sobj.views]) + distances = [] + for v in sobj.views: + distxy = (allcoords - v.coord) + distr = np.sqrt(distxy[:,0]**2 + distxy[:,1]**2) + distances.append(np.sort(distr)[1:number_of_neighbours+1]) + ass[sname] = np.array(distances).mean() + return ass + def calculate_metrics(ptycho): print('metrics') ptycho.print_stats() @@ -17,6 +32,8 @@ def calculate_metrics(ptycho): metrics = {} metrics['probe_size'] = measure_probe_size(ptycho) + ass = calculate_average_step_size(ptycho) + metrics["average_step_size"] = ass return metrics From 8874581dd09816733b4dafb74ed34d4f4d79e074 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Mon, 15 Sep 2025 15:03:26 +0200 Subject: [PATCH 09/29] prepared for more fancy probe size / shape estimation --- ptypy/utils/reporting.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 0f21d1dfb..36e7d5a68 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -62,7 +62,8 @@ def map(ptycho, ID='fluence'): def measure_probe_size(ptycho): """ - Measuring the size of the reconstructed probe + main function for measuring the size of the reconstructed probes + via different methods. """ results = {} @@ -74,10 +75,15 @@ def measure_probe_size(ptycho): results[sname] = {} probe_intensity = np.sum(abs2(probes), axis=0) + + # measure FWHM of projected intensities fwhm_x,fwhm_y = size_estimate_FWHM(probe_intensity) results[sname]['FWHM_px'] = (fwhm_y, fwhm_x) results[sname]['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) + # measure probe size by 90% intensty criterion + # ToDo + return results def size_estimate_FWHM(probe_intensity): @@ -97,4 +103,18 @@ def size_estimate_FWHM(probe_intensity): fwhm_x = abs(edges_x[0] - edges_x[-1]) # measured in pixels fwhm_y = abs(edges_y[0] - edges_y[-1]) # measured in pixels - return fwhm_x,fwhm_y \ No newline at end of file + return fwhm_x,fwhm_y + + +def size_estimate_90pecent_intensity(probe_intensity): + """ + estimate the probe size by masking the hottest pixels + until 90% of the overall probe intensity is explained + """ + + threshold = 0.1 # actual function to be placed here be put here + probe_mask = np.zeros_like(probe_intensity) + probe_mask[probe_intensity>=threshold] = 1 + + + return None \ No newline at end of file From 7582687bd8b958a87c5fb3e12a17df7d03f66246 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Mon, 15 Sep 2025 15:48:01 +0200 Subject: [PATCH 10/29] fixed bug --- ptypy/utils/reporting.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index be92d443d..777618bd8 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -31,9 +31,7 @@ def calculate_metrics(ptycho): metrics = {} metrics['probe_size'] = measure_probe_size(ptycho) - - ass = calculate_average_step_size(ptycho) - metrics["average_step_size"] = ass + metrics["average_step_size"] = calculate_average_step_size(ptycho) return metrics @@ -82,7 +80,7 @@ def map(ptycho, ID='fluence'): elif ID == 'coverage': sobj[v] += np.ones_like(pod.probe.real) - return {sname: sobj.data[0] for sname, sobj in fmap.S} + return {sname: sobj.data[0] for sname, sobj in fmap.S.items()} def measure_probe_size(ptycho): """ From d5eed004b1b27969c5fd381d604d94bbaae0b2f8 Mon Sep 17 00:00:00 2001 From: Marcello <40807932+marcellofonda@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:56:08 +0200 Subject: [PATCH 11/29] Add `find_threshold` function Add `find_threshold` function to find the pixels responsible for a given fraction of the total intensity --- ptypy/utils/reporting.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 777618bd8..9d8b0b72e 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -139,4 +139,30 @@ def size_estimate_90pecent_intensity(probe_intensity): probe_mask[probe_intensity>=threshold] = 1 - return None \ No newline at end of file + return None + +def find_threshold(intensities, fraction=0.9): + """ + Find the intensity threshold such that a specified fraction of the total intensity is below this threshold. + + Parameters: + - intensities (numpy.ndarray): An array of pixel intensities. + - fraction (float): The fraction of total intensity to use as a cutoff (default is 0.9 for 90%). + + Returns: + - float: The intensity threshold. + """ + import numpy as np + + # Flatten the array to 1D for processing and sort it + sorted_vals = np.sort(intensities.ravel()) + # Compute the cumulative sum from the end to find the 90% cutoff + cumulative = np.cumsum(sorted_vals[::-1]) + # Total intensity (sum of all pixel values) + total = cumulative[-1] + # Find the index where cumulative sum reaches fraction of total + inverted_index = np.searchsorted(cumulative, fraction * total) + # Reverse the index to get the correct position in the sorted array + cutoff_index = len(sorted_vals) - inverted_index - 1 if inverted_index < len(sorted_vals) else 0 + + return float(sorted_vals[cutoff_index]) \ No newline at end of file From c3ff2d85d1483750c0e4aec81959facc5e48d055 Mon Sep 17 00:00:00 2001 From: Benedikt Daurer Date: Mon, 15 Sep 2025 14:51:20 +0100 Subject: [PATCH 12/29] Cleaning up --- ptypy/utils/reporting.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 9d8b0b72e..9ec2db7f6 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -10,25 +10,7 @@ import numpy as np from scipy.interpolate import UnivariateSpline -def calculate_average_step_size(ptycho, number_of_neighbours=3): - """ - Calculates the average step size - """ - ass = {} - for sname, sobj in ptycho.obj.S.items(): - allcoords = np.array([v.coord for v in sobj.views]) - distances = [] - for v in sobj.views: - distxy = (allcoords - v.coord) - distr = np.sqrt(distxy[:,0]**2 + distxy[:,1]**2) - distances.append(np.sort(distr)[1:number_of_neighbours+1]) - ass[sname] = np.array(distances).mean() - return ass - def calculate_metrics(ptycho): - print('metrics') - ptycho.print_stats() - metrics = {} metrics['probe_size'] = measure_probe_size(ptycho) metrics["average_step_size"] = calculate_average_step_size(ptycho) @@ -165,4 +147,19 @@ def find_threshold(intensities, fraction=0.9): # Reverse the index to get the correct position in the sorted array cutoff_index = len(sorted_vals) - inverted_index - 1 if inverted_index < len(sorted_vals) else 0 - return float(sorted_vals[cutoff_index]) \ No newline at end of file + return float(sorted_vals[cutoff_index]) + +def calculate_average_step_size(ptycho, number_of_neighbours=3): + """ + Calculates the average step size + """ + ass = {} + for sname, sobj in ptycho.obj.S.items(): + allcoords = np.array([v.coord for v in sobj.views]) + distances = [] + for v in sobj.views: + distxy = (allcoords - v.coord) + distr = np.sqrt(distxy[:,0]**2 + distxy[:,1]**2) + distances.append(np.sort(distr)[1:number_of_neighbours+1]) + ass[sname] = np.array(distances).mean() + return ass From a358a456e8e94834843b609148397999026dd754 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Mon, 15 Sep 2025 16:06:39 +0200 Subject: [PATCH 13/29] calculate mask for 90% probe intensity --- ptypy/utils/reporting.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 9ec2db7f6..458b6951c 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -86,7 +86,8 @@ def measure_probe_size(ptycho): results[sname]['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) # measure probe size by 90% intensty criterion - # ToDo + illumated_area = size_estimate_90pecent_intensity(probe_intensity) + results[sname]['illumated_area'] = illumated_area return results @@ -116,12 +117,10 @@ def size_estimate_90pecent_intensity(probe_intensity): until 90% of the overall probe intensity is explained """ - threshold = 0.1 # actual function to be placed here be put here + threshold = find_threshold(probe_intensity, fraction=0.9) probe_mask = np.zeros_like(probe_intensity) probe_mask[probe_intensity>=threshold] = 1 - - - return None + return probe_mask def find_threshold(intensities, fraction=0.9): """ From ebefff2375302f439c28a8df971552faedab956f Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 16:08:02 +0200 Subject: [PATCH 14/29] maps with mask --- ptypy/utils/reporting.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 458b6951c..1761985ea 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -30,7 +30,7 @@ def calculate_maps(ptycho): 'coverage': coverage} -def map(ptycho, ID='fluence'): +def map(ptycho, ID='fluence', mask=None): """ Compute fluence or transmission map(s) for all object storages and modes @@ -60,7 +60,10 @@ def map(ptycho, ID='fluence'): elif ID == 'fluence': sobj[v] += abs2(pod.probe) elif ID == 'coverage': - sobj[v] += np.ones_like(pod.probe.real) + if mask is not None: + sobj[v] += mask + else: + sobj[v] += np.ones_like(pod.probe.real) return {sname: sobj.data[0] for sname, sobj in fmap.S.items()} From 1ed8794df9e0ec2b53cdbf416bb33546a55875c4 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Mon, 15 Sep 2025 16:47:56 +0200 Subject: [PATCH 15/29] area overlap metric --- ptypy/utils/reporting.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 1761985ea..6a1dba564 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -12,10 +12,33 @@ def calculate_metrics(ptycho): metrics = {} + + # Probe size metrics['probe_size'] = measure_probe_size(ptycho) + + # Average step size metrics["average_step_size"] = calculate_average_step_size(ptycho) + + # Coverage map using the illuminated area + metrics['area_overlap'] = calculate_area_overlap(ptycho, metrics['probe_size']) + return metrics +def calculate_area_overlap(ptycho, probe_size): + + # Extract pre-computed probe mask + mask = {sname: c['illuminated_area'] for sname, c in probe_size.items()} + + # Compute coverage map + cmap = map(ptycho, ID='coverage', mask=mask) + + # Compute metric + metric = {} + for sname, cm in cmap.items(): + cm1 = cm > 0.5 + metric[sname] = (cm-cm1).sum() / cm.sum() + + return metric def calculate_maps(ptycho): # Fluence map @@ -38,6 +61,8 @@ def map(ptycho, ID='fluence', mask=None): ---------- ptycho : The Ptycho object instance to draw information from. ID : one of 'fluence' (default), 'transmission' or 'coverage' + mask: a dictionary containing the thresholded mask of the probe for each probe storage + (used only if ID=='coverage') """ if ID not in ['fluence', 'transmission', 'coverage']: raise NotImplementedError(f'Unknown map {ID}') @@ -55,13 +80,14 @@ def map(ptycho, ID='fluence', mask=None): if not v.active: continue for pid, pod in v.pods.items(): + probe_storage = pod.pr_view.storage.ID if ID == 'transmission': sobj[v] += abs2(pod.probe*pod.object) elif ID == 'fluence': sobj[v] += abs2(pod.probe) elif ID == 'coverage': if mask is not None: - sobj[v] += mask + sobj[v] += mask[probe_storage] else: sobj[v] += np.ones_like(pod.probe.real) @@ -89,8 +115,8 @@ def measure_probe_size(ptycho): results[sname]['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) # measure probe size by 90% intensty criterion - illumated_area = size_estimate_90pecent_intensity(probe_intensity) - results[sname]['illumated_area'] = illumated_area + illuminated_area = size_estimate_90pecent_intensity(probe_intensity) + results[sname]['illuminated_area'] = illuminated_area return results From b3646945bb42855c85752e4f6aed8f22823ab933 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 08:43:26 +0200 Subject: [PATCH 16/29] added first estimation of FOV --- ptypy/utils/reporting.py | 84 +++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 6a1dba564..30e4df40f 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -13,32 +13,20 @@ def calculate_metrics(ptycho): metrics = {} - # Probe size + # probe size metrics['probe_size'] = measure_probe_size(ptycho) - # Average step size + # average step size metrics["average_step_size"] = calculate_average_step_size(ptycho) - # Coverage map using the illuminated area + # scanned vield of view + metrics["field_of_view"] = measure_field_of_view(ptycho, metrics['probe_size']) + + # area overlap metrics['area_overlap'] = calculate_area_overlap(ptycho, metrics['probe_size']) return metrics -def calculate_area_overlap(ptycho, probe_size): - - # Extract pre-computed probe mask - mask = {sname: c['illuminated_area'] for sname, c in probe_size.items()} - - # Compute coverage map - cmap = map(ptycho, ID='coverage', mask=mask) - - # Compute metric - metric = {} - for sname, cm in cmap.items(): - cm1 = cm > 0.5 - metric[sname] = (cm-cm1).sum() / cm.sum() - - return metric def calculate_maps(ptycho): # Fluence map @@ -116,7 +104,9 @@ def measure_probe_size(ptycho): # measure probe size by 90% intensty criterion illuminated_area = size_estimate_90pecent_intensity(probe_intensity) - results[sname]['illuminated_area'] = illuminated_area + results[sname]['90perI_image'] = illuminated_area + results[sname]['90perI_area_px'] = np.sum(illuminated_area) + results[sname]['90perI_area_sqm'] = np.sum(illuminated_area)*pixel_size[0]*pixel_size[1] return results @@ -177,6 +167,43 @@ def find_threshold(intensities, fraction=0.9): return float(sorted_vals[cutoff_index]) + +def measure_field_of_view(ptycho, probe_size): + """ + main function for measuring the scanned field of view + via different methods. + """ + + results = {} + # for each storage do: + for sname, sprobe in ptycho.probe.S.items(): + pixel_size = sprobe._psize + results[sname] = {} + + # estimate FOV from coverage map + FOV_from_coverage_px = FOV_estimate_coverage(ptycho, probe_size[sname]['90perI_image'], sname) + results[sname]['coverage_px'] = FOV_from_coverage_px + results[sname]['coverage_sqm'] =FOV_from_coverage_px * pixel_size[0] * pixel_size[1] + + # estimate from convex hull + #ToDo + + return results + + +def FOV_estimate_coverage(ptycho, probe_mask, sname): + """ + Estimating the imaged field of view from the probe intensity mask + and coverage map funtion. + """ + cmap = map(ptycho, ID='coverage', mask={sname: probe_mask}) + cm1 = cmap[sname] > 0.5 + return np.sum(cm1) + + #ToDo: add this to maps as well + + + def calculate_average_step_size(ptycho, number_of_neighbours=3): """ Calculates the average step size @@ -191,3 +218,22 @@ def calculate_average_step_size(ptycho, number_of_neighbours=3): distances.append(np.sort(distr)[1:number_of_neighbours+1]) ass[sname] = np.array(distances).mean() return ass + + + + +def calculate_area_overlap(ptycho, probe_size): + + # Extract pre-computed probe mask + mask = {sname: c['90perI_image'] for sname, c in probe_size.items()} + + # Compute coverage map + cmap = map(ptycho, ID='coverage', mask=mask) + + # Compute metric + metric = {} + for sname, cm in cmap.items(): + cm1 = cm > 0.5 + metric[sname] = (cm-cm1).sum() / cm.sum() + + return metric \ No newline at end of file From e12d0e7c6b1a2a4d746f92d0b9f5e26e956d2b05 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 10:34:50 +0200 Subject: [PATCH 17/29] reworked report metrics to return storage first --- ptypy/utils/reporting.py | 139 ++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 61 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 30e4df40f..854bfc268 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -12,23 +12,30 @@ def calculate_metrics(ptycho): metrics = {} - - # probe size - metrics['probe_size'] = measure_probe_size(ptycho) - # average step size - metrics["average_step_size"] = calculate_average_step_size(ptycho) + # for each storage do: + for sname, sprobe in ptycho.probe.S.items(): + metrics[sname] = {} + + # probe size + metrics[sname]['probe_size'] = measure_probe_size(ptycho, sname) + + # average step size + metrics[sname]["average_step_size"] = calculate_average_step_size(ptycho, sname) - # scanned vield of view - metrics["field_of_view"] = measure_field_of_view(ptycho, metrics['probe_size']) + # scanned vield of view + metrics[sname]["field_of_view"] = measure_field_of_view(ptycho, sname, metrics[sname]['probe_size']) - # area overlap - metrics['area_overlap'] = calculate_area_overlap(ptycho, metrics['probe_size']) + # area overlap + metrics[sname]['area_overlap'] = calculate_area_overlap(ptycho, sname, metrics[sname]['probe_size']) return metrics def calculate_maps(ptycho): + ## + pass + # Fluence map fluence = map(ptycho, ID='fluence') # Transmission map @@ -81,32 +88,28 @@ def map(ptycho, ID='fluence', mask=None): return {sname: sobj.data[0] for sname, sobj in fmap.S.items()} -def measure_probe_size(ptycho): +def measure_probe_size(ptycho, sname): """ main function for measuring the size of the reconstructed probes via different methods. """ results = {} + probes = ptycho.probe.S[sname].data + pixel_size = ptycho.probe.S[sname]._psize - # for each storage do: - for sname, sprobe in ptycho.probe.S.items(): - probes = sprobe.data - pixel_size = sprobe._psize - results[sname] = {} - - probe_intensity = np.sum(abs2(probes), axis=0) + probe_intensity = np.sum(abs2(probes), axis=0) - # measure FWHM of projected intensities - fwhm_x,fwhm_y = size_estimate_FWHM(probe_intensity) - results[sname]['FWHM_px'] = (fwhm_y, fwhm_x) - results[sname]['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) + # measure FWHM of projected intensities + fwhm_x,fwhm_y = size_estimate_FWHM(probe_intensity) + results['FWHM_px'] = (fwhm_y, fwhm_x) + results['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) - # measure probe size by 90% intensty criterion - illuminated_area = size_estimate_90pecent_intensity(probe_intensity) - results[sname]['90perI_image'] = illuminated_area - results[sname]['90perI_area_px'] = np.sum(illuminated_area) - results[sname]['90perI_area_sqm'] = np.sum(illuminated_area)*pixel_size[0]*pixel_size[1] + # measure probe size by 90% intensty criterion + illuminated_area = size_estimate_90pecent_intensity(probe_intensity) + results['90perI_image'] = illuminated_area + results['90perI_area_px'] = np.sum(illuminated_area) + results['90perI_area_sqm'] = np.sum(illuminated_area)*pixel_size[0]*pixel_size[1] return results @@ -168,25 +171,22 @@ def find_threshold(intensities, fraction=0.9): return float(sorted_vals[cutoff_index]) -def measure_field_of_view(ptycho, probe_size): +def measure_field_of_view(ptycho, sname, probe_size): """ main function for measuring the scanned field of view via different methods. """ results = {} - # for each storage do: - for sname, sprobe in ptycho.probe.S.items(): - pixel_size = sprobe._psize - results[sname] = {} + pixel_size = ptycho.probe.S[sname]._psize - # estimate FOV from coverage map - FOV_from_coverage_px = FOV_estimate_coverage(ptycho, probe_size[sname]['90perI_image'], sname) - results[sname]['coverage_px'] = FOV_from_coverage_px - results[sname]['coverage_sqm'] =FOV_from_coverage_px * pixel_size[0] * pixel_size[1] + # estimate FOV from coverage map + FOV_from_coverage_px = FOV_estimate_coverage(ptycho, probe_size['90perI_image'], sname) + results['coverage_px'] = FOV_from_coverage_px + results['coverage_sqm'] = FOV_from_coverage_px * pixel_size[0] * pixel_size[1] - # estimate from convex hull - #ToDo + # estimate from convex hull + #ToDo return results @@ -203,37 +203,54 @@ def FOV_estimate_coverage(ptycho, probe_mask, sname): #ToDo: add this to maps as well - -def calculate_average_step_size(ptycho, number_of_neighbours=3): +def calculate_area_overlap(ptycho, sname, probe_size): """ - Calculates the average step size + main function for measuring the scanned field of view + via different methods. """ - ass = {} - for sname, sobj in ptycho.obj.S.items(): - allcoords = np.array([v.coord for v in sobj.views]) - distances = [] - for v in sobj.views: - distxy = (allcoords - v.coord) - distr = np.sqrt(distxy[:,0]**2 + distxy[:,1]**2) - distances.append(np.sort(distr)[1:number_of_neighbours+1]) - ass[sname] = np.array(distances).mean() - return ass + results = {} + # estimate via the coverage map + area_overlap = estimate_area_overlap_via_coverage(ptycho, probe_size['90perI_image'], sname) + results['from_coverage'] = area_overlap + # estimate from convex hull + #ToDo -def calculate_area_overlap(ptycho, probe_size): + return results + + +def estimate_area_overlap_via_coverage(ptycho, probe_mask, sname): + cmap = map(ptycho, ID='coverage', mask={sname: probe_mask}) + cm1 = cmap[sname] > 0.5 + return (cmap[sname]-cm1).sum() / cmap[sname].sum() - # Extract pre-computed probe mask - mask = {sname: c['90perI_image'] for sname, c in probe_size.items()} - # Compute coverage map - cmap = map(ptycho, ID='coverage', mask=mask) - # Compute metric - metric = {} - for sname, cm in cmap.items(): - cm1 = cm > 0.5 - metric[sname] = (cm-cm1).sum() / cm.sum() - return metric \ No newline at end of file +def calculate_average_step_size(ptycho, sname, number_of_neighbours=3): + """ + Calculates the average step size is different ways: + """ + ass = {} + + # from the n nearest neighbors. + ass['from_nearest_neighbors'] = estimate_step_size_NN(ptycho, sname, number_of_neighbours) + return ass + + +def estimate_step_size_NN(ptycho, sname, number_of_neighbours=3): + """ + Estimating the average step size from nearest neighbors + """ + allcoords = np.array([v.coord for v in ptycho.obj.S[sname].views]) + distances = [] + for v in ptycho.obj.S[sname].views: + distxy = (allcoords - v.coord) + distr = np.sqrt(distxy[:,0]**2 + distxy[:,1]**2) + distances.append(np.sort(distr)[1:number_of_neighbours+1]) + ass = np.array(distances).mean() + return ass + + From fd15baa70da84adeeafb547f0d410506c916fecc Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 10:57:38 +0200 Subject: [PATCH 18/29] getting rid of reporting maps, putting it in metrics, making it go storage first --- ptypy/core/ptycho.py | 11 ----------- ptypy/utils/reporting.py | 24 +++++++++--------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/ptypy/core/ptycho.py b/ptypy/core/ptycho.py index a14db0950..b85f9fa05 100644 --- a/ptypy/core/ptycho.py +++ b/ptypy/core/ptycho.py @@ -282,15 +282,6 @@ class Ptycho(Base): [``'all'``] choices = ['all'] userlevel = 2 - - [io.report.maps] - default = 'all' - type = str - help = Compute maps for the report - doc = Choose which maps to compute for the final report. - [``'all'``] - choices = ['all'] - userlevel = 2 [io.report.benchmark] default = None @@ -1212,8 +1203,6 @@ def create_report(self): logger.info(headerline('Ptycho final report', 'l', '=')) if self.p.io.report.metrics == 'all': self.report['metrics'] = u.reporting.calculate_metrics(self) - if self.p.io.report.maps == 'all': - self.report['maps'] = u.reporting.calculate_maps(self) def _redistribute_data(self, div = 'rect', obj_storage=None): """ diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 854bfc268..f27a5ee22 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -13,6 +13,10 @@ def calculate_metrics(ptycho): metrics = {} + + # ToDo: should actually go over all probe storages and all obj storages + # in two for loops + # for each storage do: for sname, sprobe in ptycho.probe.S.items(): metrics[sname] = {} @@ -29,23 +33,13 @@ def calculate_metrics(ptycho): # area overlap metrics[sname]['area_overlap'] = calculate_area_overlap(ptycho, sname, metrics[sname]['probe_size']) - return metrics - - -def calculate_maps(ptycho): - ## - pass + # some maps + metrics[sname]['maps'] = {} + for x in ['fluence', 'transmission', 'coverage']: + metrics[sname]['maps'][x] = map(ptycho, ID=x)[sname] - # Fluence map - fluence = map(ptycho, ID='fluence') - # Transmission map - transmission = map(ptycho, ID='transmission') - # Coverage map - coverage = map(ptycho, ID='coverage') + return metrics - return {'fluence': fluence, - 'transmission': transmission, - 'coverage': coverage} def map(ptycho, ID='fluence', mask=None): From 4d9f5f0f58c7a60ea88ac014ade59e06d41c386e Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Tue, 16 Sep 2025 10:53:41 +0200 Subject: [PATCH 19/29] WIP oversampling --- ptypy/utils/reporting.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index f27a5ee22..3a673acd4 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -82,6 +82,22 @@ def map(ptycho, ID='fluence', mask=None): return {sname: sobj.data[0] for sname, sobj in fmap.S.items()} +def calculate_geometric_oversampling(probe_size, probe_shape): + """ + Calulate the oversampling based on the evaluated probe size. + Note that this likely underestimage the + + Parameters: + ----------- + probe_size: evaluated probe dimensions (Y, X) + probe_shape: probe array shape (Y,X) + + returns: + -------- + oversampling (Y, X) + """ + return probe_shape[0]/probe_size[0], probe_shape[1]/probe_size[1] + def measure_probe_size(ptycho, sname): """ main function for measuring the size of the reconstructed probes From 4c27b982bb0c00c95b22a6682da7a8c452a25329 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Tue, 16 Sep 2025 11:14:57 +0200 Subject: [PATCH 20/29] Added oversampling --- ptypy/utils/reporting.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 3a673acd4..6b9be7086 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -33,6 +33,10 @@ def calculate_metrics(ptycho): # area overlap metrics[sname]['area_overlap'] = calculate_area_overlap(ptycho, sname, metrics[sname]['probe_size']) + # oversampling + metrics[sname]['oversampling_area'] = calculate_geometric_oversampling(metrics[sname]['probe_size']['90perI_width_px'], sprobe.shape ) + metrics[sname]['oversampling_FWHM'] = calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_px'], sprobe.shape ) + # some maps metrics[sname]['maps'] = {} for x in ['fluence', 'transmission', 'coverage']: @@ -41,7 +45,6 @@ def calculate_metrics(ptycho): return metrics - def map(ptycho, ID='fluence', mask=None): """ Compute fluence or transmission map(s) for all object storages and modes @@ -115,12 +118,23 @@ def measure_probe_size(ptycho, sname): results['FWHM_px'] = (fwhm_y, fwhm_x) results['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) - # measure probe size by 90% intensty criterion + # measure probe area by 90% intensty criterion illuminated_area = size_estimate_90pecent_intensity(probe_intensity) results['90perI_image'] = illuminated_area + + # Total area results['90perI_area_px'] = np.sum(illuminated_area) results['90perI_area_sqm'] = np.sum(illuminated_area)*pixel_size[0]*pixel_size[1] + # Extent in x and y + sum_y = illuminated_area.sum(axis=0) + sum_x = illuminated_area.sum(axis=1) + y_nz = np.nonzero(sum_y)[0] + x_nz = np.nonzero(sum_x)[0] + wy, wx = (1 + y_nz[-1] - y_nz[0], 1 + x_nz[-1] - x_nz[0]) + results['90perI_width_px'] = (wy, wx) + results['90perI_width_m'] = (wy*pixel_size[0], wx*pixel_size[1]) + return results def size_estimate_FWHM(probe_intensity): From efb6ff4de9d83bffb66d04b21828052aa10ed78f Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Tue, 16 Sep 2025 11:23:10 +0200 Subject: [PATCH 21/29] debug for oversampling --- ptypy/utils/reporting.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 6b9be7086..cb547b500 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -34,8 +34,9 @@ def calculate_metrics(ptycho): metrics[sname]['area_overlap'] = calculate_area_overlap(ptycho, sname, metrics[sname]['probe_size']) # oversampling - metrics[sname]['oversampling_area'] = calculate_geometric_oversampling(metrics[sname]['probe_size']['90perI_width_px'], sprobe.shape ) - metrics[sname]['oversampling_FWHM'] = calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_px'], sprobe.shape ) + oversampling = {'area': calculate_geometric_oversampling(metrics[sname]['probe_size']['90perI_width_px'], sprobe.shape[1:]), + 'FWHM': calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_px'], sprobe.shape[1:])} + metrics[sname]['oversampling'] = oversampling # some maps metrics[sname]['maps'] = {} From b892483907a8cea34ef65084e6cbc74efdd7fc33 Mon Sep 17 00:00:00 2001 From: Pierre Thibault Date: Tue, 16 Sep 2025 11:26:13 +0200 Subject: [PATCH 22/29] debug for oversampling --- ptypy/utils/reporting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index cb547b500..eeeb9ab62 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -130,8 +130,8 @@ def measure_probe_size(ptycho, sname): # Extent in x and y sum_y = illuminated_area.sum(axis=0) sum_x = illuminated_area.sum(axis=1) - y_nz = np.nonzero(sum_y)[0] - x_nz = np.nonzero(sum_x)[0] + x_nz = np.nonzero(sum_y)[0] + y_nz = np.nonzero(sum_x)[0] wy, wx = (1 + y_nz[-1] - y_nz[0], 1 + x_nz[-1] - x_nz[0]) results['90perI_width_px'] = (wy, wx) results['90perI_width_m'] = (wy*pixel_size[0], wx*pixel_size[1]) From 3a9f5482bc5e85341e262272c88703ca5bc5ba89 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 14:01:03 +0200 Subject: [PATCH 23/29] adding final things to the report --- ptypy/utils/reporting.py | 45 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index eeeb9ab62..43b819c71 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -29,19 +29,21 @@ def calculate_metrics(ptycho): # scanned vield of view metrics[sname]["field_of_view"] = measure_field_of_view(ptycho, sname, metrics[sname]['probe_size']) + metrics[sname]['field_of_view']['coverage_image'] = map(ptycho, ID='coverage', mask={sname: metrics[sname]['probe_size']['90perI_image']})[sname] # area overlap - metrics[sname]['area_overlap'] = calculate_area_overlap(ptycho, sname, metrics[sname]['probe_size']) + metrics[sname]['overlap'] = calculate_overlap(ptycho, sname, metrics[sname]) # oversampling oversampling = {'area': calculate_geometric_oversampling(metrics[sname]['probe_size']['90perI_width_px'], sprobe.shape[1:]), - 'FWHM': calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_px'], sprobe.shape[1:])} + 'FWHM': calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_px'], sprobe.shape[1:])} metrics[sname]['oversampling'] = oversampling # some maps metrics[sname]['maps'] = {} for x in ['fluence', 'transmission', 'coverage']: metrics[sname]['maps'][x] = map(ptycho, ID=x)[sname] + return metrics @@ -228,16 +230,47 @@ def FOV_estimate_coverage(ptycho, probe_mask, sname): #ToDo: add this to maps as well -def calculate_area_overlap(ptycho, sname, probe_size): +def calculate_overlap(ptycho, sname, metrics): """ - main function for measuring the scanned field of view - via different methods. + calculating different froms ov overlap + """ + results = {} + results['linear'] = calculate_linear_overlap(ptycho, sname, metrics) + results['area'] = calculate_area_overlap(ptycho, sname, metrics) + return results + +def calculate_linear_overlap(ptycho, sname, metrics): + """ + calculating the linear overlap in different ways + """ + + results = {} + + # estimate from FWHM + linover_fwhm = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['FWHM_m'] + linover_fwhm[linover_fwhm<0] = 0 + results['from_fwhm'] = linover_fwhm + + # estimate from FWHM + linover_90perI = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['90perI_width_m'] + linover_90perI[linover_fwhm<0] = 0 + results['from_90perI'] = linover_90perI + + # estimate from convex hull + #ToDo + + return results + + +def calculate_area_overlap(ptycho, sname, metrics): + """ + calculating the area overlap in different ways """ results = {} # estimate via the coverage map - area_overlap = estimate_area_overlap_via_coverage(ptycho, probe_size['90perI_image'], sname) + area_overlap = estimate_area_overlap_via_coverage(ptycho, metrics['probe_size']['90perI_image'], sname) results['from_coverage'] = area_overlap # estimate from convex hull From 9bc35236ed14599114a583ea3f4448adbda71b15 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 15:09:34 +0200 Subject: [PATCH 24/29] added FOV calculation via convex hull of scan positions --- ptypy/utils/reporting.py | 59 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 43b819c71..901eef338 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -213,10 +213,25 @@ def measure_field_of_view(ptycho, sname, probe_size): results['coverage_sqm'] = FOV_from_coverage_px * pixel_size[0] * pixel_size[1] # estimate from convex hull - #ToDo + hullcoords_m, hull_area_sqm = FOV_estimate_convex_hull(ptycho, sname) + results['convex_hull_coords_m'] = hullcoords_m + results['convex_hull_area_sqm'] = hull_area_sqm + results['convex_hull_area_px'] = hull_area_sqm / pixel_size[0] / pixel_size[1] + return results +def FOV_estimate_convex_hull(ptycho, sname, number_of_neighbours=3): + """ + Estimating the field of view by calculating the convex hull of + the scan positions + """ + allcoords_m = np.array([v.coord for v in ptycho.obj.S[sname].views]) + hullcoords_m = convex_hull_graham(allcoords_m) + hull_area_sqm = PolyArea(x_pos=hullcoords_m[:,1], y_pos=hullcoords_m[:,0]) + + return hullcoords_m, hull_area_sqm + def FOV_estimate_coverage(ptycho, probe_mask, sname): """ @@ -312,3 +327,45 @@ def estimate_step_size_NN(ptycho, sname, number_of_neighbours=3): return ass + +# a bunch of cunctions for the graham scan algorithm to find the complex hull +# inspired from https://gist.github.com/arthur-e/5cf52962341310f438e96c1f3c3398b8 + +def cmp(a, b): + return float(a > b) - float(a < b) + +def turn(p, q, r): + return cmp((q[..., 0] - p[..., 0])*(r[..., 1] - p[..., 1]) - (r[..., 0] - p[..., 0])*(q[..., 1] - p[..., 1]), 0) + + +def reduce_keepleft(points): + left = [] + for r in points: + while len(left) > 1 and turn(left[-2], left[-1], r) != 1: + left.pop() + if not len(left) or not np.allclose(left[-1], r, 1e-5, 1e-8, False): + left.append(r) + return left + +def sort(points): + for i in range(points.shape[-1]-1, -1, -1): + idx = np.argsort(points[:, i], kind="mergesort") + points = points[idx] + return points + +def stack(alist): + out = np.empty((len(alist), *alist[0].shape)) + for i, r in enumerate(alist): + out[i] = r + return out + +def convex_hull_graham(points): + points = sort(points) + l = reduce_keepleft(points) + u = reduce_keepleft(points[::-1]) + hull = l + u[1:-1] + return stack(hull) + +def PolyArea(x_pos,y_pos): + return 0.5*np.abs(np.dot(x_pos,np.roll(y_pos,1))-np.dot(y_pos,np.roll(x_pos,1))) + From 0cf95d1dc1725dfb43f942786502b2bfd79edfbf Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 15:40:08 +0200 Subject: [PATCH 25/29] a few doc strings and renaming the dictionary keys by same logic --- ptypy/utils/reporting.py | 43 +++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 901eef338..087fd1eac 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -6,13 +6,19 @@ :copyright: Copyright 2014 by the PTYPY team, see AUTHORS. :license: see LICENSE for details. """ + from .math_utils import abs2 import numpy as np from scipy.interpolate import UnivariateSpline def calculate_metrics(ptycho): - metrics = {} + """ + Calculates all the post reconstruction metrics that could be important. + Many of these metrics reuse other metrics calculcated earlier. + Returns a dict that ends up in the .ptyr file. + """ + metrics = {} # ToDo: should actually go over all probe storages and all obj storages # in two for loops @@ -28,23 +34,23 @@ def calculate_metrics(ptycho): metrics[sname]["average_step_size"] = calculate_average_step_size(ptycho, sname) # scanned vield of view - metrics[sname]["field_of_view"] = measure_field_of_view(ptycho, sname, metrics[sname]['probe_size']) - metrics[sname]['field_of_view']['coverage_image'] = map(ptycho, ID='coverage', mask={sname: metrics[sname]['probe_size']['90perI_image']})[sname] + metrics[sname]["field_of_view"] = measure_field_of_view(ptycho, sname, probe_size=metrics[sname]['probe_size']) + metrics[sname]['field_of_view']['probe_coverage_image'] = map(ptycho, ID='coverage', mask={sname: metrics[sname]['probe_size']['90perI_image']})[sname] # area overlap - metrics[sname]['overlap'] = calculate_overlap(ptycho, sname, metrics[sname]) + metrics[sname]['overlap'] = calculate_overlap(ptycho, sname, metrics = metrics[sname]) # oversampling - oversampling = {'area': calculate_geometric_oversampling(metrics[sname]['probe_size']['90perI_width_px'], sprobe.shape[1:]), - 'FWHM': calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_px'], sprobe.shape[1:])} + oversampling = {'from_area': calculate_geometric_oversampling(metrics[sname]['probe_size']['90perI_width_px'], sprobe.shape[1:]), + 'from_FWHM': calculate_geometric_oversampling(metrics[sname]['probe_size']['FWHM_width_px'], sprobe.shape[1:])} metrics[sname]['oversampling'] = oversampling # some maps metrics[sname]['maps'] = {} - for x in ['fluence', 'transmission', 'coverage']: - metrics[sname]['maps'][x] = map(ptycho, ID=x)[sname] + metrics[sname]['maps']['fluence'] = map(ptycho, ID='fluence')[sname] + metrics[sname]['maps']['transmission'] = map(ptycho, ID='transmission')[sname] + metrics[sname]['maps']['view_coverage'] = map(ptycho, ID='coverage')[sname] - return metrics @@ -118,10 +124,10 @@ def measure_probe_size(ptycho, sname): # measure FWHM of projected intensities fwhm_x,fwhm_y = size_estimate_FWHM(probe_intensity) - results['FWHM_px'] = (fwhm_y, fwhm_x) - results['FWHM_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) + results['FWHM_width_px'] = (fwhm_y, fwhm_x) + results['FWHM_width_m'] = (fwhm_y*pixel_size[0], fwhm_x*pixel_size[1]) - # measure probe area by 90% intensty criterion + # measure probe area by 90% intensity criterion illuminated_area = size_estimate_90pecent_intensity(probe_intensity) results['90perI_image'] = illuminated_area @@ -209,8 +215,8 @@ def measure_field_of_view(ptycho, sname, probe_size): # estimate FOV from coverage map FOV_from_coverage_px = FOV_estimate_coverage(ptycho, probe_size['90perI_image'], sname) - results['coverage_px'] = FOV_from_coverage_px - results['coverage_sqm'] = FOV_from_coverage_px * pixel_size[0] * pixel_size[1] + results['probe_coverage_px'] = FOV_from_coverage_px + results['probe_coverage_sqm'] = FOV_from_coverage_px * pixel_size[0] * pixel_size[1] # estimate from convex hull hullcoords_m, hull_area_sqm = FOV_estimate_convex_hull(ptycho, sname) @@ -247,7 +253,8 @@ def FOV_estimate_coverage(ptycho, probe_mask, sname): def calculate_overlap(ptycho, sname, metrics): """ - calculating different froms ov overlap + calculating different forms of overlap using + previously calculated metrics """ results = {} results['linear'] = calculate_linear_overlap(ptycho, sname, metrics) @@ -262,9 +269,9 @@ def calculate_linear_overlap(ptycho, sname, metrics): results = {} # estimate from FWHM - linover_fwhm = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['FWHM_m'] + linover_fwhm = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['FWHM_width_m'] linover_fwhm[linover_fwhm<0] = 0 - results['from_fwhm'] = linover_fwhm + results['from_FWHM'] = linover_fwhm # estimate from FWHM linover_90perI = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['90perI_width_m'] @@ -286,7 +293,7 @@ def calculate_area_overlap(ptycho, sname, metrics): # estimate via the coverage map area_overlap = estimate_area_overlap_via_coverage(ptycho, metrics['probe_size']['90perI_image'], sname) - results['from_coverage'] = area_overlap + results['from_probe_coverage'] = area_overlap # estimate from convex hull #ToDo From c7b81669089e07b3a1f748ca3a9414291a28e015 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 15:43:41 +0200 Subject: [PATCH 26/29] tiny change to dict keys --- ptypy/utils/reporting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 087fd1eac..3de150968 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -269,12 +269,12 @@ def calculate_linear_overlap(ptycho, sname, metrics): results = {} # estimate from FWHM - linover_fwhm = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['FWHM_width_m'] + linover_fwhm = 1 - metrics['average_step_size']['from_NN_m'] / metrics['probe_size']['FWHM_width_m'] linover_fwhm[linover_fwhm<0] = 0 results['from_FWHM'] = linover_fwhm # estimate from FWHM - linover_90perI = 1 - metrics['average_step_size']['from_nearest_neighbors'] / metrics['probe_size']['90perI_width_m'] + linover_90perI = 1 - metrics['average_step_size']['from_NN_m'] / metrics['probe_size']['90perI_width_m'] linover_90perI[linover_fwhm<0] = 0 results['from_90perI'] = linover_90perI @@ -316,7 +316,7 @@ def calculate_average_step_size(ptycho, sname, number_of_neighbours=3): ass = {} # from the n nearest neighbors. - ass['from_nearest_neighbors'] = estimate_step_size_NN(ptycho, sname, number_of_neighbours) + ass['from_NN_m'] = estimate_step_size_NN(ptycho, sname, number_of_neighbours) return ass From 309bde16efc65ac8f9986cf9a6c3310acc00d90c Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 16:01:46 +0200 Subject: [PATCH 27/29] added report entry for some very basic things --- ptypy/utils/reporting.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 3de150968..06a868564 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -50,9 +50,26 @@ def calculate_metrics(ptycho): metrics[sname]['maps']['fluence'] = map(ptycho, ID='fluence')[sname] metrics[sname]['maps']['transmission'] = map(ptycho, ID='transmission')[sname] metrics[sname]['maps']['view_coverage'] = map(ptycho, ID='coverage')[sname] - + + # some basics + metrics[sname]['basics'] = measure_basics(ptycho, sname, metrics[sname]) + return metrics +def measure_basics(ptycho, sname, metrics): + """ + some basic numbers that are usually to be mentioned in a publication + """ + basics = {} + # list of all scan positions + basics['scan_positions_m'] = np.array([v.coord for v in ptycho.obj.S[sname].views]) + basics['number_of_scan_points'] = len(basics['scan_positions_m']) + + # flux that made it to the detector detector + basics['total_detected_photons'] = np.sum(metrics['maps']['transmission']) + basics['average_detected_photons_per_scan_point'] = basics['total_detected_photons'] / basics['number_of_scan_points'] + + return basics def map(ptycho, ID='fluence', mask=None): """ From 77277f3ac8aac99eed5cf75cd67c5526270f1601 Mon Sep 17 00:00:00 2001 From: Maik Kahnt Date: Tue, 16 Sep 2025 16:21:28 +0200 Subject: [PATCH 28/29] report switches on position recording --- ptypy/core/ptycho.py | 3 ++- ptypy/utils/reporting.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ptypy/core/ptycho.py b/ptypy/core/ptycho.py index b85f9fa05..7baa898b4 100644 --- a/ptypy/core/ptycho.py +++ b/ptypy/core/ptycho.py @@ -466,7 +466,8 @@ def _configure(self): if self.p.io.report: # Initialize report dict self.report = {} - + # switch on the recording of positions + self.record_positions = True # Benchmark if self.p.io.report.benchmark == 'all': self.benchmark = u.Param() diff --git a/ptypy/utils/reporting.py b/ptypy/utils/reporting.py index 06a868564..287073ff5 100644 --- a/ptypy/utils/reporting.py +++ b/ptypy/utils/reporting.py @@ -51,25 +51,25 @@ def calculate_metrics(ptycho): metrics[sname]['maps']['transmission'] = map(ptycho, ID='transmission')[sname] metrics[sname]['maps']['view_coverage'] = map(ptycho, ID='coverage')[sname] - # some basics - metrics[sname]['basics'] = measure_basics(ptycho, sname, metrics[sname]) + # some flux on detectors + metrics[sname]['photons'] = measure_photons(ptycho, sname, metrics[sname]) return metrics -def measure_basics(ptycho, sname, metrics): +def measure_photons(ptycho, sname, metrics): """ some basic numbers that are usually to be mentioned in a publication """ - basics = {} + results = {} # list of all scan positions - basics['scan_positions_m'] = np.array([v.coord for v in ptycho.obj.S[sname].views]) - basics['number_of_scan_points'] = len(basics['scan_positions_m']) + scan_positions_m = np.array([v.coord for v in ptycho.obj.S[sname].views]) + number_of_scan_points = len(scan_positions_m) # flux that made it to the detector detector - basics['total_detected_photons'] = np.sum(metrics['maps']['transmission']) - basics['average_detected_photons_per_scan_point'] = basics['total_detected_photons'] / basics['number_of_scan_points'] + results['total_detected_photons'] = np.sum(metrics['maps']['transmission']) + results['average_detected_photons_per_scan_point'] = results['total_detected_photons'] / number_of_scan_points - return basics + return results def map(ptycho, ID='fluence', mask=None): """ From d05f8c6b313ed56461f4bfc1f33b7bf70c5f3ab0 Mon Sep 17 00:00:00 2001 From: Benedikt Daurer Date: Thu, 18 Sep 2025 08:22:17 +0100 Subject: [PATCH 29/29] moved reporting example to its own folder --- templates/{misc => reporting}/moonflower_reporting.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/{misc => reporting}/moonflower_reporting.py (100%) diff --git a/templates/misc/moonflower_reporting.py b/templates/reporting/moonflower_reporting.py similarity index 100% rename from templates/misc/moonflower_reporting.py rename to templates/reporting/moonflower_reporting.py