MathOnco / valis

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

Apply registration to new image #49

Closed Celestenono closed 1 year ago

Celestenono commented 1 year ago

Hi, Thank you for your contribution to slide registration. I have some issues to apply the same registration deformation to multiple images.

I have 8 images in the same referential (IF images) and 2 (PAS1 and PAS2) in another. I used one of the IF image as reference to register PAS1, it is working well and now I want to apply the same deformation to PAS2. I used get_slide and warp_img functions, but I have a warning "UserWarning: scaling transformation for image with different shape." and while it seem to do the registration I don't obtain an image with the same size as IF images and PAS1 after registration (they don't overlap). PAS1 and PAS2 have the same shape before registration, which defer from IF images shape.

    PAS1_slide_name = moving_image_name
    IF_slide_name = fixed_image_name
    registrar = registration.Valis(slide_src_dir, results_dst_dir, reference_img_f=IF_slide_name,
                                   align_to_reference=True)
    rigid_registrar, non_rigid_registrar, error_df = registrar.register()
    registrar.warp_and_save_slides(results_dir, non_rigid=False, crop="reference")
    slide_obj = registrar.get_slide(PAS1_slide)
    PAS2_image = Image.open(PAS2_slide_name)
    PAS2_array = np.array(PAS2_image)
    PAS2_registered = slide_obj.warp_img(PAS2_array, non_rigid=False, crop="reference")
    PAS2_registered = Image.fromarray(PAS2_array_registered)
    PAS2_registered.save

Do you have any recommendation to resolve my problem ?

Best, Noémie

cdgatenbee commented 1 year ago

Hi @Celestenono, Glad to hear valis is working well for the PAS1 and IF images, but sorry you've run into the issue with PAS2. The reason this isn't working is because when an image being passed to warp_img differs in shape than the processed images (very likely, since valis resizes the processed images), valis has to estimate what the registered image's shape would be. Unfortunately, that shape might be different that the shape valis determined in the original registration. I should probably add some arguments to let the user specify the shape and bounding box if those things are known (as in this case). However, in the meantime, I do have two suggestions that might make this work:

  1. If you haven't done this already, try re-running the registration, but with the IF slide, PAS1, and PAS2 in slide_src_dir. This would then align PAS1 and PAS2 to the IF slide, and so there wouldn't be a need to apply the PAS1 transformations to the PAS2 image, as PAS2 would have its own warping parameters. If PAS2 isn't aligning well to the IF image, try this with align_to_reference=False, which might align PAS2 to PAS1 (or vice versa), which is in turn aligned to your IF image, and so in the end both images are aligned to the IF image. This can sometimes work better that setting align_to_reference=True, as it ensures that similar images are always aligned to one another, increasing the chances of successful registration. Afterwards, if you would like to better align each image to the IF image, you could perform the "micro-registration step", setting align_to_reference=True (although this would involve non-rigid transforms, which you may or may not want):
registrar = registration.Valis(slide_src_dir, results_dst_dir, reference_img_f=IF_slide_name,
                                   align_to_reference=True)
rigid_registrar, non_rigid_registrar, error_df = registrar.register()
registrar.register_micro(reference_img_f=IF_slide_name, align_to_reference=True)
  1. You could take a similar approach to your current one, but instead of calling warp_img, call the PAS1 slide's warp_slide or warp_and_save_slide function, passing the PAS2 slide's filename as src_f. It looks like you would like to skip the non-rigid transforms, so the code to apply the PAS1 transformations to the PAS2 slide and then save the registered image would be something like:
slide_obj = registrar.get_slide(PAS1_slide_fname)
slide_obj.warp_and_save_slide(dst_f=warped_PAS2_slide_fname, src_f=PAS2_slide_fname, non_rigid=False, crop="reference")

Please try one of the above solutions and let me know if either works for you.

Best, -Chandler

Celestenono commented 1 year ago

Thank you for your quick answer !

I only tested the second solution and it is working ! I just had to modify the slide_obj.warp_and_save_slide function (registration.py line 933-935) otherwise it still was using Slide.src_f (aka PAS1)

warped_slide = self.warp_slide(level=level, non_rigid=non_rigid,
                                       crop=crop,
                                       interp_method=interp_method, **src_f = src_f**)

Best, Noémie

cdgatenbee commented 1 year ago

Great, glad to hear it worked out! I'll update the code to address the missing src_f argument in warp_slide. Thanks for pointing this out.

Best, -Chandler