-------------------------------------------
vars,function: for spherical morph
  [load data - see below]]
  set ws <geometry_weight>
  set wcrv <curv_weight>
  set wsta <stat_weight>
  set wcpx <complex_angle_weight>
  set wcpx <complex_angle2_weight>
  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 target curvature, and/or real-valued
  target data, and/or complex-valued data, and
  possibly a second complex-valued data set 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

  wcpx2: angle error -- atan2(.val2bak,.valbak) -- times
    gradient component of target angle times
    complex amplitude -- hypot(.valbak,.val2bak) -- 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), complex (optional),
and complex2 (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 (.val)
  imag target (in reverse paint voxel) => e.g., _i
  imag at vertex of moveable (.val2)
COMPLEX2
  real2 target (in reverse paint voxel) => e.g., _r
  real2 at vertex of moveable (.valbak)
  imag2 target (in reverse paint voxel) => e.g., _i
  imag2 at vertex of moveable (.val2bak)

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 in place, in .val and .val2 for the
first complex data set and .val2 and .val2bak for
the second).  This speeds up the morph (avoids
expensive per-vertex atan2's and hypot's).

Note that this will disrupt display of the
complex-valued surface data.


Example

Here is how to use 8 data sets (sulc, stat, real,
imaginary for both moveable and target) 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

N.B.: to load two complex-valued data sets for
moveable and target, load the second set first.

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
 wcpx2 (0.0) -- 2nd 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


Actual C-code

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

/*---------------------------------------*/
   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  t2=0.0, targt2=0.0;  /* ang2 after conv2polar (re-use targtn=tmpnei)*/
  float  a2=0.0, targa2=0.0;  /* amp2 after conv2polar (re-use targan=tmpnei)*/
  float  revpnt_st=REVPNT_PIX, revpnt_ps=REVPNT_PIX;
  vertex_type  *v;

  /* write r,i -> th,r in place (was: save r,i on top for disp, th,r -> bot)*/
  if (wcpx!=0.0 && !valims_cart2polarflag) {
    if (vals_cart2polarflag) {
      printf("tksurfer: ### sphere_morph: val,val2 r,i=>th,r: but not ims!\n");
      printf("tksurfer: ###   reload val,val2 and try again\n");PR return;}
    for (k=0; k<vertexcnt; k++) {
      t = atan2(vertex[k].val2,vertex[k].val);
      a = hypot(vertex[k].val,vertex[k].val2);
      vertex[k].val = t;    /* write converted in place (was to baks) */
      vertex[k].val2 = a;
    }
    vals_cart2polarflag = 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");PR
  }
  if (wcpx2!=0.0 && !valbakims_cart2polarflag) {
    if (valbaks_cart2polarflag) {
      printf("tksurfer: ### sphere_morph:valbak,valbak2 r,i=>th,r: not ims!\n");
      printf("tksurfer: ###   reload valbak,valbak2, try again\n");PR return;}
    for (k=0; k<vertexcnt; k++) {
      t = atan2(vertex[k].val2bak,vertex[k].valbak);
      a = hypot(vertex[k].valbak,vertex[k].val2bak);
      vertex[k].valbak = t;   /* write converted in place */
      vertex[k].val2bak = a;
    }
    valbaks_cart2polarflag = 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_imag2[k][i][j],revpnt_real2[k][i][j]);
      a = hypot(revpnt_real2[k][i][j],revpnt_imag2[k][i][j]);
      revpnt_real2[k][i][j] = t;
      revpnt_imag2[k][i][j] = a;
    }
    valbakims_cart2polarflag = 1;
    printf("tksurfer: sphere_morph: onetime conv surf/ims r,i -> th,r done");PR
  }

  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 */
      if (locklabelflag && v->annot) continue;  /* skip all: speedup */
      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]; }
      if (wcpx2!=0.0){ t2 = v->val; targt2 = revpnt_real2[imnr][i][j];
                       a2 = v->val2;targa2 = revpnt_imag2[imnr][i][j]; }
      /*if (wcpx!=0.0) targt -= 1.57;*/  /*debug: 90deg,rawFamp,wcpx=0.0002*/
      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;
        }  /* xxxxxxxxxxx -- curr:*targa, tried: *a, *(0.5*a*targa), *1.0 */
        if (wcpx2!=0.0) {  /*** err times grad comp times targ amp */
          targtn = revpnt_real2[imnr][i][j];  /* neighbor phase (OK to reuse) */
          targan = revpnt_imag2[imnr][i][j];  /* neighbor amp (not used) */
          w += wcpx2*circsubtract(t2,targt2)*circsubtract(targtn,targt)*targa2;
        }
        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);PR
  }
}

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

#define SPH_CURV  0
#define SPH_STAT  1
#define SPH_REAL  2
#define SPH_IMAG  3
#define SPH_REAL2 4
#define SPH_IMAG2 5

void surf2sphim(int sphvoltype) /* nearest-neigh reverse paint w/min downsamp*/
{
  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 (sphvoltype==SPH_CURV)  revpnt = revpnt_curv;
  if (sphvoltype==SPH_STAT)  revpnt = revpnt_stat;
  if (sphvoltype==SPH_REAL)  revpnt = revpnt_real;
  if (sphvoltype==SPH_IMAG)  revpnt = revpnt_imag;
  if (sphvoltype==SPH_REAL2) revpnt = revpnt_real2;
  if (sphvoltype==SPH_IMAG2) revpnt = revpnt_imag2;
  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;
  if (sphvoltype==SPH_REAL2) revpnt_real2 = revpnt;
  if (sphvoltype==SPH_IMAG2) revpnt_imag2 = 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;
  if (sphvoltype==SPH_REAL2) realim2loadedflag = 1;
  if (sphvoltype==SPH_IMAG2) imagim2loadedflag = 1;
}

