Skip to content
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

How to register multiple .ome.tif at the same time & issue registering .ome.tif #12

Open
Boehmin opened this issue Sep 4, 2023 · 30 comments

Comments

@Boehmin
Copy link

Boehmin commented Sep 4, 2023

Hi @Yu-AnChen !

I just tried palom on two cycles of FISH and the registration so far seems really really impressive (in particular since one of the cycles is half out of focus). First of all, thank you so much for making this tool available!

I was wondering how it would be possible to register multiple .ome.tif images simultaneously. Additionally, the registration worked fine with one of my datasets (converted from .h5 to .ome.tif which was a big pain already), however I have issues with a smaller .ome.tif dataset. I get the below error message:

(palom) [boehm0002@sgi27 ]$python3 palom_dan_ome_R1toR2.py
2023-09-04 14:12:42.717 | WARNING  | palom.reader:auto_format_pyramid:64 - Unable to detect pyramid levels, it may take a while to compute thumbnails during coarse alignment
2023-09-04 14:12:42.729 | WARNING  | palom.reader:auto_format_pyramid:64 - Unable to detect pyramid levels, it may take a while to compute thumbnails during coarse alignment
Traceback (most recent call last):
  File "/scicore/home/rueegg/boehm0002/spatial_transcriptomics/dans_test1/HiPlex_Example_DJH/palom_dan_ome_R1toR2.py", line 27, in <module>
    ref_thumbnail=c1r.read_level_channels(THUMBNAIL_LEVEL, 2).compute(),
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/dask/threaded.py", line 89, in get
    results = get_async(
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/dask/local.py", line 511, in get_async
    raise_exception(exc, tb)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/dask/local.py", line 319, in reraise
    raise exc
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/dask/local.py", line 224, in execute_task
    result = _execute_task(task, data)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/zarr/core.py", line 821, in __getitem__
    result = self.get_basic_selection(pure_selection, fields=fields)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/zarr/core.py", line 947, in get_basic_selection
    return self._get_basic_selection_nd(selection=selection, out=out,
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/zarr/core.py", line 990, in _get_basic_selection_nd
    return self._get_selection(indexer=indexer, out=out, fields=fields)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/zarr/core.py", line 1285, in _get_selection
    self._chunk_getitem(chunk_coords, chunk_selection, out, out_selection,
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/zarr/core.py", line 1994, in _chunk_getitem
    cdata = self.chunk_store[ckey]
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/zarr/storage.py", line 738, in __getitem__
    return self._mutable_mapping[key]
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 12274, in __getitem__
    return self._getitem(key)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 12951, in _getitem
    keyframe, page, chunkindex, offset, bytecount = self._parse_key(key)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 13050, in _parse_key
    page = series[pageindex]
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 12135, in __getitem__
    return self._getitem(int(key))
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 12115, in _getitem
    return self.parent.pages._getitem(page.index + key)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 7568, in _getitem
    self._seek(key)
  File "/scicore/home/rueegg/boehm0002/miniconda3/envs/palom/lib/python3.10/site-packages/tifffile/tifffile.py", line 7402, in _seek
    raise ValueError('seek of closed file')
ValueError: seek of closed file

Thanks ever so much for your help!

@Yu-AnChen
Copy link
Collaborator

Hi @Boehmin thanks for the kind words :)

Palom might be able to read h5 files depending on the h5 file structure. If you have a small dataset to share I'll find time to take a look.

For the .ome.tif dataset that gave you the error, it looks to me that the file might have some issues. Would you be able to share that?

To register multiple files, our recommended approach is to register everything (cycles 2, 3, 4, and so on) to the first cycle. Please checkout the example script and lmk for any questions.

@Boehmin
Copy link
Author

Boehmin commented Sep 8, 2023

Hi @Yu-AnChen

Thanks for pointing me to the example script. I'll try this!

I have a smaller fused example h5 file (only one timepoint) and the ome tif dataset here. The .h5 consists of z-stacks so I do not know if that would be an issue for palom? If not that would be even better. Channel 4 is DAPI and channel 3 is a fiducial channel so in theory I could register over the fiducial channel as well if that helps?

Again, thanks for looking into this.

@Yu-AnChen
Copy link
Collaborator

Thanks for sharing the images, I was able to reproduce the error when using your ome-tiff files as input. Will look into a solution there.

On the h5 dataset (seems to be generated with BigDataViewer), I was able to convert it into a pyramidal ome-tiff using palom. Note that installing h5py in the environment that you run palom is required.

Since there are Z-stacks, I did a maximum intensity projection on the Z-axis in the following script (the output file size is ~50 MB; it can be opened in QuPath)

import palom
import h5py
import dask.array as da
import pathlib

img_path = pathlib.Path('h5_small/small_fuse_fused.h5')
h5_img = h5py.File(img_path)

# X-Y pixel size at full resolution; unit is µm/pixel
PIXEL_SIZE = 1
CHANNEL_NAMES = ['marker-1', 'marker-2', 'marker-fiducial', 'DNA']

mosaics = [
    # max projection on the Z axis
    da.from_array(h5_img[f"t00000/{channel}/0/cells"]).max(axis=0)
    for channel in h5_img['t00000']
]

palom.pyramid.write_pyramid(
    mosaics=mosaics,
    output_path=img_path.parent / f"{img_path.stem}-pyramid.ome.tif",
    pixel_size=PIXEL_SIZE,
    channel_names=CHANNEL_NAMES,
    downscale_factor=2,
    compression='zlib',
    tile_size=1024,
    save_RAM=True
)

With the output ome-tiffs from the above script, I think you should be able to run palom without running into the ValueError: seek of closed file error in your first comment.

@Yu-AnChen
Copy link
Collaborator

As for the issue of ValueError: seek of closed file, I've found a workaround. Please try it out on your end - I don't have a complete solution to it yet but hopefully that can get you up and running.

@lguerard
Copy link

Since there are Z-stacks, I did a maximum intensity projection on the Z-axis in the following script (the output file size is ~50 MB; it can be opened in QuPath)

Sorry to hijack this, is palom working on 2D files only or did you do a projection to speed it up ?
Thanks !

@Boehmin
Copy link
Author

Boehmin commented Sep 14, 2023

@Yu-AnChen, I tried both solutions and they both worked! Thank you.
Also curious whether this could work in 3D. :)

@Yu-AnChen
Copy link
Collaborator

The current image file reader and writer only works with images that have 2 or 3 dimensions. In our own application, it's (Channel/Band, Y, X). Palom could compute and find out the transformation/mapping between two (C, Z, Y, X) datasets and apply the transformation plane by plane, but the image file reader and writing will need some custom formatting.

@moataz-youssef
Copy link

moataz-youssef commented Dec 15, 2024

Hi @Boehmin thanks for the kind words :)

Palom might be able to read h5 files depending on the h5 file structure. If you have a small dataset to share I'll find time to take a look.

For the .ome.tif dataset that gave you the error, it looks to me that the file might have some issues. Would you be able to share that?

To register multiple files, our recommended approach is to register everything (cycles 2, 3, 4, and so on) to the first cycle. Please checkout the example script and lmk for any questions.

Hello Yu-AnChen,

Sorry to hijack the thread and thanks for this great tool. I discovered your tool after hitting a wall with Ashlar, as my data is already stitched. I used your sample script in this thread and just passed in the location of 3 of my images (CyCIF runs, each DAPI + 3 other channels) as well as an output destination. My files were originally .VSI images, which I converted to OME-TIFF files using QuPath.

I get the following error:

2024-12-15 23:21:24.168 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:5000, keypts R:5000
2024-12-15 23:21:24.261 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:5000, keypts R:5000
2024-12-15 23:21:24.355 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:4165, keypts R:4165
2024-12-15 23:21:31.402 | INFO     | palom.align:compute_shifts:203 - Computing block-wise shifts
Computing shifts: 100%|##############################################################################################################################################################################| 34021/34021 [01:49<00:00, 309.32it/s]
Traceback (most recent call last):
  File "D:\valis_container\palom_v1.py", line 74, in <module>
    fig = aligner.plot_shifts()
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py", line 276, in plot_shifts
    viz_shifts(shifts, self.grid_shape, ax=axs[1])
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py", line 90, in viz_shifts
    dcenter = skimage.filters.threshold_triangle(distances[is_finite])
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\skimage\filters\thresholding.py", line 960, in threshold_triangle
    arg_level = np.argmax(length) + arg_low_level
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\numpy\core\fromnumeric.py", line 1229, in argmax
    return _wrapfunc(a, 'argmax', axis=axis, out=out, **kwds)
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\numpy\core\fromnumeric.py", line 59, in _wrapfunc
    return bound(*args, **kwds)
ValueError: attempt to get argmax of an empty sequence

@Yu-AnChen
Copy link
Collaborator

Hi @moataz-youssef , no worries and thanks for giving it a try. Apologies that I should have included this detail in my recent updates - VSI files are now supported through slideio (github repo) in palom v2024.10.2. You can pass the path to the VSI file to the palom.reader.VsiReader (ref).

From the above message, I'm guessing when converting to ome-tiff via QuPath, the tile-size wasn't specified and the default is 256x256 pixels. I found that small tile size will break how it's currently filtering out tiles that failed the alignment.

If you could start from the VSI files, the tile size will be set to 1024x1024 pixels internally, and the error you are running into would likely be gone.

It would be helpful if you could share a thumbnail of your scan. And here's a jupyter notebook that might give you some ideas.

@moataz-youssef
Copy link

Thanks a lot @Yu-AnChen.

I actually specified in QuPath that the tile size is 1024 pixels, but I will double check, what the final tile size in the converted OME-TIF files was. I can also share the scans/converted files with you privately, if you have time for this.

Thanks a lot for this great tool, I will update you on my progress.

@moataz-youssef
Copy link

@Yu-AnChen your solution worked! Using palom.reader.VsiReader worked and the script finished. The output had only 3 channels from the 3 original files (each with DAPI + 3 channels), so I should expect 12 channels. I can't seem to find how to do this, other than to specify that the registration should be done on channel 0 (DAPI). Do you have any suggestions for me? Thanks a lot in advance.

@moataz-youssef
Copy link

And just to add, the jupyter notebook also worked great and gave me a composite OME-TIFF with the DAPI channels from the 3 VSI images. How can I add the other channels through this method as well?

I am really impressed with the results, they are comparable, if not better, than manual adjustment and elastic deformation using Warpy in QuPath and Fiji.

@Yu-AnChen
Copy link
Collaborator

Thanks, and glad to hear that the alignment seemed to work well!

Would you be able to share the image or images from some test samples? I suspect that the num_channels property of the scene in the VSI file wasn't detected properly. If that's the case, I think it's also valuable to the slideio author.

@Yu-AnChen
Copy link
Collaborator

Re: the jupyter notebook that only gives you the first channel of all the "cycles", try changing the reader.pyramid[0][0] in block In [11]: to reader.pyramid[0], this should give you all the channels detected in the output.

@moataz-youssef
Copy link

moataz-youssef commented Dec 17, 2024

Re: the jupyter notebook that only gives you the first channel of all the "cycles", try changing the reader.pyramid[0][0] in block In [11]: to reader.pyramid[0], this should give you all the channels detected in the output.

Thanks for this suggestion. Removing one [0] from reader.pyramid, gave me 7 channels. Removing one [0] from r1.pyramid in the same block. The output of r1.pyramid is:

 dask.array<transpose, shape=(3, 30757, 39900), dtype=uint16, chunksize=(3, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 15378, 19950), dtype=uint16, chunksize=(3, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 7689, 9975), dtype=uint16, chunksize=(3, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 3844, 4987), dtype=uint16, chunksize=(3, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 1922, 2493), dtype=uint16, chunksize=(3, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 961, 1246), dtype=uint16, chunksize=(3, 961, 1024), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 480, 623), dtype=uint16, chunksize=(3, 480, 623), chunktype=numpy.ndarray>,
 dask.array<transpose, shape=(3, 240, 311), dtype=uint16, chunksize=(3, 240, 311), chunktype=numpy.ndarray>]

So I guess somehow, my images are seen as 3-channel images.

Assembling mosaic  1/ 3 (channel  1/ 3): 100%|###################################| 14275/14275 [04:47<00:00, 49.59it/s]
Assembling mosaic  1/ 3 (channel  2/ 3): 100%|###################################| 14275/14275 [04:56<00:00, 48.22it/s]
Assembling mosaic  1/ 3 (channel  3/ 3): 100%|###################################| 14275/14275 [04:56<00:00, 48.11it/s]
Assembling mosaic  2/ 3 (channel  1/ 3): 100%|###################################| 24230/24230 [05:32<00:00, 72.81it/s]
Assembling mosaic  2/ 3 (channel  2/ 3): 100%|###################################| 24230/24230 [05:25<00:00, 74.54it/s]
Assembling mosaic  2/ 3 (channel  3/ 3): 100%|###################################| 24230/24230 [05:24<00:00, 74.72it/s]
Assembling mosaic  3/ 3 (channel  1/ 3): 100%|###################################| 23834/23834 [05:47<00:00, 68.53it/s]
Assembling mosaic  3/ 3 (channel  2/ 3): 100%|###################################| 23834/23834 [05:46<00:00, 68.76it/s]
Assembling mosaic  3/ 3 (channel  3/ 3): 100%|###################################| 23834/23834 [05:47<00:00, 68.65it/s]
2024-12-17 22:31:30.615 | INFO     | palom.pyramid:write_pyramid:182 - Generating pyramid
2024-12-17 22:31:30.615 | INFO     | palom.pyramid:write_pyramid:185 -     Level 1 (30758 x 39901)
Processing channel: 100%|################################################################| 9/9 [01:57<00:00, 13.04s/it]
2024-12-17 22:33:28.146 | INFO     | palom.pyramid:write_pyramid:185 -     Level 2 (15379 x 19951)
Processing channel: 100%|################################################################| 9/9 [00:36<00:00,  4.02s/it]
2024-12-17 22:34:04.411 | INFO     | palom.pyramid:write_pyramid:185 -     Level 3 (7690 x 9976)
Processing channel: 100%|################################################################| 9/9 [00:15<00:00,  1.74s/it]
2024-12-17 22:34:20.349 | INFO     | palom.pyramid:write_pyramid:185 -     Level 4 (3845 x 4988)
Processing channel: 100%|################################################################| 9/9 [00:10<00:00,  1.22s/it]
2024-12-17 22:34:31.365 | INFO     | palom.pyramid:write_pyramid:185 -     Level 5 (1923 x 2494)
Processing channel: 100%|################################################################| 9/9 [00:09<00:00,  1.09s/it]
2024-12-17 22:34:41.161 | INFO     | palom.pyramid:write_pyramid:185 -     Level 6 (962 x 1247)
Processing channel: 100%|################################################################| 9/9 [00:09<00:00,  1.06s/it]
2024-12-17 22:34:50.687 | INFO     | palom.pyramid:write_pyramid:185 -     Level 7 (481 x 624)
Processing channel: 100%|################################################################| 9/9 [00:09<00:00,  1.04s/it]

The registration of the 9 channels is just fantastic. So I have the 3 DAPIs + 6 other channels. Unfortunately, I can't distinguish which is which. Is there a way to add the channel names to the final mosaic OME-TIFF, either by supplying a csv file with the channel names and order or by querying the file name? This will be just awesome!

I will send you my images (6 in total from 6 staining cycles) as well as the jupyter notebook with the output of all blocks on the email linked to your profile.

@moataz-youssef
Copy link

moataz-youssef commented Dec 17, 2024

By the way, installing napari in the same palom environment, to view the final file, breaks the palom packages.

In the environment I only had palom (according to your instructions on this github) and jupyter-lab installed. Once I install napari, I get the following errors:

TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
TIFFReadDirectory: Warning, Unknown field with tag 33560 (0x8318) encountered.
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\_core\fromnumeric.py:3596: RuntimeWarning: Mean of empty slice.
  return _methods._mean(a, axis=axis, dtype=dtype,
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\_core\_methods.py:138: RuntimeWarning: invalid value encountered in divide
  ret = ret.dtype.type(ret / rcount)
2024-12-17 23:09:33.224 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:0, keypts R:0
2024-12-17 23:09:33.256 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:0, keypts R:0
2024-12-17 23:09:33.287 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:0, keypts R:0
2024-12-17 23:09:33.320 | DEBUG    | palom.register:cv2_feature_detect_and_match:288 - keypts L:0, keypts R:0
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\palom\register.py:249: FutureWarning: `plot_matches` is deprecated since version 0.23 and will be removed in version 0.25. Use `skimage.feature.plot_matched_features` instead.
  skimage.feature.plot_matches(
2024-12-17 23:09:34.622 | WARNING  | palom.register:feature_based_registration:153 - Feature matching failed. Returning identity matrix as placeholder
2024-12-17 23:09:35.552 | INFO     | palom.align:compute_shifts:203 - Computing block-wise shifts
Computing shifts:  58%|################################3                       | 19471/33745 [00:10<00:07, 1822.06it/s]
Traceback (most recent call last):
  File "D:\valis_container\palom_v2.py", line 74, in <module>
    aligner.compute_shifts()
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\palom\align.py", line 210, in compute_shifts
    shifts = shifts_da.compute()
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\dask\base.py", line 372, in compute    (result,) = compute(self, traverse=False, **kwargs)
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\dask\base.py", line 660, in compute    results = schedule(dsk, keys, **kwargs)
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\palom\block_affine.py", line 24, in block_affine_dask
    return block_affine(
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\palom\block_affine.py", line 83, in block_affine
    block_tform = skimage.transform.AffineTransform(
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\transform\_geometric.py", line 1046, in __init__
    b2 = translation[1]
IndexError: index 1 is out of bounds for axis 0 with size 1

By installing palom in a fresh environment, the problem is solved.

@Yu-AnChen
Copy link
Collaborator

I see, it's unfortunate that only the first 3 channels are detected. Let me look into it once I got your files. For adding channel names to the output ome-tiff, you'll need to specify it in the write_pyramid call since it's not parsed from the VSI file. For example to write a 3-cycle image -

palom.pyramid.write_pyramid(
    mosaics,
    "registered-image.ome.tif",
    r1.pixel_size,
    channel_names=[
        ["DAPI", "Marker A (cycle 1)", "Marker B (cycle 1)"],
        ["DAPI", "Marker C (cycle 2)", "Marker D (cycle 2)"],
        ["DAPI", "Marker E (cycle 3)", "Marker F (cycle 3)"],
    ],
    downscale_factor=2,
    compression="lzw",
    tile_size=1024,
    save_RAM=True,
)

If you open the output image in QuPath, the channel names should show up in the interface.

For napari, we use ome_types (github repo) to parse the channel name in the registered-image.ome.tif like so

import napari
import ome_types
import palom

ome = ome_types.from_tiff("registered-image.ome.tif")
channel_names = [cc.name for cc in ome.images[0].pixels.channels]

v = napari.Viewer()
reader = palom.reader.OmePyramidReader("registered-image.ome.tif")

v.add_image(reader.pyramid, channel_axis=0, name=channel_names)

@Yu-AnChen
Copy link
Collaborator

By the way, installing napari in the same palom environment, to view the final file, breaks the palom packages.

The error seems to be due to the scikit-image version, you could try re-install palom (via python -m pip install --force-reinstall palom) in the env that it was broke. This should downgrade scikit-image to the compatible version and hopefully will run through.

@moataz-youssef
Copy link

Thanks. I am uploading the images now. These datasets are trial ones, for the actual experiments, we have a choice between saving in OME-TIFF or in VSI, but it will take sometime till we have them running.

Is there an option to skip the 2nd and 3rd DAPI channels when writing the mosaic OME-TIFF? This will save time and disk space, once the workflow is standardized.

Another question, the output mosaic OME-TIFF file from the jupyter notebook has the same pixel size as the original images, although the downscale_factor is set to 2. Shouldn't it be halved?

@moataz-youssef
Copy link

python -m pip install --force-reinstall palom

After running this, I get a different error:

  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\_core\arrayprint.py", line 34, in <module>
    from . import numerictypes as _nt
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\_core\numerictypes.py", line 102, in <module>
    from ._type_aliases import (
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\_core\_type_aliases.py", line 38, in <module>
    allTypes[_abstract_type_name] = getattr(ma, _abstract_type_name)
AttributeError: module 'numpy.core.multiarray' has no attribute 'unsignedinteger'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "D:\valis_container\palom_v2.py", line 7, in <module>
    import palom
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\palom\__init__.py", line 9, in <module>
    from . import (
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\palom\align.py", line 3, in <module>
    import skimage.exposure
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\__init__.py", line 157, in <module>
    from .util.dtype import (img_as_float32,
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\util\__init__.py", line 16, in <module>
    from ._montage import montage
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\util\_montage.py", line 4, in <module>
    from .. import exposure
  File "<frozen importlib._bootstrap>", line 1075, in _handle_fromlist
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\_shared\lazy.py", line 62, in __getattr__
    return importlib.import_module(f'{package_name}.{name}')
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\importlib\__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\exposure\__init__.py", line 6, in <module>
    from ._adapthist import equalize_adapthist
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\exposure\_adapthist.py", line 21, in <module>
    from ..color.adapt_rgb import adapt_rgb, hsv_value
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\color\__init__.py", line 1, in <module>
    from .colorconv import (convert_colorspace,
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\skimage\color\colorconv.py", line 407, in <module>
    rgbcie_from_rgb = rgbcie_from_xyz @ xyz_from_rgb
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\core\_internal.py", line 855, in array_ufunc_errmsg_formatter
    args_string = ', '.join(['{!r}'.format(arg) for arg in inputs] +
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom-clone\lib\site-packages\numpy\core\_internal.py", line 855, in <listcomp>
    args_string = ', '.join(['{!r}'.format(arg) for arg in inputs] +
RuntimeError: Unable to configure default ndarray.__repr__

@Yu-AnChen
Copy link
Collaborator

After running this, I get a different error:

Woof, I see, will try to see what is the right sequence to set it up - it's annoying to have separate envs.

Is there an option to skip the 2nd and 3rd DAPI channels when writing the mosaic OME-TIFF? This will save time and disk space, once the workflow is standardized.

Yes you can slice the channels you want and append it to the mosaics. For example this will only take the second and third channels from the aligned_img

mosaics = []
aligned_img = palom.align.block_affine_transformed_moving_img(
    aligner.ref_img, reader.pyramid[0], aligner.block_affine_matrices_da
)
mosaics.append(aligned_img[1:3])

Another question, the output mosaic OME-TIFF file from the jupyter notebook has the same pixel size as the original images, although the downscale_factor is set to 2. Shouldn't it be halved?

The downscale_factor is for specifying the downsizing factor between image pyramid levels. Default is 4 which some tools will have a hard time to render it properly.

@moataz-youssef
Copy link

The downscale_factor is for specifying the downsizing factor between image pyramid levels. Default is 4 which some tools will have a hard time to render it properly.

Thanks. I think it is the same in QuPath as well (4).

Another question this time regarding brightfield images. The examples listed in the readme of this GitHub under SVS both are either pure brightfield images (Example 1) or a brightfield and IF image (Example 2). The question is, what is the output? How can you make a composite OME-TIFF of that? As far as I know, other WSI alignment tools, e.g. VALIS or CODA, can't do that. Can you share an example?

@Yu-AnChen
Copy link
Collaborator

Here's a brief doc for aligning "same section" H&E to Orion (an IF image) image (example output here). Note that the default channels that works for me might not work for your images, the key settings are --thumbnail_channel1 and 2 and --channel1 and 2.

As for the combined view, QuPath experts would prob know how to load multiple images and overlay them, if you figure out a way to do so, please share with us here :)

I personally use napari as you can add IF channels and R/G/B channels from the BF to the same viewer and toggle on/off, setting colormap, etc.

If you really want to generate one single file by merging the IF and BF images, palom-pyramid merge (source code) can combine multiple ome-tiffs into one output. The syntax is a bit exotic (via fire), see the following example

$ palom-pyramid merge "[r'C:\Users\me\Desktop\palom-align-he-test\merge-test-1.ome.tif', r'C:\Users\me\Desktop\palom-align-he-test\merge-test-2.ome.tif']"
2024-12-17 16:32:11.742 | INFO     | palom.reader:pixel_size:147 - Detected pixel size: 0.3250 µm
2024-12-17 16:32:11.750 | INFO     | palom.cli.pyramid_tools:merge_channels:54 -
    Processing:
        merge-test-1.ome.tif
        merge-test-2.ome.tif

2024-12-17 16:32:11.756 | INFO     | palom.pyramid:write_pyramid:166 - Writing to C:\Users\me\Desktop\palom-align-he-test\merged-merge-test-1-zlib.ome.tif

@moataz-youssef
Copy link

I decided to do some further troubleshooting:
To solve the problem of not all channels being read from the VSI file, I decided to convert again to OME-TIFF, but this time not through QuPath but by using the NGFF-Converter from Glenco. I ran the script first and got this error:

  mx_ref = affine(scale=1/self.ref_thumbnail_down_factor).params
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\skimage\transform\_geometric.py:899: RuntimeWarning: invalid value encountered in scalar multiply
  [sx * math.cos(rotation), -sy * math.sin(rotation + shear), 0],
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\skimage\transform\_geometric.py:900: RuntimeWarning: invalid value encountered in scalar multiply
  [sx * math.sin(rotation),  sy * math.cos(rotation + shear), 0],
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py:178: RuntimeWarning: divide by zero encountered in scalar divide
  mx_moving = affine(scale=1/self.moving_thumbnail_down_factor).params
Computing shifts:  42%|###########################################################9                                                                                   | 235/561 [00:00<00:00, 2342.93it/s]C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\block_affine.py:68: RuntimeWarning: invalid value encountered in cast
  ).astype(int)
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\block_affine.py:71: RuntimeWarning: invalid value encountered in cast
  ).astype(int)
Computing shifts: 100%|###############################################################################################################################################| 561/561 [00:00<00:00, 2223.51it/s]
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py:250: RuntimeWarning: divide by zero encountered in divide
  np.divide(self.ref_img.chunksize, self.ref_thumbnail_down_factor)
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py:257: UserWarning: Attempting to set identical low and high xlims makes transformation singular; automatically expanding.
  ax.imshow(
C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py:257: UserWarning: Attempting to set identical low and high ylims makes transformation singular; automatically expanding.
  ax.imshow(
Traceback (most recent call last):
  File "D:\valis_container\palom_v2.2.py", line 77, in <module>
    fig = aligner.plot_shifts()
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py", line 276, in plot_shifts
    viz_shifts(shifts, self.grid_shape, ax=axs[1])
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\palom\align.py", line 90, in viz_shifts
    dcenter = skimage.filters.threshold_triangle(distances[is_finite])
  File "C:\Users\yousseah\AppData\Local\anaconda3\envs\palom\lib\site-packages\skimage\filters\thresholding.py", line 932, in threshold_triangle
    arg_low_level, arg_high_level = np.where(hist > 0)[0][[0, -1]]
IndexError: index 0 is out of bounds for axis 0 with size 0

So I checked the first file with QuPath and the OME-TIFF had all series in it. Using the NGFF-Converter, I did a second conversion, but this time I extracted only the series which is important (Series 2, counting from 0), the rest was ignored.

I ran the output this time with the jupyter notebook and voila! The 4 channels per image were detected:

 dask.array<from-zarr, shape=(4, 30757, 39900), dtype=uint16, chunksize=(1, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 15378, 19950), dtype=uint16, chunksize=(1, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 7689, 9975), dtype=uint16, chunksize=(1, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 3844, 4987), dtype=uint16, chunksize=(1, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 1922, 2493), dtype=uint16, chunksize=(1, 1024, 1024), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 961, 1246), dtype=uint16, chunksize=(1, 961, 1024), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 480, 623), dtype=uint16, chunksize=(1, 480, 623), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 240, 311), dtype=uint16, chunksize=(1, 240, 311), chunktype=numpy.ndarray>,
 dask.array<from-zarr, shape=(4, 120, 155), dtype=uint16, chunksize=(1, 120, 155), chunktype=numpy.ndarray>]

And running the cells further resulted in getting all 12 channels :-D

2024-12-18 12:19:16.657 | INFO     | palom.pyramid:write_pyramid:166 - Writing to combined_IF.ome.tif
Assembling mosaic  1/ 3 (channel  1/ 4): 100%|####################################################################################################################| 14275/14275 [00:09<00:00, 1466.50it/s]
Assembling mosaic  1/ 3 (channel  2/ 4): 100%|####################################################################################################################| 14275/14275 [00:10<00:00, 1325.58it/s]
Assembling mosaic  1/ 3 (channel  3/ 4): 100%|####################################################################################################################| 14275/14275 [00:11<00:00, 1291.96it/s]
Assembling mosaic  1/ 3 (channel  4/ 4): 100%|####################################################################################################################| 14275/14275 [00:09<00:00, 1487.64it/s]
Assembling mosaic  2/ 3 (channel  1/ 4): 100%|####################################################################################################################| 24230/24230 [00:20<00:00, 1195.50it/s]
Assembling mosaic  2/ 3 (channel  2/ 4): 100%|####################################################################################################################| 24230/24230 [00:20<00:00, 1173.52it/s]
Assembling mosaic  2/ 3 (channel  3/ 4): 100%|####################################################################################################################| 24230/24230 [00:20<00:00, 1207.78it/s]
Assembling mosaic  2/ 3 (channel  4/ 4): 100%|####################################################################################################################| 24230/24230 [00:21<00:00, 1143.24it/s]
Assembling mosaic  3/ 3 (channel  1/ 4): 100%|####################################################################################################################| 23834/23834 [00:20<00:00, 1173.06it/s]
Assembling mosaic  3/ 3 (channel  2/ 4): 100%|####################################################################################################################| 23834/23834 [00:20<00:00, 1160.65it/s]
Assembling mosaic  3/ 3 (channel  3/ 4): 100%|####################################################################################################################| 23834/23834 [00:21<00:00, 1099.74it/s]
Assembling mosaic  3/ 3 (channel  4/ 4): 100%|####################################################################################################################| 23834/23834 [00:20<00:00, 1188.01it/s]
2024-12-18 12:26:11.806 | INFO     | palom.pyramid:write_pyramid:182 - Generating pyramid
2024-12-18 12:26:11.807 | INFO     | palom.pyramid:write_pyramid:185 -     Level 1 (30758 x 39901)
Processing channel: 100%|#################################################################################################################################################| 12/12 [04:19<00:00, 21.59s/it]
2024-12-18 12:30:31.175 | INFO     | palom.pyramid:write_pyramid:185 -     Level 2 (15379 x 19951)
Processing channel: 100%|#################################################################################################################################################| 12/12 [01:22<00:00,  6.91s/it]
2024-12-18 12:31:54.107 | INFO     | palom.pyramid:write_pyramid:185 -     Level 3 (7690 x 9976)
Processing channel: 100%|#################################################################################################################################################| 12/12 [00:39<00:00,  3.30s/it]
2024-12-18 12:32:34.068 | INFO     | palom.pyramid:write_pyramid:185 -     Level 4 (3845 x 4988)
Processing channel: 100%|#################################################################################################################################################| 12/12 [00:21<00:00,  1.78s/it]
2024-12-18 12:32:55.441 | INFO     | palom.pyramid:write_pyramid:185 -     Level 5 (1923 x 2494)
Processing channel: 100%|#################################################################################################################################################| 12/12 [00:15<00:00,  1.32s/it]
2024-12-18 12:33:11.360 | INFO     | palom.pyramid:write_pyramid:185 -     Level 6 (962 x 1247)
Processing channel: 100%|#################################################################################################################################################| 12/12 [00:15<00:00,  1.31s/it]
2024-12-18 12:33:27.090 | INFO     | palom.pyramid:write_pyramid:185 -     Level 7 (481 x 624)
Processing channel: 100%|#################################################################################################################################################| 12/12 [00:15<00:00,  1.27s/it]

Naming the channels worked in the jupyter notebook with:

    mosaics,
    'combined_IF_channel_names.ome.tif',
    r1.pixel_size,
    channel_names=[
        ["DAPI", "XX-488 (cycle 1)", "YY-550 (cycle 1)", "ZZ-647 (cycle 1)"],
        ["DAPI", "XX-488 (cycle 2)", "YY-550 (cycle 2)", "ZZ-647 (cycle 2)"],
        ["DAPI", "XX-488 (cycle 3)", "YY-550 (cycle 3)", "ZZ-647 (cycle 3)"],
    ],
    downscale_factor=2,
    compression='lzw',
    tile_size=1024,
    save_RAM=True
)

This is an awesome tool! Thanks a lot for the development and your support. Looking forward to what you will find out from the VSI files. I will try it out now by scripting instead of the jupyter notebook.

@moataz-youssef
Copy link

A small update, as I have communicated this not very clearly. Running the script gives me the output of just the first image (3 channels while using VSI and all 4 when using properly converted OME-TIFFs).

The jupyter notebook is the one that gave me first the 3 DAPI channels from all 3 images, then all channels from the moving images plus DAPI from the reference image, then finally all detected channels in one OME-TIFF (9 total when using VSI from a maximum of 12). Converting the VSI images to OME-TIFF and running the notebook gave me all 12 channels, with labels, according to your suggestion.

So the question is what exactly does the jupyter notebook does differently than the python script?

@moataz-youssef
Copy link

Here's a brief doc for aligning "same section" H&E to Orion (an IF image) image (example output here). Note that the default channels that works for me might not work for your images, the key settings are --thumbnail_channel1 and 2 and --channel1 and 2.

As for the combined view, QuPath experts would prob know how to load multiple images and overlay them, if you figure out a way to do so, please share with us here :)

I personally use napari as you can add IF channels and R/G/B channels from the BF to the same viewer and toggle on/off, setting colormap, etc.

If you really want to generate one single file by merging the IF and BF images, palom-pyramid merge (source code) can combine multiple ome-tiffs into one output. The syntax is a bit exotic (via fire), see the following example

$ palom-pyramid merge "[r'C:\Users\me\Desktop\palom-align-he-test\merge-test-1.ome.tif', r'C:\Users\me\Desktop\palom-align-he-test\merge-test-2.ome.tif']"
2024-12-17 16:32:11.742 | INFO     | palom.reader:pixel_size:147 - Detected pixel size: 0.3250 µm
2024-12-17 16:32:11.750 | INFO     | palom.cli.pyramid_tools:merge_channels:54 -
    Processing:
        merge-test-1.ome.tif
        merge-test-2.ome.tif

2024-12-17 16:32:11.756 | INFO     | palom.pyramid:write_pyramid:166 - Writing to C:\Users\me\Desktop\palom-align-he-test\merged-merge-test-1-zlib.ome.tif

I did align an IF image and HE together but it was a virtual 32-bit image in QuPath using Warpy. However, I did not save it as a OME-TIFF.

I will try your suggestions, and update.

@moataz-youssef
Copy link

Yes you can slice the channels you want and append it to the mosaics. For example this will only take the second and third channels from the aligned_img

mosaics = []
aligned_img = palom.align.block_affine_transformed_moving_img(
    aligner.ref_img, reader.pyramid[0], aligner.block_affine_matrices_da
)
mosaics.append(aligned_img[1:3])

Last question for the day :-)

Any advice on how to get only certain channels from one of the moving images? For the rest, I would like to take all channels.

@Yu-AnChen
Copy link
Collaborator

Yu-AnChen commented Dec 19, 2024

Thanks for all the updates and testing! I was able to fixe the 3 channel instead of 4 channel bug (I hardcoded that thinking all the VSI are RGB bright-field images). Try the v2024.12.1. And here's the script with some comments hopefully is helpful to you. I noticed that there are 2 scenes (ROIs) in the scan - not sure if you can configure that during the imaging.

We should discuss your bright-field to IF registration in a separate issue - don't want to spam others' inbox in this issue.

Any advice on how to get only certain channels from one of the moving images? For the rest, I would like to take all channels.

You can slice the aligned_img to only take the channels you want and append that to the mosaics. The dimension in aligned_img is (channel, row, column). The slicing does not add compute time because the actual pixel data is only touched in the final write_pyramid step.

@moataz-youssef
Copy link

moataz-youssef commented Dec 19, 2024

Thanks a lot. It works fantastically out of the box.

Assembling mosaic  1/ 6 (channel  1/ 4): 100%|########################################################################################################################################################| 14581/14581 [04:36<00:00, 52.70it/s]
Assembling mosaic  1/ 6 (channel  2/ 4): 100%|########################################################################################################################################################| 14581/14581 [04:34<00:00, 53.14it/s]
Assembling mosaic  1/ 6 (channel  3/ 4): 100%|########################################################################################################################################################| 14581/14581 [04:35<00:00, 52.99it/s]
Assembling mosaic  1/ 6 (channel  4/ 4): 100%|########################################################################################################################################################| 14581/14581 [04:38<00:00, 52.43it/s]
Assembling mosaic  2/ 6 (channel  1/ 4): 100%|########################################################################################################################################################| 24098/24098 [05:07<00:00, 78.47it/s]
Assembling mosaic  2/ 6 (channel  2/ 4): 100%|########################################################################################################################################################| 24098/24098 [05:06<00:00, 78.50it/s]
Assembling mosaic  2/ 6 (channel  3/ 4): 100%|########################################################################################################################################################| 24098/24098 [05:06<00:00, 78.56it/s]
Assembling mosaic  2/ 6 (channel  4/ 4): 100%|########################################################################################################################################################| 24098/24098 [05:06<00:00, 78.58it/s]
Assembling mosaic  3/ 6 (channel  1/ 4): 100%|########################################################################################################################################################| 24536/24536 [05:35<00:00, 73.15it/s]
Assembling mosaic  3/ 6 (channel  2/ 4): 100%|########################################################################################################################################################| 24536/24536 [05:38<00:00, 72.44it/s]
Assembling mosaic  3/ 6 (channel  3/ 4): 100%|########################################################################################################################################################| 24536/24536 [05:41<00:00, 71.75it/s]
Assembling mosaic  3/ 6 (channel  4/ 4): 100%|########################################################################################################################################################| 24536/24536 [06:04<00:00, 67.33it/s]
Assembling mosaic  4/ 6 (channel  1/ 4): 100%|########################################################################################################################################################| 24140/24140 [06:16<00:00, 64.18it/s]
Assembling mosaic  4/ 6 (channel  2/ 4): 100%|########################################################################################################################################################| 24140/24140 [06:16<00:00, 64.06it/s]
Assembling mosaic  4/ 6 (channel  3/ 4): 100%|########################################################################################################################################################| 24140/24140 [06:13<00:00, 64.67it/s]
Assembling mosaic  4/ 6 (channel  4/ 4): 100%|########################################################################################################################################################| 24140/24140 [06:16<00:00, 64.14it/s]
Assembling mosaic  5/ 6 (channel  1/ 4): 100%|########################################################################################################################################################| 24302/24302 [05:23<00:00, 75.06it/s]
Assembling mosaic  5/ 6 (channel  2/ 4): 100%|########################################################################################################################################################| 24302/24302 [06:43<00:00, 60.16it/s]
Assembling mosaic  5/ 6 (channel  3/ 4): 100%|########################################################################################################################################################| 24302/24302 [06:53<00:00, 58.72it/s]
Assembling mosaic  5/ 6 (channel  4/ 4): 100%|########################################################################################################################################################| 24302/24302 [05:19<00:00, 76.05it/s]
Assembling mosaic  6/ 6 (channel  1/ 2): 100%|#######################################################################################################################################################| 24220/24220 [02:49<00:00, 142.97it/s]
Assembling mosaic  6/ 6 (channel  2/ 2): 100%|#######################################################################################################################################################| 24220/24220 [02:49<00:00, 143.18it/s]
2024-12-19 15:02:02.306 | INFO     | palom.pyramid:write_pyramid:182 - Generating pyramid
2024-12-19 15:02:02.308 | INFO     | palom.pyramid:write_pyramid:185 -     Level 1 (30700 x 41028)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [05:08<00:00, 14.00s/it]
2024-12-19 15:07:10.564 | INFO     | palom.pyramid:write_pyramid:185 -     Level 2 (15350 x 20514)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [01:24<00:00,  3.83s/it]
2024-12-19 15:08:34.929 | INFO     | palom.pyramid:write_pyramid:185 -     Level 3 (7675 x 10257)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [00:29<00:00,  1.34s/it]
2024-12-19 15:09:04.734 | INFO     | palom.pyramid:write_pyramid:185 -     Level 4 (3838 x 5129)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [00:16<00:00,  1.34it/s]
2024-12-19 15:09:21.235 | INFO     | palom.pyramid:write_pyramid:185 -     Level 5 (1919 x 2565)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [00:13<00:00,  1.69it/s]
2024-12-19 15:09:34.292 | INFO     | palom.pyramid:write_pyramid:185 -     Level 6 (960 x 1283)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [00:12<00:00,  1.81it/s]
2024-12-19 15:09:46.483 | INFO     | palom.pyramid:write_pyramid:185 -     Level 7 (480 x 642)
Processing channel: 100%|###################################################################################################################################################################################| 22/22 [00:12<00:00,  1.83it/s]

The whole thing took around 3 hours (registration + writing the image to disk). It didn't stress out my system at all. Is there room for parallelization? I do have access to a HPC as well and can gladly test.

I will start a separate issue with brightfield images, once I have some nice scans to play with. Thanks again.

@Yu-AnChen
Copy link
Collaborator

By the way, installing napari in the same palom environment, to view the final file, breaks the palom packages.

I was able to install both palom and napari in the same conda environment (using micromamba, if you have a recent version of miniconda, you probably will not need mamba/micromamba (info)).

I used these to set up the env, replace micromamba with conda as needed.

micromamba create -y -n test-palom python=3.10 "scikit-image<0.20" scikit-learn "zarr<2.15" tifffile=2024.7.2 imagecodecs matplotlib tqdm scipy dask numpy "loguru=0.5.3" ome-types pydantic pint "yamale<5" fire termcolor openslide napari pyqt -c conda-forge -c sdvillal

micromamba activate test-palom

python -m pip install palom

# for viewing the ome-tiff output in napari
# drag-and-drop file in napari window and select "WSI Reader"
pip install git+https://github.com/yu-anchen/napari-wsi-reader.git

And here's my conda env lockfile (info) on my Windows machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants