Skip to content

Export

PciSeq

For probabilistic cell typing with pciSeq, a DAPI background image and gene spot positions must be exported.

DAPI image export

Filtered DAPI

To export the anchor's filtered DAPI stitched image

from coppafisher import Notebook
from coppafisher.results import export_pciseq_dapi_image

nb = Notebook("/path/to/notebook")
export_pciseq_dapi_image(nb)

The DAPI image can then be loaded into memory by

import numpy as np

dapi_image = np.load("/path/to/dapi_image.npz")["arr_0"]

Unfiltered DAPI

To export the anchor's unfiltered DAPI stitched image

from coppafisher import Notebook
from coppafisher.results import export_pciseq_unfiltered_dapi_image

nb = Notebook("/path/to/notebook")
config_path = "/path/to/config.ini"
export_pciseq_unfiltered_dapi_image(nb, config_path, radius_norm_file=None)

You can set radius_norm_file="default" to use the default dapi radius normalisation when the dapi channel is 0.

The DAPI image can then be loaded into memory by

import numpy as np

dapi_image = np.load("/path/to/dapi_image.npz")["arr_0"]

Gene spots export

Export gene spots into a compatible csv file by

from coppafisher import Notebook
from coppafisher.results import export_to_pciseq

nb = Notebook("/path/to/notebook")
export_to_pciseq(nb, method)

where method can be "omp", "prob", or "anchor" for each gene calling method. To set a score and/or intensity minimum threshold:

from coppafisher import Notebook
from coppafisher.results import export_to_pciseq

nb = Notebook("/path/to/notebook")
export_to_pciseq(nb, method, score_thresh, intensity_thresh)

score_thresh and intensity_thresh must be numbers. Use the Viewer to help decide on thresholds. intensity_thresh is set to 0.15 for OMP in the Viewer by default.

Merge cell mask chunks

Multiple cell masks for separate tiles can be merged together that are produced by cellpose. The cell masks must be uint16 3d images all of the same shape (im_z, im_y, im_x). They must be saved as .npy files.

from coppafisher.results import merge_cell_masks

merged_cell_mask = merge_cell_masks(
    cell_mask_file_paths=["/path/to/cell_mask_0.npy", "/path/to/cell_mask_1.npy"],
    cell_mask_origin_yxzs=[[0.0, 0.0, 0.0], [1100, 1.0, 0.0]],
    expected_tile_overlap=0.15,
    merge_cells_method="",
)

where cell_mask_origin_yxzs is the bottom-leftmost position for each cell mask in a global coordinate frame. For more details, see the merge_cell_masks docstring at coppafisher/results/cell_mask.py.

If you have a coppafisher notebook, you can base the merging off the tile stitch results:

from coppafisher import Notebook
from coppafisher.results import merge_cell_masks

nb = Notebook("/path/to/notebook")
merged_cell_mask = merge_cell_masks(
    cell_mask_file_paths=["/path/to/cell_mask_0.npy", "/path/to/cell_mask_1.npy"],
    cell_mask_origin_yxzs=nb.stitch.tile_origin,
    expected_tile_overlap=nb.stitch.associated_config["stitch"]["expected_overlap"],
    merge_cells_method="",
)
Merging methods

There are two strategies for merging cell masks together:

1) merge_cells_method="" does no cell merging and the tile that has the closest tile centre to the overlapping pixel data is used. This can leave erroneous split cells at the midpoint between the tile overlap.

Image title
No merging result.

2) merge_cells_method="merge 0.5" merges cells together in the overlapping region when the cells have at least 50% overlap. You can adjust the number 0.5 for anything between 0 and 1 to best suit your data. For more details on the merging method:

from coppafisher.results import merge_cell_masks
print(merge_cell_masks.__doc__)

Image title
Merging with a low overlap threshold.

Image title
Merging with a high overlap threshold.

Saving the merged cell mask

You can save the resulting merged cell mask as a .npy file

import numpy as np

np.save("/path/to/merged_cell_mask.npy", merged_cell_mask)

Or a compressed .npz file

import numpy as np

np.savez_compressed("/path/to/merged_cell_mask.npy", merged_cell_mask)

Custom Images

Additional custom images can be aligned with coppafisher images and gene spots provided that you have a dapi channel (or something similar to align with the anchor-DAPI image).

Extract the additional image(s)

The additional images must be extracted from ND2 files. You will likely require the DAPI channel for best results. They are saved as tiff files. If you do not have ND2 input files, you need to first manually convert them to tiff files.

from coppafisher.custom_alignment import extract_raw
from coppafisher import Notebook

config_file = "/path/to/used/config.ini"
custom_nd2 = "/path/to/input/file.nd2"
output_dir = "/path/to/extract/directory/"

nb = Notebook("/path/to/notebook")
extract_raw(
    nb,
    config_file,
    save_dir=output_dir,
    read_dir=custom_nd2,
    use_tiles=nb.basic_info.use_tiles,
    use_channels=[nb.basic_info.dapi_channel, 9, 23],
    reverse_custom_z=False,
    radius_norm_file=None,
    radius_norm_channels=None,
    dapi_radius_norm_file=None,
)

use_channels can be any valid channel(s) inside the custom image .nd2 file. This will also extract the anchor round in the DAPI channel. You can reverse the z planes in the custom image by setting reverse_custom_z to True.

Set radius_norm_file="default_nine" to use the default nine channel tile radius/channel normalisation file at coppafisher/setup/nine_channel_normalisations.npz.

Set radius_norm_file="default_seven" to use the default seven channel tile radius/channel normalisation file at coppafisher/setup/seven_channel_normalisations.npz.

Set dapi_radius_norm_file="default" to use the default dapi channel tile radius/channel normalisation file at coppafisher/setup/dapi_channel_normalisations.npz.

Config File

The config file must be a valid configuration, like the one used during the experiment. input_dir must be a real input directory.

Stitch

The extracted raw anchor-DAPI images are stitched using coppafisher's stitch method. The custom image is stitched by the same method separately. This then needs to be registered with the anchor-DAPI in the next step. Do this for each custom image channel separately. I suggest starting with the dapi channels first

from coppafisher.custom_alignment import fuse_custom_and_dapi

fused_custom_dapi_image, fused_anchor_dapi_image = fuse_custom_and_dapi(nb, output_dir, channel=nb.basic_info.dapi_channel)

Dapi Register

Alignment is done using the package LineStuffUp maintained by Max Shinn (m.shinn@ucl.ac.uk). This allows control over the type of transformation to apply based on their custom images. Install via pip

python -m pip install LineStuffUp

Then start the interactive alignment process

import linestuffup.gui
from linestuffup.base import TranslateRotate

round_transform = linestuffup.gui.alignment_gui(
    fused_custom_dapi_image, fused_anchor_dapi_image, transform_type=TranslateRotate
)

Press Add new point and click twice to place two corresponding points. Do this a few times, preferably in various z planes too. Press Perform transform to see the resulting transform. Once you are happy with the result, close the napari window.

Type of Transform

You can change the type of transform you wish to find, please see the transfrom readme for details.

For example, you could use the more robust transform type of TranslateRotateRescale. It requires four points in every corner of the image on both edges of the z stack for best results.

Save and Load Transforms

Every transform can be saved and reloaded at a later point. You just need to save the text representation of the transform which can be found by

str(round_transform)

You can save it using Python

with open("/path/to/saved/transform.txt", "w") as file:
    file.write(str(round_transform))

It can be reloaded by

from linestuffup.base import *

with open("/path/to/saved/transform.txt", "r") as file:
    exec("round_transform = " + "\n".join(file.readlines()))

You can rename the variable from round_transform to anything.

Do not run exec on stranger's code (it could be malicious)!

You can now apply the resulting transform to the custom dapi image and save the result as a .tif file

import numpy as np
import tifffile

fused_custom_dapi_image_transformed = round_transform.transform_image(
    fused_custom_dapi_image, output_size=fused_anchor_dapi_image.shape, force_size=True, labels=True
)
tifffile.imwrite("/path/to/saved/custom_dapi_image_transformed.tif", fused_custom_dapi_image_transformed)
del fused_custom_dapi_image_transformed
del fused_anchor_dapi_image

Non-Dapi Register

For a non-dapi custom image channel c, it is recommended to find a specific transform to move to the dapi channel for best registration. To do this, first find a transform to move into the dapi custom image's frame

from coppafisher.custom_alignment import fuse_custom_and_dapi
import linestuffup.gui
from linestuffup.base import TranslateRotate

fused_custom_channel_image, _ = fuse_custom_and_dapi(nb, output_dir, channel=c)

channel_transform = linestuffup.gui.alignment_gui(
    fused_custom_channel_image, fused_custom_dapi_image, transform_type=TranslateRotate
)

Now save the fully registered channel image

import tifffile

fused_custom_channel_image_transformed = (channel_transform + round_transform).transform_image(
    fused_custom_channel_image, output_size=fused_custom_channel_image.shape, force_size=True, labels=True
)
tifffile.imwrite(f"/path/to/saved/custom_channel_{c}_image_transformed.tif", fused_custom_channel_image_transformed)
del fused_custom_channel_image_transformed
del fused_custom_channel_image