This is a follow-up to this an earlier thread about the
The core problem is that
ShapeRoi doesn’t handle “open”
rois properly – things like
Lines and polyline
I’ve also created a github issue about this:
If people think that it makes sense to address this and
that this is the right approach, I will try to finish up
a replacement version of ShapeRoi.java (following the
ShapeRoi2 is supposed to work correctly for the
ShapeRoi operations, and generally behave no worse
ShapeRoi. (Constructing a
ShapeRoi2 from a
Shape is not expected to work properly with this version.)
Please let me know if you find any issues with the core
Here are two test images that illustrate some of the
ShapeRoi and compare
ShapeRoi with a
draft of the proposed fix.
The first image shows
ShapeRoi performing correct set
arithmetic with closed rois (in this case
The second image shows how
ShapeRoi fails with an open
roi (in this case a
Line) while the proposed fix seems
These two test images are laid out as follows:
The first three rows show how two rois are rendered by:
- the “classical” roi (viz.
ShapeRoi(constructed from the classical roi);
ShapeRoi2, the proposed fix for
The orange outlines are boundaries drawn by
roi.drawPixels (ip). The cyan solid shapes are
The next three rows (rows 4 through 6) are again classical,
ShapeRoi2, now illustrating set arithmetic.
Classical rois don’t do set arithmetic, so they are just
the two rois drawn on top of one another to guide the eye.
ShapeRoi2 rows (rows 5 and 6), the
five columns (of boundary / interior pairs) are the results
(symmetric difference), and the two orders of
and B-A) operations.
In the first image everything works as expected. In the
second image, the
Line roi displays correctly as the
Line and as the fixed
Line. But when a
ShapeRoi is constructed from
Line, it displays only with
drawPixels(), not with
fill(), and vanishes entirely when used in set arithmetic.
Here is the jython script that generates these (and other)
from java.awt import Color from ij import IJ from ij.gui import EllipseRoi from ij.gui import Line from ij.gui import PointRoi from ij.gui import PolygonRoi from ij.gui import Roi from ij.gui import ShapeRoi from ij.gui import ShapeRoi2 def setRoiCentroidLocation (roi, xc, yc): x1 = int (roi.getBounds().getX()) y1 = int (roi.getBounds().getY()) xd = xc - int (round (roi.getContourCentroid())) yd = yc - int (round (roi.getContourCentroid())) roi.setLocation (x1 + xd, y1 + yd) return roi # image size iWidth = 1024 iHeight = 640 # roi locations hBase = 50 # centroid-x of first roi vBase = 60 # centroid-y of first roi hFOff = 85 # offset of "fill" from "drawPixels" hOff = 205 # x-offset for next roi column vOff = 95 # y-offset for next roi row vXOff = 315 # y-offset for first set-arithmetic row cBase = 375 # centroid-y of first "combo" roi # rois roisA =  roisB =  titles =  roisA.append (EllipseRoi (0, 0, 50, 50, 0.2)) # ellipse roisB.append (EllipseRoi (0, 50, 50, 0, 0.2)) # ellipse titles.append ('ellipses') roisA.append (Roi (0, 0, 50, 30, 25)) # rounded-rectangle roisB.append (Line (0, 50, 50, 0)) # line titles.append ('rounded-rectangle / line') ptx = [ 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 10, 14, 10, 14, 10, 14, 10, 14, 10, 14, 20, 24, 20, 24, 20, 24, 20, 24, 20, 24 ] pty = [ 0, 0, 10, 10, 20, 20, 30, 30, 40, 40, 1, 1, 11, 11, 21, 21, 31, 31, 41, 41, 2, 2, 12, 12, 22, 22, 32, 32, 42, 42 ] roisA.append (PointRoi (ptx, pty, len (ptx))) # points roisB.append (Roi (0, 1, 50, 20)) # rectangle titles.append ('points / rectangle') pgx = [ 25, 50, 50, 25, 0, 0 ] pgy = [ 5, 20, 30, 45, 30, 20 ] roisA.append (PolygonRoi (pgx, pgy, Roi.POLYGON)) # polygon (hexagon) plx = [ 20, 40, 10, 30, 30, 10, 40, 20 ] ply = [ 20, 0, 0, 20, 30, 50, 50, 30 ] roisB.append (PolygonRoi (plx, ply, Roi.POLYLINE)) # polyline titles.append ('polygon / polyline') roisA.append (Line (0, 50, 50, 0)) # line roisB.append (Line (0, 0, 50, 50)) # line titles.append ('lines -- intersecting') roisA.append (Line (0, 51, 51, 0)) # line roisB.append (Line (0, 0, 51, 51)) # line titles.append ('lines -- "missed" intersection') ops = ['or', 'and', 'xor', 'A-B', 'B-A'] for i in range (len (titles)): imp = IJ.createImage (titles[i], 'RGB ramp', iWidth, iHeight, 1) ip = imp.getProcessor() ip.multiply (0.125) ip.add (31.0) # draw rois for ir in [0, 1]: r = roisA[i] if ir == 0 else roisB[i] for j in range (3): # "classical", ShapeRoi, ShapeRoi2 cx = hBase + ir * hOff cy = vBase + j * vOff rd = r.clone() rf = r.clone() setRoiCentroidLocation (rd, cx, cy) setRoiCentroidLocation (rf, cx + hFOff, cy) if j == 1: rd = ShapeRoi (rd) rf = ShapeRoi (rf) if j == 2: rd = ShapeRoi2 (rd) rf = ShapeRoi2 (rf) ip.setColor (Color.orange) rd.drawPixels (ip) ip.setColor (Color.cyan) ip.fill (rf) # draw set-arithmetic rois io = 0 for op in ops: for j in range (3): rad = roisA[i].clone() raf = roisA[i].clone() rbd = roisB[i].clone() rbf = roisB[i].clone() cx = hBase + io * hOff cy = vBase + vXOff + j * vOff setRoiCentroidLocation (rad, cx, cy) setRoiCentroidLocation (raf, cx + hFOff, cy) setRoiCentroidLocation (rbd, cx, cy) setRoiCentroidLocation (rbf, cx + hFOff, cy) if j == 0: if op == 'or': ip.setColor (Color.orange) rad.drawPixels (ip) rbd.drawPixels (ip) ip.setColor (Color.cyan) ip.fill (raf) ip.fill (rbf) else: if j == 1: rad = ShapeRoi (rad) rbd = ShapeRoi (rbd) raf = ShapeRoi (raf) rbf = ShapeRoi (rbf) if j == 2: rad = ShapeRoi2 (rad) rbd = ShapeRoi2 (rbd) raf = ShapeRoi2 (raf) rbf = ShapeRoi2 (rbf) if op == 'or': rad.or (rbd) raf.or (rbf) rxd = rad rxf = raf if op == 'and': rad.and (rbd) raf.and (rbf) rxd = rad rxf = raf if op == 'xor': rad.xor (rbd) raf.xor (rbf) rxd = rad rxf = raf if op == 'A-B': rad.not (rbd) raf.not (rbf) rxd = rad rxf = raf if op == 'B-A': rbd.not (rad) rbf.not (raf) rxd = rbd rxf = rbf ip.setColor (Color.orange) rxd.drawPixels (ip) ip.setColor (Color.cyan) ip.fill (rxf) io += 1 imp.show()
To run this script you will need
ShapeRoi2. Here is its
jar file, shape_roi_2.jar:
shape_roi_2.jar (13.9 KB)
Add it to a directory from which Fiji / ImageJ loads classes.
(I put it in the plugins directory.)
For completeness, the code for ShapeRoi2.java appears below.
(It is renamed and posted as ShapeRoi2_java.tif to get by the
forum limitations.) It is almost entirely copy-pasted from the
original ShapeRoi.java, with changes isolated to the
roiToShape() method, primarily in the new
if (!roi.isArea()) if-block.
ShapeRoi2_java.tif (51.4 KB)