XY Coordinates of Multiple ROI Boundaries that have Width Greater than 1 pixel

I am using ImageJ version 1.52p (Fiji); Java 1.8.0_172 (64-bit). I want the XY coordinate of every pixel defining thick, freehand ROI boundaries. Below is the baseline image and ROIs I am working with:
image
Here are the image and roi.zip files:
imagej forum help 1.tif (46.2 KB)
hand_ROIs_iterated_4.zip (5.2 KB)

Note I have only clicked Show All to display the ROIs on the image, and have not generated an Overlay layer. I can easily get the XY coordinates of the pixels just inside the border of each ROI:

[Deselect any active ROI]
ROI Manager - More… - OR (Combine)
File - Save As - XY Coordinates…

Note freehand interpolation is deactivated, and I do not wish to activate it.

I would like to change the properties of the ROI widths in the ROI manager, then export the XY coordinates of the ROI boundaries. For example, if I change the ROIs width from 0 (seen above) to 2, the resulting ROIs are displayed:
image
Here are the modified ROI files:
imagej help_ large width ROIs.zip (5.2 KB)

How do I get the XY pixel coordinates of all the red pixels in the above image?

Performing the File - Save As - XY Coordinates above does not work; it gives me the same XY coordinates as the 0 width ROIs (i.e. the XY coordinates of the pixels just inside the boundary). I can get the XY coordinates just outside the boundary by using the following procedure:

ROI Manager - More… - OR (Combine)
Edit - Selection - Create Mask
[Select Mask image]
Process - Binary - Outline (note Black Background option is checked!)
File - Save As - Text Image

The format of the exported text file does not matter; I do not care if the xy values are exported as pairs (as Save As - XY Coordinates exports) or implicitly in a 2D matrix (as Save As - Text Image exports). Although I do prefer if I can Save As - Text Image.

I’m new to ImageJ and not sure if there is some trick with Image Subtraction that may be useful here? I look forward to your suggestions, and thank you very much for your help!!

Hello tzwalker -

If changing the ROI’s width (e.g., from the ROI Manager) gives
you the thick boundary you want, you can then run Flatten (F)
from the ROI Manager to make the thick ROI boundary part of
the image.

(If the colors / structure / complexity of your original image cause
problems for the subsequent processing, you could first apply
your thick-boundary ROI to a simple image (e.g., all black) before
flattening.)

Your could then run:

Image > Adjust > Color Threshold...
Process > Binary > Convert to Mask
Edit > Selection > Create Selection

This newly created ROI (selection) will be made up of the points
in the thick boundary of your original ROI,

You could then, for example, add this ROI (selection) to the ROI
Manager, and then save / export it as you see fit.

Thanks, mm

Your procedure got me very close! I used your suggestion and applied the thick-boundary ROI to a simple, all-black image (I changed the ROI boundaries to white with the hope of easier subsequent processing):
image
However, when Flatten (F) renders the corresponding RGB image—decribed here—there seems to be an amount of data loss:
image

To see how close we could get I carried out a modified version of the thresholding procedure you provided.

Image > Type > 8-bit
Image> Adjust > Threshold… > min=1, max=255 > Apply

I have excluded the last two, straightforward mask/create selection steps. I chose a minimum threshold value of 1 because that captures all pixels that are non-zero; the hope was that the resulting threshold would yield boundaries exactly the same as the ROI Manager Overlay, which basically shows the values of a binary image (though ImageJ does not recognize it as Binary at this point).

However, there is a clear difference between the ROI Manager Overlay and the rendered thresholded image we’ve created:
image
The boundaries with the desired 2-pixel width (left) are smaller than the boundaries of the thresholded image (right). Is it possible to be more precise?

Referencing the same link above, I tried

Plugins > Utilities > Capture

because it said it would produce a “What you see is what you get” image. This is inadequate because the resulting image does not maintain the individual pixel information of the original image; the result is a product of the screen resolution and the magnification of the original. A 99x101 image gets converted into a NxM pixel image.

Is there an alternative to Flatten (F) that combines an image and its Overlay without rendering them into an RGB image?

Thanks again!

Hello tzwalker -

I believe that Flatten is doing what you want. I suspect that when
you create your “boundary ROI” you still have its Properties...
set to include a non-zero thickness, and that this confuses the way
you visualize the “boundary ROI”.

Here is what I get when I create a free-hand ROI (displayed with
a thick boundary), and then use the flatten-threshold-mask
procedure to create the “boundary ROI” (displayed with the
default thickness of 0 pixels).

thick_boundary.roi (renamed to “.txt” so I can upload it – rename
it back so that it will be recognized as an “.roi” file by Fiji):

thick_boundary_roi.txt (936 Bytes)

roi_of_boundary.roi (also renamed to “.txt”):

roi_of_boundary_roi.txt (6.5 KB)

Here is the mask image:

thick_boundary_mask

Here are images of the two ROIs. I applied the ROIs to a grayscale
image, and then flattened them so I could upload them:

The ROI displayed with the thick (10-pixel) boundary:

thick_boundary_flat

The ROI created from the thick boundary (displayed with the
default (“0-pixel”) boundary):

roi_of_boundary_flat

To my eye, the flattening and masking procedures look spot on.

Thanks, mm

Thanks mountain_man, I appreciate your assistance. I was able to open the roi files you shared, but I think there might be some confusion about the exact problem I am experiencing. To clarify, I am limited to a 99x101 resolution and want to replicate your precision with multiple ROIs. When I do the following:

File > New > Image (8-bit, width 99, height 101)
Analyze > Tools > ROI Manager > More > Open > imagej help_ large width ROIs.zip

I get this image:
image
In the ROI Manager, I click Flatten (F), and get this image:
image
Below is a close-up comparison of the top-most ROI in the original overlay (left) and the rendered image using Flatten (F) (right):


Below is a comparison of the rendered image (left) and the rendered image with the original overlay of this ROI (right):

By observing the right image, it’s clear some of the shaded pixels–pixels with values between 0 and 255—are filled by the overlay, but other shaded pixels are not. Some of the ones that are filled are circled in red. The original overlay contains the pixels for which we want indices.

I believe a threshold will not work here because Fiji cannot discriminate between the shaded pixels that are filled by the overlay, and the shaded pixels that are not filled by the overlay.

So there’s an example of the problem I’m experiencing with the suggested procedure. I am very interested in whether you can replicate your level of precision at the , and with the multiple ROIs I shared: imagej help_ large width ROIs.zip.

I have been unable to, but will try some different approaches in the meantime. I have also slightly edited my bolded question in Post 3 to try and better reflect the problem at hand.

Thanks again for your help and advice!

Hello tzwalker -

My experience doesn’t line up with what your are describing.
In particular, you talk about partially “shaded pixels” that look
in the images you posted to be what I would call “dithered”.
I can’t get Flatten to produce dithered pixels.

(Also, the images you posted look like screenshots. It’s much
better to post your original images using a non-lossy format to
avoid the possibility of introducing artifacts.)

Could you post a complete, runnable script (that starts from
scratch) that creates the kind of ROI / mask that shows your
issue?

Thanks, mm

mountain_man,

The images I posted were indeed screenshots; in the future I will follow your advice to upload the images in their original state.

As I’ve mentioned, I am pretty new to Fiji and completely unfamiliar with both its scripting language and Java. That said, I recorded executable macros in IMJ script from scratch:
Flatten_dithered_ijm.txt (254 Bytes)

Note you will need to edit the path. I’ve used your same filename trick; replace .txt with the adjacent extension. Without recording, I saved the images that resulted from running the .imj macro and include them here:
flattened image.tif (29.4 KB)
original overlay.tif (16.8 KB)

I hope this helps; best of luck!

I’ve produced the desired output by abandoning editing the width in the ROI Manager, and instead applying the command Enlarge.

Procedure:

setup
File - New - Image
Analyze - Tools - ROI manager - More - Open - [rois_of_interest.zip]
More - OR (Combine)
outer boundary
Edit - Selection - Enlarge - 1pix
ROI Manager - Add
inner boundary
[In ROI Manager, select all original, zero-width ROIs; these would be all but the outer boundary ROI just added]
More - OR (Combine)
Edit - Selection - Enlarge - (-1pix)
ROI Manager - Add
generate mask
[In ROI Manager, delete all original, zero-width ROIs; these would be all but the last two ROIs just added]
ROI Manager - Deselect
ROI Manager - More - XOR
Edit - Selection - Create Mask
File - Save As - Text Image

Starting from scratch, the recorded macro looks something like this:
final_solution_ijm.txt (1.6 KB)
Note the .txt extension will have to be replaced by the ijm extension, and the path to the ROI_set.zip, as well as the correct .zip filename, will have to be edited.

As a final note:
I believe when Flatten goes to render an RGB image from two vector-graphic images (e.g. the image and its overlay), it performs some raster operation that imprecisely assigns pixel values. This is how I would explain the considerable difference between pixel values and locations after zooming in on a rendered Flattened image (see Post 5).

Even so, thanks to mountain_man for the assistance and suggestions. If he or any one else has a comment feel free to post!

Hello tzwalker -

First, I see (and understand what you mean by) the “shaded
pixels” (what I had called “dithering”) now. (I had let myself be
misled by the zoomed-in images.)

I would phrase this a little differently.

First, “the image” is not a vector-graphics image. It is a bitmap
(or pixel or raster) image. Overlays (and ROIs) are indeed
vector graphics*.

I would not say exactly that the pixel values are “imprecisely”
assigned. The issue is that the continuum of points (real-number,
or at least floating-point curves) of vector graphics and discrete
pixels are different in character. One has to make a decision
about – that is, define – which pixels correspond fully or partially
to which real-number points. For visual purposes, anti-aliasing
(probably a better term for what I had called “dithering”) is often
used.

The flattening process is not wrong or imprecise. It just has to
make a (well-defined) choice about how to map between two
different representations that have an essential difference in
character.

I believe that the flattening’s anti-aliasing is (partially) including
pixels you don’t want by partially shading them.

But I believe that the information is there for you to get what (I
believe) you want from thresholding a flattened ROI.

Try this:

Draw a freehand ROI on a black or gray image. Thicken its
boundary width to, way, 10 pixels. Leave its stroke color “yellow”
(so that the saturation thresholding described below will work).
Flatten the image. (You will indeed see anti-aliasing / dithering
at the edges of the thick boundary.)

Now run Color Threshold. Set “Hue” and “Brightness” to pass
everything (min = 0, and max = 255 for both). Then use “Saturation”
to threshold the pixels to keep. Leave “Saturation” max = 255, and
start adjusting “Saturation” min upwards. As you tighten up the
“Saturation” min threshold, you will the number of pixels included
in your flattened ROI boundary decreasing.

By doing this your are, in effect, tuning the definition of which
pixels correspond to the vector-graphics ROI.

I think if you set the min level to its highest level of 255, or
perhaps somewhere near the top (say, in the 200+ range),
you will get the pixels you want when you create the mask
from the thresholded flattened image.

*) Although still technically vector graphics, the ShapeRoi
you get when you run Create Selection on a mask is the
next best thing to a bitmap ROI (Overlay).

Thanks, mm

mountain_man,

Thanks for the clarifications. I’ve quoted the points I took away from your reply.

-First, “the image” is not a vector-graphics image. It is a bitmap (pixel or raster) image. Overlays (and ROIs) are indeed vector graphics.
-Anti-aliasing (probably a better term for what I had called “dithering”) is often used [to define which pixels in a vector-graphic (overlay) correspond fully or partially to the real-number points of the bitmap image; the Flatten function employs anti-aliasing when it merges an overlay and a bitmap]
-The flattening process is not wrong or imprecise. It just has to make a (well-defined) choice about how to map between two different representations that have an essential difference in character. I believe that the flattening’s anti-aliasing is (partially) including pixels you don’t want by partially shading them.

The bracketed text I have inserted myself, and is not meant to be a correction. That text is simply my own interpretation of your explanation, and hope it is accurate. Your points are certainly instructive; I learned something more about ImageJ!

As I have already obtained the result I was looking for, I leave it to other readers to experiment with your suggestion. I did try it myself, but will not share my results here. While I do agree with your point that the information is there and your procedure could absolutely benefit another user, it is simply unnecessary for my purposes.

It’s unclear whether two different ROIs will be be anti-aliased by Flatten in the same way; I don’t know if the shape/perimeter of a hand-drawn, vector-graphic ROI is coupled to how Flatten anti-aliases the ROI. I do not intend to find out if I need to threshold one of my many ROI individually, or if a single threshold can remove the unwanted pixels from all ROIs at one time.

Even so, I am thankful for your input; it definitely got me thinking about how I may be able to apply different features of ImageJ in the future. If users are willing to read this entire post, they may learn a good amount from our discussion here.

Thanks again,
tzwalker

Hello tzwalker -

A brief reply, and a comment, below:

If I understand what you mean by your bracketed phrasing, I
believe that you have it backwards.

A vector-graphics overlay doesn’t have pixels. It consists
(typically) of real-number curves (and those curves might
enclose a real-number interior). (Sure, you could deem a
vector-graphics overlay to consist of the pixels you would
get when you draw it on a pixel image, but that would be
stretching the point, and begs the question of what happens
when you translate or scale the overlay (by amounts that
don’t correspond to whole pixels).)

And the pixel image consists of pixels. not real-number points.
(Sure, you could assign to each pixel the real-number point of
its upper-left corner, or the set of real-number points that make
up the its interior, but that would be stretching the point.)

Yes, it’s undesirable to have to rely on a black-box and not
(easily-accessibly) documented anti-aliasing algorithm, and
potentially tune some thresholding scheme.

It turns out that the Roi class has a boolean antiAlias
property. I didn’t find a way to access it from the Fiji gui, but
it is programmatically accessible via Roi.setAntiAlias().
(ImagePlus has the broadly-equivalent setAntialiasRendering().)

Here is a script that flattens a (thick) Roi with and without
anti-aliasing:

from ij import IJ
from ij.gui import OvalRoi
impOn = IJ.createImage ('ROI anti-alias', '8-bit ramp', 256, 256, 1)
impOff = IJ.createImage ('ROI no anti-alias', '8-bit ramp', 256, 256, 1)
impOn.show()
impOff.show()
roi = OvalRoi (70, 70, 50, 100)
roi.setStrokeWidth (10)
impOn.setRoi (roi)
impOnFlat = impOn.flatten()
impOnFlat.show()
roi.setAntiAlias (False)
impOff.setRoi (roi)
impOffFlat = impOff.flatten()
impOffFlat.show()

And here, for your image-processing delectation, are the resulting
images:

Flat_ROI anti-alias.png (default - with anti-aliasing):

Flat_ROI anti-alias

Flat_ROI no anti-alias.png (anti-aliasing turned off):

Flat_ROI no anti-alias

Again, I’m not completely sure what pixels you wanted to retrieve
in your analysis, but from the overall discussion I expect that
flattening with anti-aliasing turned off would have given you (if
we had discovered how to control anti-aliasing) exactly what
you wanted.

Thanks, mm