-
Notifications
You must be signed in to change notification settings - Fork 30
Improve the readability of beam shift calibration code & results #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve the readability of beam shift calibration code & results #144
Conversation
stefsmeets
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. This is probably one of the first routines I wrote, so it was due for a refresh. Happy to hear you find it useful enough to spend some time on.
I left some minor comments and type suggestions.
| return c | ||
|
|
||
| @classmethod | ||
| def from_file(cls, fn=CALIB_BEAMSHIFT) -> Self: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def from_file(cls, fn=CALIB_BEAMSHIFT) -> Self: | |
| def from_file(cls, fn: str =CALIB_BEAMSHIFT) -> Self: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I'll make it an AnyPath i.e. Union[str, bytes, os.PathLike] though.
| return c | ||
|
|
||
| def to_file(self, fn=CALIB_BEAMSHIFT, outdir='.'): | ||
| def to_file(self, fn=CALIB_BEAMSHIFT, outdir: AnyPath = '.') -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def to_file(self, fn=CALIB_BEAMSHIFT, outdir: AnyPath = '.') -> None: | |
| def to_file(self, fn: str=CALIB_BEAMSHIFT, outdir: AnyPath = '.') -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to from_file, I'd prefer to make it an AnyPath, though in this case it actually is appended to outdir so it is less straightforward. Is there any reason to split outdir and fn here? Other than consistency across configs, a single param would be cleaner, see CalibStageRotation.to_file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I wrote this, the goal for splitting fn and outdir was to be able to have a global default for fn. Not sure if I still like that choice so feel free to change it. I agree that it makes more sense to specify the directory or full path here.
| import yaml | ||
|
|
||
|
|
||
| def _numpy_2d_representer(dumper: yaml.Dumper, array: np.ndarray) -> yaml.nodes.SequenceNode: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used for dumping simple coordinates like the beam centers and shifts and not images, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, images are removed before dumping to yaml to avoid duplicating several MB worth of data. I though about including image paths in calib instead, but this proved to require a lengthier rework I could not afford. Nota bene, without this, serialized ndarrays are really disgusting:
pixels:
- - 129.90703125000002
- 3.225
- - -13.1015625
- 232.8046875
- - 8.969531250000001
- -21.970312500000002
- - -131.015625
- 245.80546875000002
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good, just wondering. Storing image arrays in a yaml is something I tried at some point, but it was a real pain to work with. I usually convert to/from list but a dumper also works.
I think this is where libraries like attrs (or dataclasses) really shine. You can have a schema in the form of your dataclass, so you can decouple structuring/unstructuring (i.e. reading/writing yaml) from your data definition.
Co-authored-by: Stef Smeets <stefsmeets@users.noreply.github.com>
Co-authored-by: Stef Smeets <stefsmeets@users.noreply.github.com>
The beam center algorith assumes a well-formed peak: instamatic/src/instamatic/tools.py Line 116 in 3cf45a1
With a large beam, I could imagine the contrast is quite flat without an obvious peak in the center of the bundle. You may have better luck first applying a large gaussion filter before using the algorithm. Or try a different algorithm based on thresholding and blob-finding: https://scikit-image.org/docs/stable/auto_examples/features_detection/plot_blob.html#sphx-glr-auto-examples-features-detection-plot-blob-py |
|
Btw, the fact that you can render the calibration results directly in the gui is really cool. |
|
I read this morning that apparently the phase correlation method loses accuracy very quickly with growing peak size. In general, I do not consider this PR to be a "fix", I mostly just needed to identify when the calibration goes wrong because I noticed my FastADT manual tracking was misbehaving. This PR is mostly to address that. In general, I would like to 1) decouple data collection and interpretation logic, 2) run interpretation in separate thread while next image is collected, 3) implement additional data collection methods, 4) have several methods for interpretation and 5) auto-choose the method with best fit. Before that I should probably study calib_directbeam (similar logic) and autocred (some beam size calibration) more though. Unfortunately I have higher priorities now. As for rendering in GUI, I am pretty happy with the flexibility Given is mostly non-intrusive tested it on my TEM (will keep testing this week), I intend to have it merged sometime this week to make space for #145 that will benefit from it :). |
While the beam shift calibration class is marked in doc-string as "deprecated", I find it useful for my experiments. However, I sometimes had problems getting it to work, in particular when the beam was larger. The wording across the file is inconsistent, the ways to investigate the results are very limited, and in overall while the calibration asks you if you would like to repeat it when calibrating live, there is little that can be used to evaluate whether the calibration was successful.
This PR involves mostly refactor of the
calibrate_beamshift.pyfile intended to increase its readability. This involves several changes to the code structure; however, the overall code capability, including existing API, remains unchanged:CalibBeamShift: is now a dataclass;CalibBeamShift: all use of "pixel" and "shift" are now ordered this way and consistent;CalibBeamShift:beamshift_to_pixelcoordandpixelcoord_to_beamshiftdon't crash at list/tuple-like inputs;CalibBeamShift:from_datacan now store images (for plotting) instead of headers (unused);CalibBeamShift:from_file/to_filenow serializes to a human-readableyamlinstead ofpickle;np.ndarrays are easily readable upon dumping (see below);CalibBeamShift: simplifiedplot, raises rather than exiting at no data (unused, more future-proof);CalibBeamShift: Addedannotate_videostreamused inliveto show calibration results in the GUI (see below);calibrate_beamshiftwas de-cluttered and simplified,calibrate_beamshiftcan now usecamera.calib_beamshift.delaybetween images (if beam shift is slow);calibrate_beamshift: reimplementedinstamatic.tools.printerprogress bar display usingtqdm.tqdmcalibrate_beamshift_from_image_fn: fixed (unusable due to broken import - still unsure why it needs a hole though);This PR does not introduce any changes to API, just code quality and ease of interpretation. Existing code should work mostly just as before. Running calibration with even-sized grid should now work properly. The calibration will now be saved as a human-readable yaml file, e.g. for grid size 3:
After the calibration is complete and when
liveasks whether it should be repeated, the GUI will now display the maximum over all collected calibration images with some dots, blue representing fit- and orange representing modeled- beam positions:One note for future is that the calibration gets progressively less accurate the bigger the beam is: