// A is the user object associated with each mask (aka a "label")
sclass G22HashedMasks {
record noeq Mask(Image2B image, A label) is IG22Mask {
int hash, count;
public int hashCode() {
if (hash == 0)
hash = (int) hashImage2B(image);
ret hash;
}
public Image2B image() { ret image; }
public A label() { ret label; }
}
settable WidthAndHeight maskSize;
new Map masks;
new MultiSet labels;
// default mask size
{ maskSize(16); }
selfType maskSize(int size) {
ret maskSize(widthAndHeight(size));
}
Image2B regionToMaskImage(IImageRegion region) {
ret toImage2B(scaledIBinaryImage(maskSize, regionToIBinaryImage(region)));
}
void addRegion(IImageRegion region, A label) {
var mask = new Mask(regionToMaskImage(region), label);
var existingMask = masks.get(mask.hashCode());
if (existingMask != null) {
labels.remove(existingMask.label);
existingMask.label = combineLabels(existingMask.label, mask.label);
mask = existingMask;
} else
masks.put(mask.hashCode(), mask);
ghost_cache = null;
certainty_cache = null;
labels.add(mask.label);
mask.count++;
}
swappable A combineLabels(A a, A b) {
if (a == null) ret b;
if (b == null) ret a;
if (eq(a, b)) ret a;
fail("Label conflict: " + a + " / " + b);
}
L masks() { ret valuesList(masks); }
L maskImages() {
ret map(masks(), mask -> mask.image);
}
BufferedImage masksSquare() {
ret mergeBufferedImagesAsSquare(allToBufferedImage(maskImages()));
}
toString {
ret renderVars(shortClassName(this), +maskSize, masks := n2(masks));
}
simplyCached FloatBWImage ghost() {
ret preciseAverageOfBinaryImages(maskImages());
}
simplyCached double certainty() {
ret preciseCertaintyImage(ghost()).averageBrightness();
}
}