Beispiel #1
0
/*!
* \return	Woolz error code.
* \ingroup	WlzBinaryOps
* \brief	Make temporary objects as in WlzRCCTOIdx for
* 		WlzRegConCalcRCC().
* \param	o			Array of objects, o[0] and o[1].
* \param	t			Array of temporary objects.
* \param	i			Temporary object index.
*/
static WlzErrorNum 		WlzRCCMakeT(
				  WlzObject **o,
				  WlzObject **t,
				  WlzRCCTOIdx i)
{
  WlzErrorNum	errNum = WLZ_ERR_NONE;

  if((i >= 0) && (i < WLZ_RCCTOIDX_CNT))
  {
    if(t[i] == NULL)
    {
      switch(i)
      {
	case WLZ_RCCTOIDX_O0O1U:			/* o_0 \cup \o_1 */
	  t[i] = WlzAssignObject(
		 WlzUnion2(o[0], o[1], &errNum), NULL);
	  break;
	case WLZ_RCCTOIDX_O0O1I:			/* o_0 \cap \o_1 */
	  t[i] = WlzAssignObject(
		 WlzIntersect2(o[0], o[1], &errNum), NULL);
	  break;
	case WLZ_RCCTOIDX_O0D:			/* o_0^+ */
	  t[i] = WlzAssignObject(
		 WlzDilation(o[0],
			     (o[0]->type == WLZ_2D_DOMAINOBJ)?
			     WLZ_8_CONNECTED: WLZ_26_CONNECTED,
			     &errNum), NULL);
	  break;
	case WLZ_RCCTOIDX_O1D:			/* o_1^+ */
	  t[i] = WlzAssignObject(
		 WlzDilation(o[1],
			     (o[1]->type == WLZ_2D_DOMAINOBJ)?
			     WLZ_8_CONNECTED: WLZ_26_CONNECTED,
			     &errNum), NULL);
	  break;

	case WLZ_RCCTOIDX_O0F:			/* o_0^{\bullet} */
	  t[i] = WlzAssignObject(
		 WlzDomainFill(o[0], &errNum), NULL);
	  break;
	case WLZ_RCCTOIDX_O1F:			/* o_1^{\bullet} */
	  t[i] = WlzAssignObject(
		 WlzDomainFill(o[1], &errNum), NULL);
	  break;

	case WLZ_RCCTOIDX_O0DO1U:		/* o_0^+ \cup o_1 */
	  errNum = WlzRCCMakeT(o, t, WLZ_RCCTOIDX_O0D);
	  if(errNum == WLZ_ERR_NONE)
	  {
	    t[i] = WlzAssignObject(
		   WlzUnion2(t[WLZ_RCCTOIDX_O0D], o[1], &errNum), NULL);
	  }
	  break;
	case WLZ_RCCTOIDX_O0O1DU:		/* o_0   \cup o_1^+ */
	  errNum = WlzRCCMakeT(o, t, WLZ_RCCTOIDX_O1D);
	  if(errNum == WLZ_ERR_NONE)
	  {
	    t[i] = WlzAssignObject(
		   WlzUnion2(o[0], t[WLZ_RCCTOIDX_O1D], &errNum), NULL);
	  }
	  break;
	case WLZ_RCCTOIDX_O0CO1I:               /* o_0^{\circ} \cap o_1   */
	case WLZ_RCCTOIDX_O0O1CI:  /* FALLTHROUGH  o_0   \cap o_1^{\circ} */
	  {
	    int		i0;
	    WlzObject	*c = NULL,
			  *x = NULL;

	    i0 = (i == WLZ_RCCTOIDX_O0CO1I)? 0: 1;
	    c = WlzObjToConvexHull(o[i0], &errNum);
	    if((errNum == WLZ_ERR_NONE) || (errNum == WLZ_ERR_DEGENERATE))
	    {
	      x = WlzAssignObject(
		  WlzConvexHullToObj(c, o[i0]->type, &errNum), NULL);
	    }
	    if(errNum == WLZ_ERR_NONE)
	    {
	      t[i] = WlzAssignObject(
		     WlzIntersect2(o[!i0], x, &errNum), NULL);
	    }
	    (void )WlzFreeObj(c);
	    (void )WlzFreeObj(x);
	  }
	  break;
	case WLZ_RCCTOIDX_O0FO1U:		/* o_0^{\bullet} \cup o_1 */
	  errNum = WlzRCCMakeT(o, t, WLZ_RCCTOIDX_O0F);
	  if(errNum == WLZ_ERR_NONE)
	  {
	    t[i] = WlzAssignObject(
		   WlzUnion2(t[WLZ_RCCTOIDX_O0F], o[1], &errNum), NULL);
	  }
	  break;
	case WLZ_RCCTOIDX_O0O1FU:		/* o_0 \cup o_1^{\bullet} */
	  errNum = WlzRCCMakeT(o, t, WLZ_RCCTOIDX_O1F);
	  if(errNum == WLZ_ERR_NONE)
	  {
	    t[i] = WlzAssignObject(
		   WlzUnion2(o[0], t[WLZ_RCCTOIDX_O1F], &errNum), NULL);
	  }
	  break;
	default:
	  errNum = WLZ_ERR_PARAM_DATA;
	  break;
      }
    }
  }
  else
  {
    errNum = WLZ_ERR_PARAM_DATA;
  }
  return(errNum);
}
Beispiel #2
0
/*!
* \return	Thresholded object or NULL on error.
* \ingroup      WlzThreshold
* \brief	Hysteresis thresholds the given Woolz object.
*               Values are in the domain of the hysteresis threshold'd
*               object if they are above/below the primary threshold
*               or above/below the secondary threshold and connected
*               to values above/below the primary threshold.
* \param	srcObj			Object to be thresholded.
* \param	pThrV			Primary hysteresis threshold
*                                       value.
* \param	sThrV			Threshold for above or below
*                                       values.
* \param	hilo			Threshold for above or below
*                                       values.
* \param	con			Connectivity to examine for
*                                       hysteresis.
* \param	dstErr			Destination error pointer, may
*                                       be null.
*/
WlzObject	*WlzHyThreshold(WlzObject *srcObj,
			       WlzPixelV pThrV, WlzPixelV sThrV,
			       WlzThresholdType hilo, WlzConnectType con,
			       WlzErrorNum *dstErr)
{
  int		simpleThr = 0;
  WlzPixelV	tmpV;
  WlzObject	*dstObj = NULL,
  		*pThrObj = NULL,
  		*sThrObj = NULL,
		*dThrObj = NULL,
		*iThrObj = NULL,
		*uThrObj = NULL;
  WlzErrorNum	errNum = WLZ_ERR_NONE;

  if(srcObj == NULL)
  {
    errNum = WLZ_ERR_OBJECT_NULL;
  }
  else if(srcObj->domain.core == NULL)
  {
    errNum = WLZ_ERR_DOMAIN_NULL;
  }
  else if(srcObj->values.core == NULL)
  {
    errNum = WLZ_ERR_VALUES_NULL;
  }
  else
  {
    if(con == WLZ_0_CONNECTED)
    {
      simpleThr = 1;
    }
    else
    {
      if((errNum = WlzValueConvertPixel(&tmpV, sThrV,
      					pThrV.type)) == WLZ_ERR_NONE)
      {
        switch(pThrV.type)
	{
	  case WLZ_GREY_INT:
	    if(tmpV.v.inv == pThrV.v.inv)
	    {
	      simpleThr = 1;
	    }
	    break;
	  case WLZ_GREY_SHORT:
	    if(tmpV.v.shv == pThrV.v.shv)
	    {
	      simpleThr = 1;
	    }
	    break;
	  case WLZ_GREY_UBYTE:
	    if(tmpV.v.ubv == pThrV.v.ubv)
	    {
	      simpleThr = 1;
	    }
	    break;
	  case WLZ_GREY_FLOAT:
	    if(fabs(tmpV.v.flv - pThrV.v.flv) <= FLT_EPSILON)
	    {
	      simpleThr = 1;
	    }
	    break;
	  case WLZ_GREY_DOUBLE:
	    if(fabs(tmpV.v.dbv - pThrV.v.dbv) <= DBL_EPSILON)
	    {
	      simpleThr = 1;
	    }
	    break;
	  case WLZ_GREY_RGBA:
	    if( tmpV.v.rgbv == pThrV.v.rgbv )
	    {
	      simpleThr = 1;
	    }
	    break;
	  default:
	    errNum = WLZ_ERR_GREY_TYPE;
	    break;
	}
      }
    }
  }
  if(errNum == WLZ_ERR_NONE)
  {
    if(simpleThr)
    {
      dstObj = WlzThreshold(srcObj, pThrV, hilo, &errNum);
    }
    else
    {
      pThrObj = WlzThreshold(srcObj, pThrV, hilo, &errNum);
      if(errNum == WLZ_ERR_NONE)
      {
	if(pThrObj->type == WLZ_EMPTY_OBJ)
	{
	  dstObj = pThrObj;
	  pThrObj = NULL;
	}
	else
	{
	  sThrObj = WlzThreshold(srcObj, sThrV, hilo, &errNum);
	  if(errNum == WLZ_ERR_NONE)
	  {
	    if(sThrObj->type == WLZ_EMPTY_OBJ)
	    {
	      dstObj = pThrObj;
	      pThrObj = NULL;
	    }
	    else
	    {
	      dThrObj = WlzDilation(pThrObj, con, &errNum);
	      if(errNum == WLZ_ERR_NONE)
	      {
		iThrObj = WlzIntersect2(dThrObj, sThrObj, &errNum);
	      }
	      if(errNum == WLZ_ERR_NONE)
	      {
		uThrObj = WlzUnion2(pThrObj, iThrObj, &errNum);
	      }
	      if(errNum == WLZ_ERR_NONE)
	      {
		dstObj = WlzMakeMain(uThrObj->type,
				     uThrObj->domain, srcObj->values,
				     srcObj->plist, srcObj, &errNum);
	      }
	      if(dThrObj)
	      {
		WlzFreeObj(dThrObj);
	      }
	      if(iThrObj)
	      {
		WlzFreeObj(iThrObj);
	      }
	      if(uThrObj)
	      {
		WlzFreeObj(uThrObj);
	      }
	    }
	    if(sThrObj)
	    {
	      WlzFreeObj(sThrObj);
	    }
	    if(pThrObj)
	    {
	      WlzFreeObj(pThrObj);
	    }
	  }
	}
      }
    }
  }
  if(dstErr)
  {
    *dstErr = errNum;
  }
  return(dstObj);
}
Beispiel #3
0
/*!
* \return	Distance object which shares the given foreground object's
*		domain and has integer distance values, null on error.
* \ingroup	WlzMorphologyOps
* \brief	Computes the distance of every pixel/voxel in the foreground
* 		object from the reference object.
*
*		A distance transform maps all position within a  forground
*		domain to their distances from a reference domain.
*		The distance transforms implemented within this function
*		use efficient morphological primitives.
*		
*		Given two domains,
*		\f$\Omega_r\f$ the reference domain and \f$\Omega_f\f$
*		the domain specifying the region of interest,
*		a domain with a thin shell \f$\Omega_i\f$
*		is iteratively expanded from it's initial domain
*		corresponding to the reference domain \f$\Omega_r\f$.
*		At each iteration
*		\f$\Omega_i\f$ is dilated and clipped
*		by it's intersection with \f$\Omega_f\f$ until \f$\Omega_i\f$
*		becomes the null domain \f$\emptyset\f$.
*		At each iteration the current distance is recorded in a value
*		table which
*		covers the domain \f$\Omega_f\f$.
*
*		An octagonal distance scheme may be used in which
*		the distance metric is alternated between 4 and 8
*		connected for 2D and 6 and 26 connectivities in 3D.
*		See: G. Borgefors. "Distance Transformations in Arbitrary
*		Dimensions" CVGIP 27:321-345, 1984.
*
* 		An approximate Euclidean distance transform may be computed
* 		by: Scaling the given foreground and reference objects using
* 		the given approximation scale parameter, dilating the
* 		reference domain using a sphere with a radius having the same
* 		value as the scale parameter and then finaly sampling the
* 		scaled distances.
* \param	forObj			Foreground object.
* \param	refObj			Reference object.
* \param	dFn			Distance function which must be
*					appropriate to the dimension of
*					the foreground and reference objects.
* \param	dParam			Parameter required for distance
* 					function. Currently only
* 					WLZ_APX_EUCLIDEAN_DISTANCE requires a
* 					parameter. In this case the parameter
* 					is the approximation scale.
* \param	dstErr			Destination error pointer, may be NULL.
*/
WlzObject 	*WlzDistanceTransform(WlzObject *forObj, WlzObject *refObj,
				   WlzDistanceType dFn, double dParam,
				   WlzErrorNum *dstErr)
{
  int 		idP,
		lastP,
		dim,
  		notDone = 1;
  double	scale;
  WlzObject	*tmpObj,
		*sObj = NULL,
		*sForObj = NULL,
		*sRefObj = NULL,
		*dilObj = NULL,
  		*dstObj = NULL,
		*difObj = NULL,
  		*curItrObj = NULL;
  WlzObject 	*bothObj[2];
  WlzDomain	*difDoms;
  WlzPixelV 	dstV,
  		bgdV;
  WlzValues 	*difVals;
  WlzAffineTransform *tr = NULL;
  WlzConnectType con;
  WlzObjectType dstGType;
  WlzErrorNum	errNum = WLZ_ERR_NONE;
  WlzValues 	difVal,
  		dstVal,
		nullVal;
  /* By defining WLZ_DIST_TRANSFORM_ENV these normalization parameters
   * are read from the environment. This is useful for optimization. */
#ifndef WLZ_DIST_TRANSFORM_ENV
  const
#endif /* ! WLZ_DIST_TRANSFORM_ENV */
  /* These normalizarion factors have been choosen to minimize the sum of
   * squares of the deviation of the distance values from Euclidean values
   * over a radius 100 circle or sphere, where the distances are computed
   * from the circumference of the sphere towards it's centre. The values
   * were established by experiment. */
  double	nrmDist4 =  0.97,
		nrmDist6 =  0.91,
		nrmDist8 =  1.36,
		nrmDist18 = 1.34,
		nrmDist26 = 1.60;

#ifdef WLZ_DIST_TRANSFORM_ENV
  double	val;
  char		*envStr;

  if(((envStr = getenv("WLZ_DIST_TRANSFORM_NRMDIST4")) != NULL) &&
     (sscanf(envStr, "%lg", &val) == 1))
  {
    nrmDist4 = val;
  }
  if(((envStr = getenv("WLZ_DIST_TRANSFORM_NRMDIST6")) != NULL) &&
     (sscanf(envStr, "%lg", &val) == 1))
  {
    nrmDist6 = val;
  }
  if(((envStr = getenv("WLZ_DIST_TRANSFORM_NRMDIST8")) != NULL) &&
     (sscanf(envStr, "%lg", &val) == 1))
  {
    nrmDist8 = val;
  }
  if(((envStr = getenv("WLZ_DIST_TRANSFORM_NRMDIST18")) != NULL) &&
     (sscanf(envStr, "%lg", &val) == 1))
  {
    nrmDist18 = val;
  }
  if(((envStr = getenv("WLZ_DIST_TRANSFORM_NRMDIST26")) != NULL) &&
     (sscanf(envStr, "%lg", &val) == 1))
  {
    nrmDist26 = val;
  }
#endif /* WLZ_DIST_TRANSFORM_ENV */
  scale = dParam;
  nullVal.core = NULL;
  /* Check parameters. */
  if((forObj == NULL) || (refObj == NULL))
  {
    errNum = WLZ_ERR_OBJECT_NULL;
  }
  else if(((forObj->type != WLZ_2D_DOMAINOBJ) &&
           (forObj->type != WLZ_3D_DOMAINOBJ)) ||
          ((refObj->type != WLZ_POINTS) &&
	   (refObj->type != forObj->type)))
  {
    errNum = WLZ_ERR_OBJECT_TYPE;
  }
  else if((forObj->domain.core == NULL) || (refObj->domain.core == NULL))
  {
    errNum = WLZ_ERR_DOMAIN_NULL;
  }
  if(errNum == WLZ_ERR_NONE)
  {
    bgdV.type = WLZ_GREY_INT;
    bgdV.v.inv = 0;
    dstV.type = WLZ_GREY_DOUBLE;
    dstV.v.dbv = 0.0;
    switch(forObj->type)
    {
      case WLZ_2D_DOMAINOBJ:
	switch(dFn)
	{
	  case WLZ_4_DISTANCE: /* FALLTHROUGH */
	  case WLZ_8_DISTANCE: /* FALLTHROUGH */
	  case WLZ_OCTAGONAL_DISTANCE: /* FALLTHROUGH */
	  case WLZ_APX_EUCLIDEAN_DISTANCE:
	    dim = 2;
	    break;
	  default:
	    errNum = WLZ_ERR_PARAM_DATA;
	    break;
	}
        break;
      case WLZ_3D_DOMAINOBJ:
	switch(dFn)
	{
	  case WLZ_6_DISTANCE:  /* FALLTHROUGH */
	  case WLZ_18_DISTANCE: /* FALLTHROUGH */
	  case WLZ_26_DISTANCE: /* FALLTHROUGH */
	  case WLZ_OCTAGONAL_DISTANCE: /* FALLTHROUGH */
	  case WLZ_APX_EUCLIDEAN_DISTANCE:
	    dim = 3;
	    break;
	  default:
	    errNum = WLZ_ERR_PARAM_DATA;
	    break;
	}
        break;
      default:
        errNum = WLZ_ERR_OBJECT_TYPE;
	break;
    }
  }
  if(errNum == WLZ_ERR_NONE)
  {
    switch(dFn)
    {
      case WLZ_4_DISTANCE:
        con = WLZ_4_CONNECTED;
	break;
      case WLZ_6_DISTANCE:
        con = WLZ_6_CONNECTED;
	break;
      case WLZ_8_DISTANCE:
        con = WLZ_8_CONNECTED;
	break;
      case WLZ_18_DISTANCE:
        con = WLZ_18_CONNECTED;
	break;
      case WLZ_26_DISTANCE:
        con = WLZ_26_CONNECTED;
	break;
      case WLZ_OCTAGONAL_DISTANCE:
        con = (dim == 2)? WLZ_8_CONNECTED: WLZ_26_CONNECTED;
	break;
      case WLZ_APX_EUCLIDEAN_DISTANCE:
        con = (dim == 2)? WLZ_8_CONNECTED: WLZ_26_CONNECTED;
	if(scale < 1.0)
	{
	  errNum = WLZ_ERR_PARAM_DATA;
	}
	break;
      case WLZ_EUCLIDEAN_DISTANCE:
	errNum = WLZ_ERR_UNIMPLEMENTED;
	break;
      default:
        errNum = WLZ_ERR_PARAM_DATA;
	break;
    }
  }
  /* Create scaled domains and a sphere domain for structual erosion if the
   * distance function is approximate Euclidean. */
  if(errNum == WLZ_ERR_NONE)
  {
    if(dFn == WLZ_APX_EUCLIDEAN_DISTANCE)
    {
      tr = (dim == 2)?
	   WlzAffineTransformFromScale(WLZ_TRANSFORM_2D_AFFINE,
	                               scale, scale, 0.0, &errNum):
	   WlzAffineTransformFromScale(WLZ_TRANSFORM_3D_AFFINE,
	                               scale, scale, scale, &errNum);
      if(errNum == WLZ_ERR_NONE)
      {
	tmpObj = WlzMakeMain(forObj->type, forObj->domain, nullVal,
			     NULL, NULL, &errNum);
	if(tmpObj)
	{
	  sForObj = WlzAssignObject(
	            WlzAffineTransformObj(tmpObj, tr,
		                          WLZ_INTERPOLATION_NEAREST,
		    			  &errNum), NULL);
	  (void )WlzFreeObj(tmpObj);
	}
      }
      if(errNum == WLZ_ERR_NONE)
      {
	if(refObj->type == WLZ_POINTS)
	{
	  sRefObj = WlzPointsToDomObj(refObj->domain.pts, scale, &errNum);
	}
	else /* type == WLZ_2D_DOMAINOBJ || type == WLZ_3D_DOMAINOBJ */
	{
	  tmpObj = WlzMakeMain(refObj->type, refObj->domain, nullVal,
			       NULL, NULL, &errNum);
	  if(errNum == WLZ_ERR_NONE)
	  {
	    sRefObj = WlzAssignObject(
		      WlzAffineTransformObj(tmpObj, tr,
					    WLZ_INTERPOLATION_NEAREST,
					    &errNum), NULL);
	  }
	}
	(void )WlzFreeObj(tmpObj);
      }
      if(errNum == WLZ_ERR_NONE)
      {
	sObj = WlzAssignObject(
	       WlzMakeSphereObject(forObj->type, scale,
	                           0.0, 0.0, 0.0, &errNum), NULL);
      }
      (void )WlzFreeAffineTransform(tr);
    }
    else
    {
      sForObj = WlzAssignObject(
	        WlzMakeMain(forObj->type, forObj->domain, nullVal,
	  		    NULL, NULL, &errNum), NULL);
      if(errNum == WLZ_ERR_NONE)
      {
	if(refObj->type == WLZ_POINTS)
	{
	  sRefObj = WlzPointsToDomObj(refObj->domain.pts, 1.0, &errNum);
	}
	else
	{
	  sRefObj = WlzAssignObject(
		    WlzMakeMain(refObj->type, refObj->domain, nullVal,
				NULL, NULL, &errNum), NULL);
	}
      }
    }
  }
  /* Create new values for the computed distances. */
  if(errNum == WLZ_ERR_NONE)
  {
    dstGType = WlzGreyTableType(WLZ_GREY_TAB_RAGR, WLZ_GREY_INT, NULL);
    if(dim == 2)
    {
      dstVal.v = WlzNewValueTb(sForObj, dstGType, bgdV, &errNum);
    }
    else
    {
      dstVal.vox = WlzNewValuesVox(sForObj, dstGType, bgdV, &errNum);
    }
  }
  /* Create a distance object using the foreground object's domain and
   * the new distance values. */
  if(errNum == WLZ_ERR_NONE)
  {
    dstObj = WlzMakeMain(sForObj->type, sForObj->domain, dstVal,
			 NULL, NULL, &errNum);
  }
  if(errNum == WLZ_ERR_NONE)
  {
    bothObj[0] = sForObj;
    errNum = WlzGreySetValue(dstObj, dstV);
  }
  /* Dilate the reference object while setting the distances in each
   * dilated shell. */
  while((errNum == WLZ_ERR_NONE) && notDone)
  {
    if(dFn == WLZ_APX_EUCLIDEAN_DISTANCE)
    {
      dstV.v.dbv += 1.0;
    }
    else
    {
      switch(con)
      {
	case WLZ_4_CONNECTED:
	  dstV.v.dbv += nrmDist4;
	  break;
	case WLZ_6_CONNECTED:
	  dstV.v.dbv += nrmDist6;
	  break;
	case WLZ_8_CONNECTED:
	  dstV.v.dbv += nrmDist8;
	  break;
	case WLZ_18_CONNECTED:
	  dstV.v.dbv += nrmDist18;
	  break;
	case WLZ_26_CONNECTED:
	  dstV.v.dbv += nrmDist26;
	  break;
        default:
	  errNum = WLZ_ERR_CONNECTIVITY_TYPE;
	  break;
      }
    }
    if(dFn == WLZ_APX_EUCLIDEAN_DISTANCE)
    {
      dilObj = WlzStructDilation(sRefObj, sObj, &errNum);
    }
    else
    {
      dilObj = WlzDilation(sRefObj, con, &errNum);
    }
    if(errNum == WLZ_ERR_NONE)
    {
      switch(sForObj->type)
      {
        case WLZ_2D_DOMAINOBJ:
	  curItrObj = WlzAssignObject(
	              WlzIntersect2(dilObj, sForObj, &errNum), NULL);
	  break;
        case WLZ_3D_DOMAINOBJ:
	  bothObj[1] = dilObj;
	  curItrObj = WlzAssignObject(
	              WlzIntersectN(2, bothObj, 1, &errNum), NULL);
	  break;
        default:
	  errNum = WLZ_ERR_OBJECT_TYPE;
	  break;
      }
    }
    (void)WlzFreeObj(dilObj);
    /* Create difference object for the expanding shell. */
    if(errNum == WLZ_ERR_NONE)
    {
      difObj = WlzDiffDomain(curItrObj, sRefObj, &errNum);
    }
    if((difObj == NULL) || WlzIsEmpty(difObj, &errNum))
    {
      notDone = 0;
    }
    else
    {
      /* Assign the distance object's values to the difference object
       * and set all it's values to the current distance. */
      if(errNum == WLZ_ERR_NONE)
      {
	switch(sForObj->type)
	{
	  case WLZ_2D_DOMAINOBJ:
	    difObj->values = WlzAssignValues(dstObj->values, NULL);
	    errNum = WlzGreySetValue(difObj, dstV);
	    break;
	  case WLZ_3D_DOMAINOBJ:
	    /* 3D is more complex than 2D: Need to create a temporary
	     * voxel valuetable and assign the individual 2D values. */
	    difVal.vox = WlzMakeVoxelValueTb(WLZ_VOXELVALUETABLE_GREY,
					     difObj->domain.p->plane1,
					     difObj->domain.p->lastpl,
					     bgdV, NULL, &errNum);
	    if(errNum == WLZ_ERR_NONE)
	    {
	      difObj->values = WlzAssignValues(difVal, NULL);
	      difDoms = difObj->domain.p->domains;
	      difVals = difObj->values.vox->values;
	      idP = difObj->domain.p->plane1;
	      lastP = difObj->domain.p->lastpl;
	      while(idP <= lastP)
	      {
		if((*difDoms).core)
		{
		  dstVal = dstObj->values.vox->values[idP - 
						      dstObj->domain.p->plane1];
		  *difVals = WlzAssignValues(dstVal, NULL);
		}
		++idP;
		++difDoms;
		++difVals;
	      }
	      if(difObj->domain.p->lastpl > difObj->domain.p->plane1)
	      {
		errNum = WlzGreySetValue(difObj, dstV);
	      }
	    }
	    break;
	  default:
	    errNum = WLZ_ERR_OBJECT_TYPE;
	    break;
	}
      }
      (void )WlzFreeObj(sRefObj);
      sRefObj = WlzAssignObject(curItrObj, NULL);
      (void )WlzFreeObj(curItrObj);
    }
    (void )WlzFreeObj(difObj); difObj = NULL;
    if(dFn == WLZ_OCTAGONAL_DISTANCE)
    {
      /* Alternate connectivities for octagonal distance. */
      if(dim == 2)
      {
	con = (con == WLZ_4_CONNECTED)? WLZ_8_CONNECTED: WLZ_4_CONNECTED;
      }
      else /* dim == 3 */
      {
	con = (con == WLZ_6_CONNECTED)? WLZ_26_CONNECTED: WLZ_6_CONNECTED;
      }
    }
  }
  (void )WlzFreeObj(sObj);
  (void )WlzFreeObj(sForObj);
  (void )WlzFreeObj(sRefObj);
  (void )WlzFreeObj(curItrObj);
  if((errNum == WLZ_ERR_NONE) && (dFn == WLZ_APX_EUCLIDEAN_DISTANCE))
  {
    tmpObj = WlzDistSample(dstObj, dim, scale, &errNum);
    (void )WlzFreeObj(dstObj);
    dstObj = tmpObj;
  }
  if(errNum != WLZ_ERR_NONE)
  {
    (void )WlzFreeObj(dstObj); dstObj = NULL;
  }
  if(dstErr)
  {
    *dstErr = errNum;
  }
  return(dstObj);
}
void postProcMorphCb(
  Widget	w,
  XtPointer	client_data,
  XtPointer	call_data)
{
  MAPaintOperatorType	opType=(MAPaintOperatorType) client_data;
  WlzObject		*seObj, *newDomain=NULL, *tmpObj;
  WlzConnectType	conn;
  WlzErrorNum		errNum=WLZ_ERR_NONE;

  /* check signal object */
  if( warpGlobals.sgnlObj == NULL ){
    return;
  }

  /* build the structuring element */
  seObj = NULL;
  conn = WLZ_4_CONNECTED;
  switch( warpGlobals.seType ){
  case MAPAINT_8_CONN_SE:
    conn = WLZ_8_CONNECTED;
    break;

  case MAPAINT_4_CONN_SE:
    conn = WLZ_4_CONNECTED;
    break;

  case MAPAINT_CIRCLE_SE:
    if( warpGlobals.seElemRadius != 1 ){
      seObj = WlzMakeCircleObject((double) warpGlobals.seElemRadius,
				  0.0, 0.0, &errNum);
    }
    break;

  case MAPAINT_SQUARE_SE:
    if( warpGlobals.seElemRadius != 1 ){
      seObj = WlzMakeRectangleObject((double) warpGlobals.seElemRadius,
				     (double) warpGlobals.seElemRadius,
				     0.0, 0.0, &errNum);
    }
    break;

  case MAPAINT_SPHERE_SE:
  case MAPAINT_CUBE_SE:
  default:
    return;
  }

  /* apply the required operation */
  switch( opType ){
  case MAPAINT_OP_ERODE:
    if( seObj ){
      newDomain = WlzStructErosion(warpGlobals.sgnlObj, seObj, &errNum);
    }
    else {
      newDomain = WlzErosion(warpGlobals.sgnlObj, conn, &errNum);
    }      
    break;

  case MAPAINT_OP_DILATE:
    if( seObj ){
      newDomain = WlzStructDilation(warpGlobals.sgnlObj, seObj, &errNum);
    }
    else {
      newDomain = WlzDilation(warpGlobals.sgnlObj, conn, &errNum);
    }      
    break;

  case MAPAINT_OP_OPEN:
    if( seObj ){
      if((tmpObj = WlzStructErosion(warpGlobals.sgnlObj, seObj, &errNum))){
	newDomain = WlzStructDilation(tmpObj, seObj, &errNum);
      }
      else {
	newDomain = NULL;
      }
    }
    else {
      if((tmpObj = WlzErosion(warpGlobals.sgnlObj, conn, &errNum))){
	newDomain = WlzDilation(tmpObj, conn, &errNum);
      }
      else {
	newDomain = NULL;
      }
    }
    if( tmpObj ){
      WlzFreeObj(tmpObj);
    }
    break;

  case MAPAINT_OP_CLOSE:
    if( seObj ){
      if((tmpObj = WlzStructDilation(warpGlobals.sgnlObj, seObj, &errNum))){
	newDomain = WlzStructErosion(tmpObj, seObj, &errNum);
      }
      else {
	newDomain = NULL;
      }
    }
    else {
      if((tmpObj = WlzDilation(warpGlobals.sgnlObj, conn, &errNum))){
	newDomain = WlzErosion(tmpObj, conn, &errNum);
      }
      else {
	newDomain = NULL;
      }
    }      
    if( tmpObj ){
      WlzFreeObj(tmpObj);
    }
    break;

  default:
    break;
  }

  /* push existing onto undo stack
     note the domain is not incremented */
  /* should have an independent undo and redo stack here */
  if( newDomain ){
    WlzObjListPush(sgnlPostProcUndoList, warpGlobals.sgnlObj);
    WlzObjListClear(sgnlPostProcRedoList);
    WlzFreeObj(warpGlobals.sgnlObj);
    warpGlobals.sgnlObj = WlzAssignObject(newDomain, &errNum);
    warpCanvasExposeCb(w, (XtPointer) &(warpGlobals.sgnl), NULL);
    warpDisplayDomain(&(warpGlobals.sgnl), warpGlobals.sgnlObj, 1);
    XFlush(XtDisplayOfObject(w));
  }

  return;
}