%\VignetteIndexEntry{MaxContrastProjection} %\VignetteKeywords{MaxContrastProjection} %\VignettePackage{MaxContrastProjection} %\VignetteEngine{knitr::knitr} \documentclass[10pt,a4paper]{article} <>= BiocStyle::latex() @ \RequirePackage{amsfonts,amsmath,amstext,amssymb,amscd} \usepackage{graphicx} \usepackage{color,colortbl} \usepackage{caption} \usepackage{subcaption} %\usepackage{courier} %\graphicspath{ {../inst/extdata/} } %\SweaveOpts{keep.source=TRUE,eps=FALSE} \begin{document} %\SweaveOpts{concordance=TRUE} \title{MaxContrastProjection} \author{Jan Sauer} \maketitle \tableofcontents \section{Getting Started} \Biocpkg{MaxContrastProjection} is an \R{} package to project 3D image stacks including common projections, such as the maximum and minimum intensity projections, as well as a novel maximum contrast projection to retain information about fine structures that would otherwise be lost in intensity- based projections. This package is distributed as part of the \Bioconductor{} project and can be installed by starting \R{} and running: <>= if (!requireNamespace("BiocManager", quietly=TRUE)) install.packages("BiocManager") BiocManager::install("MaxContrastProjection") @ \section{Introduction} A common problem when recording 3D fluorescent microscopy images is how to properly present these results in 2D. Large structures with elements in multiple focal planes become very difficult to image properly without the use of some form of projection. Objects in the focal plane will be sharp and the emitted light focused, whereas objects outside of the focal plane will be blurred and the emitted light is spread out over a larger area of the detector. Consequently, a maximum intensity projection is commonly used on the image stack in order to find the focal plane of an object. The problem with this approach is that the blurring makes it more difficult to tell apart foreground and background of the image. If the exact position of an object in a medium is unknown, or if it has a 3D structure, it must be imaged at various focal lengths. Fig. \ref{fig:dapizstacks} shows a segment of an organoid imaged at different focal lengths. \begin{figure}[!htb] \centering \includegraphics[width=0.95\textwidth]{imgs/DAPI_montage.eps} \caption{A cluster of cells in an organoid at different focal lengths} \label{fig:dapizstacks} \end{figure} A look at the maximum intensity projection, shown in Fig. \ref{fig:dapimaxproj}, reveals that much of the distinction between foreground and background is lost in this projection. Consequently, a different type of projection is necessary to retain important information. The z-layer in which a region of an image is in focus is the z-layer with the least amount of blurring, i.e. with the highest contrast of intensity values within that area. Fig. \ref{fig:dapicontrastproj} shows this "maximum contrast projection". Comparing it to the maximum intensity projection shows that the individual cells are much easier to differentiate. \begin{figure}[!htb] \centering \begin{subfigure}[b]{0.45\textwidth} \includegraphics[width=\textwidth]{imgs/DAPI_maxproj.eps} \caption{Maximum intensity projection} \label{fig:dapimaxproj} \end{subfigure} \begin{subfigure}[b]{0.45\textwidth} \includegraphics[width=\textwidth]{imgs/DAPI_contrastproj.eps} \caption{Maximum contrast projection} \label{fig:dapicontrastproj} \end{subfigure} \caption{A comparison of the maximum intensity projection and the maximum contrast projection of the images seen in Fig. \ref{fig:dapizstacks}} \end{figure} \section{Contrast Projection} The contrast $C_{xy}$ at a point $(x,y)$ is defined here as the variance of all intensity scores in a defined area around this point. In the simplest case, this is is a rectangle centered around $(x,y)$ with side lengths of $2 \cdot w_x + 1$ and $2 \cdot w_y + 1$. \begin{equation} \label{eq:contrast1} C_{xy} = \frac{1}{(2 \cdot w_x + 1) \cdot (2 \cdot w_y + 1)} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} (I_{kl} - \langle I_{xy} \rangle)^2 \end{equation} \begin{equation} \label{eq:contrast2} C_{xy} = \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl}^2 - 2 \cdot I_{kl} \cdot \langle I_{xy} \rangle + \langle I_{xy} \rangle ^2 \end{equation} \begin{equation} \label{eq:contrast3} C_{xy} = {\langle I_{xy} \rangle}^2 - 2 \cdot \langle I_{xy} \rangle \cdot \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl} + \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl}^2 \end{equation} where $\langle I_{xy} \rangle$ is the intensity of the image at the position $(x,y)$ and $\langle I_{xy} \rangle$ is the mean intensity of the window centered around $(x,y)$: \begin{equation} \label{eq:mean} \begin{split} \langle I_{xy} \rangle &= \frac{1}{(2 \cdot w_x + 1) \cdot (2 \cdot w_y + 1)} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl} \\ &= \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl} \end{split} \end{equation} This mean intensity over a window can be quickly determined in R with the function \texttt{EBImage::filter2()} by applying a normalized, uniform filter to the image. For brevity, the normalizing constant, i.e. the area of the window, is abbreviated as $A = (2 \cdot w_x + 1) \cdot (2 \cdot w_y + 1)$. A comparison of Eqs. \ref{eq:mean} and \ref{eq:contrast3} shows that the variance takes on the known form $Var = E[X^2] - (E[X])^2$: \begin{equation} \label{eq:contrast4} \begin{split} C_{xy} &= {\langle I_{xy} \rangle}^2 + \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl}^2 - 2 \cdot {\langle I_{xy} \rangle}^2 \\ &= \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl}^2 - {\langle I_{xy} \rangle}^2 = \langle I^2_{xy} \rangle - {\langle {I}_{xy} \rangle}^2 \end{split} \end{equation} where the first term of Eq. \ref{eq:contrast4} is the mean of the squared intensities over the same window as used for the mean of the intensities, $\langle I^2_{xy} \rangle = \frac{1}{A} \sum_{k=x-w_x}^{x+w_x} \sum_{l=y-w_y}^{y+w_y} I_{kl}^2$. This quantity can be calculated by the same method as for the intensity using \texttt{EBImage::filter2()} \section{Application Example} We will use the image data for the organoids already presented in the Introduction of this vignette. In the most general case, the data should be an array representing a stack of images, i.e. the first two dimensions correspond to the $x-$ and $y-$ dimensions of the image and the third dimension of the array corresponds to the number of images in the stack. The $cells$ dataset represents a stack of 11 images, each with a size of 261 x 251 pixels (see Fig. \ref{fig:dapizstacks}) <<>>= library(MaxContrastProjection) library(EBImage) data(cells) dim(cells) @ The maximum contrast projection returns a single image with the same $x-$ and $y-$ dimensions as the input image stack <<>>= max_contrast = contrastProjection(imageStack = cells, w_x = 15, w_y = 15, smoothing = 5, brushShape = "box") @ $w_x$ and $w_y$ are the radii of the area around each pixel over which to calculate the variance. The area of this window should be at least as large as the structures of interest in the image, otherwise artefacts may occur in the projection. Figs. \ref{fig:windowsizelarge} and \ref{fig:windowsizesmall} show the impact of the window size on the projection quality. If the parameter $w_y$ is not explicitly set, it is assumed to be equal to $w_x$. <<>>= max_contrast_large = contrastProjection(imageStack = cells[30:95, 55:115,], w_x = 15, w_y = 15, smoothing = 5, brushShape = "box") max_contrast_small = contrastProjection(imageStack = cells[30:95, 55:115,], w_x = 3, w_y = 3, smoothing = 5, brushShape = "box") @ \begin{figure}[!htb] \centering \begin{subfigure}[b]{0.45\textwidth} <>= display(max_contrast_large / max(cells), method = "raster") @ \caption{Large window with $w_x = w_y = 15$} \label{fig:windowsizelarge} \end{subfigure} \begin{subfigure}[b]{0.45\textwidth} <>= display(max_contrast_small / max(cells), method = "raster") @ \caption{Small window with $w_x = w_y = 3$} \label{fig:windowsizesmall} \end{subfigure} \caption{A comparison of window sizes for the maximum contrast projection on a segment of the \texttt{cells} data set. The small window size ($w_x = w_y = 3$) shows some artefacts, i.e. several cells seem to be incorrectly projected from different z-stacks. The large window size ($w_x = w_y = 15$) ensures that any single cell is projected from the same z-stack.} \end{figure} Similarily, the \texttt{smoothing} parameter applies a median filter onto the projection index map to avoid harsh jumps between z-stacks. It is unlikely that any given object would rapidly fluctuate between different focal plains and the median filter ensures the smoothness of the projection. The \texttt{brushShape} parameter indicates the shape of the window to be used when determining the contrast at each pixel. By default ("box"), this is a rectangle with the side lengths $2 \cdot w_x + 1$ and $2 \cdot w_y + 1$. Currently supported is also a circle ("disc"). Depending on the geometry of the structures of interest in the image, different window shapes may have an impact on the quality of the projection. For radially symmetrical shapes, such as "disc", the parameter $w_y$ is assumed to be equal to $w_x$ regardless of whether it was explicitly defined. It is also possible to retrieve the actual contrast values as well as the projection index map. The contrast stack is simply the contrast, i.e. variance, for every single pixel of every image in the input image stack. The projection index map then effectively indicates the z-layer with the maximum contrast for every pixel in the $x-y$-plane (after optional smoothing). <<>>= contrastStack = getContrastStack(imageStack = cells, w_x = 15, w_y = 15, brushShape = "box") indexMap = getIndexMap(contrastStack = contrastStack, smoothing = 5) max_contrast_fromMap = projection_fromMap(imageStack = cells, indexMap = indexMap) @ The maximum contrast projection can then be executed with just the image stack as well as the index map using \texttt{projection{\_}fromMap}. In fact, any projection index map can be used here so long as all its values lie between $1$ and the number of z-stacks in the input image stack. A problem of the contrast projection is that if an object lies in multiple focal planes, the contrast projection generates artifical boundaries between these individual areas. This is an unavoidable consequence of imaging an object in discrete layers. Fig. \ref{fig:interpolation} shows what this looks like. In principle, the projection index map is blurred, so that boundaries between focal regions are softened. The projection is then a weighted linear combination of two values. For example, if the projection index map at a given pixel $(x, y)$ has a value of $7.4$, then the value of that pixel in the projection is $I_{xy} = 0.6 \cdot I_{xy}^{(7)} + 0.4 \cdot I_{xy}^{(8)}$, where $I_{xy}^{(j)}$ is the intensity of the pixel in the $j$-th layer of image stack. \begin{figure}[!htb] \centering \begin{subfigure}[b]{0.45\textwidth} \includegraphics[width=\textwidth]{imgs/no_interpolation.eps} \caption{No interpolation} \label{fig:interpolation_no} \end{subfigure} \begin{subfigure}[b]{0.45\textwidth} \includegraphics[width=\textwidth]{imgs/with_interpolation.eps} \caption{With interpolation} \label{fig:interpolation_yes} \end{subfigure} \caption{A comparison of the effect of linear interpolation on the maximum contrast projection. Note that the central area in Fig. \ref{fig:interpolation_no} is distinctly separated from the outside of the organoid. This separation is undesirable as it may lead to issues with image segmentation later on.} \label{fig:interpolation} \end{figure} Lastly, this package also includes the possibility for other projection types. Currently, these are the maximum and minimum intensity projection, the mean and median intensity projection, the standard deviation projection, which calculates the standard deviation of the z-stack at each pixel in the $x-y$-plane, and the sum projection, which simply adds together all the values of the z-stack at each pixel in the $x-y$-plane. <<>>= max_intensity_proj = intensityProjection(imageStack = cells, projType = "max") min_intensity_proj = intensityProjection(imageStack = cells, projType = "min") mean_intensity_proj = intensityProjection(imageStack = cells, projType = "mean") median_intensity_proj = intensityProjection(imageStack = cells, projType = "median") sd_intensity_proj = intensityProjection(imageStack = cells, projType = "sd") sum_intensity_proj = intensityProjection(imageStack = cells, projType = "sum") @ \section{Session Info} <>= toLatex(sessionInfo()) @ \end{document}