blob: a9aa066448e473147da6b4c0e6f6e23c7ecb08e9 [file] [log] [blame]
/*====================================================================*
- Copyright (C) 2001 Leptonica. All rights reserved.
- This software is distributed in the hope that it will be
- useful, but with NO WARRANTY OF ANY KIND.
- No author or distributor accepts responsibility to anyone for the
- consequences of using this software, or for whether it serves any
- particular purpose or works at all, unless he or she says so in
- writing. Everyone is granted permission to copy, modify and
- redistribute this source code, for commercial or non-commercial
- purposes, with the following restrictions: (1) the origin of this
- source code must not be misrepresented; (2) modified versions must
- be plainly marked as such; and (3) this notice may not be removed
- or altered from any source or modified source distribution.
*====================================================================*/
/*
* partition.c
*
* Whitespace block extraction
* BOXA *boxaGetWhiteblocks()
*
* Helpers
* static PARTEL *partelCreate()
* static void partelDestroy()
* static l_int32 partelSetSize()
* static BOXA *boxaGenerateSubboxes()
* static BOX *boxaSelectPivotBox()
* static l_int32 boxaCheckIfOverlapIsSmall()
* BOXA *boxaPruneSortedOnOverlap()
*/
#include <stdio.h>
#include <stdlib.h>
#include "allheaders.h"
struct PartitionElement {
l_float32 size; /* sorting key */
BOX *box; /* region of the element */
BOXA *boxa; /* set of intersecting boxes */
};
typedef struct PartitionElement PARTEL;
static PARTEL * partelCreate(BOX *box);
static void partelDestroy(PARTEL **ppartel);
static l_int32 partelSetSize(PARTEL *partel, l_int32 sortflag);
static BOXA * boxaGenerateSubboxes(BOX *box, BOXA *boxa, l_int32 maxperim,
l_float32 fract);
static BOX * boxaSelectPivotBox(BOX *box, BOXA *boxa, l_int32 maxperim,
l_float32 fract);
static l_int32 boxCheckIfOverlapIsBig(BOX *box, BOXA *boxa,
l_float32 maxoverlap);
static const l_int32 DEFAULT_MAX_POPS = 20000; /* a big number! */
#ifndef NO_CONSOLE_IO
#define OUTPUT_HEAP_STATS 0
#endif /* ~NO_CONSOLE_IO */
/*------------------------------------------------------------------*
* Whitespace block extraction *
*------------------------------------------------------------------*/
/*!
* boxaGetWhiteblocks()
*
* Input: boxas (typically, a set of bounding boxes of fg components)
* box (initial region; typically including all boxes in boxas;
* if null, it computes the region to include all boxes
* in boxas)
* sortflag (L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT,
* L_SORT_BY_MIN_DIMENSION, L_SORT_BY_MAX_DIMENSION,
* L_SORT_BY_PERIMETER, L_SORT_BY_AREA)
* maxboxes (maximum number of output whitespace boxes; e.g., 100)
* maxoverlap (maximum fractional overlap of a box by any
* of the larger boxes; e.g., 0.2)
* maxperim (maximum half-perimeter, in pixels, for which
* pivot is selected by proximity to box centroid;
* e.g., 200)
* fract (fraction of box diagonal that is an acceptable
* distance from the box centroid to select the pivot;
* e.g., 0.2)
* maxpops (maximum number of pops from the heap; use 0 as default)
* Return: boxa (of sorted whitespace boxes), or null on error
*
* Notes:
* (1) This uses the elegant Breuel algorithm, found in "Two
* Geometric Algorithms for Layout Analysis", 2002,
* url: "citeseer.ist.psu.edu/breuel02two.html".
* It starts with the bounding boxes (b.b.) of the connected
* components (c.c.) in a region, along with the rectangle
* representing that region. It repeatedly divides the
* rectangle into four maximal rectangles that exclude a
* pivot rectangle, sorting them in a priority queue
* according to one of the six sort flags. It returns a boxa
* of the "largest" set that have no intersection with boxes
* from the input boxas.
* (2) If box == NULL, the initial region is the minimal region
* that includes the origin and every box in boxas.
* (3) maxboxes is the maximum number of whitespace boxes that will
* be returned. The actual number will depend on the image
* and the values chosen for maxoverlap and maxpops. In many
* cases, the actual number will be 'maxboxes'.
* (4) maxoverlap allows pruning of whitespace boxes depending on
* the overlap. To avoid all pruning, use maxoverlap = 1.0.
* To select only boxes that have no overlap with each other
* (maximal pruning), choose maxoverlap = 0.0.
* Otherwise, no box can have more than the 'maxoverlap' fraction
* of its area overlapped by any larger (in the sense of the
* sortflag) box.
* (5) Choose maxperim (actually, maximum half-perimeter) to
* represent a c.c. that is small enough so that you don't care
* about the white space that could be inside of it. For all such
* c.c., the pivot for 'quadfurcation' of a rectangle is selected
* as having a reasonable proximity to the rectangle centroid.
* (6) Use fract in the range [0.0 ... 1.0]. Set fract = 0.0
* to choose the small box nearest the centroid as the pivot.
* If you choose fract > 0.0, it is suggested that you call
* boxaPermuteRandom() first, to permute the boxes (see usage below).
* This should reduce the search time for each of the pivot boxes.
* (7) Choose maxpops to be the maximum number of rectangles that
* are popped from the heap. This is an indirect way to limit the
* execution time. Use 0 for default (a fairly large number).
* At any time, you can expect the heap to contain about
* 2.5 times as many boxes as have been popped off.
* (8) The output result is a sorted set of overlapping
* boxes, constrained by 'maxboxes', 'maxoverlap' and 'maxpops'.
* (9) The main defect of the method is that it abstracts out the
* actual components, retaining only the b.b. for analysis.
* Consider a component with a large b.b. If this is chosen
* as a pivot, all white space inside is immediately taken
* out of consideration. Furthermore, even if it is never chosen
* as a pivot, as the partitioning continues, at no time will
* any of the whitespace inside this component be part of a
* rectangle with zero overlapping boxes. Thus, the interiors
* of all boxes are necessarily excluded from the union of
* the returned whitespace boxes.
* (10) USAGE: One way to accommodate to this weakness is to remove such
* large b.b. before starting the computation. For example,
* if 'box' is an input image region containing 'boxa' b.b. of c.c.:
*
* // Faster pivot choosing
* boxaPermuteRandom(boxa, boxa);
*
* // Remove anything either large width or height
* boxat = boxaSelectBySize(boxa, maxwidth, maxheight,
* L_SELECT_IF_BOTH, L_SELECT_IF_LT,
* NULL);
*
* boxad = boxaGetWhiteblocks(boxat, box, type, maxboxes,
* maxoverlap, maxperim, fract,
* maxpops);
*
* The result will be rectangular regions of "white space" that
* extend into (and often through) the excluded components.
* (11) As a simple example, suppose you wish to find the columns on a page.
* First exclude large c.c. that may block the columns, and then call:
*
* boxad = boxaGetWhiteblocks(boxa, box, L_SORT_BY_HEIGHT,
* 20, 0.15, 200, 0.2, 2000);
*
* to get the 20 tallest boxes with no more than 0.15 overlap
* between a box and any of the taller ones, and avoiding the
* use of any c.c. with a b.b. half perimeter greater than 200
* as a pivot.
*/
BOXA *
boxaGetWhiteblocks(BOXA *boxas,
BOX *box,
l_int32 sortflag,
l_int32 maxboxes,
l_float32 maxoverlap,
l_int32 maxperim,
l_float32 fract,
l_int32 maxpops)
{
l_int32 i, w, h, n, nsub, npush, npop;
BOX *boxsub;
BOXA *boxa, *boxa4, *boxasub, *boxad;
PARTEL *partel;
L_HEAP *lh;
PROCNAME("boxaGetWhiteblocks");
if (!boxas)
return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
if (sortflag != L_SORT_BY_WIDTH && sortflag != L_SORT_BY_HEIGHT &&
sortflag != L_SORT_BY_MIN_DIMENSION &&
sortflag != L_SORT_BY_MAX_DIMENSION &&
sortflag != L_SORT_BY_PERIMETER && sortflag != L_SORT_BY_AREA)
return (BOXA *)ERROR_PTR("invalid sort flag", procName, NULL);
if (maxboxes < 1) {
maxboxes = 1;
L_WARNING("setting maxboxes = 1", procName);
}
if (maxoverlap < 0.0 || maxoverlap > 1.0)
return (BOXA *)ERROR_PTR("invalid maxoverlap", procName, NULL);
if (maxpops == 0)
maxpops = DEFAULT_MAX_POPS;
if (!box) {
boxaGetExtent(boxas, &w, &h, NULL);
box = boxCreate(0, 0, w, h);
}
/* Prime the heap */
lh = lheapCreate(20, L_SORT_DECREASING);
partel = partelCreate(box);
partel->boxa = boxaCopy(boxas, L_CLONE);
partelSetSize(partel, sortflag);
lheapAdd(lh, partel);
boxad = boxaCreate(0);
npush = npop = 0;
while (1) {
if ((partel = (PARTEL *)lheapRemove(lh)) == NULL) /* we're done */
break;
npop++; /* How many boxes have we retrieved from the queue? */
if (npop > maxpops)
break;
/* Extract the contents */
boxa = boxaCopy(partel->boxa, L_CLONE);
box = boxClone(partel->box);
partelDestroy(&partel);
/* Can we output this one? */
n = boxaGetCount(boxa);
if (n == 0) {
if (boxCheckIfOverlapIsBig(box, boxad, maxoverlap) == 0)
boxaAddBox(boxad, box, L_INSERT);
else
boxDestroy(&box);
boxaDestroy(&boxa);
if (boxaGetCount(boxad) >= maxboxes) /* we're done */
break;
continue;
}
/* Generate up to 4 subboxes and put them on the heap */
boxa4 = boxaGenerateSubboxes(box, boxa, maxperim, fract);
boxDestroy(&box);
nsub = boxaGetCount(boxa4);
for (i = 0; i < nsub; i++) {
boxsub = boxaGetBox(boxa4, i, L_CLONE);
boxasub = boxaIntersectsBox(boxa, boxsub);
partel = partelCreate(boxsub);
partel->boxa = boxasub;
partelSetSize(partel, sortflag);
lheapAdd(lh, partel);
boxDestroy(&boxsub);
}
npush += nsub; /* How many boxes have we put on the queue? */
/* boxaWriteStream(stderr, boxa4); */
boxaDestroy(&boxa4);
boxaDestroy(&boxa);
}
#if OUTPUT_HEAP_STATS
fprintf(stderr, "Heap statistics:\n");
fprintf(stderr, " Number of boxes pushed: %d\n", npush);
fprintf(stderr, " Number of boxes popped: %d\n", npop);
#endif /* OUTPUT_HEAP_STATS */
/* Clean up the heap */
while ((partel = (PARTEL *)lheapRemove(lh)) != NULL)
partelDestroy(&partel);
lheapDestroy(&lh, FALSE);
return boxad;
}
/*------------------------------------------------------------------*
* Helpers *
*------------------------------------------------------------------*/
/*!
* partelCreate()
*
* Input: box (region; inserts a copy)
* Return: partel, or null on error
*/
static PARTEL *
partelCreate(BOX *box)
{
PARTEL *partel;
PROCNAME("partelCreate");
if ((partel = (PARTEL *)CALLOC(1, sizeof(PARTEL))) == NULL)
return (PARTEL *)ERROR_PTR("partel not made", procName, NULL);
partel->box = boxCopy(box);
return partel;
}
/*!
* partelDestroy()
*
* Input: &partel (<will be set to null before returning>)
* Return: void
*/
static void
partelDestroy(PARTEL **ppartel)
{
PARTEL *partel;
PROCNAME("partelDestroy");
if (ppartel == NULL) {
L_WARNING("ptr address is null!", procName);
return;
}
if ((partel = *ppartel) == NULL)
return;
boxDestroy(&partel->box);
boxaDestroy(&partel->boxa);
FREE(partel);
*ppartel = NULL;
return;
}
/*!
* partelSetSize()
*
* Input: partel
* sortflag (L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT,
* L_SORT_BY_MIN_DIMENSION, L_SORT_BY_MAX_DIMENSION,
* L_SORT_BY_PERIMETER, L_SORT_BY_AREA)
* Return: 0 if OK, 1 on error
*/
static l_int32
partelSetSize(PARTEL *partel,
l_int32 sortflag)
{
l_int32 w, h;
PROCNAME("partelSetSize");
if (!partel)
return ERROR_INT("partel not defined", procName, 1);
boxGetGeometry(partel->box, NULL, NULL, &w, &h);
if (sortflag == L_SORT_BY_WIDTH)
partel->size = (l_float32)w;
else if (sortflag == L_SORT_BY_HEIGHT)
partel->size = (l_float32)h;
else if (sortflag == L_SORT_BY_MIN_DIMENSION)
partel->size = (l_float32)L_MIN(w, h);
else if (sortflag == L_SORT_BY_MAX_DIMENSION)
partel->size = (l_float32)L_MAX(w, h);
else if (sortflag == L_SORT_BY_PERIMETER)
partel->size = (l_float32)(w + h);
else if (sortflag == L_SORT_BY_AREA)
partel->size = (l_float32)(w * h);
else
return ERROR_INT("invalid sortflag", procName, 1);
return 0;
}
/*!
* boxaGenerateSubboxes()
*
* Input: box (region to be split into up to four overlapping subregions)
* boxa (boxes of rectangles intersecting the box)
* maxperim (maximum half-perimeter for which pivot
* is selected by proximity to box centroid)
* fract (fraction of box diagonal that is an acceptable
* distance from the box centroid to select the pivot)
* Return: boxa (of four or less overlapping subrectangles of the box),
* or null on error
*/
static BOXA *
boxaGenerateSubboxes(BOX *box,
BOXA *boxa,
l_int32 maxperim,
l_float32 fract)
{
l_int32 x, y, w, h, xp, yp, wp, hp;
BOX *boxp; /* pivot box */
BOX *boxsub;
BOXA *boxa4;
PROCNAME("boxaGenerateSubboxes");
if (!box)
return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
if (!boxa)
return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
boxa4 = boxaCreate(4);
boxp = boxaSelectPivotBox(box, boxa, maxperim, fract);
boxGetGeometry(box, &x, &y, &w, &h);
boxGetGeometry(boxp, &xp, &yp, &wp, &hp);
boxDestroy(&boxp);
if (xp > x) { /* left sub-box */
boxsub = boxCreate(x, y, xp - x, h);
boxaAddBox(boxa4, boxsub, L_INSERT);
}
if (yp > y) { /* top sub-box */
boxsub = boxCreate(x, y, w, yp - y);
boxaAddBox(boxa4, boxsub, L_INSERT);
}
if (xp + wp < x + w) { /* right sub-box */
boxsub = boxCreate(xp + wp, y, x + w - xp - wp, h);
boxaAddBox(boxa4, boxsub, L_INSERT);
}
if (yp + hp < y + h) { /* bottom sub-box */
boxsub = boxCreate(x, yp + hp, w, y + h - yp - hp);
boxaAddBox(boxa4, boxsub, L_INSERT);
}
return boxa4;
}
/*!
* boxaSelectPivotBox()
*
* Input: box (containing box; to be split by the pivot box)
* boxa (boxes of rectangles, from which 1 is to be chosen)
* maxperim (maximum half-perimeter for which pivot
* is selected by proximity to box centroid)
* fract (fraction of box diagonal that is an acceptable
* distance from the box centroid to select the pivot)
* Return: box (pivot box for subdivision into 4 rectangles), or
* null on error
*
* Notes:
* (1) This is a tricky piece that wasn't discussed in the
* Breuel's 2002 paper.
* (2) Selects a box from boxa whose centroid is reasonably close to
* the centroid of the containing box (xc, yc) and whose
* half-perimeter does not exceed the maxperim value.
* (3) If there are no boxes in the boxa that are small enough,
* then it selects the smallest of the larger boxes,
* without reference to its location in the containing box.
* (4) If a small box has a centroid at a distance from the
* centroid of the containing box that is not more than
* the fraction 'fract' of the diagonal of the containing
* box, that box is chosen as the pivot, terminating the
* search for the nearest small box.
* (5) Use fract in the range [0.0 ... 1.0]. Set fract = 0.0
* to choose the small box nearest the centroid.
* (6) Choose maxperim to represent a connected component that is
* small enough so that you don't care about the white space
* that could be inside of it.
*/
static BOX *
boxaSelectPivotBox(BOX *box,
BOXA *boxa,
l_int32 maxperim,
l_float32 fract)
{
l_int32 i, n, cx, cy, bw, bh, x, y, w, h;
l_int32 smallfound, minindex, perim, minsize;
l_float32 delx, dely, mindist, threshdist, dist;
BOX *boxt;
PROCNAME("boxaSelectPivotBox");
if (!box)
return (BOX *)ERROR_PTR("box not defined", procName, NULL);
if (!boxa)
return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
n = boxaGetCount(boxa);
if (n == 0)
return (BOX *)ERROR_PTR("no boxes in boxa", procName, NULL);
if (fract < 0.0 || fract > 1.0) {
L_WARNING("fract out of bounds; using 0.0", procName);
fract = 0.0;
}
boxGetGeometry(box, NULL, NULL, &w, &h);
boxGetCentroid(box, &x, &y);
threshdist = fract * (w * w + h * h);
mindist = 1000000000.;
minindex = 0;
smallfound = FALSE;
for (i = 0; i < n; i++) {
boxt = boxaGetBox(boxa, i, L_CLONE);
boxGetGeometry(boxt, NULL, NULL, &bw, &bh);
boxGetCentroid(boxt, &cx, &cy);
boxDestroy(&boxt);
if (bw + bh > maxperim)
continue;
smallfound = TRUE;
delx = (l_float32)(cx - x);
dely = (l_float32)(cy - y);
dist = delx * delx + dely * dely;
if (dist <= threshdist)
return boxaGetBox(boxa, i, L_COPY);
if (dist < mindist) {
minindex = i;
mindist = dist;
}
}
/* If there are small boxes but none are within 'fract' of the
* centroid, return the nearest one. */
if (smallfound == TRUE)
return boxaGetBox(boxa, minindex, L_COPY);
/* No small boxes; return the smallest of the large boxes */
minsize = 1000000000;
minindex = 0;
for (i = 0; i < n; i++) {
boxaGetBoxGeometry(boxa, i, NULL, NULL, &bw, &bh);
perim = bw + bh;
if (perim < minsize) {
minsize = perim;
minindex = i;
}
}
return boxaGetBox(boxa, minindex, L_COPY);
}
/*!
* boxCheckIfOverlapIsBig()
*
* Input: box (to be tested)
* boxa (of boxes already stored)
* maxoverlap (maximum fractional overlap of the input box
* by any of the boxes in boxa)
* Return: 0 if box has small overlap with every box in boxa;
* 1 otherwise or on error
*/
static l_int32
boxCheckIfOverlapIsBig(BOX *box,
BOXA *boxa,
l_float32 maxoverlap)
{
l_int32 i, n, bigoverlap;
l_float32 fract;
BOX *boxt;
PROCNAME("boxCheckIfOverlapIsBig");
if (!box)
return ERROR_INT("box not defined", procName, 1);
if (!boxa)
return ERROR_INT("boxa not defined", procName, 1);
if (maxoverlap < 0.0 || maxoverlap > 1.0)
return ERROR_INT("invalid maxoverlap", procName, 1);
n = boxaGetCount(boxa);
if (n == 0 || maxoverlap == 1.0)
return 0;
bigoverlap = 0;
for (i = 0; i < n; i++) {
boxt = boxaGetBox(boxa, i, L_CLONE);
boxOverlapFraction(boxt, box, &fract);
boxDestroy(&boxt);
if (fract > maxoverlap) {
bigoverlap = 1;
break;
}
}
return bigoverlap;
}
/*!
* boxaPruneSortedOnOverlap()
*
* Input: boxas (sorted by size in decreasing order)
* maxoverlap (maximum fractional overlap of a box by any
* of the larger boxes)
* Return: boxad (pruned), or null on error
*
* Notes:
* (1) This selectively removes smaller boxes when they are overlapped
* by any larger box by more than the input 'maxoverlap' fraction.
* (2) To avoid all pruning, use maxoverlap = 1.0. To select only
* boxes that have no overlap with each other (maximal pruning),
* set maxoverlap = 0.0.
* (3) If there are no boxes in boxas, returns an empty boxa.
*/
BOXA *
boxaPruneSortedOnOverlap(BOXA *boxas,
l_float32 maxoverlap)
{
l_int32 i, j, n, remove;
l_float32 fract;
BOX *box1, *box2;
BOXA *boxad;
PROCNAME("boxaPruneSortedOnOverlap");
if (!boxas)
return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
if (maxoverlap < 0.0 || maxoverlap > 1.0)
return (BOXA *)ERROR_PTR("invalid maxoverlap", procName, NULL);
n = boxaGetCount(boxas);
if (n == 0 || maxoverlap == 1.0)
return boxaCopy(boxas, L_COPY);
boxad = boxaCreate(0);
box2 = boxaGetBox(boxas, 0, L_COPY);
boxaAddBox(boxad, box2, L_INSERT);
for (j = 1; j < n; j++) { /* prune on j */
box2 = boxaGetBox(boxas, j, L_COPY);
remove = FALSE;
for (i = 0; i < j; i++) { /* test on i */
box1 = boxaGetBox(boxas, i, L_CLONE);
boxOverlapFraction(box1, box2, &fract);
boxDestroy(&box1);
if (fract > maxoverlap) {
remove = TRUE;
break;
}
}
if (remove == TRUE)
boxDestroy(&box2);
else
boxaAddBox(boxad, box2, L_INSERT);
}
return boxad;
}