Browse Source

move i.segment to trunk

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@54977 15284696-431f-4ddb-bdfa-cd5b030d7da7
Markus Metz 12 years ago
parent
commit
15c6a43010

+ 10 - 0
imagery/i.segment/Makefile

@@ -0,0 +1,10 @@
+MODULE_TOPDIR = ../..
+
+PGM = i.segment
+
+LIBES = $(IMAGERYLIB) $(RASTERLIB) $(SEGMENTLIB) $(GISLIB) $(LINKMLIB) $(BTREE2LIB)
+DEPENDENCIES = $(IMAGERYDEP) $(RASTERDEP) $(SEGMENTDEP) $(GISDEP) $(LINKMDEP)
+
+include $(MODULE_TOPDIR)/include/Make/Module.make
+
+default: cmd

File diff suppressed because it is too large
+ 1244 - 0
imagery/i.segment/create_isegs.c


+ 94 - 0
imagery/i.segment/estimate_threshold.c

@@ -0,0 +1,94 @@
+/* Purpose: open input files and suggest a reasonable threshold */
+
+#include <stdlib.h>
+#include <grass/gis.h>
+#include <grass/glocale.h>
+#include <grass/imagery.h>
+#include "iseg.h"
+
+int estimate_threshold(char *image_group)
+{
+    double min, max, est_t;
+
+    /* check if the input image group is valid. */
+    check_group(image_group);
+
+    /* read the raster files to find the minimum and maximum values */
+    read_range(&min, &max, image_group);
+
+    /* perform the calculations to estimate the threshold */
+    est_t = calc_t(min, max);
+
+    /* set the output message and finish */
+    G_done_msg(_("Suggested threshold (if using -w flag and radioweight=1) is: <%g>"),
+	       est_t);
+    return TRUE;
+}
+
+/* function to validate that the user input is readable and contains files */
+int check_group(char *image_group)
+{
+    struct Ref Ref;		/* group reference list */
+
+    /* check that the input image group can be found */
+    if (!I_get_group_ref(image_group, &Ref))
+	G_fatal_error(_("Unable to read REF file for group <%s>"),
+		      image_group);
+
+    /* check that the input image group contains rasters */
+    if (Ref.nfiles <= 0)
+	G_fatal_error(_("Group <%s> contains no raster maps"), image_group);
+
+    return TRUE;
+}
+
+/* function to find the min and max values in all rasters of an image group */
+int read_range(double *min, double *max, char *image_group)
+{
+    int n;
+    struct FPRange fp_range;	/* range for a raster */
+    struct Ref Ref;		/* group reference list */
+    double candidate_min, candidate_max;	/* for min/max in each raster */
+
+    /* read the image group */
+    I_get_group_ref(image_group, &Ref);
+
+    /* initialize min/max with the first raster min/max */
+    if (Rast_read_fp_range(Ref.file[0].name, Ref.file[0].mapset, &fp_range) != 1)	/* returns -1 on error, 2 on empty range, quiting either way. */
+	G_fatal_error(_("No min/max found in raster map <%s>"),
+		      Ref.file[0].name);
+    else
+	Rast_get_fp_range_min_max(&fp_range, min, max);
+
+    /* check the min/max for any remaining rasters */
+    for (n = 1; n < Ref.nfiles; n++) {
+	if (Rast_read_fp_range(Ref.file[n].name, Ref.file[n].mapset, &fp_range) != 1) {	/* returns -1 on error, 2 on empty range, quiting either way. */
+	    G_fatal_error(_("No min/max found in raster map <%s>"),
+			  Ref.file[n].name);
+
+	    Rast_get_fp_range_min_max(&fp_range, &candidate_min,
+				      &candidate_max);
+
+	    if (candidate_min < *min)
+		*min = candidate_min;
+	    if (candidate_max > *max)
+		*max = candidate_max;
+	}
+    }
+
+    return TRUE;
+}
+
+/* function to calculate a suggested threshold based on the min and max values of the rasters */
+double calc_t(double min, double max)
+{
+    double t, fraction;
+
+    /* Empirical testing indicated 1 to 5% of the differences was a good starting point. */
+    fraction = 0.03;
+
+    /* TODO: allow the community to test this estimate, the formula can be updated based on their advice. */
+    t = fraction * (max - min);
+
+    return t;
+}

+ 49 - 0
imagery/i.segment/flag.c

@@ -0,0 +1,49 @@
+#include <grass/gis.h>
+#include "flag.h"
+
+int flag_clear_all(FLAG * flags)
+{
+    register int r, c;
+
+    for (r = 0; r < flags->nrows; r++) {
+	for (c = 0; c < flags->leng; c++) {
+	    flags->array[r][c] = 0;
+	}
+    }
+
+    return 0;
+}
+
+FLAG *flag_create(int nrows, int ncols)
+{
+    unsigned char *temp;
+    FLAG *new_flag;
+    register int i;
+
+    new_flag = (FLAG *) G_malloc(sizeof(FLAG));
+    new_flag->nrows = nrows;
+    new_flag->ncols = ncols;
+    new_flag->leng = (ncols + 7) / 8;
+    new_flag->array =
+	(unsigned char **)G_malloc(nrows * sizeof(unsigned char *));
+
+    temp =
+	(unsigned char *)G_malloc(nrows * new_flag->leng *
+				  sizeof(unsigned char));
+
+    for (i = 0; i < nrows; i++) {
+	new_flag->array[i] = temp;
+	temp += new_flag->leng;
+    }
+
+    return (new_flag);
+}
+
+int flag_destroy(FLAG * flags)
+{
+    G_free(flags->array[0]);
+    G_free(flags->array);
+    G_free(flags);
+
+    return 0;
+}

+ 65 - 0
imagery/i.segment/flag.h

@@ -0,0 +1,65 @@
+#ifndef __FLAG_H__
+#define __FLAG_H__
+
+
+/* flag.[ch] is a set of routines which will set up an array of bits
+ ** that allow the programmer to "flag" cells in a raster map.
+ **
+ ** FLAG *
+ ** flag_create(nrows,ncols)
+ ** int nrows, ncols;
+ **     opens the structure flag.  
+ **     The flag structure will be a two dimensional array of bits the
+ **     size of nrows by ncols.  Will initalize flags to zero (unset).
+ **
+ ** flag_destroy(flags)
+ ** FLAG *flags;
+ **     closes flags and gives the memory back to the system.
+ **
+ ** flag_clear_all(flags)
+ ** FLAG *flags;
+ **     sets all values in flags to zero.
+ **
+ * following 3 were changed to macros, same usage
+ * 
+ ** flag_unset(flags, row, col)
+ ** FLAG *flags;
+ ** int row, col;
+ **     sets the value of (row, col) in flags to zero.
+ **
+ ** flag_set(flags, row, col)
+ ** FLAG *flags;
+ ** int row, col;
+ **     will set the value of (row, col) in flags to one.
+ **
+ ** int
+ ** flag_get(flags, row, col)
+ ** FLAG *flags;
+ ** int row, col;
+ **     returns the value in flags that is at (row, col).
+ **
+ ** idea by Michael Shapiro
+ ** code by Chuck Ehlschlaeger
+ ** April 03, 1989
+ */
+#define FLAG struct _flagsss_
+FLAG {
+    int nrows, ncols, leng;
+    unsigned char **array;
+};
+
+#define FLAG_UNSET(flags,row,col) \
+	(flags)->array[(row)][(col)>>3] &= ~(1<<((col) & 7))
+
+#define FLAG_SET(flags,row,col) \
+	(flags)->array[(row)][(col)>>3] |= (1<<((col) & 7))
+
+#define FLAG_GET(flags,row,col) \
+	(flags)->array[(row)][(col)>>3] & (1<<((col) & 7))
+
+/* flag.c */
+int flag_clear_all(FLAG *);
+FLAG *flag_create(int, int);
+int flag_destroy(FLAG *);
+
+#endif /* __FLAG_H__ */

+ 226 - 0
imagery/i.segment/i.segment.html

@@ -0,0 +1,226 @@
+<h2>DESCRIPTION</h2>
+Image segmentation is the process of grouping similar pixels into 
+unique segments. Boundary and region based algorithms are described 
+in the literature, currently a region growing and merging algorithm 
+is implemented.  Each grouping (usually refered to as objects or 
+segments) found during the segmentation process is given a unique ID 
+and is a collection of contiguous pixels meeting some criteria.  
+(Note the contrast with image classification, where continuity and 
+spatial characteristics are not important, but rather only the 
+spectral similarity.)  The results can be useful on their own, or 
+used as a preprocessing step for image classification.  The 
+segmentation preprocessing step can reduce noise and speed up the 
+classification.
+
+<H2>NOTES</h2>
+
+<h3>Region Growing and Merging</h3>
+This segmentation algorithm sequentially examines all current 
+segments in the map.  The similarity between the current segment and 
+each of its neighbors is calculated according to the given distance 
+formula. Segments will be merged if they meet a number of criteria, 
+including: 1.  The pair is mutually most similar to each other (the 
+similarity distance will be smaller then all other neighbors), and 
+2. The similarity must be lower then the input threshold.  All 
+segments are checked once per pass.  The process is repeated until 
+no merges are made during a complete pass.
+
+<h3>Similarity and Threshold</h3>
+The similarity between segments and unmerged pixels is used to 
+determine which are merged.  The Euclidean version uses the 
+radiometric distance between the two segments and also the shape 
+characteristics. The Manhatten calculations currently only uses only 
+the radiometric distance between the two segments, but eventually 
+shape characteristics will be included as well.  NOTE: 
+Closer/smaller distances mean a lower value for the similarity 
+indicates a closer match, with a similarity score of zero for 
+identical pixels.
+<p>
+During normal processing, merges are only allowed when the 
+similarity between two segments is lower then the calculated 
+threshold value. During the final pass, however, if a minimum 
+segment size of 2 or larger is given with the <em>minsize</em> 
+parameter, segments with a smaller pixel count will be merged with 
+their most similar neighbor even if the similarity is greater then 
+the threshold.
+<p>
+Unless the <em>-w</em> flag for weighted data is used, the threshold 
+should be set by the user between 0 and 1.0. A threshold of 0 would 
+allow only identical valued pixels to be merged, while a threshold 
+of 1 would allow everything to be merged.
+<p>
+The threshold will be multiplied by the number of rasters included 
+in the image group.  This will allow the same threshold to achieve 
+similar segmentation results when the number of rasters in the image 
+group varies.
+<p>
+The -t flag will estimate the threshold, it is calculated at 3% of 
+the range of data in the imagery group.  Initial empirical tests 
+indicate threshold values of 1% to 5% are reasonable places to start.
+
+<h4>Calculation Formulas</h4>
+Both Euclidean and Manhattan distances use the normal definition, 
+considering each raster in the image group as a dimension.
+
+Furthermore, the Euclidean calculation also takes into account the 
+shape characteristics of the segments.  The normal distances are 
+multiplied by the input radiometric weight.  Next an additional 
+contribution is added: (1-radioweight) * {smoothness * smoothness 
+weight + compactness * (1-smoothness weight)}, where compactness = 
+the Perimeter Length / sqrt( Area ) and smoothness = Perimeter 
+Length / the Bounding Box.  The perimeter length is estimated as the 
+number of pixel sides the segment has.
+
+<h3>Seeds</h3>
+The seeds map can be used to provide either seed pixels (random or 
+selected points from which to start the segmentation process) or 
+seed segments (results of previous segmentations or 
+classifications).  The different approaches are automatically 
+detected by the program: any pixels that have identical seed values 
+and are contiguous will be lumped into a single segment ID.
+<p>
+It is expected that the <em>minsize</em> will be set to 1 if a seed 
+map is used, but the program will allow other values to be used.  If 
+both options are used, the final iteration that ignores the 
+threshold also will ignore the seed map and force merges for all 
+pixels (not just segments that have grown/merged from the seeds).
+
+<h3>Maximum number of starting segments</h3>
+For the region growing algorithm without starting seeds, each pixel 
+is sequentially numbered.  The current limit with CELL storage is 2 
+billion starting segment ID's.  If the initial map has a larger 
+number of non-null pixels, there are two workarounds:
+<p>
+1.  Use starting seed pixels.  (Maximum 2 billion pixels can be 
+labeled with positive integers.)
+<p>
+2.  Use starting seed segments.  (By initial classification or other 
+methods.)
+
+<h3>Boundary Constraints</h3>
+Boundary constraints limit the adjacency of pixels and segments.  
+Each unique value present in the <em>bounds</em> raster are 
+considered as a MASK.  Thus no segments in the final segmentated map 
+will cross a boundary, even if their spectral data is very similar.
+
+<h3>Minimum Segment Size</h3>
+To reduce the salt and pepper affect, a <em>minsize</em> greater 
+than 1 will add one additional pass to the processing.  During the 
+final pass, the threshold is ignored for any segments smaller then 
+the set size, thus forcing very small segments to merge with their 
+most similar neighbor.
+
+<h2>EXAMPLES</h2>
+This example uses the ortho photograph included in the NC Sample 
+Dataset.  Set up an imagery group:<br>
+<div class="code"><pre>
+i.group group=ortho_group input=ortho_2001_t792_1m@PERMANENT
+</pre></div>
+
+<p>Because the segmentation process is computationally expensive, 
+start with a small processing area to confirm if the segmentation 
+results meet your requirements.  Some manual adjustment of the 
+threshold may be required. <br>
+
+<div class="code"><pre>
+g.region rast=ortho_2001_t792_1m@PERMANENT n=220400 s=220200 e=639000 w=638800
+</pre></div>
+
+Try out a first threshold and check the results.<br>
+<div class="code"><pre>
+i.segment -w -l group=ortho_group output=ortho_segs threshold=4 \
+          method=region_growing 
+</pre></div>
+<p></p>
+From a visual inspection, it seems this results in oversegmentation.  
+Increasing the threshold: <br>
+<div class="code"><pre>
+i.segment -w -l --overwrite group=ortho_group output=ortho_segs \
+          threshold=10 method=region_growing
+</pre></div>
+<p></p>
+This looks better.  There is some noise in the image, lets next force 
+all segments smaller then 5 pixels to be merged into their most similar 
+neighbor (even if they are less similar then required by our 
+threshold):<br>
+<div class="code"><pre>
+i.segment -w -l --overwrite group=ortho_group output=ortho_segs \
+          threshold=10 method=region_growing minsize=5
+</pre></div>
+<p></p>
+Each of these segmentation steps took less then 1 minute on a decent 
+machine.  Now that we are satisfied with the settings, we'll process 
+the entire image:
+<div class="code"><pre>
+g.region rast=ortho_2001_t792_1m@PERMANENT<br>
+i.segment -w -l --overwrite group=ortho_group output=ortho_segs \
+          threshold=10 method=region_growing minsize=5 endt=5000
+</pre></div>
+<p>
+Processing the entire ortho image (over 9 million pixels) took about 
+a day.
+
+<h2>TODO</h2>
+<h3>Functionality</h3>
+<ul>
+<li>Further testing of the shape characteristics (smoothness, 
+compactness), if it looks good it should be added to the Manhatten 
+option.
+<b>in progress</b></li>
+<li>Malahanobis distance for the similarity calculation.</li>
+</ul>
+<h3>Use of Segmentation Results</h3>
+<ul>
+<li>Improve the optional output from this module, or better yet, add a 
+module for <em>i.segment.metrics</em>.</li>
+<li>Providing updates to i.maxlik to ensure the segmentation output can 
+be used as input for the existing classification functionality.</li>
+<li>Integration/workflow for <em>r.fuzzy</em>.</li>
+</ul>
+<h3>Speed</h3>
+<ul>
+<li>See create_isegs.c</li>
+</ul>
+<h3>Memory</h3>
+<ul>
+<li>User input for how much RAM can be used.</li>
+<li>Check input map type(s), currently storing in DCELL sized SEG file, 
+could reduce this dynamically depending on input map time. (Could only 
+reduce to FCELL, since will be storing mean we can't use CELL. Might 
+not be worth the added code complexity.)</li>
+</ul>
+<h2>BUGS</h2>
+If the seeds map is used to give starting seed segments, the segments 
+are renumbered starting from 1.  There is a chance a segment could be 
+renumbered to a seed value that has not yet been processed.  If they 
+happen to be adjacent, they would be merged.  (Possible fixes: a.  use 
+a processing flag to make sure the pixels hasn't been previously used, 
+or b. use negative segment ID's as a placeholder and negate all values 
+after the seed map has been processed.)
+<H2>REFERENCES</h2>
+This project was first developed during GSoC 2012.  Project 
+documentation, Image Segmentation references, and other information 
+is at the <a href=
+"http://grass.osgeo.org/wiki/GRASS_GSoC_2012_Image_Segmentation">
+project wiki</a>.
+<p>
+Information about <a href=
+"http://grass.osgeo.org/wiki/Image_classification">classification in 
+GRASS GIS</a> is also available on the wiki.
+
+<h2>SEE ALSO</h2>
+<em>
+<a href="i.group.html">i.group</a>, 
+<a href="i.maxlik.html">i.maxlik</a>, 
+<a href="r.fuzzy">r.fuzzy</a>, 
+<a href="i.smap.html">i.smap</a>, 
+<a href="r.seg.html">r.seg</a> (Addon)
+</em>
+
+<h2>AUTHORS</h2>
+Eric Momsen - North Dakota State University
+<p>
+GSoC mentor: Markus Metz
+
+<p>
+<i>Last changed: $Date$

+ 149 - 0
imagery/i.segment/iseg.h

@@ -0,0 +1,149 @@
+
+/****************************************************************************
+ *
+ * MODULE:       i.segment
+ * AUTHOR(S):    Eric Momsen <eric.momsen at gmail com>
+ * PURPOSE:      structure definition and function listing
+ * COPYRIGHT:    (C) 2012 by Eric Momsen, and the GRASS Development Team
+ *
+ *               This program is free software under the GNU General Public
+ *               License (>=v2). Read the COPYING file that comes with GRASS
+ *               for details.
+ *
+ *****************************************************************************/
+
+#include <grass/segment.h>
+#include <grass/linkm.h>
+#include "flag.h"
+
+/* PROFILE will add some rough time checks for finding neighbors, merging, and pass times. */
+/* #define PROFILE */
+
+/* SIGNPOST will add some fprintf statments to indicate what segments are being merged.
+ * other diagnostics could be included here for during further development and speed improvements.
+ */
+/* #define SIGNPOST */
+
+/* pixel stack */
+struct pixels
+{
+    struct pixels *next;
+    int row;
+    int col;
+    int count_shared;		/* todo perimeter: will hold the count how 
+				 * many pixels are shared on the Border Between 
+				 * Ri and Rk.  Not used for all pixels... see if 
+				 * this is an OK way to do this... */
+};
+
+/* input and output files, as well as some other processing info */
+struct files
+{
+    /* user parameters */
+    char *image_group;
+    int weighted;		/* 0 if false/not selected, so we should scale input.  1 if the scaling should be skipped */
+
+    /* region info */
+    int nrows, ncols;
+
+    /* files */
+    char *out_name;		/* name of output raster map */
+    const char *seeds_map, *seeds_mapset, *bounds_map, *bounds_mapset;	/* optional segment seeds and polygon constraints/boundaries */
+    char *out_band;		/* for segment average values */
+
+    /* file processing */
+    /* bands_seg is initialized with the input raster valuess, then is updated with current mean values for the segment. */
+    /* for now, also storing: Area, Perimeter, maxcol, mincol, maxrow, minrow */
+    int nbands;			/* number of rasters in the image group */
+    SEGMENT bands_seg, bounds_seg;	/* bands is for input, normal application is landsat bands, but other input can be included in the group. */
+    double *bands_val;		/* array, to hold all input values at one pixel */
+    double *second_val;		/* to hold values at second point for similarity comparison */
+    int bounds_val, current_bound;
+    int minrow, maxrow, mincol, maxcol;
+
+    /* results */
+    SEGMENT iseg_seg;		/* segment ID assignment */
+    int nsegs;			/* number of segments */
+
+    /* processing flags */
+    /* candidate flag for if a cell/segment has already been merged in that pass. */
+    /* seeds flag for if a cell/segment is a seed (can be Ri to start a merge).
+     *   All cells are valid seeds if a starting seeds map is not supplied. */
+    FLAG *candidate_flag, *null_flag, *orig_null_flag, *seeds_flag;
+
+    /* memory management, linked lists */
+    struct link_head *token;	/* for linkm.h linked list memory management. */
+
+};
+
+struct functions
+{
+    int method;			/* Segmentation method */
+    int num_pn;			/* number of pixel neighbors  int, 4 or 8. */
+    float threshold;		/* similarity threshold */
+    int min_segment_size;	/* smallest number of pixels/cells allowed in a final segment */
+    float radio_weight, smooth_weight;	/* radiometric (bands) vs. shape and smoothness vs. compactness */
+    /* Some function pointers to set in parse_args() */
+    int (*find_pixel_neighbors) (int, int, int[8][2], struct files *);	/*parameters: row, col, pixel_neighbors */
+    double (*calculate_similarity) (struct pixels *, struct pixels *, struct files *, struct functions *);	/*parameters: two pixels (each with row,col) to compare */
+
+    /* max number of iterations/passes */
+    int end_t;
+
+    int path;			/* flag if we are using Rk as next Ri for non-mutually best neighbor. */
+    int limited;		/* flag if we are limiting merges to one per pass */
+    int estimate_threshold;	/* flag if we just want to estimate a suggested threshold value and exit. */
+    int final_merge_only;	/* flag if we want to just run the final merge portion of the algorithm. */
+
+    /* todo: is there a fast way (and valid from an algorithm 
+       standpoint) to merge all neighbors that are within some small % 
+       of the treshold? * There is some code using "very_close" that is 
+       excluded with IFDEF * The goal is to speed processing, since in 
+       the end many of these very similar neighbors will be merged. * 
+       But the problem is that the find_segment_neighbors() function 
+       only returns a single pixel, not the entire segment membership. 
+       * The commented out code actually slowed down processing times 
+       in the first tries. */
+#ifdef VCLOSE
+    double very_close;		/* segments with very_close similarity 
+				 * will be merged without changing or checking the candidate flag. 
+				 * The algorithm will continue looking for the "most similar" 
+				 * neighbor that isn't "very close". */
+#endif
+};
+
+/* main.c */
+int estimate_threshold(char *);
+int check_group(char *);
+int read_range(double *, double *, char *);
+double calc_t(double, double);
+
+/* parse_args.c */
+/* gets input from user, validates, and sets up functions */
+int parse_args(int, char *[], struct files *, struct functions *);
+
+/* open_files.c */
+int open_files(struct files *, struct functions *);
+
+/* create_isegs.c */
+int create_isegs(struct files *, struct functions *);
+int region_growing(struct files *, struct functions *);
+int find_segment_neighbors(struct pixels **, struct pixels **, int *,
+			   struct files *, struct functions *);
+int set_candidate_flag(struct pixels *, int, struct files *);
+int merge_values(struct pixels *, struct pixels *, int, int, struct files *);
+int merge_pixels(struct pixels *, int, struct files *);
+int find_four_pixel_neighbors(int, int, int[][2], struct files *);
+int find_eight_pixel_neighbors(int, int, int[8][2], struct files *);
+double calculate_euclidean_similarity(struct pixels *, struct pixels *,
+				      struct files *, struct functions *);
+double calculate_manhattan_similarity(struct pixels *, struct pixels *,
+				      struct files *, struct functions *);
+int my_dispose_list(struct link_head *, struct pixels **);
+int compare_ids(const void *, const void *);
+int compare_pixels(const void *, const void *);
+int set_all_candidate_flags(struct files *);
+
+/* write_output.c */
+int write_output(struct files *);
+int close_files(struct files *);

+ 70 - 0
imagery/i.segment/main.c

@@ -0,0 +1,70 @@
+
+/****************************************************************************
+ *
+ * MODULE:       i.segment
+ * AUTHOR(S):    Eric Momsen <eric.momsen at gmail com>
+ * PURPOSE:      Segments an image group.
+ * COPYRIGHT:    (C) 2012 by Eric Momsen, and the GRASS Development Team
+ *
+ *               This program is free software under the GNU General Public
+ *               License (>=v2). Read the COPYING file that comes with GRASS
+ *               for details.
+ * 
+ *
+ *               NOTE: the names "segment" and "SEG" are already used by the Segmentation
+ *               Library for the data files/tiling, so "iseg" (image segmentation)
+ *               will be used to refer to the image segmentation.
+ * 
+ * 				 First developed for GSoC 2012 with mentor: Markus Metz
+ * 
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <grass/gis.h>
+#include <grass/glocale.h>
+#include <grass/imagery.h>
+#include "iseg.h"
+
+int main(int argc, char *argv[])
+{
+    struct files files;		/* input and output file descriptors, data structure, buffers */
+    struct functions functions;	/* function pointers and parameters for the calculations */
+    struct GModule *module;
+
+    G_gisinit(argv[0]);
+
+    module = G_define_module();
+    G_add_keyword(_("imagery"));
+    G_add_keyword(_("segmentation"));
+    module->description =
+	_("Outputs a single segmented map (raster) based on input values in an image group.");
+
+    if (parse_args(argc, argv, &files, &functions) != TRUE)
+	G_fatal_error(_("Error in parse_args()"));
+
+    /* check if we are doing normal processing or if the estimate threshold and exit flag has been selected */
+
+    if (functions.estimate_threshold == FALSE) {
+
+	G_debug(1, "Main: starting open_files()");
+	if (open_files(&files, &functions) != TRUE)
+	    G_fatal_error(_("Error in open_files()"));
+
+	G_debug(1, "Main: starting create_isegs()");
+	if (create_isegs(&files, &functions) != TRUE)
+	    G_fatal_error(_("Error in create_isegs()"));
+
+	G_debug(1, "Main: starting write_output()");
+	if (write_output(&files) != TRUE)
+	    G_fatal_error(_("Error in write_output()"));
+
+	G_debug(1, "Main: starting close_files()");
+	close_files(&files);
+
+	G_done_msg(_("Number of segments created: <%d>"), files.nsegs);
+    }
+    else {
+	estimate_threshold(files.image_group);
+    }
+    exit(EXIT_SUCCESS);
+}

+ 368 - 0
imagery/i.segment/open_files.c

@@ -0,0 +1,368 @@
+/* PURPOSE:      opening input rasters and creating segmentation files */
+
+#include <limits.h>		/* for INT_MAX */
+#include <stdlib.h>
+#include <grass/gis.h>
+#include <grass/glocale.h>
+#include <grass/imagery.h>
+#include <grass/segment.h>	/* segmentation library */
+#include "iseg.h"
+
+int open_files(struct files *files, struct functions *functions)
+{
+    struct Ref Ref;		/* group reference list */
+    int *in_fd, seeds_fd, bounds_fd, null_check, out_fd, mean_fd;
+    int n, s, row, col, srows, scols, inlen, nseg, borderPixels;
+    DCELL **inbuf;		/* buffer array, to store lines from each of the imagery group rasters */
+    CELL *boundsbuf, *seedsbuf;
+    void *ptr;			/* for iterating seedsbuf */
+    size_t ptrsize;
+    struct FPRange *fp_range;	/* for getting min/max values on each input raster */
+    DCELL *min, *max;
+    struct Range range;		/* for seeds range */
+    int seeds_min, seeds_max;
+
+
+    /* for merging seed values */
+    struct pixels *R_head, *Rn_head, *newpixel, *current;
+    int R_count;
+
+    G_verbose_message(_("Opening files and initializing"));
+
+    /* confirm output maps can be opened (don't want to do all this work for nothing!) */
+    out_fd = Rast_open_new(files->out_name, CELL_TYPE);
+    if (out_fd < 0)
+	G_fatal_error(_("Could not open output raster for writing segment ID's"));
+    else
+	Rast_unopen(out_fd);
+
+    if (files->out_band != NULL) {
+	mean_fd = Rast_open_new(files->out_band, DCELL_TYPE);
+	if (mean_fd < 0)
+	    G_fatal_error(_("Could not open output raster for writing mean segment values"));
+	else
+	    Rast_unopen(mean_fd);
+    }
+
+    /*allocate memory for flags */
+    files->null_flag = flag_create(files->nrows, files->ncols);
+    flag_clear_all(files->null_flag);
+    files->candidate_flag = flag_create(files->nrows, files->ncols);
+
+    if (files->bounds_map != NULL)
+	files->orig_null_flag = flag_create(files->nrows, files->ncols);
+
+    files->seeds_flag = flag_create(files->nrows, files->ncols);
+    flag_clear_all(files->seeds_flag);
+
+    /* references for segmentation library: i.cost r.watershed/seg and http://grass.osgeo.org/programming7/segmentlib.html */
+
+    /* ****** open the input rasters ******* */
+
+    /* Note: I confirmed, the API does not check this: */
+    if (!I_get_group_ref(files->image_group, &Ref))
+	G_fatal_error(_("Unable to read REF file for group <%s>"),
+		      files->image_group);
+
+    if (Ref.nfiles <= 0)
+	G_fatal_error(_("Group <%s> contains no raster maps"),
+		      files->image_group);
+
+    /* Read Imagery Group */
+
+    in_fd = G_malloc(Ref.nfiles * sizeof(int));
+    inbuf = (DCELL **) G_malloc(Ref.nfiles * sizeof(DCELL *));
+    fp_range = G_malloc(Ref.nfiles * sizeof(struct FPRange));
+    min = G_malloc(Ref.nfiles * sizeof(DCELL));
+    max = G_malloc(Ref.nfiles * sizeof(DCELL));
+
+    for (n = 0; n < Ref.nfiles; n++) {
+	inbuf[n] = Rast_allocate_d_buf();
+	in_fd[n] = Rast_open_old(Ref.file[n].name, Ref.file[n].mapset);
+	if (in_fd[n] < 0)
+	    G_fatal_error(_("Error opening %s@%s"), Ref.file[n].name,
+			  Ref.file[n].mapset);
+    }
+
+    /* open seeds raster and confirm all positive integers were given */
+    if (files->seeds_map != NULL) {
+	seeds_fd = Rast_open_old(files->seeds_map, "");
+	seedsbuf = Rast_allocate_c_buf();
+	ptrsize = sizeof(CELL);
+
+	if (Rast_read_range(files->seeds_map, files->seeds_mapset, &range) != 1) {	/* returns -1 on error, 2 on empty range, quiting either way. */
+	    G_fatal_error(_("No min/max found in seeds raster map <%s>"),
+			  files->seeds_map);
+	}
+	Rast_get_range_min_max(&range, &seeds_min, &seeds_max);
+	if (seeds_min < 0)
+	    G_fatal_error(_("Seeds raster should have postive integers for starting seeds, and zero or NULL for all other pixels."));
+    }
+
+    /* Get min/max values of each input raster for scaling */
+
+    if (files->weighted == FALSE) {	/*default, we will scale */
+	for (n = 0; n < Ref.nfiles; n++) {
+	    if (Rast_read_fp_range(Ref.file[n].name, Ref.file[n].mapset, &fp_range[n]) != 1)	/* returns -1 on error, 2 on empty range, quiting either way. */
+		G_fatal_error(_("No min/max found in raster map <%s>"),
+			      Ref.file[n].name);
+	    Rast_get_fp_range_min_max(&(fp_range[n]), &min[n], &max[n]);
+	}
+    }
+
+    /* ********** find out file segmentation size ************ */
+
+    files->nbands = Ref.nfiles;
+
+    /* size of each element to be stored */
+
+    /* save for bands, plus area, perimeter, bounding box. */
+
+    inlen = sizeof(double) * (Ref.nfiles + 6);
+
+    /* when fine tuning, should be a power of 2 and not larger than 256 for speed reasons */
+    srows = 64;
+    scols = 64;
+
+    /* RAM enhancement: have user input limit and make calculations for this, reference i.cost and i.watershed 
+     * One segment tile is tile_mb = (nbands * sizeof(double) + sizeof(CELL) * srows * scols / (1024*1024) 
+     * (check if sizeof(CELL) was from when iseg would be included, or the extra overhead?)
+     * If user inputs total RAM available, need to subtract the size of the flags, and the size of the linked lists.
+     * I'm not sure how to estimate the size of the linked lists, it is allowed to grow when needed.  Assume one segment
+     * could be 50% of the map?  Or more?  So ll_mb = sizeof(pixels) * nrows * ncols * 0.5 / (1024*1024)
+     * then split the remaining RAM between bands_seg and iseg_seg. */
+
+    nseg = 16;
+
+
+    /* ******* create temporary segmentation files ********* */
+    /* Initalize access to database and create temporary files */
+
+    G_debug(1, "Image size:  %d rows, %d cols", files->nrows, files->ncols);
+    G_debug(1, "Segmented to tiles with size:  %d rows, %d cols", srows,
+	    scols);
+    G_debug(1, "Data element size, in: %d", inlen);
+    G_debug(1, "number of segments to have in memory: %d", nseg);
+
+    if (segment_open
+	(&files->bands_seg, G_tempfile(), files->nrows, files->ncols, srows,
+	 scols, inlen, nseg) != TRUE)
+	G_fatal_error("Unable to create input temporary files");
+
+    /* ******* remaining memory allocation ********* */
+
+    /* save the area and perimeter as well */
+    /* perimeter todo: currently saving this with the input DCELL values.  
+     * Better to have a second segment structure to save as integers ??? */
+    /* along with P and A, also saving the bounding box - min/max row/col */
+
+    /* Why was this being reset here??? commented out... 
+       inlen = inlen + sizeof(double) * 6; */
+
+    files->bands_val = (double *)G_malloc(inlen);
+    files->second_val = (double *)G_malloc(inlen);
+
+    if (segment_open
+	(&files->iseg_seg, G_tempfile(), files->nrows, files->ncols, srows,
+	 scols, sizeof(int), nseg) != TRUE)
+	G_fatal_error(_("Unable to allocate memory for initial segment ID's"));
+
+    /* bounds/constraints (start with processing constraints to get any possible NULL values) */
+    if (files->bounds_map != NULL) {
+	if (segment_open
+	    (&files->bounds_seg, G_tempfile(), files->nrows, files->ncols,
+	     srows, scols, sizeof(int), nseg) != TRUE)
+	    G_fatal_error(_("Unable to create bounds temporary files"));
+
+	boundsbuf = Rast_allocate_c_buf();
+	bounds_fd = Rast_open_old(files->bounds_map, files->bounds_mapset);
+
+	for (row = 0; row < files->nrows; row++) {
+	    Rast_get_c_row(bounds_fd, boundsbuf, row);
+	    for (col = 0; col < files->ncols; col++) {
+		files->bounds_val = boundsbuf[col];
+		segment_put(&files->bounds_seg, &files->bounds_val, row, col);
+		if (Rast_is_c_null_value(&boundsbuf[col]) == TRUE) {
+		    FLAG_SET(files->null_flag, row, col);
+		}
+	    }
+	}
+	Rast_close(bounds_fd);
+	G_free(boundsbuf);
+    }				/* end bounds/constraints opening */
+
+
+    /* ********  load input bands to segment structure and fill initial seg ID's ******** */
+    s = 0;			/* initial segment ID will be 1 */
+
+    for (row = 0; row < files->nrows; row++) {
+
+	/* read in rows of data (each input band from the imagery group and the optional seeds map) */
+	for (n = 0; n < Ref.nfiles; n++) {
+	    Rast_get_d_row(in_fd[n], inbuf[n], row);
+	}
+	if (files->seeds_map != NULL) {
+	    Rast_get_c_row(seeds_fd, seedsbuf, row);
+	    ptr = seedsbuf;
+	}
+
+	for (col = 0; col < files->ncols; col++) {
+	    if (FLAG_GET(files->null_flag, row, col))
+		continue;
+	    null_check = 1;	/*Assume there is data */
+	    for (n = 0; n < Ref.nfiles; n++) {
+		if (Rast_is_d_null_value(&inbuf[n][col]))
+		    null_check = -1;
+		if (files->weighted == TRUE)
+		    files->bands_val[n] = inbuf[n][col];	/*unscaled */
+		else
+		    files->bands_val[n] = (inbuf[n][col] - min[n]) / (max[n] - min[n]);	/* scaled */
+	    }
+
+	    /* besides the user input rasters, also save the shape parameters */
+
+	    files->bands_val[Ref.nfiles] = 1;	/* area (just using the number of pixels) */
+	    files->bands_val[Ref.nfiles + 1] = 4;	/* Perimeter Length *//* todo perimeter, not exact for edges...close enough for now? */
+	    files->bands_val[Ref.nfiles + 2] = col;	/*max col */
+	    files->bands_val[Ref.nfiles + 3] = col;	/*min col */
+	    files->bands_val[Ref.nfiles + 4] = row;	/*max row */
+	    files->bands_val[Ref.nfiles + 5] = row;	/*min row */
+
+	    segment_put(&files->bands_seg, (void *)files->bands_val, row, col);	/* store input bands */
+	    if (null_check != -1) {	/*good pixel */
+		FLAG_UNSET(files->null_flag, row, col);	/*flag */
+		if (files->seeds_map != NULL) {
+		    if (Rast_is_c_null_value(ptr) == TRUE) {
+			/* when using iseg_seg the segmentation file is already initialized to zero.  Just initialize seeds_flag: */
+			FLAG_UNSET(files->seeds_flag, row, col);
+		    }
+		    else {
+			FLAG_SET(files->seeds_flag, row, col);	/* RAM enhancement, but it might cost speed.  Could look for seg ID > 0 instead of using seed_flag. */
+			/* seed value is starting segment ID. */
+			segment_put(&files->iseg_seg, ptr, row, col);
+		    }
+		    ptr = G_incr_void_ptr(ptr, ptrsize);
+		}
+		else {		/* no seeds provided */
+		    s++;	/* sequentially number all pixels with their own segment ID */
+		    if (s < INT_MAX) {	/* Check that the starting seeds aren't too large. */
+			segment_put(&files->iseg_seg, &s, row, col);	/*starting segment number */
+			FLAG_SET(files->seeds_flag, row, col);	/*all pixels are seeds */
+		    }
+		    else
+			G_fatal_error(_("Exceeded integer storage limit, too many initial pixels."));
+		}
+	    }
+	    else {		/*don't use this pixel */
+		FLAG_SET(files->null_flag, row, col);	/*flag */
+	    }
+	}
+    }
+
+    /* keep original copy of null flag if we have boundary constraints */
+    if (files->bounds_map != NULL) {
+	for (row = 0; row < files->nrows; row++) {
+	    for (col = 0; col < files->ncols; col++) {
+		if (FLAG_GET(files->null_flag, row, col))
+		    FLAG_SET(files->orig_null_flag, row, col);
+		else
+		    FLAG_UNSET(files->orig_null_flag, row, col);
+	    }
+	}
+    }				/* end: if (files->bounds_map != NULL) */
+
+    /* linked list memory management linkm */
+    link_set_chunk_size(1000);	/* TODO RAM: fine tune this number */
+
+    files->token = link_init(sizeof(struct pixels));
+
+    /* if we have seeds that are segments (not pixels) we need to update the bands_seg */
+    /* also renumber the segment ID's in case they were classified 
+     * (duplicating numbers) instead of output from i.segment. */
+    if (files->seeds_map != NULL) {
+
+	/*initialization */
+	files->minrow = files->mincol = 0;
+	files->maxrow = files->nrows;
+	files->maxcol = files->ncols;
+	R_count = 1;
+	R_head = NULL;
+	Rn_head = NULL;
+	newpixel = NULL;
+	current = NULL;
+	set_all_candidate_flags(files);
+	for (row = 0; row < files->nrows; row++) {
+	    G_percent(row, files->nrows, 1);	/* I think this is the longest part of open_files()
+						 * - not entirely accurate for the actual %,
+						 *  but will give the user something to see. */
+	    for (col = 0; col < files->ncols; col++) {
+		if (!(FLAG_GET(files->candidate_flag, row, col)) ||
+		    FLAG_GET(files->null_flag, row, col))
+		    continue;
+		/*start R_head */
+		newpixel = (struct pixels *)link_new(files->token);
+		newpixel->next = NULL;
+		newpixel->row = row;
+		newpixel->col = col;
+		R_head = newpixel;
+
+		/* get pixel list, possible initialization speed enhancement: 
+		 * could use a custom (shorter) function, some results from 
+		 * find_segment_neighbors are not used here */
+		/* bug todo: There is a small chance that a renumbered segment 
+		 * matches and borders an original segment.  This would be a 
+		 * good reason to write a custom function - use the candidate 
+		 * flag to see if the pixel was already processed. */
+		borderPixels =
+		    find_segment_neighbors(&R_head, &Rn_head, &R_count, files,
+					   functions);
+
+		/* update the segment ID */
+
+		s++;
+		if (s == INT_MAX)	/* Check that the starting seeds aren't too large. */
+		    G_fatal_error(_("Exceeded integer storage limit, too many initial pixels."));
+
+		for (current = R_head; current != NULL;
+		     current = current->next) {
+		    segment_put(&files->iseg_seg, &s, current->row,
+				current->col);
+		    FLAG_UNSET(files->candidate_flag, current->row,
+			       current->col);
+		}
+
+		/*merge pixels (updates the bands_seg) */
+		merge_pixels(R_head, borderPixels, files);
+
+		/*todo calculate perimeter (?and area?) here? */
+
+		/*clean up */
+		my_dispose_list(files->token, &R_head);
+		my_dispose_list(files->token, &Rn_head);
+		R_count = 1;
+	    }
+	}
+    }
+
+    files->nsegs = s;
+
+    /* Free memory */
+
+    for (n = 0; n < Ref.nfiles; n++) {
+	G_free(inbuf[n]);
+	Rast_close(in_fd[n]);
+    }
+
+    if (files->seeds_map != NULL) {
+	Rast_close(seeds_fd);
+	G_free(seedsbuf);
+    }
+
+    G_free(inbuf);
+    G_free(in_fd);
+    G_free(fp_range);
+    G_free(min);
+    G_free(max);
+
+    return TRUE;
+}

+ 293 - 0
imagery/i.segment/parse_args.c

@@ -0,0 +1,293 @@
+/* PURPOSE:      Parse and validate the input */
+
+#include <stdlib.h>
+#include <string.h>
+#include <grass/gis.h>
+#include <grass/glocale.h>
+#include <grass/raster.h>
+#include "iseg.h"
+
+int parse_args(int argc, char *argv[], struct files *files,
+	       struct functions *functions)
+{
+    struct Option *group, *seeds, *bounds, *output, *method, *similarity, *threshold, *min_segment_size, *endt;	/* Establish an Option pointer for each option */
+    struct Option *radio_weight, *smooth_weight;
+    struct Flag *estimate_threshold, *diagonal, *weighted, *limited, *final;	/* Establish a Flag pointer for each option */
+    struct Option *outband;	/* optional saving of segment data, until a seperate module is written */
+
+#ifdef VCLOSE
+    struct Option *very_close;
+#endif
+
+    /* required parameters */
+    /* enhancement: consider giving the option to process just one
+     * raster directly, without creating an image group. */
+    group = G_define_standard_option(G_OPT_I_GROUP);
+
+    output = G_define_standard_option(G_OPT_R_OUTPUT);
+
+    threshold = G_define_option();
+    threshold->key = "threshold";
+    threshold->type = TYPE_DOUBLE;
+    threshold->required = YES;
+    threshold->description = _("Similarity threshold.");
+
+    method = G_define_option();
+    method->key = "method";
+    method->type = TYPE_STRING;
+    method->required = YES;
+    method->answer = "region_growing";
+    method->options = "region_growing";
+    method->description = _("Segmentation method.");
+
+    similarity = G_define_option();
+    similarity->key = "similarity";
+    similarity->type = TYPE_STRING;
+    similarity->required = YES;
+    similarity->answer = "euclidean";
+    similarity->options = "euclidean, manhattan";
+    similarity->description = _("Similarity calculation method.");
+
+    min_segment_size = G_define_option();
+    min_segment_size->key = "minsize";
+    min_segment_size->type = TYPE_INTEGER;
+    min_segment_size->required = YES;
+    min_segment_size->answer = "1";	/* default: no merges, a minimum of 1 pixel is allowed in a segment. */
+    min_segment_size->options = "1-100000";
+    min_segment_size->label = _("Minimum number of cells in a segment.");
+    min_segment_size->description =
+	_("The final iteration will ignore the threshold for any segments with fewer pixels.");
+
+#ifdef VCLOSE
+    very_close = G_define_option();
+    very_close->key = "very_close";
+    very_close->type = TYPE_DOUBLE;
+    very_close->required = YES;
+    very_close->answer = "0";
+    very_close->options = "0-1";
+    very_close->label = _("Fraction of the threshold.");
+    very_close->description =
+	_("Neighbors similarity lower then this fraction of the threshold will be merged without regard to any other processing rules.");
+#endif
+
+    /* for the weights of bands values vs. shape parameters, and weight of smoothness vs. compactness */
+
+    radio_weight = G_define_option();
+    radio_weight->key = "radioweight";
+    radio_weight->type = TYPE_DOUBLE;
+    radio_weight->required = YES;
+    radio_weight->answer = "0.9";
+    radio_weight->options = "0-1";
+    radio_weight->label =
+	_("Importance of radiometric (input raseters) values relative to shape");
+
+    smooth_weight = G_define_option();
+    smooth_weight->key = "smoothweight";
+    smooth_weight->type = TYPE_DOUBLE;
+    smooth_weight->required = YES;
+    smooth_weight->answer = "0.5";
+    smooth_weight->options = "0-1";
+    smooth_weight->label =
+	_("Importance of smoothness relative to compactness");
+
+    /* optional parameters */
+
+    estimate_threshold = G_define_flag();
+    estimate_threshold->key = 't';
+    estimate_threshold->description =
+	_("Estimate a threshold based on input image group and exit.");
+
+    diagonal = G_define_flag();
+    diagonal->key = 'd';
+    diagonal->description =
+	_("Use 8 neighbors (3x3 neighborhood) instead of the default 4 neighbors for each pixel.");
+
+    weighted = G_define_flag();
+    weighted->key = 'w';
+    weighted->description =
+	_("Weighted input, don't perform the default scaling of input maps.");
+
+    final = G_define_flag();
+    final->key = 'f';
+    final->description =
+	_("Final forced merge only (skip the growing portion of the algorithm.");
+
+    /* Raster for initial segment seeds */
+    /* future enhancement: allow vector points/centroids for seed input. */
+    seeds = G_define_standard_option(G_OPT_R_INPUT);
+    seeds->key = "seeds";
+    seeds->required = NO;
+    seeds->label = _("Optional raster map with starting seeds.");
+    seeds->description =
+	_("Pixel values with positive integers are used as starting seeds.");
+
+    /* Polygon constraints. */
+    bounds = G_define_standard_option(G_OPT_R_INPUT);
+    bounds->key = "bounds";
+    bounds->required = NO;
+    bounds->label = _("Optional bounding/constraining raster map");
+    bounds->description =
+	_("Pixels with the same integer value will be segmented independent of the others.");
+
+    /* other parameters */
+    endt = G_define_option();
+    endt->key = "endt";
+    endt->type = TYPE_INTEGER;
+    endt->required = NO;
+    endt->answer = "1000";
+    endt->description =
+	_("Maximum number of passes (time steps) to complete.");
+
+    /* Leaving path flag out of user options, will hardcode TRUE for 
+       this option.  This does change the resulting segments, but I 
+       don't see any reason why one version is more valid.  It 
+       reduced the processing time by 50% when segmenting the ortho 
+       image. Code in the segmenation algorithm remains, in case more 
+       validation of this option should be done. */
+    /*
+       path = G_define_flag();
+       path->key = 'p';
+       path->description =
+       _("temporary option, pathflag, select to use Rk as next Ri if not mutually best neighbors.");
+     */
+    limited = G_define_flag();
+    limited->key = 'l';
+    limited->description =
+	_("segments are limited to be included in only one merge per pass");
+
+    outband = G_define_standard_option(G_OPT_R_OUTPUT);
+    outband->key = "final_mean";
+    outband->required = NO;
+    outband->description =
+	_("Save the final mean values for the first band in the imagery group.");
+    /* enhancement: save mean values for all bands.  Multiple rasters or switch to polygons? */
+
+
+    if (G_parser(argc, argv))
+	exit(EXIT_FAILURE);
+
+    /* Validation */
+
+    /* Check and save parameters */
+
+    files->image_group = group->answer;
+
+    functions->estimate_threshold = estimate_threshold->answer;
+
+    /* if we are just estimating a threshold, skip remaining input validation */
+    if (functions->estimate_threshold == TRUE)
+	return TRUE;
+
+    if (G_legal_filename(output->answer) == TRUE)
+	files->out_name = output->answer;	/* name of output (segment ID) raster map */
+    else
+	G_fatal_error(_("Invalid output raster name."));
+
+    functions->threshold = atof(threshold->answer);	/* Note: this threshold is scaled after we know more at the beginning of create_isegs() */
+
+    if (weighted->answer == FALSE &&
+	(functions->threshold <= 0 || functions->threshold >= 1))
+	G_fatal_error(_("threshold should be >= 0 and <= 1"));
+
+    /* segmentation methods: 1 = region growing */
+    if (strncmp(method->answer, "region_growing", 10) == 0)
+	functions->method = 1;
+    else
+	G_fatal_error(_("Couldn't assign segmentation method."));	/*shouldn't be able to get here */
+
+    /* distance methods for similarity measurement */
+    if (strncmp(similarity->answer, "euclidean", 5) == 0)
+	functions->calculate_similarity = &calculate_euclidean_similarity;
+    else if (strncmp(similarity->answer, "manhattan", 5) == 0)
+	functions->calculate_similarity = &calculate_manhattan_similarity;
+    else
+	G_fatal_error(_("Couldn't assign similarity method."));	/*shouldn't be able to get here */
+
+#ifdef VCLOSE
+    functions->very_close = atof(very_close->answer);
+#endif
+
+    functions->min_segment_size = atoi(min_segment_size->answer);
+
+    functions->radio_weight = atof(radio_weight->answer);
+    functions->smooth_weight = atof(smooth_weight->answer);
+
+    if (diagonal->answer == FALSE) {
+	functions->find_pixel_neighbors = &find_four_pixel_neighbors;
+	functions->num_pn = 4;
+    }
+    else if (diagonal->answer == TRUE) {
+	functions->find_pixel_neighbors = &find_eight_pixel_neighbors;
+	functions->num_pn = 8;
+    }
+    /* speed enhancement: Check if function pointer or IF statement is faster */
+
+    /* default/0 for performing the scaling, but selected/1 if 
+     * user has weighted values so scaling should be skipped. */
+    files->weighted = weighted->answer;
+
+	functions->final_merge_only = final->answer;
+
+    /* check if optional seeds map was given, if not, use all pixels as starting seeds. */
+    if (seeds->answer == NULL) {
+	files->seeds_map = NULL;
+    }
+    else {			/* seeds provided, check if valid map */
+	files->seeds_map = seeds->answer;
+	if ((files->seeds_mapset =
+	     G_find_raster2(files->seeds_map, "")) == NULL) {
+	    G_fatal_error(_("Starting seeds map not found."));
+	}
+	if (Rast_map_type(files->seeds_map, files->seeds_mapset) != CELL_TYPE) {
+	    G_fatal_error(_("Starting seeds map must be CELL type (integers)"));
+	}
+    }
+
+    /* check if optional processing boundaries were provided */
+    if (bounds->answer == NULL) {	/*no processing constraints */
+	files->bounds_map = NULL;
+    }
+    else {			/* processing constraints given, check if valid map */
+	files->bounds_map = bounds->answer;
+	if ((files->bounds_mapset =
+	     G_find_raster2(files->bounds_map, "")) == NULL) {
+	    G_fatal_error(_("Segmentation constraint/boundary map not found."));
+	}
+	if (Rast_map_type(files->bounds_map, files->bounds_mapset) !=
+	    CELL_TYPE) {
+	    G_fatal_error(_("Segmentation constraint map must be CELL type (integers)"));
+	}
+    }
+
+    /* other data */
+    files->nrows = Rast_window_rows();
+    files->ncols = Rast_window_cols();
+
+    if (endt->answer != NULL && atoi(endt->answer) >= 0)
+	functions->end_t = atoi(endt->answer);
+    else {
+	functions->end_t = 1000;
+	G_warning(_("invalid number of iterations, 1000 will be used."));
+    }
+
+    /* other parameters */
+
+    /* default/0 for no pathflag, but selected/1 to use 
+     * Rk as next Ri if not mutually best neighbors. */
+    /* functions->path = path->answer; */
+    functions->path = TRUE;
+    /* see notes above about pathflag. */
+
+    functions->limited = limited->answer;
+
+    if (outband->answer == NULL)
+	files->out_band = NULL;
+    else {
+	if (G_legal_filename(outband->answer) == TRUE)
+	    files->out_band = outband->answer;	/* name of current means */
+	else
+	    G_fatal_error(_("Invalid output raster name for means."));
+    }
+
+    return TRUE;
+}

+ 94 - 0
imagery/i.segment/write_output.c

@@ -0,0 +1,94 @@
+/* write_output(): transfer the segmented regions from the segmented data file to a raster file */
+/* close_files(): close SEG files and free memory */
+
+#include <stdlib.h>
+#include <grass/gis.h>
+#include <grass/raster.h>
+#include <grass/segment.h>	/* segmentation library */
+#include "iseg.h"
+
+int write_output(struct files *files)
+{
+    int out_fd, mean_fd, row, col;	/* mean_fd for validiating/debug of means */
+    CELL *outbuf;
+    DCELL *meanbuf;
+    struct Colors colors;
+    struct History history;
+
+
+    outbuf = Rast_allocate_c_buf();	/* hold one row of data to put into raster */
+    meanbuf = Rast_allocate_d_buf();
+
+    /* force all data to disk */
+    segment_flush(&files->bands_seg);
+    segment_flush(&files->iseg_seg);
+
+    /* open output raster map */
+    out_fd = Rast_open_new(files->out_name, CELL_TYPE);
+    if (files->out_band != NULL)
+	mean_fd = Rast_open_new(files->out_band, DCELL_TYPE);
+
+    /* transfer data from segmentation file to raster */
+    for (row = 0; row < files->nrows; row++) {
+	Rast_set_c_null_value(outbuf, files->ncols);	/*set buffer to NULLs, only write those that weren't originally masked */
+	Rast_set_d_null_value(meanbuf, files->ncols);
+	for (col = 0; col < files->ncols; col++) {
+	    segment_get(&files->bands_seg, (void *)files->bands_val, row,
+			col);
+	    if (!(FLAG_GET(files->null_flag, row, col))) {
+		segment_get(&files->iseg_seg, &(outbuf[col]), row, col);
+		meanbuf[col] = files->bands_val[0];
+	    }
+	}
+	Rast_put_row(out_fd, outbuf, CELL_TYPE);
+	if (files->out_band != NULL)
+	    Rast_put_row(mean_fd, meanbuf, DCELL_TYPE);
+
+	G_percent(row, files->nrows, 1);
+    }
+
+    /* close and save file */
+    Rast_close(out_fd);
+    if (files->out_band != NULL)
+	Rast_close(mean_fd);
+
+    /* set colors */
+    Rast_init_colors(&colors);
+    Rast_make_random_colors(&colors, 1, files->nrows * files->ncols);
+    Rast_write_colors(files->out_name, G_mapset(), &colors);
+
+    /* add command line to history */
+    /* see i.pca as an example of setting custom info */
+    Rast_short_history(files->out_name, "raster", &history);
+    Rast_command_history(&history);
+    Rast_write_history(files->out_name, &history);
+
+    /* free memory */
+    G_free(outbuf);
+    G_free(meanbuf);
+    Rast_free_colors(&colors);
+
+    return TRUE;
+}
+
+int close_files(struct files *files)
+{
+
+    /* close segmentation files and output raster */
+    segment_close(&files->bands_seg);
+    if (files->bounds_map != NULL)
+	segment_close(&files->bounds_seg);
+
+    G_free(files->bands_val);
+    G_free(files->second_val);
+
+    segment_close(&files->iseg_seg);
+
+    flag_destroy(files->null_flag);
+    flag_destroy(files->candidate_flag);
+    flag_destroy(files->seeds_flag);
+
+    link_cleanup(files->token);
+
+    return TRUE;
+}