MathOnco / valis

Virtual Alignment of pathoLogy Image Series
https://valis.readthedocs.io/en/latest/
MIT License
119 stars 29 forks source link

Applying tranformations from the registrar to other slides. #63

Closed Gibbsdavidl closed 1 year ago

Gibbsdavidl commented 1 year ago

I think this is working, but I would just love some feedback on it.

Some background, this is a set of imaging measures, where there's three images taken, each with a dapi stain (wavelength 1) and several other channels (wavelengths 2-4). The non-dapi channels come out as very wispy cloudy images with no features for registration. When I include them in the registration process, the results are "not-so-great". Kind of like two different registrations happen and it looks really wonky.

Instead, I'm registering the 3 dapi channels, then applying those transformations to the other images.

  1. With set of 3 dapi stained images.
  2. I registered those and saved the registrar as a pickle.
  3. Looks good, so now I get the slide object from the registrar, and apply the warping to a new slide.
  4. Last I save the newly warped slide.

Code is below.. does it seem OK? PS: I've been using the docker image, and it's working well.


import sys
from valis import registration
from valis import slide_io
import pickle

src_reg   = sys.argv[1]  # the pickled registrar
src_slide = sys.argv[2]  # path to the reference slide
dst_slide = sys.argv[3]  # path to the slide to warp 
dst_dir   = sys.argv[4]   # path for where to write the warped slide

# read in the registrar
registrar = pickle.load(open(src_reg,'rb'))

# pull out the previously registered slide 
slide_obj = registrar.get_slide(src_slide)

# warp the dst_slide
warped_slide = slide_obj.warp_slide(level=0, non_rigid=True, crop=True, src_f=dst_slide)

# write to ome tiff
slide_io.save_ome_tiff(img=warped_slide, dst_f=dst_dir)

# shutdown the jvm
registration.kill_jvm()

Thanks!

cdgatenbee commented 1 year ago

Hi @Gibbsdavidl, Your code looks good to me! The only thing I might suggest is that, if you wanted to, you can also create a more extensive ome-xml to include things like pixel resolution, channel names, etc.... This was meant to be built into Slide. warp_and_save_slide, but I realized that the ome-xml it's creating is based on the image associated with the Slide object (e.g. your DAPI image), not src_f(e.g. your other image) :( I'll have this fixed in the next release, though. In the meantime, you can create a more fleshed out ome-xml and use it to save the slide like this:


def get_ome_xml(warped_slide, src_f, channel_names=None):
  """Generate ome-xml for warped slide based on original metadata

  Parameters
  ----------
  warped_slide : pyvips.Image
    Registered slide that will be saved as an ome.tiff

  src_f : str
    Path to original slide that was warped

  channel_names : str, list, optional
    channel names for warped slide. If `None`, then the
    channel names from `src_f` will be used

  Returns
  -------
  ome_xml : str
    String of the ome-xml metadata

  """

  slide_reader_cls = slide_io.get_slide_reader(src_f)
  slide_reader = slide_reader_cls(src_f)
  slide_meta = slide_reader.metadata
  if channel_names is None:
     channel_names = slide_meta.channel_names

  else:
     if isinstance(channel_names, str):
        channel_names = [channel_names]

  bf_dtype = slide_io.vips2bf_dtype(warped_slide.format)
  out_xyczt = slide_io.get_shape_xyzct((warped_slide.width, warped_slide.height), warped_slide.bands)
  ome_xml_obj = slide_io.update_xml_for_new_img(current_ome_xml_str=slide_meta.original_xml,
                                                new_xyzct=out_xyczt,
                                                bf_dtype=bf_dtype,
                                                is_rgb=slide_meta.is_rgb,
                                                series=slide_meta.series,
                                                pixel_physical_size_xyu=slide_meta.pixel_physical_size_xyu,
                                                channel_names=channel_names,
                                                colormap=None
                                                )

  ome_xml = ome_xml_obj.to_xml()

  return ome_xml

If that would be useful, you could then pass that ome-xml into slide_io.save_ome_tiff:

slide_ome_xml = get_ome_xml(warped_slide, src_f= dst_slide, channel_name=marker_name)
slide_io.save_ome_tiff(img=warped_slide, dst_f=dst_dir, ome_xml=slide_ome_xml)

Would it be also useful to combine the all of the registered images into a single ome-tiff? If so, I'd be happy to put together some example code that maybe you could adapt for your project.

Best, -Chandler

Gibbsdavidl commented 1 year ago

Thank you!

Thanks for explaining about the image metadata. In my case, most of the metadata should be the same, because the target images were imaged at the same time as the registered image, so they should have the same dimensions, resolution, etc. But I should probably look into it to be sure.

By reading the docs, I actually figured out how to use Valis to create a a 3-layer ome-tiff.. But I'm not sure I'm going to put them all together or not. But that can also be done with bftools (bio-formats), right?

Thanks for your help! -dave

cdgatenbee commented 1 year ago

Happy I could help, @Gibbsdavidl :) I haven't really used bftools very much, but I believe you are correct that it can put the aligned and saved slides together. Anyhow, I'll go ahead and close this for now, but if you have any other questions please don't hesitate to re-open or create a new issue/discussion.

Best, -Chandler