示例#1
0
/*!
* \return	Distance object or null on error.
* \ingroup	WlzFeatures
* \brief	Computes a distance object where all distances are from
* 		the boundary of the given reference object.
* \param	refObj			Given reference object, known to
* 					be valid.
* \param	dstErr			Destination error pointer, may be NULL.
*/
static WlzObject		*WlzInteriorityCompDisObj(
				  WlzObject *refObj,
				  WlzErrorNum *dstErr)
{
  WlzObject	*dis = NULL,
  		*bndObj = NULL;
  WlzErrorNum	errNum = WLZ_ERR_NONE;

  bndObj = WlzAssignObject(WlzBoundaryDomain(refObj, &errNum), NULL);
  if(errNum == WLZ_ERR_NONE)
  {
    dis = WlzDistanceTransform(refObj, bndObj, WLZ_OCTAGONAL_DISTANCE,
			       0.0, 0.0, &errNum);
  }
  (void )WlzFreeObj(bndObj);
  if(dstErr)
  {
    *dstErr = errNum;
  }
  return(dis);
}
示例#2
0
/*!
* \return	Woolz error code.
* \ingroup	WlzBinaryOps
* \brief	Computes a metric which quantifies the extent to which
* 		the domain of one of the given objects is offset from 
* 		the domain of the second. This is a symetric metric, ie
* 		\f$OST(\Omega_0, \Omega_1) \equiv OST(\Omega_0, \Omega_1)\f$.
*		A domain is computed which is equidistant from the domains
*		of the two given objects, is within maxDist of each object's
*		domain and is within the convex hull of the union of the
*		domains of the two given objects. Within this domain the
*		1st, 2nd and 3rd quantiles of the distance
*		(\f$q_0\f$, \f$q_1\f$ and \f$q_2\f$) are found.
*		The object's domains are classified as offset if
*		\f[
		frac{q_1}{q_1 + (q_1  - q_0) + (q_2 - q_1)} \geq 0.5
		\f]
*		ie
*		\f[
		frac{q_1}{q_1 + q_2 - q_0} \geq 0.5
		\f]
*		Small equi-distant domains with a volume less than half
*		the maximum distance do not classify the relationship as
*		an overlap.
* \param	o			Array with the two given spatial
* 					domain objects, must not be NULL
* 					and nor must the objects.
* \param	t			Array of temporary objects as in
* 					WlzRCCTOIdx.
* \param	maxDist			Maximum distance for offset. This
* 					is used to compute a distance object,
* 					large distances will significantly
* 					increase the processing time.
* \param	dQ0			Destination pointer for 1st quantile
* 					offset distance, must not be NULL.
* \param	dQ1			Destination pointer for 2nd quantile
* 					(ie median) offset distance,
* 					must not be NULL.
* \param	dQ2			Destination pointer for 3rd quantile
* 					offset distance, must not be NULL.
*/
static WlzErrorNum		WlzRCCOffset(
				  WlzObject **o,
				  WlzObject **t,
				  int maxDist,
				  int *dQ0,
				  int *dQ1,
				  int *dQ2)
{
  int		empty = 0;
  int		q[3] = {0};
  int		*dHist = NULL;
  WlzObject	*eObj = NULL;
  WlzErrorNum	errNum = WLZ_ERR_NONE;

  if(((o[0]->type == WLZ_2D_DOMAINOBJ) &&
      (o[1]->type == WLZ_2D_DOMAINOBJ)) ||
     ((o[0]->type == WLZ_3D_DOMAINOBJ) &&
      (o[1]->type == WLZ_3D_DOMAINOBJ)))
  {
    if((o[0]->domain.core == NULL) || (o[1]->domain.core == NULL))
    {
      errNum = WLZ_ERR_DOMAIN_NULL;
    }
  }
  else
  {
    errNum = WLZ_ERR_OBJECT_TYPE;
  }
  /* Compute distance transforms of the two given objects out to a given
   * maximum distance and then using these distances the equi-distant
   * domain between these two objects. The values of the eqi-distant object
   * are those of the distance between the objects.*/
  if(errNum == WLZ_ERR_NONE)
  {
    int		i;
    WlzObject	*sObj = NULL,
    		*cObj = NULL;
    WlzObject	*dObj[2];

    dObj[0] = dObj[1] = NULL;
    /* Create structuring element with which to dilate the given object
     * domains(by maxDist). */
    sObj = WlzAssignObject(
	   WlzMakeSphereObject(o[0]->type, maxDist, 0, 0, 0,
	                       &errNum), NULL);
    /* Create domain or convex hull of the union of the two given object
     * domains. */
    if(errNum == WLZ_ERR_NONE)
    {
      WlzObject	*uObj = NULL,
      		*xObj = NULL;

      errNum = WlzRCCMakeT(o, t, WLZ_RCCTOIDX_O0O1U);
      if(errNum == WLZ_ERR_NONE)
      {
	uObj = WlzAssignObject(t[WLZ_RCCTOIDX_O0O1U], NULL);
      }
      if(errNum == WLZ_ERR_NONE)
      {
        xObj = WlzAssignObject(
	       WlzObjToConvexHull(uObj, &errNum), NULL);
      }
      if(errNum == WLZ_ERR_NONE)
      {
        cObj = WlzAssignObject(
	       WlzConvexHullToObj(xObj, o[0]->type, &errNum), NULL);
      }
      (void )WlzFreeObj(xObj);
      (void )WlzFreeObj(uObj);
    }
    /* Dilate the two given objects and find the ntersection of the
     * dilated domains with each other and the convex hull computed
     * above. Within his domain compute the distances. */
    if(errNum == WLZ_ERR_NONE)
    {
      for(i = 0; i < 2; ++i)
      {
	WlzObject *tObj = NULL,
		  *rObj = NULL;

	tObj = WlzAssignObject(
	       WlzStructDilation(o[i], sObj, &errNum), NULL);
        if(errNum == WLZ_ERR_NONE)
	{
	  rObj = WlzAssignObject(
	         WlzIntersect2(tObj, cObj, &errNum), NULL);
	}
        (void )WlzFreeObj(tObj);
        if(errNum == WLZ_ERR_NONE)
	{
	  dObj[i] = WlzAssignObject(
		    WlzDistanceTransform(rObj, o[!i],
					 WLZ_OCTAGONAL_DISTANCE,
					 0.0, maxDist, &errNum), NULL);
	}
        (void )WlzFreeObj(rObj);
        if(errNum == WLZ_ERR_NONE)
	{
	  WlzPixelV bgdV;

	  bgdV.type = WLZ_GREY_INT;
	  bgdV.v.inv = maxDist;
	  errNum = WlzSetBackground(dObj[i], bgdV);
	}
        if(errNum != WLZ_ERR_NONE)
	{
	  break;
	}
      }
    }
    /* Find the domain which is equi-distant from the two given objects,
     * within the xDist range and within the convex hull of the union of
     * the two given object's domains. */
    (void )WlzFreeObj(sObj); sObj = NULL;
    if(errNum == WLZ_ERR_NONE)
    {
      WlzLong	vol = 0;
      WlzObject *qObj = NULL,
      		*tObj = NULL;

      qObj = WlzAssignObject(
             WlzImageArithmetic(dObj[0], dObj[1], WLZ_BO_EQ, 0, &errNum), NULL);
      if(errNum == WLZ_ERR_NONE)
      {
	WlzPixelV thrV;

	thrV.type = WLZ_GREY_INT;
	thrV.v.inv = 1;
        tObj = WlzAssignObject(
	       WlzThreshold(qObj, thrV, WLZ_THRESH_HIGH, &errNum), NULL);
      }
      /* Check that the eqi-distant domain is of a reasonable size ie has
       * a area or volume greater than half the maximum distance. */
      if(errNum == WLZ_ERR_NONE)
      {
        vol = WlzVolume(tObj, &errNum);
	if((maxDist / 2) >= vol)
	{
	  empty = 1;
	}
      }
      if((errNum == WLZ_ERR_NONE) && !empty)
      {
	WlzObject *mObj;
	WlzPixelV tmpV;

	tmpV.type = WLZ_GREY_INT;
	tmpV.v.inv = 0;
        mObj = WlzAssignObject(
	       WlzGreyTemplate(dObj[0], tObj, tmpV, &errNum), NULL);
        if(errNum == WLZ_ERR_NONE)
	{
	  tmpV.v.inv = 1;
	  eObj = WlzAssignObject(
	  	WlzThreshold(mObj, tmpV, WLZ_THRESH_HIGH, &errNum), NULL);
	}
	(void )WlzFreeObj(mObj);
      }
      (void )WlzFreeObj(tObj);
      (void )WlzFreeObj(qObj);
      if((errNum == WLZ_ERR_NONE) && !empty)
      {
	WlzLong vol;

	vol = WlzVolume(eObj, &errNum);
	if((maxDist / 2) >= vol)
	{
	  empty = 1;
	}
      }
    }
    (void )WlzFreeObj(cObj);
    (void )WlzFreeObj(sObj);
    (void )WlzFreeObj(dObj[0]);
    (void )WlzFreeObj(dObj[1]);
  }
  /* Compute a quantised distance histogram in which equi-distant distances
   * are quantized to integer values. */
  if((errNum == WLZ_ERR_NONE) && !empty)
  {
    if((dHist = (int *)AlcCalloc(maxDist + 1, sizeof(int))) == NULL)
    {
      errNum = WLZ_ERR_MEM_ALLOC;
    }
  }
  if((errNum == WLZ_ERR_NONE) && !empty)
  {
    if(eObj->type == WLZ_2D_DOMAINOBJ)
    {
      errNum = WlzRCCCompDistHist2D(maxDist, dHist, eObj);
    }
    else
    {
      errNum = WlzRCCCompDistHist3D(maxDist, dHist, eObj);
    }
#ifdef WLZ_RCC_DEBUG_OST
    {
      FILE *fP;

      fP = fopen("WLZ_RCC_DEBUG_OST.wlz", "w");
      (void )WlzWriteObj(fP, eObj);
      (void )fclose(fP);
    }
#endif
  }
  WlzFreeObj(eObj);
  if((errNum == WLZ_ERR_NONE) && !empty)
  {
    int		i,
    		n,
		s,
		nq;
    /* Compute the median, first and third quantile offset distances,
     * the ratio of median to the median plus inner inter-quantile range. */
    n = 0;
    for(i = 0; i < maxDist; ++i)
    {
      n += dHist[i];
    }
    i = 0;
    s = 0;
    nq = n / 4;
    while(s < nq)
    {
      s += dHist[i++];
    }
    q[0] = i;
    nq = n / 2;
    while(s < nq)
    {
      s += dHist[i++];
    }
    q[1] = i;
    nq = (3 * n) / 4;
    while(s < nq)
    {
      s += dHist[i++];
    }
    q[2] = i;
  }
  AlcFree(dHist);
  *dQ0 = q[0];
  *dQ1 = q[1];
  *dQ2 = q[2];
  return(errNum);
}
示例#3
0
int		main(int argc, char *argv[])
{
  int		ok,
		bnd = 0,
  		con,
  		option,
		usage = 0;
  char		*inFileStr,
  		*forFileStr,
  		*refFileStr,
		*outFileStr;
  double	dParam = 10.0;
  FILE		*fP = NULL;
  WlzObject	*tObj0,
  		*tObj1,
		*forObj = NULL,
		*refObj = NULL,
		*dstObj = NULL;
  WlzDistanceType dFn;
  WlzErrorNum	errNum = WLZ_ERR_NONE;
  const char	*errMsgStr;
  static char	optList[] = "bhd:f:p:o:";
  const char    fileStrDef[] = "-";
  const WlzConnectType defDFn = WLZ_OCTAGONAL_DISTANCE;

  /* Parse the argument list and check for input files. */
  opterr = 0;
  inFileStr = (char *)fileStrDef;
  forFileStr = (char *)fileStrDef;
  outFileStr = (char *)fileStrDef;
  dFn = defDFn;
  while((option = getopt(argc, argv, optList)) != EOF)
  {
    switch(option)
    {
      case 'b':
        bnd = 1;
	break;
      case 'd':
	if(sscanf(optarg, "%d", &con) != 1)
	{
	  usage = 1;
	}
	else
	{
	  switch(con)
	  {
	    case 0:
	      dFn = WLZ_EUCLIDEAN_DISTANCE;
	      break;
	    case 1:
	      dFn = WLZ_OCTAGONAL_DISTANCE;
	      break;
	    case 2:
	      dFn = WLZ_APX_EUCLIDEAN_DISTANCE;
	      break;
	    case 4:
	      dFn = WLZ_4_DISTANCE;
	      break;
	    case 6:
	      dFn = WLZ_6_DISTANCE;
	      break;
	    case 8:
	      dFn = WLZ_8_DISTANCE;
	      break;
	    case 18:
	      dFn = WLZ_18_DISTANCE;
	      break;
	    case 26:
	      dFn = WLZ_26_DISTANCE;
	      break;
	    default:
	      usage = 1;
	      break;
	  }
	}
	break;
      case 'f':
	forFileStr = optarg;
	break;
      case 'p':
	if(sscanf(optarg, "%lg", &dParam) != 1)
	{
	  usage = 1;
	}
	break;
      case 'o':
	outFileStr = optarg;
	break;
      case 'h':
      default:
	usage = 1;
	break;
    }
  }
  ok = !usage;
  if(ok && (optind < argc))
  {
    if((optind + 1) != argc)
    {
      usage = 1;
      ok = 0;
    }
    else
    {
      refFileStr = argv[optind];
    }
  }
  /* Read the reference object. */
  if(ok)
  {
    if((refFileStr == NULL) ||
       (*refFileStr == '\0') ||
       ((fP = (strcmp(refFileStr, "-")?
              fopen(refFileStr, "r"): stdin)) == NULL) ||
       ((refObj = WlzAssignObject(WlzReadObj(fP, &errNum), NULL)) == NULL))
    {
      ok = 0;
      (void )fprintf(stderr,
                     "%s: Failed to read reference object from file %s.\n",
                     argv[0], refFileStr);
    }
    if(fP && strcmp(refFileStr, "-"))
    {
      (void )fclose(fP); fP = NULL;
    }
  }
  /* Read the foreground object. */
  if(ok)
  {
    if((forFileStr == NULL) ||
       (*forFileStr == '\0') ||
       ((fP = (strcmp(forFileStr, "-")?
              fopen(forFileStr, "r"): stdin)) == NULL) ||
       ((forObj = WlzAssignObject(WlzReadObj(fP, &errNum), NULL)) == NULL))
    {
      ok = 0;
      (void )fprintf(stderr,
                     "%s: Failed to read foreground object from file %s.\n",
                     argv[0], forFileStr);
    }
    if(fP && strcmp(forFileStr, "-"))
    {
      (void )fclose(fP); fP = NULL;
    }
  }
  /* Check object types and parse the promote the default distance function
   * if required. */
  if(ok)
  {
    if(refObj->type != forObj->type)
    {
      ok = 0;
      (void )fprintf(stderr,
                     "%s: Foreground and reference object types differ.\n",
		     argv[0]);
    }
  }
  if((errNum == WLZ_ERR_NONE) && bnd)
  {
    tObj0 = tObj1 = NULL;
    tObj0 = WlzObjToBoundary(refObj, 1, &errNum);
    if(errNum == WLZ_ERR_NONE)
    {
      tObj1 = WlzBoundaryToObj(tObj0, WLZ_VERTEX_FILL, &errNum);
    }
    if(errNum == WLZ_ERR_NONE)
    {
      (void )WlzFreeObj(refObj);
      refObj = tObj1;
      tObj1 = NULL;
    }
    (void )WlzFreeObj(tObj0);
    (void )WlzFreeObj(tObj1);
  }
  /* Compute the distance transform object. */
  if(ok)
  {
    dstObj = WlzAssignObject(
    	     WlzDistanceTransform(forObj, refObj, dFn, dParam, &errNum), NULL);
    if(errNum != WLZ_ERR_NONE)
    {
      ok = 0;
      (void )WlzStringFromErrorNum(errNum, &errMsgStr);
      (void )fprintf(stderr,
      		     "%s: Failed to compute distance object, %s.\n",
		     argv[0], errMsgStr);
    }
  }
  /* Output the distance transform object. */
  if(ok)
  {
    if((fP = (strcmp(outFileStr, "-")?
             fopen(outFileStr, "w"): stdout)) == NULL)
    {
      ok = 0;
      (void )fprintf(stderr,
                     "%s: Failed to open output file %s.\n",
                     argv[0], outFileStr);
    }
  }
  if(ok)
  {
    errNum = WlzWriteObj(fP, dstObj);
    if(errNum != WLZ_ERR_NONE)
    {
      ok = 0;
      (void )WlzStringFromErrorNum(errNum, &errMsgStr);
      (void )fprintf(stderr,
                     "%s: Failed to write output object, %s.\n",
                     argv[0], errMsgStr);
    }
  }
  (void )WlzFreeObj(forObj);
  (void )WlzFreeObj(refObj);
  (void )WlzFreeObj(dstObj);
  if(usage)
  {
    (void )fprintf(stderr,
    "Usage: %s [-b] [-d#] [-f#] [-p#] [-o#] [-h] [<Reference input file>]\n"
    "Computes a distance transform object which has the domain of the\n"
    "foreground object and values which are the distance from the reference\n"
    "object.\n"
    "Options are:\n"
    "  -b  Use the boundary of the reference object.\n"
    "  -d  Distance function:\n"
    "              0: Euclidean (2D and 3D, unimplemented)\n"
    "              1: octagonal (2D and 3D) - default\n"
    "              2: approximate Euclidean (2D and 3D)\n"
    "              4: 4-connected (2D)\n"
    "              8: 8-connected (2D)\n"
    "              6: 6-connected (3D)\n"
    "             18: 18-connected (3D)\n"
    "             26: 26-connected (3D)\n"
    "  -f  The foreground object file.\n" 
    "  -p  Distance function parameter, which is curently only used for\n"
    "      approximate Euclidean distance transforms. The value should\n"
    "      be >= 1, with larger values giving greater accuracy at the\n"
    "      cost of increased time. Default value - 10.0.\n"
    "  -o  Output object file.\n"
    "  -h  Help - prints this usage message\n",
    argv[0]);
  }
  return(!ok);
}
示例#4
0
int             main(int argc, char *argv[])
{
  int		option,
		nReg = 0,
		tNReg = 0,
  		ok = 1,
		usage = 0,
		verbose = 0,
		threshSet = 0,
		centreSet = 0;
  double	minArea = 2;
  char		*inExt,
		*dbgExt,
		*inDir,
		*dbgDir,
		*inFile,
		*dbgFile,
  		*inPath = NULL,
		*dbgPath = NULL,
		*outFile = NULL;
  WlzRadDistVal distSort = WLZ_RADDISTVAL_AREA;
  WlzRadDistRec	*distData = NULL;
  WlzPixelV	thrVal;
  WlzDVertex2	centre;
  WlzCompThreshType thrMtd = WLZ_COMPTHRESH_OTSU;
  WlzThresholdType thrMod = WLZ_THRESH_HIGH;
  WlzEffFormat	inFmt = WLZEFF_FORMAT_NONE,
  		dbgFmt = WLZEFF_FORMAT_NONE;
  WlzObject	*inObj = NULL,
		*disObj = NULL,
  		*segObj = NULL;
  WlzGreyValueWSpace *disGVWSp = NULL;
  WlzObject	**regObjs = NULL;
  FILE		*fP = NULL;
  WlzErrorNum	errNum = WLZ_ERR_NONE;
  const int	maxObj = 1000000;
  char		pathBuf[FILENAME_MAX];
  const double	eps = 1.0e-06;
  const char	*errMsg;
  static char	optList[] = "hvAGDHELR:c:d:n:o:t:",
		defFile[] = "-";

  thrVal.type = WLZ_GREY_DOUBLE;
  thrVal.v.dbv = 0.0;
  outFile = defFile;
  while((usage == 0) && ok &&
        ((option = getopt(argc, argv, optList)) != -1))
  {
    switch(option)
    {
      case 'A':
        distSort = WLZ_RADDISTVAL_AREA;
	break;
      case 'D':
        distSort = WLZ_RADDISTVAL_DIST;
	break;
      case 'G':
        distSort = WLZ_RADDISTVAL_ANGLE;
	break;
      case 'H':
        thrMod = WLZ_THRESH_HIGH;
	break;
      case 'E':
        thrMod = WLZ_THRESH_EQUAL;
	break;
      case 'L':
        thrMod = WLZ_THRESH_LOW;
	break;
      case 'R':
        distSort = WLZ_RADDISTVAL_RADIUS;
	break;
      case 'h':
        usage = 1;
	break;
      case 'v':
        verbose = 1;
	break;
      case 'c':
	centreSet = 1;
        if(sscanf(optarg, "%lg,%lg", &(centre.vtX), &(centre.vtY)) != 2)
	{
	  usage = 1;
	}
        break;
      case 'd':
        dbgPath = optarg;
	break;
      case 'o':
        outFile = optarg;
	break;
      case 'n':
        if(sscanf(optarg, "%lg", &minArea) != 1)
	{
	  usage = 1;
	}
	break;
      case 't':
	threshSet = 1;
        if(sscanf(optarg, "%lg", &(thrVal.v.dbv)) != 1)
	{
	  usage = 1;
	}
	break;
      default:
        usage = 1;
	break;
    }
  }
  ok = !usage;
  if(ok)
  {
    if((optind + 1) != argc)
    {
      usage = 1;
      ok = 0;
    }
    else
    {
      inPath = *(argv + optind);
    }
  }
  if(ok && verbose)
  {
    (void )fprintf(stderr, "inPath = %s\n", inPath);
  }
  /* Parse input file path into path + name + ext. */
  if(ok)
  {
    ok = (usage = WlzRadDistParsePath(inPath, &inDir, &inFile, &inExt,
                                      &inFmt)) == 0;
  }
  if(ok && verbose)
  {
    (void )fprintf(stderr, "inDir = %s\n", inDir);
    (void )fprintf(stderr, "inFile = %s\n", inFile);
    (void )fprintf(stderr, "inExt = %s\n", (inExt)? inExt: "(null)");
    (void )fprintf(stderr, "inFmt = %s\n",
    		   WlzEffStringFromFormat(inFmt, NULL));
  }
  /* Read image. */
  if(ok)
  {
    errNum = WLZ_ERR_READ_EOF;
    if(inExt)
    {
      (void )sprintf(pathBuf, "%s/%s.%s", inDir, inFile, inExt);
    }
    else
    {
      (void )sprintf(pathBuf, "%s/%s", inDir, inFile);
    }
    if(((inObj = WlzAssignObject(WlzEffReadObj(NULL, pathBuf, inFmt,
    					       0, 0, 0,
					       &errNum), NULL)) == NULL) ||
       (inObj->type != WLZ_2D_DOMAINOBJ))
    {
      ok = 0;
      (void )WlzStringFromErrorNum(errNum, &errMsg);
      (void )fprintf(stderr,
      		     "%s: Failed to read 2D image object from file %s (%s)\n",
		     *argv, pathBuf, errMsg);
    }
  }
  if(ok && verbose)
  {
    (void )fprintf(stderr, "read input image ok.\n");
  }
  /* Convert to grey if needed, normalise 0 - 255 if needed and compute
   * threshold value unless already known. */
  if(ok)
  {
    if(WlzGreyTypeFromObj(inObj, NULL) == WLZ_GREY_RGBA)
    {
      WlzObject *ppObj;

      ppObj = WlzAssignObject(
	      WlzRGBAToModulus(inObj, &errNum), NULL);
      if(errNum == WLZ_ERR_NONE)
      {
	(void )WlzFreeObj(inObj);
	inObj = ppObj;
      }
    }
    if(threshSet == 0)
    {
      WlzObject *hObj = NULL;

      errNum = WlzGreyNormalise(inObj, 1);
      if(errNum == WLZ_ERR_NONE)
      {
        hObj = WlzHistogramObj(inObj, 256, 0.0, 1.0, &errNum);
      }
      if(errNum == WLZ_ERR_NONE)
      {
	threshSet = 1;
        errNum = WlzCompThreshold(&thrVal.v.dbv, hObj, thrMtd, 0);
      }
      (void )WlzFreeObj(hObj);
    }
    if(errNum != WLZ_ERR_NONE)
    {
      ok = 0;
      (void )WlzStringFromErrorNum(errNum, &errMsg);
      (void )fprintf(stderr, "%s: failed to normalise object (%s)\n",
		     *argv, errMsg);
    }
  }
  /* Segment the object. */
  if(ok)
  {
    if(inObj->values.core == NULL)
    {
      segObj = WlzAssignObject(inObj, NULL);
    }
    else
    {
      segObj = WlzAssignObject(
               WlzThreshold(inObj, thrVal, thrMod, &errNum), NULL);
      if(errNum != WLZ_ERR_NONE)
      {
	ok = 0;
	(void )WlzStringFromErrorNum(errNum, &errMsg);
	(void )fprintf(stderr, "%s: failed to segment image (%s)\n",
		       *argv, errMsg);
      }
    }
  }
  /* Compute object with the same domain as the input object but in which
   * the values are the minimum distance from an edge. */
  if(ok)
  {
    WlzObject	*bObj = NULL;

    bObj = WlzBoundaryDomain(inObj, &errNum);
    if(errNum == WLZ_ERR_NONE)
    {
      disObj = WlzAssignObject(       
               WlzDistanceTransform(inObj, bObj, WLZ_OCTAGONAL_DISTANCE,
	       			    0.0, 0.0, &errNum), NULL);
    }
    if(errNum == WLZ_ERR_NONE)
    {
      disGVWSp = WlzGreyValueMakeWSp(disObj, &errNum);
    }
    if(errNum != WLZ_ERR_NONE)
    {
      ok = 0;
      (void )WlzStringFromErrorNum(errNum, &errMsg);
      (void )fprintf(stderr, "%s: failed to compute distance object (%s)\n",
		     *argv, errMsg);
    }
    (void )WlzFreeObj(bObj);
  }
  /* Output the debug image if required. */
  if(ok && dbgPath)
  {
    WlzObject	*dbgObj;

    dbgObj = WlzAssignObject(WlzCopyObject(inObj, &errNum), NULL);
    if(errNum == WLZ_ERR_NONE)
    {
      WlzPixelV	iMin,
		iMax,
		oMin,
		oMax;

      if(dbgObj->values.core == NULL)
      {
        WlzValues tmpVal;

	oMax.type = WLZ_GREY_UBYTE;
	oMax.v.ubv = 255;
	tmpVal.v = WlzNewValueTb(dbgObj,
				 WlzGreyTableType(WLZ_GREY_TAB_RAGR,
				                  WLZ_GREY_UBYTE, NULL),
	                         oMax, &errNum);
        if(errNum == WLZ_ERR_NONE)
	{
	  dbgObj->values = WlzAssignValues(tmpVal, NULL);
	}
      }
      else
      {
        WlzObject *tmpObj = NULL;

	oMin.type = WLZ_GREY_UBYTE;
	oMin.v.ubv = 0;
	oMax.type = WLZ_GREY_UBYTE;
	oMax.v.ubv = 200;
	errNum = WlzGreyRange(dbgObj, &iMin, &iMax);
	if(errNum == WLZ_ERR_NONE)
	{
	  errNum = WlzGreySetRange(dbgObj, iMin, iMax, oMin, oMax, 0);
	}
	if(errNum == WLZ_ERR_NONE)
	{
	  tmpObj = WlzMakeMain(inObj->type, segObj->domain, dbgObj->values,
	                       NULL, NULL, &errNum);
	}
	if(errNum == WLZ_ERR_NONE)
	{
	  oMax.v.ubv = 255;
	  errNum = WlzGreySetValue(tmpObj, oMax);
	}
	(void )WlzFreeObj(tmpObj);
	if(errNum == WLZ_ERR_NONE)
	{
	  tmpObj = WlzConvertPix(dbgObj, WLZ_GREY_UBYTE, &errNum);
	  (void )WlzFreeObj(dbgObj);
	  dbgObj = WlzAssignObject(tmpObj, NULL);
	}
      }
    }
    if(errNum == WLZ_ERR_NONE)
    {
      (void )WlzRadDistParsePath(dbgPath, &dbgDir, &dbgFile, &dbgExt,
      			         &dbgFmt);
      if(dbgExt)
      {
	(void )sprintf(pathBuf, "%s/%s.%s", dbgDir, dbgFile, dbgExt);
      }
      else
      {
	(void )sprintf(pathBuf, "%s/%s", dbgDir, dbgFile);
      }
      errNum = WlzEffWriteObj(NULL, pathBuf, dbgObj, dbgFmt);
    }
    (void )WlzFreeObj(dbgObj);
    if(errNum != WLZ_ERR_NONE)
    {
      ok = 0;
      (void )WlzStringFromErrorNum(errNum, &errMsg);
      (void )fprintf(stderr, "%s: failed to output the debug image (%s)\n",
		     *argv, errMsg);
    }
  }
  /* Label the segmented object. */
  if(ok)
  {
    errNum = WlzLabel(segObj, &nReg, &regObjs, maxObj, 0, WLZ_8_CONNECTED);
    if(errNum != WLZ_ERR_NONE)
    {
      ok = 0;
      errNum = WLZ_ERR_MEM_ALLOC;
      (void )WlzStringFromErrorNum(errNum, &errMsg);
      (void )fprintf(stderr, "%s: failed to split into components (%s)\n",
		     *argv, errMsg);
    }
    if(ok && verbose)
    {
      (void )fprintf(stderr, "nReg = %d\n", nReg);
    }
  }
  /* Compute centre of mass if not known. */
  if(ok)
  {
    if(centreSet == 0)                          
    {
      centre = WlzCentreOfMass2D(inObj, 1, NULL, &errNum);
      if(errNum != WLZ_ERR_NONE)
      {
	ok = 0;
	(void )WlzStringFromErrorNum(errNum, &errMsg);
	(void )fprintf(stderr, "%s: failed to compute centre of mass (%s)\n",
		       *argv, errMsg);
      }
    }
    if(ok && verbose)
    {
      (void )fprintf(stderr, "centre = %lg,%lg\n", centre.vtX, centre.vtY);
    }
  }
  /* Allocate a radial distribution table. */
  if(ok)
  {
    if((distData = (WlzRadDistRec *)
                   AlcCalloc(nReg, sizeof(WlzRadDistRec))) == NULL)
    {
      ok = 0;
      errNum = WLZ_ERR_MEM_ALLOC;
      (void )WlzStringFromErrorNum(errNum, &errMsg);
      (void )fprintf(stderr, "%s: failed to allocate result lable (%s)\n",
		     *argv, errMsg);
    }
    
  }
  /* Compute the redial distribution data. */
  if(ok)
  {
    int		idR = 0,
    		idS = 0;

    while((errNum == WLZ_ERR_NONE) && (idR < nReg))
    {
      double	mass;
      WlzDVertex2 com;

      com = WlzCentreOfMass2D(regObjs[idR], 1, &mass, NULL);
      if(mass > minArea - eps)
      {
	WlzGreyValueGet(disGVWSp, 0.0, com.vtY, com.vtX);
	distData[idS].pos = com;
	distData[idS].area = mass;
	WLZ_VTX_2_SUB(com, centre, com);
	distData[idS].radius = WLZ_VTX_2_LENGTH(com);
	distData[idS].angle = ALG_M_PI + atan2(com.vtY, com.vtX);
	switch(disGVWSp->gType)
	{
	  case WLZ_GREY_LONG:
	    distData[idS].dist = *(disGVWSp->gPtr[0].lnp);
	    break;
	  case WLZ_GREY_INT:
	    distData[idS].dist = *(disGVWSp->gPtr[0].inp);
	    break;
	  case WLZ_GREY_SHORT:
	    distData[idS].dist = *(disGVWSp->gPtr[0].shp);
	    break;
	  case WLZ_GREY_UBYTE:
	    distData[idS].dist = *(disGVWSp->gPtr[0].ubp);
	    break;
	  case WLZ_GREY_FLOAT:
	    distData[idS].dist = *(disGVWSp->gPtr[0].flp);
	    break;
	  case WLZ_GREY_DOUBLE:
	    distData[idS].dist = *(disGVWSp->gPtr[0].dbp);
	    break;
	  default:
	    distData[idS].dist = 0.0;
	    break;
	}
	++idS;
      }
      ++idR;
    }
    tNReg = idS;
    switch(distSort)
    {
      case WLZ_RADDISTVAL_AREA:
        (void )qsort(distData, tNReg, sizeof(WlzRadDistRec),
		     WlzRadDistRecSortArea);
	break;
      case WLZ_RADDISTVAL_ANGLE:
        (void )qsort(distData, tNReg, sizeof(WlzRadDistRec), 
		     WlzRadDistRecSortAngle);
	break;
      case WLZ_RADDISTVAL_RADIUS:
        (void )qsort(distData, tNReg, sizeof(WlzRadDistRec),
		     WlzRadDistRecSortRadius);
	break;
      case WLZ_RADDISTVAL_DIST:
        (void )qsort(distData, tNReg, sizeof(WlzRadDistRec),
		     WlzRadDistRecSortDist);
	break;
    }
  }
  /* Output the sorted radial distribution table. */
  if(ok)
  {
    if(((fP = strcmp(outFile, "-")?
              fopen(outFile, "w"): stdout)) == NULL)
    {
      ok = 0;
      (void )fprintf(stderr, "%s: failed to open output file %s\n",
                     *argv, outFile);
    }
  }
  if(ok)
  {
    int		idR;

    for(idR = 0; idR < tNReg; ++idR)
    {
      double a;

      a = (distData[idR].angle > 0.0)?
	  0   + (180 * distData[idR].angle / ALG_M_PI):
          360 + (180 * distData[idR].angle / ALG_M_PI);
      (void )fprintf(fP, "%g %g %g %g,%g %g\n",
		     a,
                     distData[idR].radius,
		     distData[idR].area,
		     distData[idR].pos.vtX,
		     distData[idR].pos.vtY,
		     distData[idR].dist);
    }
  }
  if(strcmp(outFile, "-"))
  {
    (void )fclose(fP);
  }
  /* Tidy up. */
  AlcFree(distData);
  WlzGreyValueFreeWSp(disGVWSp);
  (void )WlzFreeObj(inObj);
  (void )WlzFreeObj(disObj);
  (void )WlzFreeObj(segObj);
  if(regObjs)
  {
    int		idR;

    for(idR = 0; idR < nReg; ++idR)
    {
      (void )WlzFreeObj(regObjs[idR]);
    }
    AlcFree(regObjs);
  }
  if(usage)
  {
    (void )fprintf(stderr,
    "Usage: %s [-h] [-v] [-A] [-D] [-G] [-H] [-E] [-L] [-R]\n"
    "\t\t[-c #,#] [-d <debug image>] [-n #]  [-o <out file>]\n"
    "\t\t[-t #] [<input image>]\n"
    "Segments the given object using a threshold value and outputs the \n"
    "radial distribution of the thresholded components.\n"
    "Version: %s\n"
    "Options:\n"
    "  -h  Help - prints this usage masseage.\n"
    "  -v  Verbose output.\n"
    "  -A  Sort output by area (default).\n"
    "  -D  Sort output by distance from boundary.\n"
    "  -G  Sort output by angle.\n"
    "  -H  Threshold high, use pixels at or above threshold (default).\n"
    "  -E  Threshold equal, use pixels at threshold.\n"
    "  -L  Threshold low, use pixels below threshold.\n"
    "  -R  Sort output by radial distance from centre.\n"
    "  -c  Centre (default is image centre).\n"
    "  -d  Debug image.\n"
    "  -n  Minimum area (default %g).\n"
    "  -t  Threshold value (default is to compute using Otsu's method).\n"
    "By default the input image object is read from the standard input and\n"
    "the radial distribution is written to the standard output.\n"
    "The image formats understood include wlz, jpg and tif.\n"
    "The output format is:\n"
    "  <angle> <dist from centre> <area> <x pos>,<y pos> <dist form boundary>\n"
    "Example:\n"
    "  %s -o out.txt -d debug.jpg in.tif\n"
    "The input image is read from in.tif, a debug image showing the\n"
    "segmented regions is written to debug.jpg and the radial distribution\n"
    "statistics are written to the file out.txt. With the output in\n"
    "out.txt, the following R code would plot the data as a set of circles\n"
    "with radius proportional to the square root of the component area:\n"
    "  data <- read.table(\"out.txt\")\n"
    "  attach(data)\n"
    "  symbols(x=data$V1, y=data$V2, circles=sqrt(data$V3))\n",
    argv[0],
    WlzVersion(),
    minArea,
    argv[0]);
  }
  return(!ok);
}