sclass G22CriticalPixelSearch is Steppable { // input settable IG22MasksHolder masksHolder; settable bool keepAllEntries; // internal int w, h; ItIt pixelIterator; // output new Best bestPixel; Entry bestEntry; Pt lastPixelLookedAt; Entry lastEntry; Entry[] pixelEntries; float[] pixelScores; *() {} *(IG22MasksHolder *masksHolder) {} class Entry { Pt pixel; // child mask holders for pixel dark or pixel bright //IG22MasksHolder darkChild, brightChild; L darkMasks, brightMasks; gettable IG22MasksHolder darkHolder; gettable IG22MasksHolder brightHolder; toString { ret commaCombine( "Split at " + pixel, "dark=" + darkHolder.renderCountAndCertainty(), "bright=" + brightHolder.renderCountAndCertainty()); } } public bool step() { if (w == 0) { if (!masksHolder.canSplit()) false; meta-for w also as h { w = masksHolder.maskSize().w(); } pixelEntries = new Entry[w*h]; pixelScores = new float[w*h]; arrayfill(pixelScores, -1); pixelIterator = new WeightlessShuffledIterator(pixelsInImage(w, h)); } if (!pixelIterator.hasNext()) false; lastPixelLookedAt = pixelIterator.next(); testPixel(lastPixelLookedAt); true; } int idx(Pt p) { ret p.y*w+p.x; } void testPixel(Pt p) { double score; Entry e = null; // Find out if pixel is the same in all masks float ghostBrightness = masksHolder.ghost().getFloatPixel(p); if (ghostBrightness == 0 || ghostBrightness == 1) // Pixel is determined, not usable for splitting score = -2; else { // Pixel is not determined - let's make the 2 subsets e = new Entry; e.pixel = p; lastEntry = e; if (keepAllEntries) pixelEntries[idx(p)] = e; /*Pair>*/var pair = filterAntiFilter(masksHolder.masks(), mask -> !mask.image().getBoolPixel(p)); e.darkMasks = (L) pair.a; e.brightMasks = (L) pair.b; e.darkHolder = new G22HashedMasks(e.darkMasks); e.brightHolder = new G22HashedMasks(e.brightMasks); score = scoreEntry(e); } pixelScores[idx(p)] = (float) score; if (score > 0 && bestPixel.put(p, score)) bestEntry = e; } swappable double scoreEntry(Entry e) { double c1 = e.darkHolder().certainty(); double c2 = e.brightHolder().certainty(); // ret avg(c1, c2); ret min(c1, c2); } double bestScore() { ret bestPixel.score(); } BWImage scoreImage() { double min = masksHolder.certainty(); double max = bestPixel.score(); ret bwImageFromFunction(w, h, (x, y) -> transformToZeroToOne(pixelScores[y*w+x], max, min)); } G22PixelSplitMasks splitMasksHolder() { if (!bestPixel.has()) null; ret new G22PixelSplitMasks() .splitPixel(bestPixel!) .darkHolder(bestEntry.darkHolder()) .brightHolder(bestEntry.brightHolder()); } IG22MasksHolder makeBestTree() { stepAll(this); var split = splitMasksHolder(); if (split == null) ret masksHolder; split.transformSubHolders(subHolder -> new G22CriticalPixelSearch(subHolder).makeBestTree()); ret split; } }