Skip to content

Adding various strain mapping algorithms - #284

Draft
mitis1 wants to merge 63 commits into
electronmicroscopy:devfrom
mitis1:strain-multi-optimizer-fitting-models
Draft

mitis1 wants to merge 63 commits into
electronmicroscopy:devfrom
mitis1:strain-multi-optimizer-fitting-models

Conversation

@mitis1

@mitis1 mitis1 commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

What problem this PR addresses

This PR adds an implementation of the cepstral transform, template matching, and model fitting for strain mapping. The cepstral implementation is based on this paper by Padgett et al and the template matching is based on py4dstem's algorithm. Model fitting is a new method for calculating strain using a rendered diffraction pattern and fitting it to match the original.

The cepstral implementation is mainly in strain_autocorrelation.py file.
Disk detection is in bragg_vectors.py, bragg_vectors_visualization.py, and disk_detection.py.
Model fitting uses model_fitting.py and model_fitting_visualization.py. It also implements a base fitting model that can be used for implementations beyond strain mapping.

The PR also makes a strain class which calculates and saves the strain map using the results of any of the previous algorithms. It is implemented in strain.py and strain_visualizations.py

What should the reviewer(s) do

  • Check the implementation of the various strain mapping algorithms.
  • See if the strain class works for all the listed methods
  • Run the test scripts and verify it works (the dataset is too big to attach so I can send it separately as needed)

model-fitting.ipynb
cepstral_mp.ipynb
cepstral.ipynb
disk-detection.ipynb

Copilot AI and others added 30 commits March 18, 2026 22:22
Create clean fitting_models branch tracking upstream/fitting_models_clean
…odels_clean

Fitting models without all the extra commits, for easier merging into dev in the future.
@mitis1
mitis1 requested a review from smribet August 29, 2026 21:11
qrow_unit = self.dataset.units[2]
qcol_unit = self.dataset.units[3]

if qrow_unit in {"A", "Å"}:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A^-1?


Parameters
----------
position : tuple of int, default=(0, 0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify pixels unit

ax[1].imshow(template, cmap="gray")
ax[1].set_title("template (centered)")
ax[2].imshow(corr_map, cmap="viridis")
ax[2].set_title(f"correlation @ {tuple(position)}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

specify real space pixels.

if returnfig:
return fig, ax

def peak_histogram(self, *, returnfig: bool = False, **kwargs):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These histograms are very hard to see. Pass kwargs to plotting functions to make more robust.

)
if beta is None:
continue
u_array[r, c] = beta[1]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some change between u and g1 and g2. I think we should use g1 and g2.

PEAK_FIELDS = ("q_row", "q_col", "intensity")


class BraggVectors(AutoSerialize):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to provide a consistent, user-facing API across these classes for retrieving:

  • the reference lattice vectors, g1 and g2;
  • the indexed reciprocal-lattice coordinates and associated diffraction peaks; and
  • the fitted lattice vectors and indexed peaks at each scan/probe position.

return fig, ax
return self

def index_peaks(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be helpful to define and visualize max_peak_shift earlier. The plotting here looks like you're specifying that but it's actually later it seems.

for a in _flatten_axes(ax):
_apply_center_crop_limits(a, self.transform.shape, cropping_factor)

def choose_lattice_vector(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could u and v optionally be determined automatically from the detected peaks in the mean transform, similarly to BraggVectors.choose_basis_vectors()?

Symmetric color range for the strain panels, in percent.
rotation_range_degrees : tuple of float, default=(-2.0, 2.0)
Symmetric color range for the rotation panel, in degrees.
mask_range : tuple of float, default=(0.0, 1.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this show the reference region?


return self

def preprocess(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do normalization of data here?


# ---- visualization ----

def show_template(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diffraction pattern does not look right here with the crop function if the experimental data isn't centered

}
return self

def make_template_from_probe(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should accept a 2D array or a dataset2d

self.compute_bvm()
return peaks

def correct_peak_origins(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a way to measure peak_origins too?

origins: NDArray,
origin_ref: NDArray | tuple[float, float] | None = None,
*,
inplace: bool = False,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modify_in_place is more consistent with other code

from quantem.core.visualization.visualization_utils import ScalebarConfig, add_scalebar_to_ax


def plot_strain_panels(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the color bars are overlaid for my squares scan

@mitis1
mitis1 force-pushed the strain-multi-optimizer-fitting-models branch from 2348879 to c2b207f Compare September 5, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants