Skip to content

Fitting models clean - #273

Open
maclariz wants to merge 14 commits into
electronmicroscopy:fitting_models_cleanfrom
maclariz:fitting_models_clean
Open

Fitting models clean#273
maclariz wants to merge 14 commits into
electronmicroscopy:fitting_models_cleanfrom
maclariz:fitting_models_clean

Conversation

@maclariz

Copy link
Copy Markdown

What problem this PR addreseses

This creates a new set of functions in diffraction to provide Digital Dark Field imaging, both with manual selection and cluster analysis. It is all in one py file split into three parts:

  1. import of data into Vector class
  2. regular DDF imaging that reproduces some functions in py4DSTEM
  3. the starts of cluster analysis (mainly L1 clustering)

All can be built out once the module has been accepted

The initial import is based on my own centre finding, which I can provide, but you may have code elsewhere in the repo that produces the desired kx and ky centre positions, in which case just adjust my function to use that result.

What should the reviewer(s) do

maclariz added 8 commits July 30, 2026 10:17
Digital Dark Field functions introduced
Restructured and some cluster support
bugfix (relics of old code)
Added the function to read pointsarrays from py4DSTEM and fixed a minor bug in cluster real space plot
@maclariz

Copy link
Copy Markdown
Author

And don't change "vector.py" in response to this. I initially changed it and then changed it back, so it looks changed but isn't. All the new stuff in in digital_dark_field_cluster.py and init.py was changed in diffraction to import the new functions into the repo

maclariz and others added 6 commits August 2, 2026 10:01
Now writing cluster labels into the vector at L1
Totally changes how we make images from the vector object.
This really will speed up when we make complex selections in objects since we now just select with one mask, not a stack.
Complete set of functions using new img reduction method.  Maybe I should also make a new method to reduce a selection to a diffraction pattern.
@maclariz

maclariz commented Aug 4, 2026

Copy link
Copy Markdown
Author

So, functions rebuilt to work with a new image reduce method in Vector and all built out to L2 clustering with two methods. The image reduce could be generalised, and an option for reducing to a diffraction pattern could also be made.

@arthurmccray arthurmccray self-assigned this Aug 20, 2026

@arthurmccray arthurmccray left a comment

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.

Hi Ian, overall looks good! I'm leaving a few comments in the files where errors would occur. A few more general things:

  • We are also in the process of moving Vector over to being base torch, (see #272) That will cause a problem next time you try and merge from dev, but I think an LLM should be able to adapt what you've added to torch without many issues. I don't think this needs to be solved now.
  • a lot of the things i point out in comments would be caught with a simple pre-commit. I would suggest checking out the CONTRIBUTING.md or just install and run ruff.
  • as a smaller thing, please refer to PEP8 for a style guide and conventions for naming (e.g. in the DBSCAN_L2 function)
  • please write a few tests for DDF (or have an LLM do so for you)

from quantem.diffraction.strain_autocorrelation import StrainMapAutocorrelation as StrainMapAutocorrelation
from quantem.diffraction.model_fitting import ModelDiffraction as ModelDiffraction No newline at end of file
from quantem.diffraction.model_fitting import ModelDiffraction as ModelDiffraction
from quantem.diffraction.digital_dark_field_cluster import * No newline at end of file

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.

Please do not import * , if you want certain features elevated in the namespace please import specific items (as is done in this file)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fair dos. Will name everything explicitly.


from tqdm import tqdm

from sklearn.cluster import DBSCAN

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.

sklearn isn't a dependency of quantem, so please guard the import with an error message saying something like "if you want to use this feature, please install scikit-learn to your environment"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I really suggest that you consider having scikit-learn as a dependency so we can leverage the tools they have built rather than having to reinvent the wheel ourselves.
Of course, that means we might have to take torch tensors and transform to numpy to access this, but my initial attempt at getting DBSCAN written for torch didn't work well. Maybe this can be done eventually, but until then, scikit-learn is the best show on the road for clustering algorithms.


from tqdm import tqdm

from sklearn.cluster import DBSCAN

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.

sklearn isn't a dependency of quantem, so please guard the import with an error message saying something like "if you want to use this feature, please install scikit-learn to your environment"

Comment on lines +441 to +442
assert isinstance(fig, Figure)
assert isinstance(ax, Axes)

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 lines throw an import error

if ordering == "sequential":
cluster_list = unique_labels[1:]
elif ordering == "size":
cluster_list = L1_unique_labels[1:][np.argsort(L1_all_cluster_sizes[1:])[::-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.

gives a NameError


"""
assert isinstance(maskstack, np.ndarray), "the maskstack must be a numpy array"
assert maskstack.shape[-1] == pointsvector.flatten.shape[1], "the mask size does not match the Vector size"

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 be .flatten(), and i'm not actually sure it's comparing row to row


L1_unique_labels = np.unique(L1labels)[1:]

COMs = np.zeros_like(np.vstack((L1_unique_labels,L1_unique_labels)).T)

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.

are COMs supposed to be integer valued? this will truncate sub-pixel offsets

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

easy to fix

assert len(scaling)==len(fields), "the scalings and fields must have the same number of entries"

# We need to return a new Vector as it is changing length once we select only part of the data
pointsvector2 = pointsvector.copy()

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 line isn't used

plot_L1_clusters_kspace(
pointsvector2,
fields,
kr_max_plot=int(pointsvector2.select_fields('kx').flatten().max()*1.05)

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 this meant to be kr not kx?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, this is intentional. It rather assumes the detector is square, which it probably is. In the rare cases where it is not (e.g. Merlin T4 in frame-based mode), kx_max > ky_max, so this sets the maximum radius used in deciding maximum axis limits to the largest k value possible +5%

Comment on lines +50 to +51
for rx in tqdm(range(Rshape[0])):
for ry in range(Rshape[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.

I think there should be a way to vectorize this

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.

3 participants