Relabelling objects - EBImage
2
0
Entering edit mode
@mathieu-gendarme-15885
Last seen 5.9 years ago

Dear all,

I am trying to relabel objects identified in images from immunofluroescence microscopy.

In a first step I am segmenting my nuclei to get individual cells than I am generating a voronoi as a proxy for the cytoplasm of the cells.

Aside of this I am detecting specks from another channel. Here comes the problem: I do not have one speck for one cell. Because of this I was unable to map back the detected objects (specks)  to their belonging cells (that is where the voronoi should be usefull in principle). Despite all my attempts I wasn't able to change the label of the detected specks with their overlapping voronoi (I tried multiple things and nothing worked).

Does someone know how to do this? I would be happy to hear about your solution!

I have posted example images below:

https://www.dropbox.com/s/t0lkxnc1jvoqx0w/Img405.tiff?dl=0

https://www.dropbox.com/s/eb1kbnjcc2zt4lk/Img488.tiff?dl=0

https://www.dropbox.com/s/t6o3dycq4eebw2x/SegmentationComplete.tiff?dl=0

The script for the object detection:

library(EBImage)

Img405 = readImage("Img405.tiff")
Img488 = readImage("Img488.tiff")

# Nucleus segmentation
FilterNuc = makeBrush(size = 51, shape = "gaussian", sigma=2)
Img405smooth = filter2(Img405, filter = FilterNuc)
nucThrManual = thresh(Img405smooth, w = 100, h = 100, offset = 0.0005)
nucOpening = opening(nucThrManual, kern = makeBrush(15, shape="disc"))
nucSeed = bwlabel(nucOpening)
nucFill = fillHull(thresh(Img405smooth, w = 20, h = 20, offset = 0.001))
nucRegions = propagate(Img405smooth, nucSeed, mask = nucFill)
nucMask = watershed(distmap(nucOpening), tolerance = 1, ext = 1)

# Voronoi for cytoplasm proxy
VoR = propagate(nucMask, nucMask, lambda = 100000)

# Speck detection
specThr = thresh(Img488, w = 200, h = 200, offset = 0.005)
specOpening = opening(specThr, makeBrush(5, shape = 'disc'))
specMask = bwlabel(specOpening)

# Visualisation of segmentation
NImgCol405_488 = rgbImage(blue = normalize(Img405) * 1.5, green = normalize(Img488) * 1.5)
SegmSpec = paintObjects(nucMask, NImgCol405_488, col = 'purple')
SegmSpec = paintObjects(VoR, SegmSpec, col = 'red')
SegmSpec = paintObjects(specMask, SegmSpec, col = 'yellow')
display(SegmSpec)

Many thanks in advance for you help.

Best regards,

Mathieu

 

ebimage image analysis image processing • 2.1k views
ADD COMMENT
1
Entering edit mode

Hi Mathieu, thank you for reaching out. Could you maybe share your files such that accessing them doesn't require setting up an account with a third-party service? Thanks!

ADD REPLY
0
Entering edit mode

Hi Andrzej, thanks for replying quickly. The files are now publicly available.

Cheers,

Mathieu

ADD REPLY
0
Entering edit mode

When I just tried (Wed May 23 13:40:08 CEST 2018), I still get a window asking me to "Log in to figshare" when I follow the link in your post. As Andrzej says, a method of providing these files that does not require others to login somewhere would be preferable.

 

ADD REPLY
1
Entering edit mode
Andrzej Oleś ▴ 750
@andrzej-oles-5540
Last seen 4.1 years ago
Heidelberg, Germany

Dear Mathieu,

in order to re-label the specks you need to overlay the Voronoi partitions with the specks mask to extract the corresponding labels. The difficulty here is that occasionally a speckle might end up on the border of two or more partitions. You can account for such situations by assigning to these speckles the label of the region where most of their pixels fall into.

This approach can be implemented similarly as in the example below. After creating the binary specks_mask we use tapply on the subset of pixels v_labels from the Voronoi map VoR which correspond to the speckle regions with the grouping defined by s_labels. The function uses unique to check whether all the pixels from a given speckle lie in the same Voronoi partition. If not, sapply is used to count the occurrences of labels, and the one with the largest pixel area is chosen with the help of which.max. Eventually rep returns a vector of an appropriate label and length equal the number of pixels for a given speckle. The replacement of v_labels by the result of tapply through split <- is necessary in order to preserve the distribution of labels. This is because object labels extracted from a 2D matrix and serialized to a vector might end up being arranged non-sequentially when there is more than one object in a given column. The actual re-labeling is performed by assigning the altered v_labels to the speckle pixels selected by the speckle mask.

Please note that I've used a different approach to identify the speckles in the image. As far as I understand it correctly you are interested in the small bright spots whose intensity is much higher than the overall intensity of in the image. These can be easily identified by a hard threshold such as sum(range(Img488))/2.

By the way, I'm a bit surprised by the range of the pixel values in your images, which start only > 0.5 and cover just a small fraction of the usual 0:1 range. Could you maybe briefly explain your workflow of converting the raw microscopy images into tiff files which you load in R?

Cheers, Andrzej

specks_mask <- Img488 > sum(range(Img488))/2
specks_labeled <- bwlabel(specks_opening)

v_labels <- VoR[specks_mask]
s_labels <- specks_labeled[specks_mask]
split(v_labels, s_labels) <- tapply(v_labels, s_labels, function(x) {
  u <- unique(x)
  if ( length(u) == 1L ) {
    x
  }
  else {
    s <- sapply(u, function(i) sum(x == i))
    rep(u[which.max(s)], length(x))
  }
})
specks_labeled[specks_mask] <- v_labels
ADD COMMENT
0
Entering edit mode
@mathieu-gendarme-15885
Last seen 5.9 years ago

Dear Andrzej,

Thank you very much, you solved the problem! The idea of using the largest fraction of the speck in a Voronoi in the case of multiple overlap looks great to me, thanks for that too.

Coming back to your thresholding method for detecting only the bright and small objects; it is true that we are mainly interested in them but not only. Our final workflow would contain more than one thresholding method to catch the true "specks" but also other objects that might appear depending on the biological conditions encountered. That's why I only showed in my code a "mild" thresholding method.

Finally regarding the wired scale of the images it comes from a ScanR microscope, the images were not converted or rescaled from my side. The "issue" is explained in detail here, I am adding a small citation:

"- Our Olympus ScanR microscopes produce tiff files with an integer 16 bit/pixel container.

- The actual image data only has 4096 grey levels. However, as the images are saved as "signed" 16bit images these gray values have an offset of 2^15, i.e. they range from 2^15 (32768) to 2^15+4095 (=36863).

- CellProfiler expects pixel data in the range from 0.0 to 1.0 floating point.

- Upon loading the raw ScanR pictures, CellProfiler already rescales them such that the range from 0 to 2^16-1 is mapped to 0.0 to 1.0. This means that the useful image range is rescaled to a range from 2^15/(2^16-1) to (2^15+4095)/(2^16-1) which is 0.50000762951 to 0.56249332417."

Of course the images can be easily rescaled to fit the usual 0 to 1 scale.

Cheers, Mathieu

 

ADD COMMENT

Login before adding your answer.

Traffic: 853 users visited in the last hour
Help About
FAQ
Access RSS
API
Stats

Use of this site constitutes acceptance of our User Agreement and Privacy Policy.

Powered by the version 2.3.6