Giter VIP home page Giter VIP logo

itkcolornormalization's Introduction

ITKColorNormalization

Apache 2.0 License DOI Build, test, package status

Overview

This Insight Toolkit (ITK) module performs Structure Preserving Color Normalization on an H & E image using a reference image. The module is in C++ and is also packaged for Python.

H & E (hematoxylin and eosin) are stains used to color parts of cells in a histological image, often for medical diagnosis. Hematoxylin is a compound that stains cell nuclei a purple-blue color. Eosin is a compound that stains extracellular matrix and cytoplasm pink. However, the exact color of purple-blue or pink can vary from image to image, and this can make comparison of images difficult. This routine addresses the issue by re-coloring one image (the first image supplied to the routine) using the color scheme of a reference image (the second image supplied to the routine). The technique requires that the images have at least 3 colors, such as red, green, and blue (RGB).

Structure Preserving Color Normalization is a technique described in Vahadane et al., 2016 and modified in Ramakrishnan et al., 2019. The idea is to model the color of an image pixel as something close to pure white, which is reduced in intensity in a color-specific way via an optical absorption model that depends upon the amounts of hematoxylin and eosin that are present. Non-negative matrix factorization is used on each analyzed image to simultaneously derive the amount of hematoxylin and eosin stain at each pixel and the image-wide effective colors of each stain.

The implementation in ITK accelerates the non-negative matrix factorization by choosing the initial estimate for the color absorption characteristics using a technique mimicking that presented in Arora et al., 2013 and modified in Newberg et al., 2018. This approach finds a good solution for a non-negative matrix factorization by first transforming it to the problem of finding a convex hull for a set of points in a cloud.

The lead developer of InsightSoftwareConsortium/ITKColorNormalization is Lee Newberg.

Try it online

We have set up a demonstration of this using Binder that anyone can try. Click here: Binder

Installation for Python

PyPI Version

ITKColorNormalization and all its dependencies can be easily installed with Python wheels. Wheels have been generated for macOS, Linux, and Windows and several versions of Python, 3.6, 3.7, 3.8, and 3.9. If you do not want the installation to be to your current Python environment, you should first create and activate a Python virtual environment (venv) to work in. Then, run the following from the command-line:

pip install itk-spcn

Launch python, import the itk package, and set variable names for the input images

import itk

input_image_filename = "path/to/image_to_be_normalized"
reference_image_filename = "path/to/image_to_be_used_as_color_reference"

Usage in Python

The following example transforms this input image

Input image to be normalized

using the color scheme of this reference image

Reference image for normalization

to produce this output image

Output of spcn_filter

Functional interface to ITK

You can use the functional, eager interface to ITK to choose when each step will be executed as follows. The input_image and reference_image are processed to produce normalized_image, which is the input_image with the color scheme of the reference_image. The color_index_suppressed_by_hematoxylin and color_index_suppressed_by_eosin arguments are optional if the input_image pixel type is RGB or RGBA. Here you are indicating that the color channel most suppressed by hematoxylin is 0 (which is red for RGB and RGBA pixels) and that the color most suppressed by eosin is 1 (which is green for RGB and RGBA pixels); these are the defaults for RGB and RGBA pixels.

input_image = itk.imread(input_image_filename)
reference_image = itk.imread(reference_image_filename)

eager_normalized_image = itk.structure_preserving_color_normalization_filter(
    input_image,
    reference_image,
    color_index_suppressed_by_hematoxylin=0,
    color_index_suppressed_by_eosin=1,
)

itk.imwrite(eager_normalized_image, output_image_filename)

ITK pipeline interface

Alternatively, you can use the ITK pipeline infrastructure that waits until a call to Update() or Write() before executing the pipeline. The function itk.StructurePreservingColorNormalizationFilter.New() uses its argument to determine the pixel type for the filter; the actual image is not used there but is supplied with the spcn_filter.SetInput(0, input_reader.GetOutput()) call. As above, the calls to SetColorIndexSuppressedByHematoxylin and SetColorIndexSuppressedByEosin are optional if the pixel type is RGB or RGBA.

input_reader = itk.ImageFileReader.New(FileName=input_image_filename)
reference_reader = itk.ImageFileReader.New(FileName=reference_image_filename)

spcn_filter = itk.StructurePreservingColorNormalizationFilter.New(
    Input=input_reader.GetOutput()
)
spcn_filter.SetColorIndexSuppressedByHematoxylin(0)
spcn_filter.SetColorIndexSuppressedByEosin(1)
spcn_filter.SetInput(0, input_reader.GetOutput())
spcn_filter.SetInput(1, reference_reader.GetOutput())

output_writer = itk.ImageFileWriter.New(spcn_filter.GetOutput())
output_writer.SetInput(spcn_filter.GetOutput())
output_writer.SetFileName(output_image_filename)
output_writer.Write()

Note that if spcn_filter is used again with a different input_image, for example from a different reader,

spcn_filter.SetInput(0, input_reader2.GetOutput())

but the reference_image is unchanged then the filter will use its cached analysis of the reference_image, which saves about half the processing time.

itkcolornormalization's People

Contributors

dzenanz avatar jcfr avatar leengit avatar tbirdso avatar thewtex avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

itkcolornormalization's Issues

[Feature request] Provide python wrapper / implementation to obtain stain matrix and concentrations only from image

Hi all,

First of all, great work with this package! It is amazing how fast it can work and produce stain normalization, great work!

A bit of background: I have been working on H&E stain images with large TIFF files, and I've found that, in my case, I need a more convolved preprocessing for my images. The preprocessing I want to apply consists in obtaining the stain matrix from each individual patch in a large TIFF file, and using these partial stain matrices to obtain a global stain matrix to obtain the final concentrations. The issue with usual preprocessing is that neighboring patches (or windows) are mapped to the same target image but have different characteristics in terms of distribution of tissue (e.g. some patch might have a larger nuclei count, which is bound to have an impact on the stain matrix and produce some artifacts when normalizing neighboring patches):

download (4)

However, all implementations I could find "hide" these intermediate steps in other functions, as they usually have wrappers that take a source and a target image and produce the final output without a chance to obtain the intermediate results: the concentration maps before going back to the RGB space and the stain matrix associated to a specific image patch.

I think it would be useful to "expose" these methods to be public (i.e. the result from the dictionary learning for obtaining the stain matrix and the lasso implementation to obtain the concentration maps). Still have the function / filter structure_preserving_color_normalization_filter but also provide two extra functions:

  1. A function which takes an image and obtains a stain matrix (e.g. def structure_preserving_stain_matrix_extractor(image: np.ndarray) -> np.ndarray)
  2. A function which takes an image and a stain matrix and obtains the concentration maps (e.g. def structure_preserving_concentration_extractor(image: np.ndarray, stain_matrix: np.ndarray, index_hematoxylin: int = 0, index_eosin: int = 1) -> Tuple[np.ndarray, np.ndarray]

I truly believe this would be useful, as your implementation is extremely fast and efficient (again, great work!).

Thanks,
Guille

cannot import name 'itkHelpers' from 'itk.support'

eager_normalized_image = itk.structure_preserving_color_normalization_filter(
    input_image,
    reference_image,
    color_index_suppressed_by_hematoxylin=0,
    color_index_suppressed_by_eosin=1)

I attempt to run the routine but I met the following error

Traceback (most recent call last):
  File "/home/chris/anaconda3/envs/mmlab/lib/python3.7/site-packages/itk/support/../StructurePreservingColorNormalizationPython.py", line 73, in <module>
    from itk.itkStructurePreservingColorNormalizationFilterPython import *
  File "/home/chris/anaconda3/envs/mmlab/lib/python3.7/site-packages/itk/itkStructurePreservingColorNormalizationFilterPython.py", line 1727, in <module>
    from itk.support import itkHelpers
ImportError: cannot import name 'itkHelpers' from 'itk.support' (unknown location)

My running environment
python =='3.7.10', system=='ubuntu18.04', itk=='5.2.0.post3'

Hematoxylin and Eosin are getting mixed up; failed

Traceback (most recent call last):
File "/home/rainco/Yuhan/Models/Data_Process/Local_Data_Process/201201_color_norm_test.py", line 18, in
color_index_suppressed_by_eosin=1)
File "/home/rainco/anaconda3/envs/py36_env/lib/python3.6/site-packages/itkHelpers.py", line 118, in image_filter_wrapper
return image_filter(*args, **kwargs)
File "/home/rainco/anaconda3/envs/py36_env/lib/python3.6/site-packages/itk/itkStructurePreservingColorNormalizationFilterPython.py", line 1180, in structure_preserving_color_normalization_filter
return instance.internal_call()
File "/home/rainco/anaconda3/envs/py36_env/lib/python3.6/site-packages/itk/ITKCommonBasePython.py", line 1585, in internal_call
self.UpdateLargestPossibleRegion()
RuntimeError: ../../../include/itkStructurePreservingColorNormalizationFilter.hxx:193:
itk::ERROR: Hematoxylin and Eosin are getting mixed up; failed

Process finished with exit code 1

Update README.md, wrapping, include files in response to code review

README todos:
    - Installation
    - Usage
    - License
    - Jupyter MyBinder link (mybinder.org)
Wrapping:
     - rm itkMinimalStandardRandomVariateGenerator.wrap
include:
    - rm itkMinimalStandardRandomVariateGenerator.h 
    - rm "itk::" namespace spec
    - Add brief description, long description to class Doxygen
    - Doxygen comments for all parameters, describing them
    - PixelHelper protected
    - Virtanen algorithm names should include NMF
    - *m_inputPtr -> m_Input (cache only reference image)
    -  rm const ImageType *m_inputPtr;
    - Test suite:
        - 3D dataset test
        - ...
        - caching check test (check whether suppressed colors have changed)
TimeStamp m_inputTimeStamp;

not working for some H&E images

I attached a sample image on which it is not working and through following error.
RuntimeError: D:\a\im\include\itkStructurePreservingColorNormalizationFilter.hxx:156:
itk::ERROR: The image to be normalized could not be processed; does it have white, blue, and pink pixels?
img_Adrenal_gland_3_01134

running example in binder: itk.ShrinkImageFilter is not wrapped for input type `None`

Step to reproduce

  1. Open https://mybinder.org/v2/gh/InsightSoftwareConsortium/ITKColorNormalization/8b61138b70d780763c3aeac909c27a3026887d2e

  2. Go to examples and open the notebook

  3. Menu Cell -> Run All

Execution of cell with view(input_image) returns the following error:

---------------------------------------------------------------------------
TemplateTypeError                         Traceback (most recent call last)
<ipython-input-5-4c9c2af28ff6> in <module>
----> 1 view(input_image)

/srv/conda/envs/notebook/lib/python3.7/site-packages/itkwidgets/widget_viewer.py in view(image, label_image, label_image_names, label_image_weights, label_image_blend, cmap, lut, select_roi, interpolation, gradient_opacity, opacity_gaussians, channels, slicing_planes, shadow, blend_mode, point_sets, point_set_colors, point_set_opacities, point_set_representations, point_set_sizes, geometries, geometry_colors, geometry_opacities, ui_collapsed, rotate, annotations, axes, mode, **kwargs)
   1084                     rotate=rotate, ui_collapsed=ui_collapsed,
   1085                     annotations=annotations, axes=axes, mode=mode,
-> 1086                     **kwargs)
   1087     return viewer

/srv/conda/envs/notebook/lib/python3.7/site-packages/itkwidgets/widget_viewer.py in __init__(self, **kwargs)
    408                 if size[dim] > self.size_limit_3d[dim]:
    409                     self._downsampling = True
--> 410         self._update_rendered_image()
    411         if self._downsampling:
    412             self.observe(self._on_roi_changed, ['roi'])

/srv/conda/envs/notebook/lib/python3.7/site-packages/itkwidgets/widget_viewer.py in _update_rendered_image(self)
    488             if self.image:
    489                 self.extractor = itk.ExtractImageFilter.New(self.image)
--> 490                 self.shrinker = itk.ShrinkImageFilter.New(self.extractor)
    491                 self.shrinker.SetShrinkFactors(scale_factors[:dimension])
    492             if self.label_image:

/srv/conda/envs/notebook/lib/python3.7/site-packages/itkTemplate.py in New(self, *args, **kwargs)
    522 or via one of the following keyword arguments: %s""" % ", ".join(primary_input_methods))
    523             else:
--> 524                 raise TemplateTypeError(self, input_type)
    525         return self[list(keys)[0]].New(*args, **kwargs)
    526 

TemplateTypeError: itk.ShrinkImageFilter is not wrapped for input type `None`.

To limit the size of the package, only a limited number of
types are available in ITK Python. To print the supported
types, run the following command in your python environment:

    itk.ShrinkImageFilter.GetTypes()

Possible solutions:
* If you are an application user:
** Convert your input image into a supported format (see below).
** Contact developer to report the issue.
* If you are an application developer, force input images to be
loaded in a supported pixel type.

    e.g.: instance = itk.ShrinkImageFilter[itk.Image[itk.SS,2], itk.Image[itk.SS,2]].New(my_input)

* (Advanced) If you are an application developer, build ITK Python yourself and
turned to `ON` the corresponding CMake option to wrap the pixel type or image
dimension you need. When configuring ITK with CMake, you can set
`ITK_WRAP_${type}` (replace ${type} with appropriate pixel type such as
`double`). If you need to support images with 4 or 5 dimensions, you can add
these dimensions to the list of dimensions in the CMake variable
`ITK_WRAP_IMAGE_DIMS`.

Supported input types:

itk.Image[itk.SS,2]
itk.Image[itk.SS,3]
itk.Image[itk.UC,2]
itk.Image[itk.UC,3]
itk.Image[itk.US,2]
itk.Image[itk.US,3]
itk.Image[itk.F,2]
itk.Image[itk.F,3]
itk.Image[itk.D,2]
itk.Image[itk.D,3]

Output separate images for Hematoxylin and Eosin.

This issue is motivated by https://discourse.itk.org/t/itk-error-hematoxylin-and-eosin-are-getting-mixed-up-failed/4767/36

To be determined:

  1. Should these images be produced always or only at the user's request? Tentative answer: only if requested. Incrementally they are very little computational effort; however, including them means that three images are produced rather than just one, and that could be a lot of memory.
  2. Should the normalized image always be produced, or only the two new optional images if that is what the user requests? Tentative answer: let the user decide. Saving the output memory may be valuable to the user.
  3. If the user requests only the two new images, should they be numbered output[0] and output[1] or, alternatively, output[1] and output[2], with the latter case reflecting that the originally supported output[0] is missing from this set of output images. Tentative answer is: output[0] and output[1].

Help with installation?

Hi!

I installed itk-spcn using pip, within a conda env with python=3.9.

However, when using the itk within the same environment, it does not work properly. I manage to import and read/write images.

But when calling itk.structure_preserving_color_normalization_filter(). I get
AttributeError: PyCapsule_Import "_ITKCommonPython._C_API" is not valid

I get a similar outcome when trying itk.StructurePreservingColorNormalizationFilter.New()

There seems to be a problem with the installation of the python interface. I've wrapped my head around it and spent hours searching but can't figure out why this is happening. Any help would be greatly appreciated! Thank you!

Fix long description associated pypi package

Instead of:

long_description='By perfoming a non-negative matrix factorization on an input image and a reference image, the colors in use in the reference image are transfered to the input image.',

Consider doing something like this:

with open('README.md', 'r') as fp:
    readme = fp.read()

and add the following arguments to the setup function:

    long_description=readme,
    long_description_content_type='text/markdown',

For example, see https://github.com/CastXML/CastXML-python-distributions/blob/master/setup.py

README: Define hematoxylin and eosin, reference publication

Look like the code requires an understanding of hematoxylin and eosin

itkGetMacro( ColorIndexSuppressedByHematoxylin, Eigen::Index )
itkSetMacro( ColorIndexSuppressedByHematoxylin, Eigen::Index )
itkGetMacro( ColorIndexSuppressedByEosin, Eigen::Index )
itkSetMacro( ColorIndexSuppressedByEosin, Eigen::Index )
// This algorithm is defined for H&E (Hematoxylin (blue) and
// Eosin (pink)), which is a total of 2 stains. However, this
// approach could in theory work in other circumstances. In that
// case it might be better to have NumberOfStains be a template
// parameter or a setable class member.

May be worth describing this in the README. Referencing a publication would be nice too.

Allow Python to catch errors

The current use of itkAssertOrThrowMacro to report errors, such as inputs that cannot be processed, is not caught by a Python try-except block. We want a solution where the Python program can recover from these errors.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.