package org.bytedeco.javacv;
import static org.bytedeco.javacpp.opencv_core.*;
//***************************************************************//
//* Blob analysis package Version3.0 3 Oct 2012 *//
//* - Version 1.0: 8 Aug 2003 *//
//* - Version 1.2: 3 Jan 2008 *//
//* - Version 1.3: 5 Jan 2008 Add BLOBCOLOR *//
//* - Version 1.4: 13 January 2008 Add ROI function *//
//* - Version 1.5: 13 April 2008 Fix perimeter on Region 0 *//
//* - Version 1.6: 1 May 2008 Reduce size of working storage *//
//* - Version 1.7: 2 May 2008 Speed up run code initialization *//
//* - Version 1.8: 4 May 2008 Fix bugs in perimeter & Reg 0 *//
//* - Version 2.0: 3 Jan 2009 Add labeling functionality *//
//* - Version 3.0: 3 Oct 2012 Convert to Java *//
//* - Eliminate labeling functionality (but it's still there) *//
//* - Simplify (at slight expense of performance) *//
//* - Reduce to 4 connectivity *//
//* *//
//* Input: IplImage binary image *//
//* Output: attributes of each connected region *//
//* Internal data: labeled array (could easily be externalized) *//
//* Author: Dave Grossman *//
//* Email: dgrossman2@gmail.com *//
//* Acknowledgement: my code is based on an algorithm that was *//
//* to the best of my knowledge originally developed by Gerry *//
//* Agin of SRI around the year 1973. I have not been able to *//
//* find any published references to his earlier work. I posted *//
//* early versions of my program to OpenCV, where they morphed *//
//* eventually into cvBlobsLib. *//
//* *//
//* As the author of this code, I place all of this code into *//
//* the public domain. Users can use it for any legal purpose. *//
//* *//
//* - Dave Grossman *//
//* *//
//* Typical calling sequence: *//
//* Blobs Blob = new Blobs(); *//
//* Blob.BlobAnalysis( *//
//* image3, // image *//
//* -1, -1, // ROI start col, row (-1 means full image) *//
//* -1, -1, // ROI cols, rows *//
//* 0, // border (0 = black; 1 = white) *//
//* 20); // minarea *//
//* Blob.PrintRegionData(); *//
//* int BlobLabel = Blob.NextRegion( *//
//* -1, // parentcolor (-1 = ignore) *//
//* 0, // color (0 = black; 1 = white; -1 = ignore *//
//* 100, // minarea *//
//* 500, // maxarea *//
//* 15); // starting label (default 0) *//
//* *//
//* Ellipse properties can be derived from moments: *//
//* h = (XX + YY) / 2 *//
//* Major axis = h + sqrt ( h^2 - XX * YY + XY^2) *//
//* Minor axis = h - sqrt ( h^2 - XX * YY2 + XY^2) *//
//* Eccentricity = (sqrt(abs(XX - YY)) + 4 * XY)/AREA *//
//***************************************************************//
public class Blobs
{
// The following parameters should be configured by the user:
// On ScanSnap Manager, "Best" setting = 300dpi gray level
// jpg compression is set to minimum so that quality is highest
// Each page jpg image is then a little under 1 MB
static int BLOBROWCOUNT = 3500; // 11 inches * 8.5 inches standard page
static int BLOBCOLCOUNT = 2700; // with some added cushion to be safe
// Allow for vast number of blobs so there is no memory overrun
static int BLOBTOTALCOUNT = (BLOBROWCOUNT + BLOBCOLCOUNT) * 5;
//--------------------------------------------------------------
// Do not change anything below this line
public static int BLOBLABEL = 0;
public static int BLOBPARENT = 1;
public static int BLOBCOLOR = 2;
public static int BLOBAREA = 3;
public static int BLOBPERIMETER = 4;
public static int BLOBSUMX = 5;
public static int BLOBSUMY = 6;
public static int BLOBSUMXX = 7;
public static int BLOBSUMYY = 8;
public static int BLOBSUMXY = 9;
public static int BLOBMINX = 10;
public static int BLOBMAXX = 11;
public static int BLOBMINY = 12;
public static int BLOBMAXY = 13;
public static int BLOBDATACOUNT = 14;
public static int [][] LabelMat = new int [BLOBROWCOUNT][BLOBCOLCOUNT];
public static double [][] RegionData = new double [BLOBTOTALCOUNT][BLOBDATACOUNT];
public static int MaxLabel;
public int LabelA, LabelB, LabelC, LabelD;
public int ColorA, ColorB, ColorC, ColorD;
public int jrow, jcol; // index within ROI
public static int [] SubsumedLabel = new int [BLOBTOTALCOUNT];
public static int [] CondensationMap = new int [BLOBTOTALCOUNT];
// Print out all the data for all the regions (blobs)
public void PrintRegionData() { PrintRegionData(0, MaxLabel); }
public void PrintRegionData(int Label0, int Label1)
{
if(Label0 < 0) Label0 = 0;
if(Label1 > MaxLabel) Label1 = MaxLabel;
if(Label1 < Label0) return;
for(int Label = Label0; Label <= Label1; Label++)
{
double [] Property = RegionData[Label];
int ThisLabel = (int)Property[BLOBLABEL];
int ThisParent = (int)Property[BLOBPARENT];
int ThisColor = (int)Property[BLOBCOLOR];
double ThisArea = Property[BLOBAREA];
double ThisPerimeter = Property[BLOBPERIMETER];
double ThisSumX = Property[BLOBSUMX];
double ThisSumY = Property[BLOBSUMY];
double ThisSumXX = Property[BLOBSUMXX];
double ThisSumYY = Property[BLOBSUMYY];
double ThisSumXY = Property[BLOBSUMXY];
int ThisMinX = (int)Property[BLOBMINX];
int ThisMaxX = (int)Property[BLOBMAXX];
int ThisMinY = (int)Property[BLOBMINY];
int ThisMaxY = (int)Property[BLOBMAXY];
String Str1 = " " + Label + ": L[" + ThisLabel + "] P[" + ThisParent + "] C[" + ThisColor + "]";
String Str2 = " AP[" + ThisArea + ", " + ThisPerimeter + "]";
String Str3 = " M1[" + ThisSumX + ", " + ThisSumY + "] M2[" + ThisSumXX + ", " + ThisSumYY + ", " + ThisSumXY + "]";
String Str4 = " MINMAX[" + ThisMinX + ", " + ThisMaxX + ", " + ThisMinY + ", " + ThisMaxY + "]";
String Str = Str1 + Str2 + Str3 + Str4;
System.out.println(Str);
}
System.out.println();
}
// Determine the next (higher number) region that meets the desired conditions
public static int NextRegion(int Parent, int Color, double MinArea, double MaxArea, int Label)
{
double DParent = (double) Parent;
double DColor = (double) Color; if(DColor > 0) DColor = 1;
int i;
for(i = Label; i <= MaxLabel; i++)
{
double [] Region = RegionData[i];
double ThisParent = Region[BLOBPARENT];
double ThisColor = Region[BLOBCOLOR];
if(DParent >= 0 && DParent != ThisParent) continue;
if(DColor >= 0 && DColor != ThisColor) continue;
if(Region[BLOBAREA] < MinArea || Region[BLOBAREA] > MaxArea) continue;
break; // We have a match!
}
if(i > MaxLabel) i = -1; // Use -1 to flag that there was no match
return i;
}
// Determine the prior (lower number) region that meets the desired conditions
public static int PriorRegion(int Parent, int Color, double MinArea, double MaxArea, int Label)
{
double DParent = (double) Parent;
double DColor = (double) Color; if(DColor > 0) DColor = 1;
int i;
for(i = Label; i >= 0; i--)
{
double [] Region = RegionData[i];
double ThisParent = Region[BLOBPARENT];
double ThisColor = Region[BLOBCOLOR];
if(DParent >= 0 && DParent != ThisParent) continue;
if(DColor >= 0 && DColor != ThisColor) continue;
if(Region[BLOBAREA] < MinArea || Region[BLOBAREA] > MaxArea) continue;
break; // We have a match!
}
if(i < 0) i = -1; // Use -1 to flag that there was no match
return i;
}
public void ResetRegion(int Label)
{
double [] RegionD = RegionData[Label];
RegionD[BLOBLABEL] =
RegionD[BLOBPARENT] =
RegionD[BLOBCOLOR] =
RegionD[BLOBAREA] =
RegionD[BLOBPERIMETER] =
RegionD[BLOBSUMX] =
RegionD[BLOBSUMY] =
RegionD[BLOBSUMXX] =
RegionD[BLOBSUMYY] =
RegionD[BLOBSUMXY] =
RegionD[BLOBMINX] =
RegionD[BLOBMAXX] =
RegionD[BLOBMINY] =
RegionD[BLOBMAXY] = 0.0;
System.arraycopy(RegionD,0,RegionData[Label],0,BLOBDATACOUNT); // RegionData[Label] <- RegionD;
}
public void OldRegion(
int NewLabelD, // 3rd update this (may be the same as Label1 or Label2)
int Label1, // 1st increment this by 1
int Label2) // 2nd increment this by 1
{
int DeltaPerimeter = 0;
if(Label1 >= 0 && Label1 != NewLabelD)
{
DeltaPerimeter++;
double [] Region1 = RegionData[Label1];
Region1[BLOBPERIMETER]++;
System.arraycopy(Region1,0,RegionData[Label1],0,BLOBDATACOUNT); // RegionData[Label1] <- Region1;
}
if(Label2 >= 0 && Label2 != NewLabelD)
{
DeltaPerimeter++;
double [] Region2 = RegionData[Label2];
Region2[BLOBPERIMETER]++;
System.arraycopy(Region2,0,RegionData[Label2],0,BLOBDATACOUNT); // RegionData[Label2] <- Region2;
}
LabelD = NewLabelD;
double [] RegionD = RegionData[LabelD];
RegionD[BLOBLABEL] = LabelD;
RegionD[BLOBPARENT] += 0.0; // no change
RegionD[BLOBCOLOR] += 0.0; // no change
RegionD[BLOBAREA] += 1.0;
RegionD[BLOBPERIMETER] += DeltaPerimeter;
RegionD[BLOBSUMX] += jcol;
RegionD[BLOBSUMY] += jrow;
RegionD[BLOBSUMXX] += jcol*jcol;
RegionD[BLOBSUMYY] += jrow*jrow;
RegionD[BLOBSUMXY] += jcol*jrow;
RegionD[BLOBMINX] = Math.min(RegionD[BLOBMINX], jcol);
RegionD[BLOBMAXX] = Math.max(RegionD[BLOBMAXX], jcol);
RegionD[BLOBMINY] = Math.min(RegionD[BLOBMINY], jrow);
RegionD[BLOBMAXY] = Math.max(RegionD[BLOBMAXY], jrow);
System.arraycopy(RegionD,0,RegionData[LabelD],0,BLOBDATACOUNT); // RegionData[LabelD] <- RegionD;
}
public void NewRegion(int ParentLabel)
{
LabelD = ++MaxLabel;
double [] RegionD = RegionData[LabelD];
RegionD[BLOBLABEL] = LabelD;
RegionD[BLOBPARENT] = (double) ParentLabel;
RegionD[BLOBCOLOR] = ColorD;
RegionD[BLOBAREA] = 1.0;
RegionD[BLOBPERIMETER] = 2.0;
RegionD[BLOBSUMX] = jcol;
RegionD[BLOBSUMY] = jrow;
RegionD[BLOBSUMXX] = jcol*jcol;
RegionD[BLOBSUMYY] = jrow*jrow;
RegionD[BLOBSUMXY] = jcol*jrow;
RegionD[BLOBMINX] = jcol;
RegionD[BLOBMAXX] = jcol;
RegionD[BLOBMINY] = jrow;
RegionD[BLOBMAXY] = jrow;
System.arraycopy(RegionD,0,RegionData[LabelD],0,BLOBDATACOUNT); // RegionData[LabelD] <- RegionD;
SubsumedLabel[LabelD] = -1; // Flag label as not subsumed
double [] RegionB = RegionData[LabelB];
RegionB[BLOBPERIMETER]++;
System.arraycopy(RegionB,0,RegionData[LabelB],0,BLOBDATACOUNT); // RegionData[LabelB] <- RegionB;
double [] RegionC = RegionData[LabelC];
RegionC[BLOBPERIMETER]++;
System.arraycopy(RegionC,0,RegionData[LabelC],0,BLOBDATACOUNT); // RegionData[LabelC] <- RegionC;
}
public void Subsume(int GoodLabel, int BadLabel, int PSign) // Combine data with parent
{
LabelD = GoodLabel;
double [] GoodRegion = RegionData[GoodLabel];
double [] BadRegion = RegionData[BadLabel];
GoodRegion[BLOBLABEL] = GoodRegion[BLOBLABEL]; // no change
GoodRegion[BLOBPARENT] = GoodRegion[BLOBPARENT]; // no change
GoodRegion[BLOBCOLOR] = GoodRegion[BLOBCOLOR]; // no change
GoodRegion[BLOBAREA] += BadRegion[BLOBAREA];
GoodRegion[BLOBPERIMETER] += BadRegion[BLOBPERIMETER] * PSign; // + external or - internal perimeter
GoodRegion[BLOBSUMX] += BadRegion[BLOBSUMX];
GoodRegion[BLOBSUMY] += BadRegion[BLOBSUMY];
GoodRegion[BLOBSUMXX] += BadRegion[BLOBSUMXX];
GoodRegion[BLOBSUMYY] += BadRegion[BLOBSUMYY];
GoodRegion[BLOBSUMXY] += BadRegion[BLOBSUMXY];
GoodRegion[BLOBMINX] = Math.min(GoodRegion[BLOBMINX], BadRegion[BLOBMINX]);
GoodRegion[BLOBMAXX] = Math.max(GoodRegion[BLOBMAXX], BadRegion[BLOBMAXX]);
GoodRegion[BLOBMINY] = Math.min(GoodRegion[BLOBMINY], BadRegion[BLOBMINY]);
GoodRegion[BLOBMAXY] = Math.max(GoodRegion[BLOBMAXY], BadRegion[BLOBMAXY]);
System.arraycopy(GoodRegion,0,RegionData[GoodLabel],0,BLOBDATACOUNT); // RegionData[GoodLabel] <- GoodRegion;
}
public static int SubsumptionChain(int x) { return SubsumptionChain(x, 0); }
public static int SubsumptionChain(int x, int Print)
{
String Str = "";
if(Print > 0) Str = "Subsumption chain for " + x + ": ";
int Lastx = x;
while(x > -1)
{
Lastx = x;
if(Print > 0) Str += " " + x;
if(x == 0) break;
x = SubsumedLabel[x];
}
if(Print > 0) System.out.println(Str);
return Lastx;
}
//---------------------------------------------------------------------------------------
// Main blob analysis routine
//---------------------------------------------------------------------------------------
// RegionData[0] is the border. It has Property[BLOBPARENT] = 0.
public int BlobAnalysis(IplImage Src, // input image
int Col0, int Row0, // start of ROI
int Cols, int Rows, // size of ROI
int Border, // border color (0 = black; 1 = white)
int MinArea) // minimum region area
{
CvMat SrcMat = Src.asCvMat();
int SrcCols = SrcMat.cols();
int SrcRows = SrcMat.rows();
if(Col0 < 0) Col0 = 0;
if(Row0 < 0) Row0 = 0;
if(Cols < 0) Cols = SrcCols;
if(Rows < 0) Rows = SrcRows;
if(Col0 + Cols > SrcCols) Cols = SrcCols - Col0;
if(Row0 + Rows > SrcRows) Rows = SrcRows - Row0;
if(Cols > BLOBCOLCOUNT || Rows > BLOBROWCOUNT )
{
System.out.println("Error in Class Blobs: Image too large: Edit Blobs.java");
System.exit(666);
return 0;
}
// Initialization
int FillLabel = 0;
int FillColor = 0; if(Border > 0) { FillColor = 1; }
LabelA = LabelB = LabelC = LabelD = 0;
ColorA = ColorB = ColorC = ColorD = FillColor;
for(int k = 0; k < BLOBTOTALCOUNT; k++) SubsumedLabel[k] = -1;
// Initialize border region
MaxLabel = 0;
double [] BorderRegion = RegionData[0];
BorderRegion[BLOBLABEL] = 0.0;
BorderRegion[BLOBPARENT] = -1.0;
BorderRegion[BLOBAREA] = Rows + Cols + 4; // Top, left, and 4 corners
BorderRegion[BLOBCOLOR] = FillColor;
BorderRegion[BLOBSUMX] = 0.5 * ( (2.0 + Cols) * (Cols - 1.0) ) - Rows - 1 ;
BorderRegion[BLOBSUMY] = 0.5 * ( (2.0 + Rows) * (Rows - 1.0) ) - Cols - 1 ;
BorderRegion[BLOBMINX] = -1;
BorderRegion[BLOBMINY] = -1;
BorderRegion[BLOBMAXX] = Cols + 1.0;
BorderRegion[BLOBMAXY] = Rows + 1.0;
System.arraycopy(BorderRegion,0,RegionData[0],0,BLOBDATACOUNT); // RegionData[0] <- BorderRegion;
// The cells are identified this way
// Last |AB|
// This |CD|
//
// With 4 connectivity, there are 8 possibilities for the cells:
// No color transition Color transition
// Case 1 2 3 4 5 6 7 8
// Last Row |pp|pp|pq|pq| |pp|pp|pq|pq|
// This Row |pP|qQ|pP|qQ| |pQ|qP|pQ|qP|
//
// Region numbers are p, q, r, x; where p<>q
// Upper case letter is the current element at column=x row=y
// Color is 0 or 1 (1 stands for 255 in the actual image)
// Note that Case 4 is complicated because it joins two regions
//--------------------------
// Case 1: Colors A=B; C=D; A=C
// Case 2: Colors A=B; C=D; A<>C
// Case 3: Colors A<>B;C=D; A=C
// Case 4: Colors A<>B;C=D; A<>C
// Case 5: Colors A=B; C<>D; A=C
// Case 6: Colors A=B; C<>D; A<>C
// Case 7: Colors A<>B;C<>D; A=C
// Case 8: Colors A<>B;C<>D; A<>C
//--------------------------
// Loop over rows of ROI. irow = Row0 is 1st row of image; irow = Row0+Row is last row of image.
for(int irow = Row0; irow < Row0+Rows; irow++) // index within Src
{
jrow = irow - Row0; // index within ROI. 0 is first row. Rows is last row.
// Loop over columns of ROI.
for(int icol = Col0; icol < Col0+Cols; icol++) // index within Src
{
jcol = icol - Col0; // index within ROI
// initialize
ColorA = ColorB = ColorC = FillColor;
LabelA = LabelB = LabelC = LabelD = 0;
ColorD = (int) SrcMat.get(jrow,jcol); // fetch color of cell
if(jrow == 0 || jcol == 0) // first column or row
{
if(jcol > 0)
{
ColorC = (int) SrcMat.get(jrow,jcol-1);
LabelC = LabelMat[jrow][jcol-1];
}
if(jrow > 0)
{
ColorB = (int) SrcMat.get(jrow-1,jcol);
LabelB = LabelMat[jrow-1][jcol];
}
}
else
{
ColorA = (int) SrcMat.get(jrow-1,jcol-1); if(ColorA > 0) ColorA = 1;
ColorB = (int) SrcMat.get(jrow-1,jcol); if(ColorB > 0) ColorB = 1;
ColorC = (int) SrcMat.get(jrow,jcol-1); if(ColorC > 0) ColorC = 1;
LabelA = LabelMat[jrow-1][jcol-1];
LabelB = LabelMat[jrow-1][jcol];
LabelC = LabelMat[jrow][jcol-1];
}
if(ColorA > 0) ColorA = 1;
if(ColorB > 0) ColorB = 1;
if(ColorC > 0) ColorC = 1;
if(ColorD > 0) ColorD = 1;
// Determine Case
int Case = 0;
if(ColorA == ColorB)
{
if(ColorC == ColorD) { if(ColorA == ColorC) Case = 1; else Case = 2; }
else { if(ColorA == ColorC) Case = 5; else Case = 6; }
}
else
{
if(ColorC == ColorD) { if(ColorA == ColorC) Case = 3; else Case = 4; }
else { if(ColorA == ColorC) Case = 7; else Case = 8; }
}
// Take appropriate action
if(Case == 1) { OldRegion(LabelC, -1, -1); }
else if(Case == 2 || Case == 3) { OldRegion(LabelC, LabelB, LabelC); }
else if(Case == 5 || Case == 8) // Isolated
{
if((jrow == Rows || jcol == Cols) && ColorD == FillColor) { OldRegion(0, -1, -1); } // attached to border region 0
else NewRegion(LabelB);
}
else if(Case == 6 || Case == 7) { OldRegion(LabelB, LabelB, LabelC); }
else // Case 4 - The complicated situation
{
int LabelBRoot = SubsumptionChain(LabelB);
int LabelCRoot = SubsumptionChain(LabelC);
int LabelRoot = Math.min(LabelBRoot, LabelCRoot);
int LabelX;
if(LabelBRoot < LabelCRoot) { OldRegion(LabelB, -1, -1); LabelX = LabelC; }
else { OldRegion(LabelC, -1, -1); LabelX = LabelB; }
int NextLabelX = LabelX;
while(LabelRoot < LabelX)
{
NextLabelX = SubsumedLabel[LabelX];
SubsumedLabel[LabelX] = LabelRoot;
LabelX = NextLabelX;
}
}
// Last column or row. Final corner was handled earlier in Cases 5 and 8.
if((jrow == Rows || jcol == Cols) && ColorD == FillColor)
{
if(jcol < Cols) // bottom row
{
if(ColorC != FillColor) // Subsume B chain to border region 0
{
int LabelRoot = SubsumptionChain(LabelB);
SubsumedLabel[LabelRoot] = 0;
}
}
else if(jrow < Rows) // right column
{
if(ColorB != FillColor) // Subsume C chain to border region 0
{
int LabelRoot = SubsumptionChain(LabelC);
SubsumedLabel[LabelRoot] = 0;
}
}
OldRegion(0, -1, -1); // attached to border region 0
}
LabelMat[jrow][jcol] = LabelD;
}
}
// Compute Condensation map
int Offset = 0;
for(int Label = 1; Label <= MaxLabel; Label++)
{
if(SubsumedLabel[Label] > -1) Offset++;
CondensationMap[Label] = Label - Offset;
}
// Subsume regions that were flagged as connected; Perimeters add
for(int Label = 1; Label <= MaxLabel; Label++)
{
int BetterLabel = SubsumptionChain(Label);
if(BetterLabel != Label) Subsume(BetterLabel, Label, 1);
}
// Condense subsumed regions
int NewMaxLabel = 0;
for(int OldLabel = 1; OldLabel <= MaxLabel; OldLabel++)
{
if(SubsumedLabel[OldLabel] < 0) // Renumber valid regions only
{
double [] OldRegion = RegionData[OldLabel];
int OldParent = (int) OldRegion[BLOBPARENT];
int NewLabel = CondensationMap[OldLabel];
int NewParent = SubsumptionChain(OldParent);
NewParent = CondensationMap[NewParent];
OldRegion[BLOBLABEL] = (double) NewLabel;
OldRegion[BLOBPARENT] = (double) NewParent;
System.arraycopy(OldRegion,0,RegionData[NewLabel],0,BLOBDATACOUNT); //RegionData[NewLabel] <- ThisRegion;
NewMaxLabel = NewLabel;
}
}
// Zero out unneeded high labels
for(int Label = NewMaxLabel+1; Label <= MaxLabel; Label++) ResetRegion(Label);
MaxLabel = NewMaxLabel;
// Flag for subsumption regions that have too small area
for(int Label = MaxLabel; Label > 0; Label--)
{
double [] ThisRegion = RegionData[Label];
int ThisArea = (int) ThisRegion[BLOBAREA];
if(ThisArea < MinArea)
{
int ThisParent = (int) ThisRegion[BLOBPARENT];
SubsumedLabel[Label] = ThisParent; // Flag this label as having been subsumed
}
else SubsumedLabel[Label] = -1;
}
// Compute Condensation map
Offset = 0;
for(int Label = 1; Label <= MaxLabel; Label++)
{
if(SubsumedLabel[Label] > -1) Offset++;
CondensationMap[Label] = Label - Offset;
}
// Subsume regions that were flagged as enclosed; Perimeters subtract
for(int Label = 1; Label <= MaxLabel; Label++)
{
int BetterLabel = SubsumptionChain(Label);
if(BetterLabel != Label) Subsume(BetterLabel, Label, -1);
}
// Condense subsumed regions
for(int OldLabel = 1; OldLabel <= MaxLabel; OldLabel++)
{
if(SubsumedLabel[OldLabel] < 0) // Renumber valid regions only
{
double [] OldRegion = RegionData[OldLabel];
int OldParent = (int) OldRegion[BLOBPARENT];
int NewLabel = CondensationMap[OldLabel];
int NewParent = SubsumptionChain(OldParent);
NewParent = CondensationMap[NewParent];
OldRegion[BLOBLABEL] = (double) NewLabel;
OldRegion[BLOBPARENT] = (double) NewParent;
System.arraycopy(OldRegion,0,RegionData[NewLabel],0,BLOBDATACOUNT); //RegionData[NewLabel] <- ThisRegion;
NewMaxLabel = NewLabel;
}
}
// Zero out unneeded high labels
for(int Label = NewMaxLabel+1; Label <= MaxLabel; Label++) ResetRegion(Label);
MaxLabel = NewMaxLabel;
// Normalize summation fields into moments
for(int Label = 0; Label <= MaxLabel; Label++)
{
double [] ThisRegion = RegionData[Label];
// Extract fields
double Area = ThisRegion[BLOBAREA];
double SumX = ThisRegion[BLOBSUMX];
double SumY = ThisRegion[BLOBSUMY];
double SumXX = ThisRegion[BLOBSUMXX];
double SumYY = ThisRegion[BLOBSUMYY];
double SumXY = ThisRegion[BLOBSUMXY];
// Get averages
SumX /= Area;
SumY /= Area;
SumXX /= Area;
SumYY /= Area;
SumXY /= Area;
// Create moments
SumXX -= SumX * SumX;
SumYY -= SumY * SumY;
SumXY -= SumX * SumY;
if(SumXY > -1.0E-14 && SumXY < 1.0E-14) SumXY = (float) 0.0; // Eliminate roundoff error
ThisRegion[BLOBSUMX] = SumX;
ThisRegion[BLOBSUMY] = SumY;
ThisRegion[BLOBSUMXX] = SumXX;
ThisRegion[BLOBSUMYY] = SumYY;
ThisRegion[BLOBSUMXY] = SumXY;
System.arraycopy(ThisRegion,0,RegionData[Label],0,BLOBDATACOUNT); // RegionData[Label] <- ThisRegion;
}
// Adjust border region
BorderRegion = RegionData[0];
BorderRegion[BLOBSUMXX] = BorderRegion[BLOBSUMYY] = BorderRegion[BLOBSUMXY] = 0; // Mark invalid fields
System.arraycopy(BorderRegion,0,RegionData[0],0,BLOBDATACOUNT); // RegionData[0] <- BorderRegion;
return MaxLabel;
}
// Sort RegionData array on any column. (I couldn't figure out how to use the built-in java sort.)
static double iField, jField;
static double [] iProperty, jProperty;
public static void SortRegions(int Col)
{
for(int i = 0; i < MaxLabel; i++)
{
for(int j = i+1; j <= Blobs.MaxLabel; j++)
{
iProperty = RegionData[i];
jProperty = RegionData[j];
iField = iProperty[Col];
jField = jProperty[Col];
if(iField > jField)
{
RegionData[i] = jProperty;
RegionData[j] = iProperty;
}
}
}
}
}