/* Parse ".desc" files from mosaiced images to generate (x,y) offsets for * every sub-image. Find all overlap stats and solve balancing with LMS. * Regenerate mosaic, with balancing fixed. * * 1/12/93 JC * - first version, unfinished! * 6/9/95 JC * - LMS fixed, now works, more or less * 12/9/95 JC * - now does positions correctly too * - ignores trivial overlaps * 19/9/95 JC * - prints correct number of balance factors! * 10/11/95 JC * - now tracks im_copy() calls too, so you can save sub-images * 12/1/96 JC * - slightly clearer diagnostics * - better centre of factors around 1.0 with log() average * 1/3/96 JC * - new im_global_balance_float variant lets our caller adjust factor * range if output has burn-out * - im_global_balance_search uses the above to produce scaled output ... * very slow! * 11/3/96 JC * - now tries current directory too for input files * 22/3/96 JC * - horrible bug in position finding! now fixed * 1/8/97 JC * - revised for new mosaic functions and non-square images * 12/9/97 JC * - code for im_lrmosaic1() support * - output type == input type, so works for short images too * 6/1/99 JC * - new gamma parameter, do scale in linear space * - removed _search version, as can now be done with ip * - renamed _float to f suffix, in line with im_conv()/im_convf() * 15/2/00 JC * - balancef() did not scale in linear space * 2/2/01 JC * - added tunable max blend width * 7/11/01 JC * - global_balance.h broken out for im_remosaic() * 25/02/02 JC * - better transform function scheme * 21/3/01 JC * - quicker bailout on error * 8/11/02 JC * - add <> around file names so you can have spaces :( * 9/12/02 JC * - track original params and always reuse them ... makes us proof * against geo reconstruct errors * 10/3/03 JC * - weed out overlaps which contain only transparent pixels * 4/1/07 * - switch to new history thing, switch im_errormsg() too */ /* This file is part of VIPS. VIPS is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk */ /* Strategy: build a tree describing the file * relationships in the desc file, then walk that passing constraints * back up to the root. Look up file names in symbol_table. */ /* Define for debug output. #define DEBUG */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include #include #include "transform.h" #include "merge.h" #include "global_balance.h" #ifdef WITH_DMALLOC #include #endif /*WITH_DMALLOC*/ #define MAX_ITEMS (50) /* How pix an overlap has to be (in pixels) before we think it's trivial and * we ignore it. */ #define TRIVIAL (20 * 20) /* Break a string into a list of strings. Write '\0's into the string. out * needs to be MAX_FILES long. -1 for error, otherwise number of args found. " " out[0] = "fred" out[1] = "jim poop" out[2] = "sn aff le" */ static int break_items( char *line, char **out ) { int i; char *p; for( i = 0; i < MAX_ITEMS; i++ ) { /* Skip to first '<'. */ if( !(p = strchr( line, '<' )) ) break; out[i] = line = p + 1; if( !(p = strchr( line, '>' )) ) { im_error( "break_files", "%s", _( "no matching '>'" ) ); return( -1 ); } *p = '\0'; line = p + 1; } if( i == MAX_ITEMS ) { im_error( "break_files", "%s", _( "too many items" ) ); return( -1 ); } return( i ); } /* Try to open a file. If full path fails, try the current directory. */ IMAGE * im__global_open_image( SymbolTable *st, char *name ) { IMAGE *im; if( (im = im_open_local( st->im, name, "r" )) ) return( im ); if( (im = im_open_local( st->im, im_skip_dir( name ), "r" )) ) return( im ); return( NULL ); } static int junk_node( JoinNode *node ) { IM_FREEF( g_slist_free, node->overlaps ); return( 0 ); } /* Hash from a filename to an index into symbol_table. */ static int hash( char *n ) { int i; int r = 0; int l = strlen( n ); for( i = 0; i < l; i++ ) r = ((r + n[i]) * 43) & 0xffffff; return( r % SYM_TAB_SIZE ); } /* Make a leaf for a file. */ static JoinNode * build_node( SymbolTable *st, char *name ) { JoinNode *node = IM_NEW( st->im, JoinNode ); int n = hash( name ); /* Fill fields. */ if( !node || !(node->name = im_strdup( st->im, name )) ) return( NULL ); node->type = JOIN_LEAF; node->dirty = 0; node->mwidth = -2; node->st = st; im__transform_init( &node->cumtrn ); node->trnim = NULL; node->arg1 = NULL; node->arg2 = NULL; node->overlaps = NULL; node->im = NULL; node->index = 0; if( im_add_close_callback( st->im, (im_callback_fn) junk_node, node, NULL ) ) return( NULL ); /* Try to open. */ if( (node->im = im__global_open_image( st, name )) ) { /* There is a file there - set width and height. */ node->cumtrn.oarea.width = node->im->Xsize; node->cumtrn.oarea.height = node->im->Ysize; } else { /* Clear the error buffer to lessen confusion. */ im_error_clear(); } st->table[n] = g_slist_prepend( st->table[n], node ); return( node ); } /* Make a new overlap struct. */ static OverlapInfo * build_overlap( JoinNode *node, JoinNode *other, Rect *overlap ) { OverlapInfo *lap = IM_NEW( node->st->im, OverlapInfo ); if( !lap ) return( NULL ); lap->node = node; lap->other = other; lap->overlap = *overlap; lap->nstats = NULL; lap->ostats = NULL; node->overlaps = g_slist_prepend( node->overlaps, lap ); node->st->novl++; return( lap ); } static void overlap_destroy( OverlapInfo *lap ) { JoinNode *node = lap->node; node->overlaps = g_slist_remove( node->overlaps, lap ); assert( node->st->novl > 0 ); node->st->novl--; } static int junk_table( SymbolTable *st ) { int i; for( i = 0; i < st->sz; i++ ) IM_FREEF( g_slist_free, st->table[i] ); return( 0 ); } /* Build a new symbol table. */ SymbolTable * im__build_symtab( IMAGE *out, int sz ) { SymbolTable *st = IM_NEW( out, SymbolTable ); int i; if( !st ) return( NULL ); if( !(st->table = IM_ARRAY( out, sz, GSList * )) ) return( NULL ); st->sz = sz; st->im = out; st->novl = 0; st->nim = 0; st->njoin = 0; st->root = NULL; st->leaf = NULL; st->fac = NULL; if( im_add_close_callback( out, (im_callback_fn) junk_table, st, NULL ) ) return( NULL ); for( i = 0; i < sz; i++ ) st->table[i] = NULL; return( st ); } /* Does this node have this file name? */ static JoinNode * test_name( JoinNode *node, char *name ) { if( strcmp( node->name, name ) == 0 ) return( node ); else return( NULL ); } /* Look up a filename in the symbol_table. */ static JoinNode * find_node( SymbolTable *st, char *name ) { return( im_slist_map2( st->table[hash( name )], (VSListMap2Fn) test_name, name, NULL ) ); } /* Given a name: return either the existing node for that name, or a new node * we have made. */ static JoinNode * add_node( SymbolTable *st, char *name ) { JoinNode *node; if( !(node = find_node( st, name )) && !(node = build_node( st, name )) ) return( NULL ); return( node ); } /* Map a user function over the whole of the symbol table. */ void * im__map_table( SymbolTable *st, void *(*fn)(), void *a, void *b ) { int i; void *r; for( i = 0; i < st->sz; i++ ) if( (r = im_slist_map2( st->table[i], (VSListMap2Fn) fn, a, b )) ) return( r ); return( NULL ); } /* Set the dirty field on a join. */ static void * set_dirty( JoinNode *node, int state ) { node->dirty = state; return( NULL ); } /* Clean the whole table. */ static void clean_table( SymbolTable *st ) { (void) im__map_table( st, set_dirty, (void *) 0, NULL ); } /* Do geometry calculations on a node, assuming geo is up to date for any * children. */ static void calc_geometry( JoinNode *node ) { Rect um; switch( node->type ) { case JOIN_LR: case JOIN_TB: case JOIN_LRROTSCALE: case JOIN_TBROTSCALE: /* Join two areas. */ im_rect_unionrect( &node->arg1->cumtrn.oarea, &node->arg2->cumtrn.oarea, &um ); node->cumtrn.iarea.left = 0; node->cumtrn.iarea.top = 0; node->cumtrn.iarea.width = um.width; node->cumtrn.iarea.height = um.height; im__transform_set_area( &node->cumtrn ); break; case JOIN_CP: /* Copy from child. */ node->cumtrn = node->arg1->cumtrn; break; case JOIN_LEAF: /* Just use leaf dimensions, if there are any. */ if( node->im ) { node->cumtrn.iarea.left = 0; node->cumtrn.iarea.top = 0; node->cumtrn.iarea.width = node->im->Xsize; node->cumtrn.iarea.height = node->im->Ysize; im__transform_set_area( &node->cumtrn ); } break; default: error_exit( "internal error #98356" ); /*NOTREACHED*/ } } /* Propogate a transform down a tree. If dirty is set, we've been here before, * so there is a doubling up of this node. If this is a leaf, then we have the * same leaf twice (which, in fact, we can cope with); if this is a node, we * have circularity. */ static int propogate_transform( JoinNode *node, Transformation *trn ) { if( !node ) return( 0 ); if( node->dirty && node->arg1 && node->arg2 ) { im_error( "im_global_balance", "%s", _( "circularity detected" ) ); return( -1 ); } node->dirty = 1; /* Transform our children. */ if( propogate_transform( node->arg1, trn ) || propogate_transform( node->arg2, trn ) ) return( -1 ); /* Transform us, and recalculate our position and size. */ im__transform_add( &node->cumtrn, trn, &node->cumtrn ); calc_geometry( node ); return( 0 ); } /* Ah ha! A leaf is actually made up of two smaller files with an lr or a tb * merge. Turn a leaf node into a join node. Propogate the transform down * arg2's side of the tree. */ static int make_join( SymbolTable *st, JoinType type, JoinNode *arg1, JoinNode *arg2, JoinNode *out, double a, double b, double dx, double dy, int mwidth ) { Transformation trn; /* Check output is ok. */ if( out->type != JOIN_LEAF ) { im_error( "im_global_balance", _( "image \"%s\" used twice as output" ), out->name ); return( -1 ); } /* Fill fields. */ out->type = type; out->mwidth = mwidth; out->a = a; out->b = b; out->dx = dx; out->dy = dy; out->arg1 = arg1; out->arg2 = arg2; out->thistrn.a = a; out->thistrn.b = -b; out->thistrn.c = b; out->thistrn.d = a; out->thistrn.dx = dx; out->thistrn.dy = dy; /* Clean the table and propogate the transform down the RHS of the * graph. */ clean_table( st ); if( propogate_transform( arg2, &out->thistrn ) ) return( -1 ); /* Find the position and size of our output. */ calc_geometry( out ); /* Now normalise the result, so that out is at (0,0) again. */ trn.a = 1.0; trn.b = 0.0; trn.c = 0.0; trn.d = 1.0; trn.dx = -out->cumtrn.oarea.left; trn.dy = -out->cumtrn.oarea.top; clean_table( st ); if( propogate_transform( out, &trn ) ) return( -1 ); return( 0 ); } /* Make a copy node. */ static int make_copy( SymbolTable *st, JoinNode *before, JoinNode *after ) { /* Check output is ok. */ if( after->type != JOIN_LEAF ) { im_error( "im_global_balance", _( "image \"%s\" used twice as output" ), after->name ); return( -1 ); } /* Fill fields. */ after->type = JOIN_CP; after->arg1 = before; after->arg2 = NULL; /* Copy over the position and size from the before to the after. */ calc_geometry( after ); return( 0 ); } /* Process a single .desc line. */ static int process_line( SymbolTable *st, const char *text ) { char line[1024]; #ifdef DEBUG printf( "read: %s\n", text ); #endif /*DEBUG*/ /* We destroy line during the parse. */ im_strncpy( line, text, 1024 ); if( im_isprefix( "#LRJOIN ", line ) || im_isprefix( "#TBJOIN ", line ) ) { /* Yes: magic join command. Break into tokens. Format is eg. #LRJOIN [] */ char *item[MAX_ITEMS]; int nitems; JoinType type; JoinNode *arg1, *arg2, *join; int dx, dy, mwidth; if( (nitems = break_items( line, item )) < 0 ) return( -1 ); if( nitems != 5 && nitems != 6 ) { im_error( "global_balance", "%s", _( "bad number of args in join line" ) ); return( -1 ); } if( !(arg1 = add_node( st, item[0] )) || !(arg2 = add_node( st, item[1] )) || !(join = add_node( st, item[2] )) ) return( -1 ); dx = atoi( item[3] ); dy = atoi( item[4] ); if( nitems == 6 ) mwidth = atoi( item[5] ); else mwidth = -1; if( im_isprefix( "#LRJOIN ", line ) ) type = JOIN_LR; else type = JOIN_TB; if( make_join( st, type, arg1, arg2, join, 1.0, 0.0, dx, dy, mwidth ) ) return( -1 ); } else if( im_isprefix( "#LRROTSCALE ", line ) || im_isprefix( "#TBROTSCALE ", line ) ) { /* Rot + scale. Format is eg. #LRROTSCALE \ [] */ char *item[MAX_ITEMS]; int nitems; JoinType type; JoinNode *arg1, *arg2, *join; double a, b, dx, dy; int mwidth; if( (nitems = break_items( line, item )) < 0 ) return( -1 ); if( nitems != 7 && nitems != 8 ) { im_error( "global_balance", "%s", _( "bad number of args in join1 line" ) ); return( -1 ); } if( !(arg1 = add_node( st, item[0] )) || !(arg2 = add_node( st, item[1] )) || !(join = add_node( st, item[2] )) ) return( -1 ); a = g_ascii_strtod( item[3], NULL ); b = g_ascii_strtod( item[4], NULL ); dx = g_ascii_strtod( item[5], NULL ); dy = g_ascii_strtod( item[6], NULL ); if( nitems == 8 ) mwidth = atoi( item[7] ); else mwidth = -1; if( im_isprefix( "#LRROTSCALE ", line ) ) type = JOIN_LRROTSCALE; else type = JOIN_TBROTSCALE; if( make_join( st, type, arg1, arg2, join, a, b, dx, dy, mwidth ) ) return( -1 ); } else if( im_isprefix( "copy ", line ) ) { /* im_copy() call ... make a JOIN_CP node. */ char *item[MAX_ITEMS]; int nitems; JoinNode *before, *after; if( (nitems = break_items( line, item )) < 0 ) return( -1 ); if( nitems != 2 ) { im_error( "global_balance", "%s", _( "bad number of args in copy line" ) ); return( -1 ); } if( !(before = add_node( st, item[0] )) || !(after = add_node( st, item[1] )) || make_copy( st, before, after ) ) return( -1 ); } return( 0 ); } /* Set the dirty flag on any nodes we reference. */ static void * set_referenced( JoinNode *node ) { if( node->arg1 ) node->arg1->dirty = 1; if( node->arg2 ) node->arg2->dirty = 1; return( NULL ); } /* Is this a root node? Should be clean. */ static void * is_root( JoinNode *node ) { if( !node->dirty ) return( (void *) node ); else return( NULL ); } /* Scan the symbol table, looking for a node which no node references. */ static JoinNode * find_root( SymbolTable *st ) { JoinNode *root; JoinNode *notroot; /* Clean the table, then scan it, setting all pointed-to nodes dirty. */ clean_table( st ); im__map_table( st, set_referenced, NULL, NULL ); /* Look for the first clean symbol. */ root = (JoinNode *) im__map_table( st, is_root, NULL, NULL ); /* No root? Hot dang! */ if( !root ) { im_error( "im_global_balance", "%s", _( "mosaic root not found in desc file\n" "is this really a mosaiced image?" ) ); return( NULL ); } /* Now dirty that - then if there are any more clean symbols, we have * more than one root. */ root->dirty = 1; if( (notroot = im__map_table( st, is_root, NULL, NULL )) ) { im_error( "im_global_balance", "%s", _( "more than one root" ) ); return( NULL ); } return( root ); } /* Walk history_list and parse each line. */ int im__parse_desc( SymbolTable *st, IMAGE *in ) { GSList *p; for( p = in->history_list; p; p = p->next ) { GValue *value = (GValue *) p->data; assert( G_VALUE_TYPE( value ) == IM_TYPE_REF_STRING ); if( process_line( st, im_ref_string_get( value ) ) ) return( -1 ); } /* Find root. */ if( !(st->root = find_root( st )) ) return( -1 ); return( 0 ); } /* Count and index all leaf images. */ static void * count_leaves( JoinNode *node ) { if( node->type == JOIN_LEAF ) { node->index = node->st->nim; node->st->nim++; } return( NULL ); } #ifdef DEBUG /* Print a JoinNode. */ static void print_node( JoinNode *node ) { printf( "%s, position %dx%d, size %dx%d, index %d\n", im_skip_dir( node->name ), node->cumtrn.oarea.left, node->cumtrn.oarea.top, node->cumtrn.oarea.width, node->cumtrn.oarea.height, node->index ); } #endif /*DEBUG*/ #ifdef DEBUG /* Print a leaf. */ static void * print_leaf( JoinNode *node ) { if( node->type == JOIN_LEAF ) print_node( node ); return( NULL ); } #endif /*DEBUG*/ /* Count all join nodes. */ static void * count_joins( JoinNode *node ) { if( node->type == JOIN_TB || node->type == JOIN_LR || node->type == JOIN_LRROTSCALE || node->type == JOIN_TBROTSCALE ) node->st->njoin++; return( NULL ); } #ifdef DEBUG /* Print a few spaces. */ static void spc( int n ) { int i; for( i = 0; i < n; i++ ) printf( " " ); } #endif /*DEBUG*/ #ifdef DEBUG static char * JoinType2char( JoinType type ) { switch( type ) { case JOIN_LR: return( "JOIN_LR" ); case JOIN_TB: return( "JOIN_TB" ); case JOIN_LRROTSCALE: return( "JOIN_LRROTSCALE" ); case JOIN_TBROTSCALE: return( "JOIN_TBROTSCALE" ); case JOIN_CP: return( "JOIN_CP" ); case JOIN_LEAF: return( "JOIN_LEAF" ); default: error_exit( "internal error #9275" ); /*NOTEACHED*/ return( NULL ); } } #endif /*DEBUG*/ #ifdef DEBUG /* Print a join node. */ static void * print_joins( JoinNode *node, int indent ) { switch( node->type ) { case JOIN_TB: case JOIN_LR: case JOIN_TBROTSCALE: case JOIN_LRROTSCALE: spc( indent ); printf( "%s to make %s, size %dx%d, pos. %dx%d, of:\n", JoinType2char( node->type ), im_skip_dir( node->name ), node->cumtrn.oarea.width, node->cumtrn.oarea.height, node->cumtrn.oarea.left, node->cumtrn.oarea.top ); spc( indent ); printf( "reference:\n" ); print_joins( node->arg1, indent+2 ); spc( indent ); printf( "secondary:\n" ); print_joins( node->arg2, indent+2 ); break; case JOIN_CP: spc( indent ); printf( "copy to make %s of:\n", im_skip_dir( node->name ) ); print_joins( node->arg1, indent+2 ); break; case JOIN_LEAF: spc( indent ); printf( "input image %s\n", im_skip_dir( node->name ) ); break; } return( NULL ); } #endif /*DEBUG*/ #ifdef DEBUG /* Print an overlap. */ static void * print_overlap( OverlapInfo *lap ) { printf( "-> %s overlaps with %s; (this, other) = (%.4G, %.4G)\n", im_skip_dir( lap->node->name ), im_skip_dir( lap->other->name ), lap->nstats->coeff[4], lap->ostats->coeff[4] ); return( NULL ); } #endif /*DEBUG*/ #ifdef DEBUG /* Print the overlaps on a leaf. */ static void * print_overlaps( JoinNode *node ) { if( node->type == JOIN_LEAF && g_slist_length( node->overlaps ) > 0 ) { printf( "overlap of %s with:\n", im_skip_dir( node->name ) ); im_slist_map2( node->overlaps, (VSListMap2Fn) print_overlap, NULL, NULL ); } return( NULL ); } #endif /*DEBUG*/ #ifdef DEBUG /* Print and accumulate the error on an overlap. */ static void * print_overlap_error( OverlapInfo *lap, double *fac, double *total ) { double na = lap->nstats->coeff[4]; double oa = lap->ostats->coeff[4]; double err; if( fac ) { na *= fac[lap->node->index]; oa *= fac[lap->other->index]; } err = na - oa; printf( "-> file %s, error = %g\n", im_skip_dir( lap->other->name ), err ); *total += err*err; return( NULL ); } #endif /*DEBUG*/ #ifdef DEBUG /* Print and accumulate the overlap errors on a leaf. */ static void * print_overlap_errors( JoinNode *node, double *fac, double *total ) { if( node->type == JOIN_LEAF && g_slist_length( node->overlaps ) > 0 ) { printf( "overlap of %s (index %d) with:\n", im_skip_dir( node->name ), node->index ); im_slist_map2( node->overlaps, (VSListMap2Fn) print_overlap_error, fac, total ); } return( NULL ); } #endif /*DEBUG*/ /* Make a DOUBLEMASK local to an image descriptor. */ static DOUBLEMASK * local_mask( IMAGE *out, DOUBLEMASK *mask ) { if( !mask ) return( NULL ); if( im_add_close_callback( out, (im_callback_fn) im_free_dmask, mask, NULL ) ) { im_free_dmask( mask ); return( NULL ); } return( mask ); } /* Extract a rect. */ static int extract_rect( IMAGE *in, IMAGE *out, Rect *r ) { return( im_extract_area( in, out, r->left, r->top, r->width, r->height ) ); } /* Two images overlap in an area ... make a mask the size of the area, which * has 255 for every pixel where both images are non-zero. */ static int make_overlap_mask( IMAGE *ref, IMAGE *sec, IMAGE *mask, Rect *rarea, Rect *sarea ) { IMAGE *t[6]; if( im_open_local_array( mask, t, 6, "mytemps", "p" ) || extract_rect( ref, t[0], rarea ) || extract_rect( sec, t[1], sarea ) || im_extract_band( t[0], t[2], 0 ) || im_extract_band( t[1], t[3], 0 ) || im_notequalconst( t[2], t[4], 0.0 ) || im_notequalconst( t[3], t[5], 0.0 ) || im_andimage( t[4], t[5], mask ) ) return( -1 ); return( 0 ); } /* Find the number of non-zero pixels in a mask image. */ static int count_nonzero( IMAGE *in, gint64 *count ) { double avg; if( im_avg( in, &avg ) ) return( -1 ); *count = (avg * in->Xsize * in->Ysize ) / 255.0; return( 0 ); } /* Find stats on an area of an IMAGE ... consider only pixels for which the * mask is true. */ static DOUBLEMASK * find_image_stats( IMAGE *in, IMAGE *mask, Rect *area ) { DOUBLEMASK *stats; IMAGE *t[4]; gint64 count; /* Extract area, build black image, mask out pixels we want. */ if( im_open_local_array( in, t, 4, "find_image_stats", "p" ) || extract_rect( in, t[0], area ) || im_black( t[1], t[0]->Xsize, t[0]->Ysize, t[0]->Bands ) || im_clip2fmt( t[1], t[2], t[0]->BandFmt ) || im_ifthenelse( mask, t[0], t[2], t[3] ) ) return( NULL ); /* Get stats from masked image. */ if( !(stats = local_mask( in, im_stats( t[3] ) )) ) return( NULL ); /* Number of non-zero pixels in mask. */ if( count_nonzero( mask, &count ) ) return( NULL ); /* And scale masked average to match. */ stats->coeff[4] *= (double) count / ((double) mask->Xsize * mask->Ysize); /* Yuk! Zap the deviation column with the pixel count. Used later to * determine if this is likely to be a significant overlap. */ stats->coeff[5] = count; #ifdef DEBUG if( count == 0 ) im_warn( "global_balance", "%s", _( "empty overlap!" ) ); #endif /*DEBUG*/ return( stats ); } /* Find the stats for an overlap struct. */ static int find_overlap_stats( OverlapInfo *lap ) { IMAGE *t1 = im_open_local( lap->node->im, "find_overlap_stats:1", "p" ); Rect rarea, sarea; /* Translate the overlap area into the coordinate scheme for the main * node. */ rarea = lap->overlap; rarea.left -= lap->node->cumtrn.oarea.left; rarea.top -= lap->node->cumtrn.oarea.top; /* Translate the overlap area into the coordinate scheme for the other * node. */ sarea = lap->overlap; sarea.left -= lap->other->cumtrn.oarea.left; sarea.top -= lap->other->cumtrn.oarea.top; /* Make a mask for the overlap. */ if( make_overlap_mask( lap->node->trnim, lap->other->trnim, t1, &rarea, &sarea ) ) return( -1 ); /* Find stats for that area. */ if( !(lap->nstats = find_image_stats( lap->node->trnim, t1, &rarea )) ) return( -1 ); if( !(lap->ostats = find_image_stats( lap->other->trnim, t1, &sarea )) ) return( -1 ); return( 0 ); } /* Sub-fn. of below. */ static void * overlap_eq( OverlapInfo *this, JoinNode *node ) { if( this->other == node ) return( this ); else return( NULL ); } /* Is this an overlapping leaf? If yes, add to overlap list. */ static void * test_overlap( JoinNode *other, JoinNode *node ) { Rect overlap; OverlapInfo *lap; /* Is other a suitable leaf to overlap with node? */ if( other->type != JOIN_LEAF || node == other ) return( NULL ); /* Is there an overlap? */ im_rect_intersectrect( &node->cumtrn.oarea, &other->cumtrn.oarea, &overlap ); if( im_rect_isempty( &overlap ) ) return( NULL ); /* Is this a trivial overlap? Ignore it if it is. */ if( overlap.width * overlap.height < TRIVIAL ) /* Too few pixels. */ return( NULL ); /* Have we already added this overlap the other way around? ie. is * node on other's overlap list? */ if( im_slist_map2( other->overlaps, (VSListMap2Fn) overlap_eq, node, NULL ) ) return( NULL ); /* A new overlap - add to overlap list. */ if( !(lap = build_overlap( node, other, &overlap )) ) return( node ); /* Calculate overlap statistics. */ if( find_overlap_stats( lap ) ) return( node ); /* If the pixel count either masked overlap is trivial, ignore this * overlap. */ if( lap->nstats->coeff[5] < TRIVIAL || lap->ostats->coeff[5] < TRIVIAL ) { #ifdef DEBUG printf( "trivial overlap ... junking\n" ); printf( "nstats count = %g, ostats count = %g\n", lap->nstats->coeff[5], lap->ostats->coeff[5] ); print_overlap( lap ); #endif /*DEBUG*/ overlap_destroy( lap ); } return( NULL ); } /* If this is a leaf, look at all other joins for a leaf that overlaps. Aside: * If this is a leaf, there should be an IMAGE. Flag an error if there is * not. */ static void * find_overlaps( JoinNode *node, SymbolTable *st ) { if( node->type == JOIN_LEAF ) { /* Check for image. */ if( !node->im ) { im_error( "im_global_balance", _( "unable to open \"%s\"" ), node->name ); return( node ); } if( !node->trnim ) error_exit( "global_balance: sanity failure #9834" ); return( im__map_table( st, test_overlap, node, NULL ) ); } return( NULL ); } /* Bundle of variables for matrix creation. */ typedef struct { SymbolTable *st; /* Main table */ JoinNode *leaf; /* Leaf to be 1.000 */ DOUBLEMASK *K; /* LHS */ DOUBLEMASK *M; /* RHS */ int row; /* Current row */ } MatrixBundle; /* Add a new row for the nominated overlap to the matricies. */ static void * add_nominated( OverlapInfo *ovl, MatrixBundle *bun, double *gamma ) { double *Kp = bun->K->coeff + bun->row; double *Mp = bun->M->coeff + bun->row*bun->M->xsize; double ns = pow( ovl->nstats->coeff[4], 1/(*gamma) ); double os = pow( ovl->ostats->coeff[4], 1/(*gamma) ); Kp[0] = ns; Mp[ovl->other->index - 1] = os; bun->row++; return( NULL ); } /* Add a new row for an ordinary overlap to the matricies. */ static void * add_other( OverlapInfo *ovl, MatrixBundle *bun, double *gamma ) { double *Mp = bun->M->coeff + bun->row*bun->M->xsize; double ns = -pow( ovl->nstats->coeff[4], 1/(*gamma) ); double os = pow( ovl->ostats->coeff[4], 1/(*gamma) ); Mp[ovl->node->index - 1] = ns; Mp[ovl->other->index - 1] = os; bun->row++; return( NULL ); } /* Add stuff for node to matrix. */ static void * add_row( JoinNode *node, MatrixBundle *bun, double *gamma ) { if( node == bun->leaf ) im_slist_map2( node->overlaps, (VSListMap2Fn) add_nominated, bun, gamma ); else im_slist_map2( node->overlaps, (VSListMap2Fn) add_other, bun, gamma ); return( NULL ); } /* Fill K and M. leaf is image selected to have factor of 1.000. */ static void fill_matricies( SymbolTable *st, double gamma, DOUBLEMASK *K, DOUBLEMASK *M ) { MatrixBundle bun; bun.st = st; bun.leaf = st->leaf; bun.K = K; bun.M = M; bun.row = 0; /* Build matricies. */ im__map_table( st, add_row, &bun, &gamma ); } /* Used to select the leaf whose coefficient we set to 1. */ static void * choose_leaf( JoinNode *node ) { if( node->type == JOIN_LEAF ) return( node ); return( NULL ); } /* Make an image from a node. */ static IMAGE * make_mos_image( SymbolTable *st, JoinNode *node, transform_fn tfn, void *a ) { IMAGE *im1, *im2, *out; switch( node->type ) { case JOIN_LR: case JOIN_TB: if( !(im1 = make_mos_image( st, node->arg1, tfn, a )) || !(im2 = make_mos_image( st, node->arg2, tfn, a )) || !(out = im_open_local( st->im, node->name, "p" )) ) return( NULL ); if( node->type == JOIN_LR ) { if( im_lrmerge( im1, im2, out, -node->dx, -node->dy, node->mwidth ) ) return( NULL ); } else { if( im_tbmerge( im1, im2, out, -node->dx, -node->dy, node->mwidth ) ) return( NULL ); } break; case JOIN_LRROTSCALE: case JOIN_TBROTSCALE: if( !(im1 = make_mos_image( st, node->arg1, tfn, a )) || !(im2 = make_mos_image( st, node->arg2, tfn, a )) || !(out = im_open_local( st->im, node->name, "p" )) ) return( NULL ); if( node->type == JOIN_LRROTSCALE ) { if( im__lrmerge1( im1, im2, out, node->a, node->b, node->dx, node->dy, node->mwidth ) ) return( NULL ); } else { if( im__tbmerge1( im1, im2, out, node->a, node->b, node->dx, node->dy, node->mwidth ) ) return( NULL ); } break; case JOIN_LEAF: /* Trivial case! */ if( !(out = tfn( node, a )) ) return( NULL ); break; case JOIN_CP: /* Very trivial case. */ out = make_mos_image( st, node->arg1, tfn, a ); break; default: error_exit( "internal error #982369824375987" ); /*NOTEACHED*/ return( NULL ); } return( out ); } /* Re-build mosaic. */ int im__build_mosaic( SymbolTable *st, IMAGE *out, transform_fn tfn, void *a ) { JoinNode *root = st->root; IMAGE *im1, *im2; switch( root->type ) { case JOIN_LR: case JOIN_TB: if( !(im1 = make_mos_image( st, root->arg1, tfn, a )) || !(im2 = make_mos_image( st, root->arg2, tfn, a )) ) return( -1 ); if( root->type == JOIN_LR ) { if( im_lrmerge( im1, im2, out, -root->dx, -root->dy, root->mwidth ) ) return( -1 ); } else { if( im_tbmerge( im1, im2, out, -root->dx, -root->dy, root->mwidth ) ) return( -1 ); } break; case JOIN_LRROTSCALE: case JOIN_TBROTSCALE: if( !(im1 = make_mos_image( st, root->arg1, tfn, a )) || !(im2 = make_mos_image( st, root->arg2, tfn, a )) ) return( -1 ); if( root->type == JOIN_LRROTSCALE ) { if( im__lrmerge1( im1, im2, out, root->a, root->b, root->dx, root->dy, root->mwidth ) ) return( -1 ); } else { if( im__tbmerge1( im1, im2, out, root->a, root->b, root->dx, root->dy, root->mwidth ) ) return( -1 ); } break; case JOIN_LEAF: /* Trivial case! Just one file in our mosaic. */ if( !(im1 = tfn( root, a )) || im_copy( im1, out ) ) return( -1 ); break; case JOIN_CP: /* Very trivial case. */ if( !(im1 = make_mos_image( st, root->arg1, tfn, a )) ) return( -1 ); if( im_copy( im1, out ) ) return( -1 ); break; default: error_exit( "internal error #982369824375987" ); /*NOTEACHED*/ } return( 0 ); } /* Find correction factors. */ static int find_factors( SymbolTable *st, double gamma ) { DOUBLEMASK *K; DOUBLEMASK *M; DOUBLEMASK *m1, *m2, *m3, *m4, *m5; double total; double avg; int i; /* Make output matricies. */ if( !(K = local_mask( st->im, im_create_dmask( "K", 1, st->novl ) )) || !(M = local_mask( st->im, im_create_dmask( "M", st->nim-1, st->novl ) )) ) return( -1 ); fill_matricies( st, gamma, K, M ); #ifdef DEBUG im_write_dmask( K ); im_write_dmask( M ); #endif /*DEBUG*/ /* Calculate LMS. */ if( !(m1 = local_mask( st->im, im_mattrn( M, "lms:1" ) )) ) return( -1 ); if( !(m2 = local_mask( st->im, im_matmul( m1, M, "lms:2" ) )) ) return( -1 ); if( !(m3 = local_mask( st->im, im_matinv( m2, "lms:3" ) )) ) return( -1 ); if( !(m4 = local_mask( st->im, im_matmul( m3, m1, "lms:4" ) )) ) return( -1 ); if( !(m5 = local_mask( st->im, im_matmul( m4, K, "lms:5" ) )) ) return( -1 ); /* Make array of correction factors. */ if( !(st->fac = IM_ARRAY( st->im, st->nim, double )) ) return( -1 ); for( i = 0; i < m5->ysize; i++ ) st->fac[i + 1] = m5->coeff[i]; st->fac[0] = 1.0; /* Find average balance factor, normalise to that average. */ total = 0.0; for( i = 0; i < st->nim; i++ ) total += st->fac[i]; avg = total / st->nim; for( i = 0; i < st->nim; i++ ) st->fac[i] /= avg; #ifdef DEBUG /* Diagnostics! */ printf( "debugging output for im_global_balance():\n" ); for( i = 0; i < st->nim; i++ ) printf( "balance factor %d = %g\n", i, st->fac[i] ); total = 0.0; printf( "Overlap errors:\n" ); im__map_table( st, print_overlap_errors, NULL, &total ); printf( "RMS error = %g\n", sqrt( total / st->novl ) ); total = 0.0; printf( "Overlap errors after adjustment:\n" ); im__map_table( st, print_overlap_errors, st->fac, &total ); printf( "RMS error = %g\n", sqrt( total / st->novl ) ); #endif /*DEBUG*/ return( 0 ); } /* Look for all leaves, make sure we have a transformed version of each. */ static void * generate_trn_leaves( JoinNode *node, SymbolTable *st ) { if( node->type == JOIN_LEAF ) { /* Check for image. */ if( !node->im ) { im_error( "im_global_balance", _( "unable to open \"%s\"" ), node->name ); return( node ); } if( node->trnim ) error_exit( "global_balance: sanity failure #765" ); /* Special case: if this is an untransformed leaf (there will * always be at least one), then skip the affine. */ if( im__transform_isidentity( &node->cumtrn ) ) node->trnim = node->im; else if( !(node->trnim = im_open_local( node->im, "trnleaf:1", "p" )) || im__affine( node->im, node->trnim, &node->cumtrn ) ) return( node ); } return( NULL ); } /* Analyse mosaic. */ static int analyse_mosaic( SymbolTable *st, IMAGE *in ) { /* Parse Hist on in. */ if( im__parse_desc( st, in ) ) return( -1 ); /* Print parsed data. */ #ifdef DEBUG printf( "Input files:\n" ); im__map_table( st, print_leaf, NULL, NULL ); printf( "\nOutput file:\n" ); print_node( st->root ); printf( "\nJoin commands:\n" ); print_joins( st->root, 0 ); #endif /*DEBUG*/ /* Generate transformed leaves. */ if( im__map_table( st, generate_trn_leaves, st, NULL ) ) return( -1 ); /* Find overlaps. */ if( im__map_table( st, find_overlaps, st, NULL ) ) return( -1 ); /* Scan table, counting and indexing input images and joins. */ im__map_table( st, count_leaves, NULL, NULL ); im__map_table( st, count_joins, NULL, NULL ); /* Select leaf to be 1.000. * This must be index == 0, unless you change stuff above! */ st->leaf = im__map_table( st, choose_leaf, NULL, NULL ); /* And print overlaps. */ #ifdef DEBUG printf( "\nLeaf to be 1.000:\n" ); print_node( st->leaf ); printf( "\nOverlaps:\n" ); im__map_table( st, print_overlaps, NULL, NULL ); printf( "\n%d input files, %d unique overlaps, %d joins\n", st->nim, st->novl, st->njoin ); #endif /*DEBUG*/ return( 0 ); } /* Scale im by fac --- if it's uchar/ushort, use a lut. If we can use a lut, * transform in linear space. If we can't, don't bother for efficiency. */ static IMAGE * transform( JoinNode *node, double *gamma ) { SymbolTable *st = node->st; IMAGE *in = node->im; double fac = st->fac[node->index]; IMAGE *out = im_open_local( st->im, node->name, "p" ); IMAGE *t1 = im_open_local( out, "transform:1", "p" ); IMAGE *t2 = im_open_local( out, "transform:2", "p" ); IMAGE *t3 = im_open_local( out, "transform:3", "p" ); IMAGE *t4 = im_open_local( out, "transform:4", "p" ); IMAGE *t5 = im_open_local( out, "transform:5", "p" ); if( !out || !t1 || !t2 || !t3 || !t4 || !t5 ) return( NULL ); if( fac == 1.0 ) { /* Easy! */ out = in; } else if( in->BandFmt == IM_BANDFMT_UCHAR ) { if( im_identity( t1, 1 ) || im_powtra( t1, t2, 1/(*gamma) ) || im_lintra( fac, t2, 0.0, t3 ) || im_powtra( t3, t4, *gamma ) || im_clip( t4, t5 ) || im_maplut( in, out, t5 ) ) return( NULL ); } else if( in->BandFmt == IM_BANDFMT_USHORT ) { if( im_identity_ushort( t1, 1, 65535 ) || im_powtra( t1, t2, 1/(*gamma) ) || im_lintra( fac, t2, 0.0, t3 ) || im_powtra( t3, t4, *gamma ) || im_clip2us( t4, t5 ) || im_maplut( in, out, t5 ) ) return( NULL ); } else { /* Just im_lintra it. */ if( im_lintra( fac, in, 0.0, t1 ) || im_clip2fmt( t1, out, in->BandFmt ) ) return( NULL ); } return( out ); } /* As above, but output as float, not matched to input. */ static IMAGE * transformf( JoinNode *node, double *gamma ) { SymbolTable *st = node->st; IMAGE *in = node->im; double fac = node->st->fac[node->index]; IMAGE *out = im_open_local( st->im, node->name, "p" ); IMAGE *t1 = im_open_local( out, "transform:1", "p" ); IMAGE *t2 = im_open_local( out, "transform:2", "p" ); IMAGE *t3 = im_open_local( out, "transform:3", "p" ); IMAGE *t4 = im_open_local( out, "transform:4", "p" ); if( !out || !t1 || !t2 || !t3 || !t4 ) return( NULL ); if( fac == 1.0 ) { /* Easy! */ out = in; } else if( in->BandFmt == IM_BANDFMT_UCHAR ) { if( im_identity( t1, 1 ) || im_powtra( t1, t2, 1/(*gamma) ) || im_lintra( fac, t2, 0.0, t3 ) || im_powtra( t3, t4, *gamma ) || im_maplut( in, out, t4 ) ) return( NULL ); } else if( in->BandFmt == IM_BANDFMT_USHORT ) { if( im_identity_ushort( t1, 1, 65535 ) || im_powtra( t1, t2, 1/(*gamma) ) || im_lintra( fac, t2, 0.0, t3 ) || im_powtra( t3, t4, *gamma ) || im_maplut( in, out, t4 ) ) return( NULL ); } else { /* Just im_lintra it. */ if( im_lintra( fac, in, 0.0, out ) ) return( NULL ); } return( out ); } /* Balance mosaic, outputting in the original format. */ int im_global_balance( IMAGE *in, IMAGE *out, double gamma ) { SymbolTable *st; if( !(st = im__build_symtab( out, SYM_TAB_SIZE )) || analyse_mosaic( st, in ) || find_factors( st, gamma ) || im__build_mosaic( st, out, (transform_fn) transform, &gamma ) ) return( -1 ); return( 0 ); } /* Balance mosaic, outputting as float. This is useful if the automatic * selection of balance range fails - our caller can search the output for the * min and max, and rescale to prevent burn-out. */ int im_global_balancef( IMAGE *in, IMAGE *out, double gamma ) { SymbolTable *st; if( !(st = im__build_symtab( out, SYM_TAB_SIZE )) || analyse_mosaic( st, in ) || find_factors( st, gamma ) || im__build_mosaic( st, out, (transform_fn) transformf, &gamma ) ) return( -1 ); return( 0 ); }