-
Notifications
You must be signed in to change notification settings - Fork 311
Description
Bug report
Describe the bug
When using complicated 'polygon' ROIs, a binary mask in QuPath can differ a lot from a binary mask generated in ImageJ.
This means that code or scripts using ImageJ for processing could potentially give unexpected results, if a mask is generated on the ImageJ side.
Some observations:
- The QuPath mask is consistent with what is shown in QuPath's viewer, and the ImageJ mask is consistent with ImageJ's behavior - so neither needs to be 'wrong'.
- A potential explanation is the winding rule: ImageJ uses
WIND_EVEN_ODD
, while QuPath doesn't appear to specify a rule - and Java's default isWIND_NON_ZERO
. - The problem affects
PolygonROIs
with self-intersections, which would be invalid in the JTS sense. - The issue goes away if the
PolygonROI
is converted to aGeometryROI
(which usually happens from any automatically-generated ROIs, just not for ones created interactively with the polygon tool in the viewer).
To Reproduce
You can import the following GeoJSON:
{
"type": "Feature",
"id": "528308d5-bb0a-46c9-9906-3812f9edfd58",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[30276.88, 45038.84],
[30376.29, 45337.05],
[31072.12, 45784.38],
[32314.67, 46231.69],
[33756.03, 46579.61],
[35147.68, 46828.12],
[36738.15, 47076.63],
[38328.62, 47374.84],
[39521.46, 47722.75],
[40465.8, 48020.96],
[41310.74, 48368.88],
[42056.27, 48766.5],
[42752.1, 49114.41],
[43597.04, 49512.03],
[44541.37, 49859.94],
[45485.71, 50058.75],
[45827.24, 50105.32],
[45535.41, 49313.22],
[45535.41, 48418.58],
[46082.14, 47523.95],
[47274.98, 46679.01],
[48815.75, 45883.78],
[50207.41, 45287.35],
[51052.34, 44790.33],
[51400.26, 44392.71],
[51449.96, 44144.21],
[51052.34, 43895.7],
[49660.68, 43597.48],
[47324.69, 43199.87],
[44342.56, 42951.36],
[40764.02, 42802.25],
[37980.7, 42752.55],
[36141.73, 42702.85],
[34948.88, 42702.85],
[34004.54, 42752.55],
[33259.01, 42802.25],
[32811.69, 42901.65],
[32513.48, 43050.76],
[32364.37, 43249.57],
[32165.56, 43448.38],
[31917.05, 43746.59],
[31569.14, 43995.1],
[31121.82, 44293.31],
[30724.2, 44541.82],
[30425.99, 44740.63],
[30276.88, 45038.84]
]
],
[
[
[45827.24, 50105.32],
[45883.33, 50257.56],
[46628.86, 51351],
[47871.41, 52593.55],
[48237.57, 52837.66],
[48368.43, 53786.4],
[49014.56, 55227.76],
[49909.2, 56470.32],
[51052.34, 57265.55],
[52394.3, 57762.57],
[53885.36, 57861.97],
[55525.53, 57712.87],
[57513.61, 57315.25],
[58071.48, 57166.48],
[61141.86, 57663.16],
[63527.56, 57812.27],
[64770.11, 57514.06],
[65962.96, 57017.04],
[66807.89, 56221.8],
[67354.62, 55178.06],
[67493.41, 54484.07],
[68795.98, 54034.91],
[70883.46, 52941.47],
[72523.63, 51698.92],
[73318.86, 50555.77],
[73666.77, 49710.84],
[73815.88, 48766.5],
[73815.88, 47822.16],
[73617.07, 46927.52],
[73318.86, 46181.99],
[72722.44, 45287.35],
[72026.61, 44392.71],
[71678.7, 43895.7],
[71380.48, 43348.97],
[71239.44, 43151.51],
[71231.38, 43150.16],
[66509.68, 42603.44],
[66121.27, 42591.55],
[66012.66, 41559.7],
[66112.06, 39273.4],
[66332.49, 38226.36],
[66211.47, 38130.26],
[64074.28, 36490.09],
[62690.41, 35229.23],
[63129.94, 35197.84],
[64223.39, 34750.52],
[65018.62, 34004.99],
[65316.83, 33110.35],
[65366.53, 32016.9],
[65068.32, 31122.27],
[64571.3, 30227.63],
[63875.47, 29432.4],
[63129.94, 28786.27],
[62264.96, 28405.68],
[61452.6, 33929.69],
[60446.03, 32563.63],
[60147.82, 30923.46],
[60943.05, 29283.29],
[62169.31, 28363.59],
[61887.39, 28239.55],
[60296.92, 27692.83],
[59696.15, 27525.17],
[58706.46, 27593.42],
[56618.97, 28090.44],
[55376.42, 28835.97],
[54680.59, 29780.31],
[54531.48, 30824.05],
[54730.29, 31867.8],
[55376.42, 32911.54],
[56519.57, 33955.29],
[58060.33, 34750.52],
[59949.01, 35197.84],
[61255.44, 35270.41],
[60191.1, 42507.96],
[57613.01, 42603.44],
[54581.19, 43249.57],
[52145.79, 44293.31],
[50406.21, 45784.38],
[49163.66, 47523.95],
[48517.54, 49114.41],
[48309.69, 50257.56],
[47622.9, 50257.56],
[46579.16, 50207.86],
[45827.24, 50105.32]
]
],
[
[
[53438.04, 24462.19],
[53438.04, 24859.81],
[54133.87, 25456.23],
[55724.34, 26251.47],
[58159.73, 27096.4],
[59696.15, 27525.17],
[61589.18, 27394.61],
[62407.63, 27435.54],
[62881.43, 24213.68],
[62782.03, 24213.68],
[62583.22, 24163.98],
[61837.69, 24114.28],
[60346.63, 23965.17],
[58507.65, 23915.47],
[57066.29, 23865.77],
[55426.12, 23965.17],
[53885.36, 24163.98],
[53438.04, 24462.19]
]
],
[
[
[62169.31, 28363.59],
[62264.96, 28405.68],
[62283.77, 28277.75],
[62169.31, 28363.59]
]
],
[
[
[62283.77, 28277.75],
[62931.13, 27792.23],
[63629.79, 27496.64],
[62407.63, 27435.54],
[62283.77, 28277.75]
]
],
[
[
[63629.79, 27496.64],
[64571.3, 27543.72],
[67702.53, 28040.74],
[70237.34, 28835.97],
[71976.91, 29680.91],
[73517.67, 30476.14],
[75008.73, 31171.97],
[76300.98, 31569.59],
[77295.02, 31619.29],
[78040.55, 31470.18],
[78438.17, 31122.27],
[78636.98, 30525.84],
[78686.68, 29482.1],
[78587.28, 28338.95],
[78338.77, 27295.21],
[77941.15, 26450.28],
[77344.73, 25953.25],
[76201.58, 25456.23],
[74611.12, 25307.13],
[72175.71, 25406.53],
[68746.27, 25903.55],
[65515.64, 26698.79],
[63629.79, 27496.64]
]
],
[
[
[66332.49, 38226.36],
[67901.34, 39472.21],
[69193.59, 40714.77],
[70137.93, 41758.51],
[70883.46, 42653.14],
[71239.44, 43151.51],
[74809.92, 43746.59],
[77493.84, 44243.61],
[79879.53, 44591.52],
[81619.1, 44740.63],
[82712.55, 44740.63],
[83358.68, 44641.23],
[83706.59, 44392.71],
[83905.4, 43945.4],
[83855.7, 43050.76],
[83358.68, 41609.4],
[82265.23, 39919.53],
[80426.26, 38130.26],
[77891.45, 36589.49],
[74809.92, 35545.75],
[71529.59, 35098.43],
[69094.19, 35396.64],
[67454.02, 36191.88],
[66509.68, 37384.73],
[66332.49, 38226.36]
]
]
]
},
"properties": {
"objectType": "annotation",
"measurements": {
"Area µm^2": 3.5567575437333584E7,
"Length µm": 62782.89619349399,
"Solidity": 0.5638625874807928,
"Max diameter µm": 12192.853113808404,
"Min diameter µm": 7673.144125811996
}
}
}
The following script creates a mask the 'QuPath' way:
def roi = getSelectedROI()
def mask = BufferedImageTools.createROIMask(roi, 10)
new ij.ImagePlus("Mask", mask).show()
You can generate an 'ImageJ' mask using Extensions → ImageJ → Send region to ImageJ and then Edit > Selection > Create Mask.
Expected behavior
When sending a QuPath ROI to ImageJ, any masks generated in either application should be identical - or at least very similar.
This might be achieved by converting complicated PolygonROI
instances into GeometryROIs
.
Screenshots
In QuPath
In ImageJ
Desktop (please complete the following information):
- OS: All
- QuPath Version: 0.5.1 (presumably earlier versions too)
Additional context
ImageJ's choice of winding rule may well be more sensible - but it is not something we should change in QuPath too quickly.
This clearly could be a serious problem for some workflows, but I struggle to think of a time when it is likely to have been a problem in reality. It is only likely to impact PolygonROIs
with self-intersections. While these can be drawn in the viewer, internally QuPath will typically convert these to be GeometryROIs
- and these seem to behave ok.
Note that ImageJ's mask generation is in general a bit more sophisticated than QuPaths (see here), which is something that warrants investigation in the future as well.