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 diff --git a/ptypy/core/ptycho.py b/ptypy/core/ptycho.py index 61dc1ccd6..7baa898b4 100644 --- a/ptypy/core/ptycho.py +++ b/ptypy/core/ptycho.py @@ -267,7 +267,23 @@ 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.benchmark] default = None type = str help = Produce timings for benchmarking the performance of data loaders and engines @@ -323,8 +339,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 +397,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() @@ -445,14 +462,20 @@ def _configure(self): # Find run name self.runtime.run = self.paths.run(p.run) - # Benchmark - if self.p.io.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 = {} + # switch on the recording of positions + self.record_positions = True + # 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): """ @@ -535,10 +558,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 +681,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 +702,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 +732,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 +763,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 +783,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 +1062,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.p.io.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 +1185,26 @@ 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', '=')) + if self.p.io.report.metrics == 'all': + self.report['metrics'] = u.reporting.calculate_metrics(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..287073ff5 --- /dev/null +++ b/ptypy/utils/reporting.py @@ -0,0 +1,395 @@ +""" +Reporting functions + +This file is part of the PTYPY package. + + :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): + """ + 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 + + # 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[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 = metrics[sname]) + + # oversampling + 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'] = {} + 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 flux on detectors + metrics[sname]['photons'] = measure_photons(ptycho, sname, metrics[sname]) + + return metrics + +def measure_photons(ptycho, sname, metrics): + """ + some basic numbers that are usually to be mentioned in a publication + """ + results = {} + # list of all scan positions + 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 + 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 results + +def map(ptycho, ID='fluence', mask=None): + """ + 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' + 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}') + + # 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(): + 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[probe_storage] + else: + sobj[v] += np.ones_like(pod.probe.real) + + 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 + via different methods. + """ + + results = {} + probes = ptycho.probe.S[sname].data + pixel_size = ptycho.probe.S[sname]._psize + + probe_intensity = np.sum(abs2(probes), axis=0) + + # measure FWHM of projected intensities + fwhm_x,fwhm_y = size_estimate_FWHM(probe_intensity) + 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% intensity 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) + 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]) + + 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 + + +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 = find_threshold(probe_intensity, fraction=0.9) + probe_mask = np.zeros_like(probe_intensity) + probe_mask[probe_intensity>=threshold] = 1 + return probe_mask + +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]) + + +def measure_field_of_view(ptycho, sname, probe_size): + """ + main function for measuring the scanned field of view + via different methods. + """ + + results = {} + pixel_size = ptycho.probe.S[sname]._psize + + # estimate FOV from coverage map + FOV_from_coverage_px = FOV_estimate_coverage(ptycho, probe_size['90perI_image'], sname) + 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) + 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): + """ + 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_overlap(ptycho, sname, metrics): + """ + calculating different forms of overlap using + previously calculated metrics + """ + 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_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_NN_m'] / 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, metrics['probe_size']['90perI_image'], sname) + results['from_probe_coverage'] = area_overlap + + # estimate from convex hull + #ToDo + + 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() + + + + +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_NN_m'] = 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 + + + +# 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))) + diff --git a/templates/reporting/moonflower_reporting.py b/templates/reporting/moonflower_reporting.py new file mode 100644 index 000000000..ee6ec07a3 --- /dev/null +++ b/templates/reporting/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