labsyspharm / ashlar

ASHLAR: Alignment by Simultaneous Harmonization of Layer/Adjacency Registration
https://labsyspharm.github.io/ashlar/
MIT License
122 stars 42 forks source link

Alignment shift on stitched images #151

Open adajnq opened 2 years ago

adajnq commented 2 years ago

Hello, I have an issue related to shift in the aligned images. We have run a set of tCycif experiments and after the stitching with Ashlar none of the resulting images have been aligned correctly, there is always a swift between cycles as the one you can see in these images:

ashlar_FASCC13_stitching ashlar_FASCC13_stitching_2

We suspect that this could be caused by our scanner's error. I have only included the --flip-x and --pyramid positional arguments for the stitching of this image, -m SHIFT has been used in a previous attempt and did not really correct the shift. Is there something we can try with Ashlar to correct this?

You can find an example image here: https://filesender.funet.fi/?s=download&token=636335cb-d88c-4094-b312-08ac752b68a7 The raw data (nd2 files) and a txt with the channel names can be found in https://datacloud.helsinki.fi/index.php/s/mqtXnDtYmFNHAxa

Best regards, Ada

jmuhlich commented 2 years ago

The datacloud site is returning an error at the moment, but if that gets fixed or if you share the images using a different method I will take a look. If you already see problems with the first 2 cycles then that's all I need to see.

adajnq commented 2 years ago

Dear Jeremy,

Thank you for your response. In the case of the file I sent you (FASCC-13) the swift is already noticeable from the first 2 cycles. However, other slides of the same experiment began showing that swift later (around cycle 4). I tried to generate new links for you to access our images. The stitched images can be found here and the unstitched nd2 files and channel names in a txt can be found here

Yu-AnChen commented 1 year ago

I looked into the first and second cycle (FASCC-13_cycle0.nd2 and FASCC-13_cycle1.nd2) you shared. Currently I would recommend the following parameters using ashlar v1.17.0 (note that you don't need --pyramid when -o is a filepath ends with .ome.tif)

ashlar cycle0.nd2 cycle1.nd2 -o output_dir/FASCC-13.ome.tif --flip-x --filter-sigma 1 -m 30

ashlar.reg.plot_layer_quality is useful to inspect the registration issues between cycles. Here's the plot when using your current parameter. Rectangles with purple-pink edges are FOVs, the brightness indicates relative registration quality. The direction of the arrow indicates the detected translation between the pairs between the two cycles, and the black arrow indicates the detected shift is discarded (essentially) due to the -m constrain. With you current setting, only the center FOVs are "valid", FOVs around the scanning area are "failed".

layer_quality-m15_s0


Using larger -m can hlep, as most of the FOVs with tissue now are valid.

layer_quality-m30_s0


However I would still recommend using adding --filter-sigma as it further stabilized the registration even when you have larger global shift from cycle to cycle.

layer_quality-m30_s1


Lastly, the global pattern of the white arrows indicates that there's a rotation between the two mosaics, it's usually due to the slide loading or the mis-alignment of the microscope stage/slide holder. I use imreg_dft to inspect and it showed a ~0.16 degrees rotation between the first two cycles.

Registering cyclic images with global rotation is on ASHLAR's roadmap and under development. Currently you can refer to the dev branch or my code to work out a solution for your dataset.

Attaching the script I used to generate the quality plots and detect the rotation between cycles.

from ashlar.scripts import ashlar
from ashlar import reg
import numpy as np
import matplotlib.pyplot as plt
​
c1r = reg.BioformatsReader('FASCC-13_cycle0.nd2')
ashlar.process_axis_flip(c1r, True, False)
c2r = reg.BioformatsReader('FASCC-13_cycle1.nd2')
ashlar.process_axis_flip(c2r, True, False)
​
c1e = reg.EdgeAligner(c1r, max_shift=30, filter_sigma=0, verbose=True)
c1e.run()
c21l = reg.LayerAligner(c2r, c1e, max_shift=30, filter_sigma=0, verbose=True)
c21l.run()
​
c21l.mosaic_shape = c1e.mosaic_shape
vmin = np.percentile(c1e.reader.thumbnail[c1e.reader.thumbnail > 0], 1)
​
reg.plot_edge_quality(c1e, img=np.log1p(c1e.reader.thumbnail), im_kwargs=dict(vmin=np.log1p(vmin)))
plt.gcf().suptitle(f"-m {c1e.max_shift} --filter-sigma {c1e.filter_sigma}", color='salmon')
# save plot to disk
plt.gcf().tight_layout()
plt.gcf().set_size_inches(16, 9)
plt.gcf().savefig(f'edge_quality-m{c1e.max_shift}_s{c1e.filter_sigma}.jpg', bbox_inches='tight', dpi=144)
​
reg.plot_layer_quality(c21l, img=np.log1p(c1e.reader.thumbnail), im_kwargs=dict(vmin=np.log1p(vmin)))
plt.gcf().suptitle(f"-m {c1e.max_shift} --filter-sigma {c1e.filter_sigma}", color='salmon')
# save plot to disk
plt.gcf().tight_layout()
plt.gcf().set_size_inches(16, 9)
plt.gcf().savefig(f'layer_quality-m{c1e.max_shift}_s{c1e.filter_sigma}.jpg', bbox_inches='tight', dpi=144)
​
​
# Inspect rotation
​
# using napari
from ashlar import viewer
v = viewer.view_edges(c1e, tiles=range(141, 146))
viewer.view_edges(c21l, tiles=range(141, 146), viewer=v)
​
# using imreg_dft
# https://pypi.org/project/imreg_dft/
import imreg_dft
for i in range(143, 151):
    print(imreg_dft.imreg._get_ang_scale(c21l.overlap(i)[1:], 0))
​
# In [80]: for i in range(143, 151):
#     ...:     print(imreg_dft.imreg._get_ang_scale(c21l.overlap(i)[1:], 0))
#     ...:
# (1.0000507033513355, 0.16342539077689366)
# (1.0001891723557386, 0.1684640597475493)
# (0.9997530781653406, 0.16884097372422957)
# (1.000237636015229, 0.1657715317512043)
# (1.000503527855453, 0.15850173196633932)
# (1.0004809408770123, 0.1665515428289268)
# (1.0003915674224064, 0.1838691158465906)
# (1.0002914071957174, 0.15521869283534784)