functions: sphere_morph <steps>
keyboard equivalent: none

The REG button runs the requested number of
cycles, each consisting of n steps (the surface
is refreshed once per cycle) of spherical surface
deformation (C/tcl function: sphere_morph).

The sphere_morph function simultaneously
minimizes error between up to four loaded 3D
target data sets -- curv, stat, complex-valued1,
complex-valued2 -- and their corresponding data
sets loaded onto the moveable/morphable surface,
all while trying to preserve local gemoetry.

The overall procedure is:

  (1) open a sphere.reg surface

  (2) load curvature, and/or real-valued target
  data, and/or complex-valued data to surface

  (3) sample target surface data to target
  volumes ("reverse paint", using alternate
  clicks on the "val:" line "W" button)

  (4) load corresponding moveable surface data
  (data to be morphed) onto the surface (N.B.:
  displaces target data)

  (5) run sphere_morph

The parameters (weights) controlling the morph
are found at the bottom left of the large F3
interface.  They are:

  ws: geometry restoration (neighboring vertices
    vector sum)

  wcrv: curvature (.curv) error times gradient
    component of target curvature, in neighboring
    vertex direction

  wsta: real-valued statistic (.stat) error times
    gradient compononent of target stat, in
    neighboring vertex direction

  wcpx: angle error -- atan2(.val2,.val) -- times
    gradient component of target angle times
    complex amplitude -- hypot(.val,.val2) -- in
    neighboring vertex direction

Setting any of these parameters to 0.0 causes
this kind of data to be ignored in during error
minimization.

The vectors to neighboring vertices are first
obtained.  For each step, each center vertex is
then moved to reduce error in original geometry,
curvature, real-valued statistic, and
complex-valued angle (weighted by complex
amplitude)

The errors are computed by comparing the curv
(optional), stat (optional), and complex
(optional), values at the current vertex to their
corresponding values from the target surface data
that has been "reverse painted" into a 3D volume
(thin shells) by the C/tcl function, surf2sphim
(see R-click help for "val:" line "W" button).

The gradients in the target images of curv, stat,
and/or angle of complex data are estimated by
sampling the respective target volumes using the
neighbors to the current vertex (neighbor vertex
basis).

The surface is forced back to the sphere every
update step by projecting the movement onto the
surface normal, after first clamping the maximum
per-step vertex movement to 1 mm ("nclips" in the
log reports number of vertices clamped each
step).

There are potentially 10 sets of vertexwise data
that need to be loaded:

CURV
  curv im target (in reverse paint voxel)
  curv at vertex of moveable surface
STAT
  stat im target (in reverse paint voxel) => e.g., qT1
  stat at vertex of moveable surface
COMPLEX
  real target (in reverse paint voxel) => e.g., _r
  real at vertex of moveable
  imag target (in reverse paint voxel) => e.g., _i
  imag at vertex of moveable

The first time sphere_morph is run, it will
convert the real and imaginary values in the
complex target images as well as the real and
imaginary values on the moveable surface data to
angle and amplitude (the converted surface data
is stored in .valbak and .val2bak so that the
display of complex-valued surface data is not
disturbed).  This speeds up the morph (avoids
expensive per-vertex atan2's and hypot's).

Example

Here is how to use all 8 kinds of data via via
the interface:

  ### load surface
  must be sphere.reg
  ### load target data to surface
  select TARGET sulc file from "curv:" dropdown
  left-click "R" on "curv:" line to read to surface
  select TARGET real-val'd stat "val:" dropdown (e.g. qT1)
  left-click "R" on "val:" line to read to surface
  left-click "S/V" on "val:" line to move it to .stat
  select TARGET complex data dropdown (e.g., pol _r,_i)
  left-click "R" on "val:" line to read to surface
  ### "reverse paint" target data to volume
  shift-middle-click "W" on "val:" line (rev paint curv)
  shift-right-click "W" on "val:" line (rev paint stat)
  shift-left-click "W" on "val:" line (rev paint complex)
  ### load moveable data (data to be morphed)
  select MOVEABLE sulc file from "curv:" dropdown
  left-click "R" on "curv:" line to read to surface
  select MOVEABLE real-val'd stat "val:" dropdown (e.g. qT1)
  left-click "R" on "val:" line to read to surface
  left-click "S/V" on "val:" line to move it to .stat
  select MOVEABLE cmplx data dropdown (e.g., pol _r,_i)
  left-click "R" on "val:" line to read to surface
  ### morph (F3 interface)
  set parms: ws, wcrv, wsta, wcpx (start small)
  set steps/cycles
  click REG

or as tcl script commands:

  ### load surface
  must be sphere.reg
  ### load target to surface
  setfile curv ~/surf/targ-$hemi.sulc
  read_binary_curv
  setfile val ~/surf/targ-qT1-$hemi.w (or .curv/.mgh/.vtk)
  read_binary_values
  swap_stat_val
  setfile val ~/surf/targ-polarangle_i-$hemi.w
  read_binary_values
  swap_val_val2
  setfile val ~/surf/targ-polarangle_r-$hemi.w
  read_binary_values
  ### "reverse paint" target data to volume
  surf2sphim 0
  surf2sphim 1
  surf2sphim 2
  surf2sphim 3
  ### load data to be morphed
  setfile curv ~/surf/moveable-$hemi.sulc
  read_binary_curv
  setfile val ~/surf/moveable-qT1-$hemi.w (or .curv/.mgh/.vtk)
  read_binary_values
  swap_stat_val
  setfile val ~/surf/moveable-polarangle_i-$hemi.w
  read_binary_values
  swap_val_val2
  setfile val ~/surf/moveable-polarangle_r-$hemi.w
  read_binary_values
  ### morph
  set wc 0.5
  set wcrv 0.5
  set wsta 0.001   ;# inv proportional to real values
  set wcpx 0.1
  sphere_morph 20

Morph Parameters

 ### sphere_morph()
 ws (0.5) -- tangent weight (restore geom)
 wcrv (0.5) -- curv targ img error weight
 wsta (0.0) -- stat targ img error weight
 wcpx (0.0) -- complex targ img error weight
 momentumflag (1) -- dr = decay*lastr + update*dr;
 update (0.9) -- frac new to use
 decay (0.9) -- frac old to use

C-code

Here is the core C-code for the two main
morph-related functions, surf2sphim() and
sphere_morph().

/*---------------------------------------*/
   morph function
/*---------------------------------------*/

#define REVPNT_PIX  1.0   /* reverse paint img pix */
#define REVPNT_SZ   256   /* reverse paint img size */
#define SPH_RAD     100.0 /* std sphere, sphere.reg */

void sphere_morph(int niter)
{
  int    k, iter, imnr, i, j, m, n, nclip, revpnt_sz=REVPNT_SZ;
  float  x, y, z, nx, ny, nz, xn, yn, zn, dx, dy, dz;
  float  dr, d, w;
  float  c=0.0, targc=0.0, targcn;
  float  s=0.0, targs=0.0, targsn;
  float  t=0.0, targt=0.0, targtn;  /* ang after conv2polar */
  float  a=0.0, targa=0.0, targan;  /* amp after conv2polar */
  float  revpnt_st=REVPNT_PIX, revpnt_ps=REVPNT_PIX;
  vertex_type  *v;

  printf("tksurfer: sphere_morph(niter=%d)\n",niter);
  if (wcpx!=0.0 && !valims_cart2polarflag) {
    for (k=0; k<vertexcnt; k++) {
      t = atan2(vertex[k].val2,vertex[k].val);
      a = hypot(vertex[k].val,vertex[k].val2);
      vertex[k].valbak = t;    /* to baks so no overwrite displayed r,i */
      vertex[k].val2bak = a;
    }
    valbaks_cart2polarflag = valbakloadedflag = val2bakloadedflag = 1;
    for (k=0; k<revpnt_sz; k++)
    for (i=0; i<revpnt_sz; i++)
    for (j=0; j<revpnt_sz; j++) {
      t = atan2(revpnt_imag[k][i][j],revpnt_real[k][i][j]);
      a = hypot(revpnt_real[k][i][j],revpnt_imag[k][i][j]);
      revpnt_real[k][i][j] = t;
      revpnt_imag[k][i][j] = a;
    }
    valims_cart2polarflag = 1;
    printf("tksurfer: sphere_morph: onetime conv surf/ims r,i -> th,r done");
  }

  for (iter=0; iter<niter; iter++) {
    nclip = 0;
    for (k=0; k<vertexcnt; k++) {
      v = &vertex[k];
      v->ox = v->x;  v->oy = v->y;  v->oz = v->z;
    }
    for (k=0; k<vertexcnt; k++) {  /* TODO: multiscale */
      v = &vertex[k];
      x  = v->ox;  y  = v->oy;  z  = v->oz;  /* samp cent (vtx and targ) */
      nx = v->nx;  ny = v->ny;  nz = v->nz;
      imnr = (int)((y - yy0)/revpnt_st + 0.5);
      i =    (int)((zz1 - z)/revpnt_ps + 0.5);
      j =    (int)((xx1 - x)/revpnt_ps + 0.5);
      if (wcrv!=0.0) { c = v->curv;    targc = revpnt_curv[imnr][i][j]; }
      if (wsta!=0.0) { s = v->stat;    targs = revpnt_stat[imnr][i][j]; }
      if (wcpx!=0.0) { t = v->valbak;  targt = revpnt_real[imnr][i][j];
                       a = v->val2bak; targa = revpnt_imag[imnr][i][j]; }
      n = 0;
      dx = dy = dz = 0.0;
      for (m=0; m<v->vnum; m++) {  /* samp one nei (targ) */
        xn = vertex[v->v[m]].ox;
        yn = vertex[v->v[m]].oy;
        zn = vertex[v->v[m]].oz;
        imnr = (int)((yn - yy0)/revpnt_st + 0.5);
        i =    (int)((zz1 - zn)/revpnt_ps + 0.5);
        j =    (int)((xx1 - xn)/revpnt_ps + 0.5);
        w = ws;  /* ws=tang=0.5 */
        if (wcrv!=0.0) {  /*** err times grad comp */
          targcn = revpnt_curv[imnr][i][j];
          w += wcrv*(c - targc)*(targcn - targc);
        }
        if (wsta!=0.0) {  /*** same */
          targsn = revpnt_stat[imnr][i][j];
          w += wsta*(s - targs)*(targsn - targs);
        }
        if (wcpx!=0.0) {  /*** err times grad comp times targ amp */
          targtn = revpnt_real[imnr][i][j];  /* neighbor phase */
          targan = revpnt_imag[imnr][i][j];  /* neighbor amp (not used) */
          w += wcpx*circsubtract(t,targt)*circsubtract(targtn,targt)*targa;
        }
        dx += (xn - x)*w;  /* neighbor basis */
        dy += (yn - y)*w;
        dz += (zn - z)*w;
        n++;
      }
      if (n>0) {dx = dx/n;  dy = dy/n;  dz = dz/n;}
      d = sqrt(dx*dx + dy*dy + dz*dz);  /* proposed mv dist */
      if (d>1.0) {nclip++; dx/=d; dy/=d; dz/=d;}  /* clip to 1mm */
      dr = SPH_RAD - sqrt((x+dx)*(x+dx) + (y+dy)*(y+dy) + (z+dz)*(z+dz));
      dx += dr*v->nx;  dy += dr*v->ny;  dz += dr*v->nz; /*bak2sph: proj2norm*/
      if (momentumflag) {
        dx = decay*v->mx + update*dx;
        dy = decay*v->my + update*dy;
        dz = decay*v->mz + update*dz;
        v->mx = dx;  v->my = dy;  v->mz = dz;
      }
      v->x += dx;  v->y += dy;  v->z += dz;   /*mv vtx; next: recalc sphnorm*/
      v->nx = v->x/SPH_RAD;  v->ny = v->y/SPH_RAD;  v->nz = v->z/SPH_RAD;
    }
    printf("tksurfer: step=%d: nclip=%d\n",iter,nclip);
  }
}

/*---------------------------------------*/
   reverse paint function
/*---------------------------------------*/

void surf2sphim(int sphvoltype)
{
  int    k, imnr, i, j;
  int    revpnt_sz=REVPNT_SZ;
  float  ***revpnt=NULL, revpnt_st=REVPNT_PIX, revpnt_ps=REVPNT_PIX;
  float  x, y, z, d, maxd, dd;
  vertex_type  *v;

  if (revpnt!=NULL) freefloat3D(revpnt);
  revpnt = makefloat3D(revpnt_sz,revpnt_sz,revpnt_sz);
  if (sphvoltype==SPH_CURV) revpnt_curv = revpnt;
  if (sphvoltype==SPH_STAT) revpnt_stat = revpnt;
  if (sphvoltype==SPH_REAL) revpnt_real = revpnt;
  if (sphvoltype==SPH_IMAG) revpnt_imag = revpnt;

  /* avg ico vtx spacing ~0.95, samp 1mm^3 (N.B.: point upsamp -> holes) */
  for (k=0; k<revpnt_sz; k++)
  for (i=0; i<revpnt_sz; i++)
  for (j=0; j<revpnt_sz; j++)
    revpnt[k][i][j] = 0.0;
  dd = 0.2*revpnt_ps;  /* don't miss/alias (localized zeros w/1.0*revpnt_ps) */
  maxd = 1.2*revpnt_ps;
  for (k=0; k<vertexcnt; k++) {
    v = &vertex[k];
    for (d=-maxd; d<=maxd; d+=dd) {  /* reverse paint 1+ vox in/out on norm */
      x = v->x + d*v->nx;
      y = v->y + d*v->ny;
      z = v->z + d*v->nz;
      imnr = (int)((y - yy0)/revpnt_st + 0.5); /*skip clamp: req's std sphere*/
      i =    (int)((zz1 - z)/revpnt_ps + 0.5);
      j =    (int)((xx1 - x)/revpnt_ps + 0.5);
      if (sphvoltype==SPH_CURV) revpnt[imnr][i][j] = v->curv;
      if (sphvoltype==SPH_STAT) revpnt[imnr][i][j] = v->stat;
      if (sphvoltype==SPH_REAL) revpnt[imnr][i][j] = v->val;
      if (sphvoltype==SPH_IMAG) revpnt[imnr][i][j] = v->val2;
    }
  }
  if (sphvoltype==SPH_CURV) curvimloadedflag = 1;
  if (sphvoltype==SPH_STAT) statimloadedflag = 1;
  if (sphvoltype==SPH_REAL) realimloadedflag = 1;
  if (sphvoltype==SPH_IMAG) imagimloadedflag = 1;
}
