From 2f05473258995a736f5d85f829bdf51d1acacd74 Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Fri, 30 Jan 2026 13:57:41 -0800 Subject: [PATCH 01/12] remove some logging. Reduce n_trials to 25 --- operators/quantem-direct-ptycho/run.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/operators/quantem-direct-ptycho/run.py b/operators/quantem-direct-ptycho/run.py index 25d243b7..12f8c1f4 100644 --- a/operators/quantem-direct-ptycho/run.py +++ b/operators/quantem-direct-ptycho/run.py @@ -114,11 +114,6 @@ def quantem_direct_ptycho( return None # --- 5. Perform Calculation --- - logger.info( - f"Scan {scan_number}: Triggering calculation after {accumulator.num_batches_added} messages." - ) - logger.info(f"Accumulator finished: {accumulator.finished}") - logger.info(f"Scan {scan_number}: Calculating ptycho images.") # Calculation parameters @@ -234,7 +229,7 @@ def quantem_direct_ptycho( aberration_coefs=opt_aberration_coefs, rotation_angle=opt_rotation_angle, deconvolution_kernel=deconvolution_kernel, - n_trials=50, + n_trials=25, max_batch_size=10, ) else: From 86eeafe9d64b900e7cc529aa55a09654b0ce7be2 Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Fri, 30 Jan 2026 14:02:59 -0800 Subject: [PATCH 02/12] use int for defocus and defocus search range. This allows comparison inside run.py --- operators/quantem-direct-ptycho/operator.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index ab1d5c68..97a30851 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -63,11 +63,19 @@ { "name": "initial_defocus", "label": "STEM defocus", - "type": "float", - "default": "0.0", + "type": "int", + "default": "0", "description": "The STEM defocus in nm.", "required": false }, + { + "name": "defocus_search_range_nm", + "label": "Defocus search range", + "type": "int", + "default": "50", + "description": "The defocus search range in nanometers. Set to zero to disable defocus optimization.", + "required": false + }, { "name": "diffraction_rotation_angle", "label": "Diffraction pattern rotation angle", @@ -92,14 +100,6 @@ "description": "The upsampling factor for the final reconstruction.", "required": false }, - { - "name": "defocus_search_range_nm", - "label": "Defocus search range", - "type": "float", - "default": "50.0", - "description": "The defocus search range in nanometers. Unused if defocus is input.", - "required": false - }, { "name": "maximum_C12_magnitude_nm", "label": "Maximum C12 magnitude", From a8e3c4e0e2d07fefc702da38e98be92b0cbf6e3c Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Fri, 30 Jan 2026 14:30:06 -0800 Subject: [PATCH 03/12] remove unused parameters --- operators/quantem-direct-ptycho/operator.json | 16 -------------- operators/quantem-direct-ptycho/run.py | 21 ++++++++++--------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index 97a30851..75a90c26 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -20,22 +20,6 @@ } ], "parameters": [ - { - "name": "calculation_frequency", - "label": "Calculation Frequency", - "type": "int", - "default": "100", - "description": "Number of frames to accumulate before recalculating the center and emitting a BF image.", - "required": true - }, - { - "name": "max_concurrent_scans", - "label": "Max Concurrent Scans", - "type": "int", - "default": "1", - "description": "Maximum number of scans to keep in memory simultaneously. Oldest scans are evicted when this limit is exceeded.", - "required": false - }, { "name": "accelerating_voltage", "label": "Accelerating voltage", diff --git a/operators/quantem-direct-ptycho/run.py b/operators/quantem-direct-ptycho/run.py index 12f8c1f4..578bb990 100644 --- a/operators/quantem-direct-ptycho/run.py +++ b/operators/quantem-direct-ptycho/run.py @@ -80,7 +80,7 @@ def quantem_direct_ptycho( scan_number = batch.header.scan_number # --- 2. Get or Create FrameAccumulator --- - max_concurrent_scans = int(parameters.get("max_concurrent_scans", 1)) + max_concurrent_scans = 1 # TODO: remove this if scan_number not in accumulators: # Check if we need to evict old accumulators before creating new one @@ -126,15 +126,21 @@ def quantem_direct_ptycho( upsampling_factor = parameters.get("upsampling_factor", 2) # Parameters for optimize_hyperparameters function - initial_defocus_nm = parameters.get( - "initial_defocus", None - ) # in nanometers, can be None + initial_defocus_nm = parameters.get("initial_defocus", 0) # in nanometers if initial_defocus_nm is not None: initial_defocus_nm = initial_defocus_nm initial_defocus_A = initial_defocus_nm * 10 # convert to Angstroms else: initial_defocus_A = None + defocus_search_range_nm = parameters.get( + "defocus_search_range", 50 + ) # in nanometers + defocus_search_range_A = defocus_search_range_nm * 10 # convert to Angstroms + + if defocus_search_range_nm == 0: + defocus_search_range_A = None + diffraction_rotation_angle = parameters.get( "diffraction_rotation_angle", None ) # in degrees, can be None @@ -144,11 +150,6 @@ def quantem_direct_ptycho( else: rotation_angle = None - defocus_search_range_nm = parameters.get( - "defocus_search_range", 50 - ) # in nanometers - defocus_search_range_A = defocus_search_range_nm * 10 # convert to Angstroms - maximum_C12_magnitude_nm = parameters.get( "maximum_C12_magnitude", 10 ) # in nanometers @@ -181,7 +182,7 @@ def quantem_direct_ptycho( dset.sampling[0] = ( probe_step_size * 10 - ) ## convert to be Anggstrom for quantem. distiller will give nanometers. + ) ## convert to be Angstrom for quantem. distiller will give nanometers. dset.sampling[1] = probe_step_size * 10 dset.units[0:2] = ["A", "A"] From 77de16a1ebf1306e5fab0d0449dc29a86a8827da Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Fri, 30 Jan 2026 17:44:39 -0800 Subject: [PATCH 04/12] add bool parameters to enable optimization of diffraction_angle, defocus, and C12. Only search one side of focus based on the initial defocus. --- operators/quantem-direct-ptycho/operator.json | 48 ++++++---- operators/quantem-direct-ptycho/run.py | 93 +++++++++---------- 2 files changed, 78 insertions(+), 63 deletions(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index 75a90c26..864d9ada 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -52,6 +52,14 @@ "description": "The STEM defocus in nm.", "required": false }, + { + "name": "optimize_defocus", + "label": "Optimize the defocus", + "type": "bool", + "default": true, + "description": "Use the optimization routine to optimize the defocus.", + "required": false + } { "name": "defocus_search_range_nm", "label": "Defocus search range", @@ -68,6 +76,30 @@ "description": "The rotation of the diffraction pattern on the detector in degrees.", "required": false }, + { + "name": "optimize_rotation_angle", + "label": "Optimize the rotation angle", + "type": "bool", + "default": false, + "description": "Use the optimization routine to optimize the rotation angle.", + "required": false + }, + { + "name": "maximum_C12_magnitude_nm", + "label": "Maximum C12 magnitude", + "type": "int", + "default": "2", + "description": "The maximum C12 magnitude in nanometers.", + "required": false + }, + { + "name": "optimize_C12", + "label": "Optimize the C12", + "type": "bool", + "default": true, + "description": "Use the optimization routine to optimize the C12.", + "required": false + }, { "name": "crop_probes", "label": "Crop probes on each side", @@ -84,14 +116,6 @@ "description": "The upsampling factor for the final reconstruction.", "required": false }, - { - "name": "maximum_C12_magnitude_nm", - "label": "Maximum C12 magnitude", - "type": "int", - "default": "2", - "description": "The maximum C12 magnitude in nanometers.", - "required": false - }, { "name": "deconvolution_kernel", "label": "Deconvolution kernel", @@ -100,14 +124,6 @@ "options": ["parallax", "ssb", "icom"], "description": "The deconvolution kernel.", "required": false - }, - { - "name": "use_optimization", - "label": "Use optimization routine", - "type": "bool", - "default": true, - "description": "Use the optimization routine with initial parameters.", - "required": false } ], "parallel_config": { diff --git a/operators/quantem-direct-ptycho/run.py b/operators/quantem-direct-ptycho/run.py index 578bb990..faf87198 100644 --- a/operators/quantem-direct-ptycho/run.py +++ b/operators/quantem-direct-ptycho/run.py @@ -117,49 +117,47 @@ def quantem_direct_ptycho( logger.info(f"Scan {scan_number}: Calculating ptycho images.") # Calculation parameters - probe_semiangle = parameters.get("probe_semiangle", 25.0) energy = parameters.get("accelerating_voltage", 300e3) - probe_step_size = parameters.get( - "probe_step_size", 0.1 - ) # test data set: 0.14383155 nm - crop_probes = parameters.get("crop_probes", 0) + probe_semiangle = parameters.get("probe_semiangle", 25.0) + # test data set: 0.14383155 nm probe step size + probe_step_size_nm = parameters.get("probe_step_size", 0.1) + probe_step_size_A = probe_step_size_nm * 10 upsampling_factor = parameters.get("upsampling_factor", 2) - # Parameters for optimize_hyperparameters function - initial_defocus_nm = parameters.get("initial_defocus", 0) # in nanometers - if initial_defocus_nm is not None: - initial_defocus_nm = initial_defocus_nm - initial_defocus_A = initial_defocus_nm * 10 # convert to Angstroms + optimize_defocus = bool(parameters.get("optimize_defocus", True)) + if optimize_defocus: + defocus_search_max_nm = parameters.get("defocus_search_range", 50) + defocus_search_max_A = defocus_search_max_nm * 10 # convert to Angstroms else: - initial_defocus_A = None - - defocus_search_range_nm = parameters.get( - "defocus_search_range", 50 - ) # in nanometers - defocus_search_range_A = defocus_search_range_nm * 10 # convert to Angstroms - - if defocus_search_range_nm == 0: - defocus_search_range_A = None - - diffraction_rotation_angle = parameters.get( - "diffraction_rotation_angle", None - ) # in degrees, can be None - if diffraction_rotation_angle is not None: - diffraction_rotation_angle = diffraction_rotation_angle - rotation_angle = diffraction_rotation_angle * np.pi / 180 # convert to radians + defocus_search_max_A = 0 + + initial_defocus_nm = parameters.get("initial_defocus", 0) + initial_defocus_A = initial_defocus_nm * 10 + + # Only search in one direction from initial guess + if initial_defocus_A > 0: + defocus_search_range_A = (0, defocus_search_max_A) + elif initial_defocus_A < 0: + defocus_search_range_A = (-defocus_search_max_A, 0) else: - rotation_angle = None + defocus_search_range_A = (-defocus_search_max_A, defocus_search_max_A) - maximum_C12_magnitude_nm = parameters.get( - "maximum_C12_magnitude", 10 - ) # in nanometers - maximum_C12_magnitude_A = maximum_C12_magnitude_nm * 10 # convert to Angstroms + # in degrees + diffraction_rotation_angle_deg = parameters.get("diffraction_rotation_angle", 0) + rotation_angle = diffraction_rotation_angle_deg * np.pi / 180 # convert to radians - deconvolution_kernel = parameters.get("deconvolution_kernel", "parallax") + optimize_angle = bool(parameters.get("optimize_angle", False)) + + optimize_C12 = bool(parameters.get("optimize_C12", False)) + if optimize_C12: + maximum_C12_magnitude_nm = parameters.get("maximum_C12_magnitude", 10) + maximum_C12_magnitude_A = maximum_C12_magnitude_nm * 10 # convert to Angstroms + else: + maximum_C12_magnitude_A = None - # Determine whether to use optimization or manual settings - use_optimization = bool(parameters.get("use_optimization", True)) + deconvolution_kernel = parameters.get("deconvolution_kernel", "parallax") + crop_probes = parameters.get("crop_probes", 0) if crop_probes == 0: logger.info(f"Scan {scan_number}: No cropping of probes applied.") dense_data = accumulator[:, :-1, :, :].to_dense() ## remove the flyback column @@ -169,6 +167,7 @@ def quantem_direct_ptycho( crop_probes:-crop_probes, crop_probes : -crop_probes - 1, :, : ].to_dense() ## crop the edges if needed and remove the flyback column + # Convert SparseArray to Dataset4dstem dset = em.datastructures.Dataset4dstem.from_array(array=dense_data) logger.debug(f"dense shape = {dense_data.shape}") @@ -180,10 +179,7 @@ def quantem_direct_ptycho( dset.sampling[3] = probe_semiangle / probe_R dset.units[2:] = ["mrad", "mrad"] - dset.sampling[0] = ( - probe_step_size * 10 - ) ## convert to be Angstrom for quantem. distiller will give nanometers. - dset.sampling[1] = probe_step_size * 10 + dset.sampling[0:2] = probe_step_size_A * 10 dset.units[0:2] = ["A", "A"] logger.info(f"Scan {scan_number}: Start direct ptycho") @@ -203,25 +199,28 @@ def quantem_direct_ptycho( rotation_angle=rotation_angle, # need radians ) - if use_optimization: + if optimize_C12 or optimize_angle or optimize_defocus: logger.info(f"Scan {scan_number}: Optimizing hyperparameters") # Build optimization aberration coefficients opt_aberration_coefs = {} - if initial_defocus_A is None: + + if optimize_defocus: opt_aberration_coefs["C10"] = OptimizationParameter( - defocus_search_range_A, defocus_search_range_A + defocus_search_range_A[0], defocus_search_range_A[1] ) else: opt_aberration_coefs["C10"] = -initial_defocus_A - opt_aberration_coefs["C12"] = OptimizationParameter( - 0, maximum_C12_magnitude_A - ) - opt_aberration_coefs["phi12"] = OptimizationParameter(-np.pi / 2, np.pi / 2) + if optimize_C12: + opt_aberration_coefs["C12"] = OptimizationParameter( + 0, maximum_C12_magnitude_A + ) + opt_aberration_coefs["phi12"] = OptimizationParameter( + -np.pi / 2, np.pi / 2 + ) - # Build rotation angle optimization - if rotation_angle is None: + if optimize_angle: opt_rotation_angle = OptimizationParameter(0, np.pi) else: opt_rotation_angle = rotation_angle From 07e3f38d5c93cfd519e6473666ab29ee721856bc Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Fri, 30 Jan 2026 17:50:11 -0800 Subject: [PATCH 05/12] fix json comma --- operators/quantem-direct-ptycho/operator.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index 864d9ada..d9507a69 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -59,7 +59,7 @@ "default": true, "description": "Use the optimization routine to optimize the defocus.", "required": false - } + }, { "name": "defocus_search_range_nm", "label": "Defocus search range", From 501aefc3fc24d247658ded98e320039a0e37e979 Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Fri, 30 Jan 2026 17:54:50 -0800 Subject: [PATCH 06/12] fix typos. Fix error where probe step size was x10 twice --- operators/quantem-direct-ptycho/operator.json | 4 ++-- operators/quantem-direct-ptycho/run.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index d9507a69..33d1eb1c 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -61,7 +61,7 @@ "required": false }, { - "name": "defocus_search_range_nm", + "name": "defocus_search_range", "label": "Defocus search range", "type": "int", "default": "50", @@ -85,7 +85,7 @@ "required": false }, { - "name": "maximum_C12_magnitude_nm", + "name": "maximum_C12_magnitude", "label": "Maximum C12 magnitude", "type": "int", "default": "2", diff --git a/operators/quantem-direct-ptycho/run.py b/operators/quantem-direct-ptycho/run.py index faf87198..b7382cac 100644 --- a/operators/quantem-direct-ptycho/run.py +++ b/operators/quantem-direct-ptycho/run.py @@ -146,7 +146,7 @@ def quantem_direct_ptycho( diffraction_rotation_angle_deg = parameters.get("diffraction_rotation_angle", 0) rotation_angle = diffraction_rotation_angle_deg * np.pi / 180 # convert to radians - optimize_angle = bool(parameters.get("optimize_angle", False)) + optimize_angle = bool(parameters.get("optimize_rotation_angle", False)) optimize_C12 = bool(parameters.get("optimize_C12", False)) if optimize_C12: @@ -179,7 +179,7 @@ def quantem_direct_ptycho( dset.sampling[3] = probe_semiangle / probe_R dset.units[2:] = ["mrad", "mrad"] - dset.sampling[0:2] = probe_step_size_A * 10 + dset.sampling[0:2] = probe_step_size_A dset.units[0:2] = ["A", "A"] logger.info(f"Scan {scan_number}: Start direct ptycho") From 14d3c5341781ff2b98508028d0bbc9ed8ce59f5d Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Mon, 2 Feb 2026 11:11:47 -0800 Subject: [PATCH 07/12] always use parallax for hyperparameter optimization --- operators/quantem-direct-ptycho/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operators/quantem-direct-ptycho/run.py b/operators/quantem-direct-ptycho/run.py index b7382cac..36560997 100644 --- a/operators/quantem-direct-ptycho/run.py +++ b/operators/quantem-direct-ptycho/run.py @@ -228,7 +228,7 @@ def quantem_direct_ptycho( direct_ptycho.optimize_hyperparameters( aberration_coefs=opt_aberration_coefs, rotation_angle=opt_rotation_angle, - deconvolution_kernel=deconvolution_kernel, + deconvolution_kernel="parallax", n_trials=25, max_batch_size=10, ) From aac6738cf4f7958439bdcde852e857ca116d27ea Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Mon, 2 Feb 2026 13:54:26 -0800 Subject: [PATCH 08/12] use only needed parameters. --- operators/quantem-direct-ptycho/operator.json | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index 33d1eb1c..b8976e2f 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -45,29 +45,21 @@ "required": false }, { - "name": "initial_defocus", - "label": "STEM defocus", + "name": "defocus_search_range_min", + "label": "Defocus search range minimum", "type": "int", "default": "0", - "description": "The STEM defocus in nm.", + "description": "The defocus search range minimum in nanometers.", "required": false }, { - "name": "optimize_defocus", - "label": "Optimize the defocus", - "type": "bool", - "default": true, - "description": "Use the optimization routine to optimize the defocus.", - "required": false - }, - { - "name": "defocus_search_range", - "label": "Defocus search range", + "name": "defocus_search_range_max", + "label": "Defocus search range maximum", "type": "int", - "default": "50", - "description": "The defocus search range in nanometers. Set to zero to disable defocus optimization.", + "default": "30", + "description": "The defocus search range maximum in nanometers.", "required": false - }, + } { "name": "diffraction_rotation_angle", "label": "Diffraction pattern rotation angle", @@ -76,30 +68,14 @@ "description": "The rotation of the diffraction pattern on the detector in degrees.", "required": false }, - { - "name": "optimize_rotation_angle", - "label": "Optimize the rotation angle", - "type": "bool", - "default": false, - "description": "Use the optimization routine to optimize the rotation angle.", - "required": false - }, { "name": "maximum_C12_magnitude", "label": "Maximum C12 magnitude", "type": "int", - "default": "2", + "default": "10", "description": "The maximum C12 magnitude in nanometers.", "required": false }, - { - "name": "optimize_C12", - "label": "Optimize the C12", - "type": "bool", - "default": true, - "description": "Use the optimization routine to optimize the C12.", - "required": false - }, { "name": "crop_probes", "label": "Crop probes on each side", @@ -116,6 +92,14 @@ "description": "The upsampling factor for the final reconstruction.", "required": false }, + { + "name": "n_trials", + "label": "Number of trials for hyperparameter optimization", + "type": "int", + "default": "25", + "description": "The number of trials for hyperparameter optimization.", + "required": false + }, { "name": "deconvolution_kernel", "label": "Deconvolution kernel", From fc3071603fe292f90e53147d210344cda154d2ec Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Mon, 2 Feb 2026 13:58:44 -0800 Subject: [PATCH 09/12] add max_batch_size parameter to better manage memory --- operators/quantem-direct-ptycho/operator.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index b8976e2f..b3b01250 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -100,6 +100,14 @@ "description": "The number of trials for hyperparameter optimization.", "required": false }, + { + "name": "max_batch_size", + "label": "Maximum batch size", + "type": "int", + "default": "10", + "description": "The maximum batch size for processing frames. Reduce if you run out of GPU memory.", + "required": false + } { "name": "deconvolution_kernel", "label": "Deconvolution kernel", From d02a30b8508c7ad309aec2feff88815d8192610f Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Mon, 2 Feb 2026 14:17:50 -0800 Subject: [PATCH 10/12] fix up all the parameters --- operators/quantem-direct-ptycho/run.py | 122 +++++++++---------------- 1 file changed, 45 insertions(+), 77 deletions(-) diff --git a/operators/quantem-direct-ptycho/run.py b/operators/quantem-direct-ptycho/run.py index 36560997..060a33c8 100644 --- a/operators/quantem-direct-ptycho/run.py +++ b/operators/quantem-direct-ptycho/run.py @@ -80,7 +80,7 @@ def quantem_direct_ptycho( scan_number = batch.header.scan_number # --- 2. Get or Create FrameAccumulator --- - max_concurrent_scans = 1 # TODO: remove this + max_concurrent_scans = 1 # TODO: remove saving of old scans if scan_number not in accumulators: # Check if we need to evict old accumulators before creating new one @@ -123,37 +123,22 @@ def quantem_direct_ptycho( probe_step_size_nm = parameters.get("probe_step_size", 0.1) probe_step_size_A = probe_step_size_nm * 10 upsampling_factor = parameters.get("upsampling_factor", 2) + n_trials = parameters.get("n_trials", 25) + max_batch_size = parameters.get("max_batch_size", 10) - optimize_defocus = bool(parameters.get("optimize_defocus", True)) - if optimize_defocus: - defocus_search_max_nm = parameters.get("defocus_search_range", 50) - defocus_search_max_A = defocus_search_max_nm * 10 # convert to Angstroms - else: - defocus_search_max_A = 0 - - initial_defocus_nm = parameters.get("initial_defocus", 0) - initial_defocus_A = initial_defocus_nm * 10 - - # Only search in one direction from initial guess - if initial_defocus_A > 0: - defocus_search_range_A = (0, defocus_search_max_A) - elif initial_defocus_A < 0: - defocus_search_range_A = (-defocus_search_max_A, 0) - else: - defocus_search_range_A = (-defocus_search_max_A, defocus_search_max_A) + defocus_search_min_nm = parameters.get("defocus_search_range_min", 50) + defocus_search_max_nm = parameters.get("defocus_search_range_max", 50) + defocus_search_min_A = defocus_search_min_nm * 10 # convert to Angstroms + defocus_search_max_A = defocus_search_max_nm * 10 # convert to Angstroms + # Need to convert signs and order because of different conventions in FEI and quantem + defocus_search_range_A = (-defocus_search_max_A, -defocus_search_min_A) # in degrees diffraction_rotation_angle_deg = parameters.get("diffraction_rotation_angle", 0) rotation_angle = diffraction_rotation_angle_deg * np.pi / 180 # convert to radians - optimize_angle = bool(parameters.get("optimize_rotation_angle", False)) - - optimize_C12 = bool(parameters.get("optimize_C12", False)) - if optimize_C12: - maximum_C12_magnitude_nm = parameters.get("maximum_C12_magnitude", 10) - maximum_C12_magnitude_A = maximum_C12_magnitude_nm * 10 # convert to Angstroms - else: - maximum_C12_magnitude_A = None + maximum_C12_magnitude_nm = parameters.get("maximum_C12_magnitude", 10) + maximum_C12_magnitude_A = maximum_C12_magnitude_nm * 10 # convert to Angstroms deconvolution_kernel = parameters.get("deconvolution_kernel", "parallax") @@ -184,76 +169,59 @@ def quantem_direct_ptycho( logger.info(f"Scan {scan_number}: Start direct ptycho") try: - # Initialize DirectPtychography with initial guesses - aberration_coefs = {} - if initial_defocus_A is not None: - aberration_coefs["C10"] = -initial_defocus_A # Note the negative sign + # Initialize DirectPtychography direct_ptycho = DirectPtychography.from_dataset4d( dset, energy=energy, semiangle_cutoff=probe_semiangle, device=QUANTEM_DEVICE, - aberration_coefs=aberration_coefs if aberration_coefs else None, - max_batch_size=10, + aberration_coefs={}, + max_batch_size=max_batch_size, rotation_angle=rotation_angle, # need radians ) - if optimize_C12 or optimize_angle or optimize_defocus: - logger.info(f"Scan {scan_number}: Optimizing hyperparameters") - - # Build optimization aberration coefficients - opt_aberration_coefs = {} - - if optimize_defocus: - opt_aberration_coefs["C10"] = OptimizationParameter( - defocus_search_range_A[0], defocus_search_range_A[1] - ) - else: - opt_aberration_coefs["C10"] = -initial_defocus_A - - if optimize_C12: - opt_aberration_coefs["C12"] = OptimizationParameter( - 0, maximum_C12_magnitude_A - ) - opt_aberration_coefs["phi12"] = OptimizationParameter( - -np.pi / 2, np.pi / 2 - ) - - if optimize_angle: - opt_rotation_angle = OptimizationParameter(0, np.pi) - else: - opt_rotation_angle = rotation_angle - - direct_ptycho.optimize_hyperparameters( - aberration_coefs=opt_aberration_coefs, - rotation_angle=opt_rotation_angle, - deconvolution_kernel="parallax", - n_trials=25, - max_batch_size=10, - ) - else: - logger.info(f"Scan {scan_number}: Using manual hyperparameter settings") + # Build optimization aberration coefficients + logger.info(f"Scan {scan_number}: Optimizing hyperparameters") + opt_aberration_coefs = {} + opt_aberration_coefs["C10"] = OptimizationParameter( + defocus_search_range_A[0], defocus_search_range_A[1] + ) + opt_aberration_coefs["C12"] = OptimizationParameter(0, maximum_C12_magnitude_A) + opt_aberration_coefs["phi12"] = OptimizationParameter(-np.pi / 2, np.pi / 2) + + # Optimize hyperparameters + direct_ptycho.optimize_hyperparameters( + aberration_coefs=opt_aberration_coefs, + deconvolution_kernel="parallax", + n_trials=n_trials, + max_batch_size=max_batch_size, + ) - initial_parallax = direct_ptycho.reconstruct( + # Do reconstruction + logger.info(f"Scan {scan_number}: Starting reconstruction") + direct_ptycho.reconstruct( deconvolution_kernel=deconvolution_kernel, upsampling_factor=upsampling_factor, - max_batch_size=10, + max_batch_size=max_batch_size, ) # Process and return result logger.info(f"Scan {scan_number}: Reconstruction done") - output_bytes = initial_parallax.obj.tobytes() + output_bytes = direct_ptycho.obj.tobytes() output_meta = { "scan_number": scan_number, - "shape": initial_parallax.obj.shape, - "dtype": str(initial_parallax.obj.dtype), + "shape": direct_ptycho.obj.shape, + "dtype": str(direct_ptycho.obj.dtype), "source_operator": "quantem-direct-ptycho", - "direct_ptycho_params": {'C12': direct_ptycho.hyperparameter_state.optimized_aberrations['C12'], - 'phi12': direct_ptycho.hyperparameter_state.optimized_aberrations['phi12'], - 'C10': -direct_ptycho.aberration_coefs['C10'], - 'rotation_angle': direct_ptycho.rotation_angle, - }, + "direct_ptycho_params": { + "C12": direct_ptycho.hyperparameter_state.optimized_aberrations["C12"], + "phi12": direct_ptycho.hyperparameter_state.optimized_aberrations[ + "phi12" + ], + "C10": -direct_ptycho.aberration_coefs["C10"], + "rotation_angle": direct_ptycho.rotation_angle, + }, } header = MessageHeader(subject=MessageSubject.BYTES, meta=output_meta) return BytesMessage(header=header, data=output_bytes) From e2298930a7a4694de5fbe5378ef82ace48d4afd9 Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Mon, 2 Feb 2026 14:24:54 -0800 Subject: [PATCH 11/12] fix json commas --- operators/quantem-direct-ptycho/operator.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index b3b01250..74892346 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -1,6 +1,6 @@ { "id": "17460a60-01ac-4144-aa32-126bd62667aa", - "label": "quantem direct ptycho", + "label": "quantem direct ptycho 2", "description": "Use quantem to calculate direct ptychography reconstructions from counted data.", "image": "ghcr.io/nersc/interactem/quantem-direct-ptycho:latest", "inputs": [ @@ -59,7 +59,7 @@ "default": "30", "description": "The defocus search range maximum in nanometers.", "required": false - } + }, { "name": "diffraction_rotation_angle", "label": "Diffraction pattern rotation angle", @@ -107,7 +107,7 @@ "default": "10", "description": "The maximum batch size for processing frames. Reduce if you run out of GPU memory.", "required": false - } + }, { "name": "deconvolution_kernel", "label": "Deconvolution kernel", From 5f0a225bcf915feba02ce22b19ba8497b00c558a Mon Sep 17 00:00:00 2001 From: Peter Ercius Date: Mon, 2 Feb 2026 14:44:41 -0800 Subject: [PATCH 12/12] remove naming typo --- operators/quantem-direct-ptycho/operator.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operators/quantem-direct-ptycho/operator.json b/operators/quantem-direct-ptycho/operator.json index 74892346..5abc3021 100644 --- a/operators/quantem-direct-ptycho/operator.json +++ b/operators/quantem-direct-ptycho/operator.json @@ -1,6 +1,6 @@ { "id": "17460a60-01ac-4144-aa32-126bd62667aa", - "label": "quantem direct ptycho 2", + "label": "quantem direct ptycho", "description": "Use quantem to calculate direct ptychography reconstructions from counted data.", "image": "ghcr.io/nersc/interactem/quantem-direct-ptycho:latest", "inputs": [