Registration¶
Final data is in one common coordinate system. It takes quite a few steps to get that. This file describe how genes and barcodes are registered together.
Overview¶
The command is run as follows:
iss register --path relative/path/to/data --prefix genes_round
By default, the script will perform only missing steps. There is a --force-redo flag
that will force the script to re-run all steps, even if the output files already exist.
The script will perform different steps depending on the type of acquisition. See details for sequencing and fluorescent acquisitions.
To check that everything worked, look in figures/registration/prefix for diagnostic plots. If there is no motion on the mp4 files for sequencing acquisitions, you’re good. For fluorescent acquisitions, you should see that the spots are aligned across channels on the png.
If it looks bad, see the relevant Troubleshooting section below.
Register Sequencing acquisitions¶
These are acquisitions that have multiple rounds, namely genes_round and barcode_round. The steps are:
flowchart TD
start[Start] --> regref[run_register_reference_tile];
batch_est(((register_tile)));
subgraph register_reference_tile
regref --> diag_ref([check_ref_tile_registration]);
end
regref --> batch_est;
batch_est --> corr;
subgraph correct_shifts
subgraph run_correct_shifts
corr[run_correct_shifts];
corr --> filt[filter_ransac_shifts];
end
subgraph check_tile_shifts
filt --> ctr([check_tile_registration]);
end
subgraph check_shift_correction
filt --> csc([check_shift_correction]);
end
subgraph check_tile_registration
ctr --> diag_tile([check_tile_registration]);
end
end
style batch_est fill:#E1BEE7,stroke:#424242
style corr stroke:#000000,fill:#E1BEE7
style regref stroke:#000000,fill:#E1BEE7
style filt stroke:#000000,fill:#E1BEE7
style csc fill:#BBDEFB,stroke:#616161,color:#000000
style diag_ref fill:#BBDEFB,stroke:#616161,color:#000000
style diag_tile fill:#BBDEFB,stroke:#616161,color:#000000
style ctr fill:#BBDEFB,stroke:#616161,color:#000000
style run_correct_shifts fill:#EEEEEE, stroke:#424242
style check_tile_shifts fill:#EEEEEE, stroke:#424242
style check_shift_correction fill:#EEEEEE, stroke:#424242
style check_tile_registration fill:#EEEEEE, stroke:#424242
style correct_shifts fill:#AAAAAA, stroke:#424242
style register_reference_tile fill:#AAAAAA, stroke:#424242
Troubleshooting¶
If the registration looks bad, we need to find which step failed.
Is the reference tile registered properly?
In the figures/registration folder, look at the files starting with
registration_reference_tile. If there is no or little signal: pick a better
tile (change the 'ref_tile' parameter in the ops.yml file). If there is signal and
it still looks bad, double check the figures/registration/PREFIX folder, look at the
affine_debug_PREFIX... png file. You might not have enough signal for affine
registration. If that’s the case, you will have to try similarity transform (not
supported anymore, but might still work).
Are most shifts estimated correctly?
Parameters you can tweak: ops["ransac_max_shift"], ops["ransac_min_tiles"], and
ops["ransac_residual_threshold"].
Registering sequencing rounds¶
We need to register the channels and rounds together and the tiles with their neighbours.
Short version:¶
Final transforms for channel and round registration are saved in
"reg" / f"tforms_best_{prefix}_{roi}_{tilex}_{tiley}.npz".
The second part must be run each time.
Detailed explanation part 1: Estimating angle, scale and shift¶
For each acquisition we need to find how the channels register together. It needs to be done for each acquisition as the mirrors wobble a bit and the gain of the stage motor seem to vary a bit.
Register reference tile¶
We do that on a manually tile that has signal with
iss register-ref-tile.
This will save f"tforms_{prefix}.npz" in the main data_folder. The npz contains:
angles_within_channels: rotation angles between rounds for each channelshifts_within_channels: shifts between rounds for each channelscales_between_channels: scaling between channels (common for all rounds)angles_between_channels: rotation angles between channels (common for all rounds)shifts_between_channels: shifts between channels (common for all rounds)
To estimate these values, the algorithm first align images for each channel across rounds. This is much more reliable than registering different channels for the same acquisition, as the sequencing dyes have limited bleedthrough across channels. On the other hand, when aligning between rounds, many rolonies will have the same base and therefore show up across rounds, providing a robust signal for registration.
Registration is done by iterative grid search. We first search over an initial range of rotation
angles and compute phase correlation for each angle. We then determine the best angle and narrow
the search range around this value. It is important that the initial spacing between angles is
fine enough that we can find this peak. This will yield angles_within_channels and
shifts_within_channels.
Once we have registered together rounds for each channel, we can use the resulting angles and shifts to compute mean and STD projections across rounds (we use the STD projections because rolonies show up very nicely on them). These projection should capture all rolonies and will look very similar across channels. They are provide ideal signal for registration across channels.
To register channels we need to correct for scaling as well as rotation due to chromatic aberration
and small differences in alignment of the tube lenses for each camera. This is done using grid search,
similar to how angles_within_channels are estimated. We search for the best angles and scales
while iteratively refining the search range. This will yield scales_between_channels,
angles_between_channels, and shifts_between_channels.
Estimate for all tiles¶
We can then use the parameters estimated for the reference tile to register all tiles with:
iss estimate-shifts
This is necessary for two reasons. First, dichroic wobble slightly
during and between acquisitions resulting in different shifts between channels. Second, the
gain of the microscope stage seems to vary from day to day. Therefore, the microscope does not
consistently move to the same position for each tile from round to round, resulting in different
shifts across rounds. Therefore, we will re-estimate shifts, both within and across channel,
but will not change angles_within_channels, angles_between_channels and
scales_between_channels.
Note
angles_within_channels and angles_between_channels might actually vary due to the
dichroic wobble but in practice registration works well using values from the reference tile.
The output is saved in the reg subfolder as
f"tforms_{prefix}_{roi}_{tilex}_{tiley}.npz"
Correct shift with ransac¶
The single tile estimation tends to fail sporadically if there is not enough signal. This can be corrected given that the main change of shifts from tile to tile is a linear function of X and Y (probably due to change in gain of the stage). We do that with RANSAC robust regression in:
iss correct-shifts.
Once again, this does not re-estimate angles and scale changes, just shifts. The
output is saved in the reg folder as
f"tforms_corrected_{prefix}_{roi}_{tilex}_{tiley}.npz"
However, this correction is not ideal for tiles that were already properly registered
and can introduce bigger shifts. Therefore, we only apply this correction to tiles
that have a shift above a certain threshold. This threshold is currently set in
ops['ransac_residual_threshold']
The final transformation is then saved in the reg folder as
f"tforms_best_{prefix}_{roi}_{tilex}_{tiley}.npz"
Detailed explanation part 2: Stitching tiles¶
The information computed above allows us to load all tiles in their “acquisition” coordinates (same for all tiles of one prefix, but different across prefixes).
Find tile shifts¶
We estimate how much overlap there is between tiles (and therefore how much we need to shift them to merge) by phase correlation. This also takes into account that the cameras may not be perfectly aligned with the stage, therefore there might be (and usually will be) a shift in both X and Y between both rows and columns.
This is done by calling:
shift_right, shift_down, tile_shape = iss.pipeline.register_adjacent_tiles(
data_path, ref_coors=ops['ref_tile'], prefix='genes_round_1_1')
The output is currently not saved.
Merge coordinates¶
With these tile shift we can find the position of each tile, simply by multiplying the tile number by the shift.
This can be done with:
roi_dims = np.load(processed_path / data_path / f"{prefix}_roi_dims.npy")
ntiles = roi_dims[roi_dims[:, 0] == 1, 1:][0] + 1
tile_origins, tile_centers = iss.pipeline.calculate_tile_positions(
shift_right, shift_down, tile_shape, ntiles)
The output is currently not saved.
Registering acquisition together¶
The final reference coordinate is (for now) genes_round. We can register each
acquisition independently first. Then we want to merge them. To do that we generate
a downsampled stitched image of the reference acquisition and the acquisition we want
to register.
This is done for raw images with iss.pipeline.stitch_and_register. It returns the
two registered mosaic at full resolution as well as the transformation parameter: shift
and angle.
This output is not saved for now.
For spots, the same function is called by iss-reg2ref align-spots