package Algorithms; import Utils.IntVector; import java.util.Arrays; public class Particles { /* struct Shape { int areaAndColor; int firstPointOrReplacement; int pointMinX; int pointMinY; int pointMaxX; int pointMaxY; int totalBrightnessHigh; int totalBrightnessLow; }; */ public static final int N_SHAPE_MEMBERS = 8; public static class Data { public IntVector shapes = new IntVector(); public IntVector spanIndex = new IntVector(); } public static void computeShapes(Data data, int[] regions, byte[] image, int[] originalRgb, int width, int height) { data.shapes.size = 0; data.spanIndex.size = 0; IntVector spans = data.spanIndex; int area = width * height; Arrays.fill(regions, 0, area, 0); int startColor = image[0]; spans.addTwo(0, 0); for (int y = 0; y < height; y++) { int prev = image[y*width]; for (int x = 0; x < width; x++) { int color = image[x+width*y]; if ((color ^ prev) < 0) spans.addTwo(spans.size + 2, 0); int idx = spans.size - 2; if (y > 0 && (image[x+width*(y-1)] ^ color) >= 0) { int aboveIdx = regions[x+width*(y-1)]; if (spans.buf[idx] > spans.buf[aboveIdx]) spans.buf[idx] = spans.buf[aboveIdx]; else if (spans.buf[idx] < spans.buf[aboveIdx]) spans.buf[aboveIdx] = spans.buf[idx]; } regions[x+width*y] = idx; prev = color; } spans.addTwo(spans.size + 2, 0); } spans.resize(spans.size - 2); int start = 0; int end = height-1; int dir = 1; boolean anyCaptures; do { anyCaptures = false; start ^= height-1; end ^= height-1; dir *= -1; for (int y = start; y != end; y += dir) { for (int x = 0; x < width; x++) { int idx = regions[x+width*y]; if ((image[x+width*y] ^ image[x+width*(y+dir)]) >= 0) { int nextIdx = regions[x+width*(y+dir)]; if (spans.buf[idx] > spans.buf[nextIdx]) { spans.buf[idx] = spans.buf[nextIdx]; anyCaptures = true; } else if (spans.buf[idx] < spans.buf[nextIdx]) { spans.buf[nextIdx] = spans.buf[idx]; anyCaptures = true; } } } } } while (anyCaptures); int nUnique = 0; for (int i = 0; i < spans.size; i += 2) { int startingSpan = spans.buf[i]; if (spans.buf[startingSpan+1] == 0) spans.buf[startingSpan+1] = ++nUnique; } data.shapes.resize(nUnique * N_SHAPE_MEMBERS); Arrays.fill(data.shapes.buf, 0, data.shapes.size, 0); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int pos = x + width * y; int r = spans.buf[spans.buf[regions[pos]] + 1]; int idx = N_SHAPE_MEMBERS * (r-1); // TODO: Use image index instead of x,y pair for min x, min y, max x, max y if (data.shapes.buf[idx] == 0) { data.shapes.buf[idx + 1] = pos; // firstPoint data.shapes.buf[idx + 2] = pos; data.shapes.buf[idx + 3] = pos; } data.shapes.buf[idx] = (data.shapes.buf[idx] | ((int)image[pos] & 0x80000000)) + 1; // areaAndColor if (x < (data.shapes.buf[idx + 2] % width)) data.shapes.buf[idx + 2] = pos; if (y < (data.shapes.buf[idx + 3] / width)) data.shapes.buf[idx + 3] = pos; if (x > (data.shapes.buf[idx + 4] % width)) data.shapes.buf[idx + 4] = pos; if (y > (data.shapes.buf[idx + 5] / width)) data.shapes.buf[idx + 5] = pos; int rgb = originalRgb[x + width * y]; int lum = ((rgb >> 16) & 0xff) + ((rgb >> 8) & 0xff) + (rgb & 0xff); long totalBrightness = (long)data.shapes.buf[idx + 6] << 32L | (data.shapes.buf[idx + 7] & 0xFFFFffffL); totalBrightness += lum; data.shapes.buf[idx + 6] = (int)(totalBrightness >> 32L); data.shapes.buf[idx + 7] = (int)(totalBrightness & 0xFFFFffffL); regions[pos] = r; } } } public static void removeVesselVoids( Data data, int[] regions, byte[] image, int width, int height, double maxHoleLevel, double minBoxness, double minAreaLengthRatio ) { double averageBrightness = 0.0; double averageWhiteShapeBrightness = 0.0; if (maxHoleLevel > 0.0) { int nFoundShapes = 0; int nWhiteShapes = 0; for (int i = 0; i < data.shapes.size; i += N_SHAPE_MEMBERS) { int shapeArea = data.shapes.buf[i]; double area = (double)(shapeArea & 0x7fffFFFF); if (area <= 0.0) continue; long totalLum = (long)data.shapes.buf[i + 6] << 32L | (data.shapes.buf[i + 7] & 0xFFFFffffL); double avg = (double)totalLum / area; averageBrightness += avg; nFoundShapes++; if (shapeArea < 0) { averageWhiteShapeBrightness += avg; nWhiteShapes++; } } if (nFoundShapes > 0) averageBrightness /= nFoundShapes; if (nWhiteShapes > 0) averageWhiteShapeBrightness /= nWhiteShapes; } for (int i = 0; i < data.shapes.size; i += N_SHAPE_MEMBERS) { // if this shape is white, then skip int shapeArea = data.shapes.buf[i]; if (shapeArea <= 0) continue; // decide if shape should be filled with white boolean shouldFill = false; if (maxHoleLevel > 0.0) { long totalLum = (long)data.shapes.buf[i + 6] << 32L | (data.shapes.buf[i + 7] & 0xFFFFffffL); double avg = (double)totalLum / (double)shapeArea; if (avg >= maxHoleLevel * averageWhiteShapeBrightness) shouldFill = true; } if (!shouldFill && minBoxness > 0.0) { int xMin = data.shapes.buf[i+2] % width; int yMin = data.shapes.buf[i+3] / width; int xMax = data.shapes.buf[i+4] % width; int yMax = data.shapes.buf[i+5] / width; int shapeW = (xMax - xMin + 1); int shapeH = (yMax - yMin + 1); double occupied = (double)shapeArea / (double)(shapeW * shapeH); double sdr = shapeW > shapeH ? (double)shapeH / (double)shapeW : (double)shapeW / (double)shapeH; double boxness = occupied * sdr; shouldFill = boxness < minBoxness; // <= 0.09375; } if (!shouldFill && minAreaLengthRatio > 0.0) { double highestDistance = 0.0; for (int a = 2; a <= 4; a++) { int xa = data.shapes.buf[i+a] % width; int ya = data.shapes.buf[i+a] / width; for (int b = a+1; b <= 5; b++) { int xb = data.shapes.buf[i+b] % width; int yb = data.shapes.buf[i+b] / width; double dx = xa - xb; double dy = ya - yb; highestDistance = Math.max(highestDistance, Math.sqrt(dx*dx + dy*dy)); } } shouldFill = (shapeArea / highestDistance) < minAreaLengthRatio; // <= 16.0; } if (shouldFill) { int firstPoint = data.shapes.buf[i + 1]; int neighbor; if ((firstPoint % width) > 0) neighbor = regions[firstPoint-1]; else if (firstPoint >= width) neighbor = regions[firstPoint-width]; else neighbor = 2; data.shapes.buf[i + 1] = -neighbor; } } int nShapes = data.shapes.size / N_SHAPE_MEMBERS; for (int i = 0; i < width * height; i++) { int s = N_SHAPE_MEMBERS * (regions[i] - 1); if (s >= 0 && data.shapes.buf[s + 1] < 0) { regions[i] = -data.shapes.buf[s + 1]; image[i] = -1; } } } public static void fillShapes(Data data, int[] regions, byte[] image, int width, int height, double maxSize, boolean lookingForWhite) { int maxPixelCount = (int)(3.14159 * maxSize * maxSize); int colorToMatch = lookingForWhite ? -1 : 0; int area = width * height; for (int i = 0; i < area; i++) { int idx = N_SHAPE_MEMBERS * (Math.abs(regions[i]) - 1); if (idx < 0) continue; int shapeArea = data.shapes.buf[idx] & 0x7fffFFFF; if (shapeArea <= maxPixelCount && (image[i] ^ colorToMatch) >= 0) image[i] = (byte)(~colorToMatch); } } }