Created
March 24, 2023 20:59
-
-
Save alisterburt/435f3ee3d3106b6f3323226dcd0b90a7 to your computer and use it in GitHub Desktop.
motioncor implementation in libtilt - fourier space
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import torch | |
| import numpy as np | |
| import mrcfile | |
| import einops | |
| import torch.nn.functional as F | |
| from torch_cubic_spline_grids import CubicBSplineGrid3d | |
| from libtilt.rotational_average import rotational_average_2d | |
| from libtilt.utils.coordinates import array_to_grid_sample | |
| from libtilt.shift.phase_shift_2d import phase_shift_dfts_2d | |
| from libtilt.mask.shapes_2d import circle | |
| from libtilt.patch.patch_indices import get_patch_indices_2d | |
| from libtilt.patch.patch_centers import get_patch_centers_3d | |
| IMAGE_FILE = 'data/TS_01_000_0_0_bin8.mrc' | |
| GT_DEFORMATION_FIELD_RESOLUTION = (3, 3, 3) # (t, h, w) | |
| LEARNED_DEFORMATION_FIELD_RESOLUTION = (3, 5, 5) # (t, h, w) | |
| LEARNING_RATE = 0.1 | |
| image = torch.tensor(mrcfile.read(IMAGE_FILE)) | |
| image = (image - torch.mean(image)) / torch.std(image) | |
| ### simulate continuous beam induced motion on the image | |
| multi_frame_micrograph = einops.repeat(image, 'h w -> 10 h w').clone() | |
| t, h, w = multi_frame_micrograph.shape[-3:] | |
| gt_deformation_field_data = np.random.uniform( | |
| low=-5, high=5, size=(2, *GT_DEFORMATION_FIELD_RESOLUTION) | |
| ) | |
| gt_deformation_field = CubicBSplineGrid3d.from_grid_data( | |
| torch.tensor(gt_deformation_field_data).float() | |
| ) | |
| _t, _y, _x = torch.linspace(0, 1, t), torch.linspace(0, 1, h), torch.linspace(0, 1, w) | |
| tt, yy, xx = torch.meshgrid(_t, _y, _x) | |
| tyx = einops.rearrange([tt, yy, xx], 'tyx t h w -> t h w tyx') | |
| shifts = gt_deformation_field(tyx) | |
| array_coordinates = torch.tensor(np.indices(image.shape[-2:])) | |
| array_coordinates = einops.rearrange(array_coordinates, 'yx ... -> ... yx') | |
| deformed_coordinates = array_coordinates + shifts | |
| grid_sample_coordinates = array_to_grid_sample(deformed_coordinates, array_shape=(h, w)) | |
| multi_frame_micrograph = F.grid_sample( | |
| input=einops.rearrange(multi_frame_micrograph, 't h w -> t 1 h w'), | |
| grid=grid_sample_coordinates, | |
| mode='bicubic', | |
| padding_mode='zeros', | |
| align_corners=True, | |
| ) | |
| multi_frame_micrograph = einops.rearrange(multi_frame_micrograph, 't 1 h w -> t h w') | |
| ### learn the motion | |
| reference = image.clone() | |
| # extract patches... | |
| PATCH_SIDELENGTH = 64 | |
| patch_idx_h, patch_idx_w = get_patch_indices_2d( | |
| image_shape=image.shape, | |
| patch_shape=(PATCH_SIDELENGTH, PATCH_SIDELENGTH), | |
| patch_step=(PATCH_SIDELENGTH // 2, PATCH_SIDELENGTH // 2), | |
| distribute_patches=True, | |
| ) | |
| patch_centers = get_patch_centers_3d( | |
| image_shape=multi_frame_micrograph.shape, | |
| patch_shape=(1, PATCH_SIDELENGTH, PATCH_SIDELENGTH), | |
| patch_step=(1, PATCH_SIDELENGTH // 2, PATCH_SIDELENGTH // 2), | |
| distribute_patches=True | |
| ) # (t, h, w, thw) | |
| patch_centers = patch_centers / torch.tensor([t - 1, h - 1, w - 1]) | |
| data_patches = multi_frame_micrograph[:, patch_idx_h, patch_idx_w].detach() | |
| reference_patches = reference[patch_idx_h, patch_idx_w].detach() | |
| # mask the reference and the data | |
| mask = circle( | |
| radius=PATCH_SIDELENGTH / 4, sidelength=PATCH_SIDELENGTH, | |
| smoothing_radius=PATCH_SIDELENGTH / 8 | |
| ) | |
| data_patches *= mask | |
| reference_patches *= mask | |
| # fft the data and the reference | |
| data_patches = torch.fft.rfftn(data_patches, dim=(-2, -1)) | |
| reference_patches = torch.fft.rfftn(reference_patches, dim=(-2, -1)) | |
| deformation_field = CubicBSplineGrid3d( | |
| resolution=LEARNED_DEFORMATION_FIELD_RESOLUTION, | |
| n_channels=2 | |
| ) | |
| motion_optimiser = torch.optim.Adam( | |
| params=deformation_field.parameters(), | |
| lr=LEARNING_RATE, | |
| ) | |
| for i in range(200): | |
| motion_optimiser.zero_grad() | |
| shifts = deformation_field(patch_centers) | |
| image_shape = (PATCH_SIDELENGTH, PATCH_SIDELENGTH) | |
| shifted_patches = phase_shift_dfts_2d( | |
| data_patches, | |
| shifts=shifts, | |
| rfft=True, | |
| image_shape=image_shape, | |
| ) | |
| loss = torch.sqrt(torch.mean((reference_patches - shifted_patches).abs() ** 2)) | |
| last_loss = loss.item() | |
| loss.backward() | |
| motion_optimiser.step() | |
| print(loss.item()) | |
| # quantify how well we're doing | |
| gt = gt_deformation_field(patch_centers) | |
| learned = deformation_field(patch_centers) | |
| print(torch.mean(torch.abs(gt - learned))) | |
| # invert the motion | |
| shifts = -1 * deformation_field(tyx) | |
| sample_coords = array_coordinates + shifts | |
| reconstruction = F.grid_sample( | |
| input=einops.rearrange(multi_frame_micrograph, 't h w -> t 1 h w'), | |
| grid=array_to_grid_sample(sample_coords, array_shape=(h, w)), | |
| mode='bicubic', | |
| padding_mode='zeros', | |
| align_corners=True, | |
| ) | |
| reconstruction = einops.rearrange(reconstruction, 't 1 h w -> t h w') | |
| # visualise | |
| import napari | |
| viewer = napari.Viewer() | |
| viewer.add_image(reference.detach().numpy(), name='reference') | |
| viewer.add_image(multi_frame_micrograph.detach().numpy(), name='wavy data') | |
| viewer.add_image(reconstruction.detach().numpy(), name='motion corrected') | |
| napari.run() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment