changeset 11346:558eb88d81b1 octave-forge

new functions cp2tform, maketform, tformfwd and tforminv by Pantxo Diribarne
author carandraug
date Thu, 03 Jan 2013 14:52:10 +0000
parents e75a08c7bfce
children 9d835921957b
files main/image/INDEX main/image/NEWS main/image/inst/cp2tform.m main/image/inst/maketform.m main/image/inst/tformfwd.m main/image/inst/tforminv.m
diffstat 6 files changed, 563 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/main/image/INDEX	Wed Jan 02 22:04:17 2013 +0000
+++ b/main/image/INDEX	Thu Jan 03 14:52:10 2013 +0000
@@ -105,6 +105,7 @@
  poly2mask
  roicolor
 Spatial transformations
+ cp2tform
  imcrop
  impad
  imperspectivewarp
@@ -114,7 +115,10 @@
  imrotate_Fourier
  imshear
  imtranslate
+ maketform
  rotate_scale
+ tformfwd
+ tforminv
 Types and Type conversions
  grayslice
  graythresh
--- a/main/image/NEWS	Wed Jan 02 22:04:17 2013 +0000
+++ b/main/image/NEWS	Thu Jan 03 14:52:10 2013 +0000
@@ -3,7 +3,12 @@
 
  ** The following functions are new:
 
-      checkerboard    strel
+      checkerboard
+      cp2tform
+      maketform
+      strel
+      tformfwd
+      tforminv
 
  ** The plot produced by `imhist' is correctly scaled on the X axis so that the
     colorbar corresponds to the actual intensity of the stems; the given
@@ -18,7 +23,7 @@
  ** The function `rgbplot' accepts a style option which alows for a composite
     display of a colormap.
 
- ** The option to create poisson noise to an image has been added to `imnoise'. 
+ ** The option to create poisson noise to an image has been added to `imnoise'.
 
  ** With the addition of the strel class, the following functions are now able
     to handle strel objects:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/cp2tform.m	Thu Jan 03 14:52:10 2013 +0000
@@ -0,0 +1,264 @@
+## Copyright (C) 2012 Pantxo Diribarne
+## 
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {@var{T} =} cp2tform (@var{rw_pt}, @var{ap_pt}, @var{transtype})
+## @deftypefnx {Function File} {@var{T} =} cp2tform (@var{rw_pt}, @var{ap_pt}, @var{transtype}, @var{opt})
+## Returns a transformation structure @var{T} (see "help maketform"
+## for the form of the structure) that can be further used to
+## transform coordinates from one space (here denoted "RW" for "real
+## world") to another (here denoted "AP" for "apparent"). The transform
+## is infered from two n-by-2 arrays, @var{rw_pt} and @var{ap_pt}, wich
+## contain the coordinates of n control points in the two 2D spaces.
+## Transform coefficients are stored
+## in @var{T}.tdata. Interpretation of transform coefficients depends on the
+## requested transform type @var{transtype}:
+##
+## @table @asis
+## @item "affine"
+## Return both forward (RW->AP) and inverse (AP->RW) transform
+## coefficients @var{T}.tdata.T and @var{T}.tdata.Tinv. Transform
+## coefficients are 3x2 matrices which can
+## be used as follows:
+##
+## @example
+## @group
+## @var{rw_pt} = [@var{ap_pt} ones(rows (ap_pt,1))] * Tinv
+## @var{ap_pt} = [@var{rw_pt} ones(rows (rw_pt,1))] * T
+## @end group
+## @end example
+## This transformation is well suited when parallel lines in one space
+## are still parallel in the other space (e.g. shear, translation, ...).
+## 
+## @item "nonreflective similarity"
+## Same as "affine" except that the transform matrices T and Tinv have
+## the form
+## @example
+## @group
+## Tcoefs = [a -b;                
+##           b  a;
+##           c  d]
+## @end group
+## @end example
+## This transformation may represent rotation, scaling and
+## translation. Reflection is not included.
+## 
+## @item "similarity"
+## Same as "nonreflective similarity" except that the transform matrices T and Tinv may also have
+## the form
+## @example
+## @group
+## Tcoefs = [a  b;                
+##           b -a;
+##           c  d]
+## @end group
+## @end example
+## This transformation may represent reflection, rotation, scaling and
+## translation. Generates a warning if the nonreflective similarity is 
+## better suited.
+## 
+## @item "projective"
+## Return both forward (RW->AP) and inverse (AP->RW) transform
+## coefficients @var{T}.tdata.T and @var{T}.tdata.Tinv. Transform
+## coefficients are 3x3 matrices which  can
+## be used as follows:
+##
+## @example
+## @group
+## [u v w] = [@var{ap_pt} ones(rows (ap_pt,1))] * Tinv
+## @var{rw_pt} = [u./w, v./w];
+## [x y z] = [@var{rw_pt} ones(rows (rw_pt,1))] * T
+## @var{ap_pt} = [x./z y./z];
+## @end group
+## @end example
+## This transformation is well suited when parallel lines in one space
+## all converge toward a vanishing point in the other space.
+##
+## @item "polynomial"
+## Here the @var{opt} input argument is the order of the polynomial
+## fit. @var{opt} must be 2, 3 or 4 and input control points number must
+## be respectively at least 6, 10 and 15. Only the inverse transform
+## (AP->RW) is included in  the structure @var{T}.
+## Denoting x and y the apparent coordinates vector and xrw, yrw the
+## the real world coordinates. Inverse transform coefficients are
+## stored in a (6,10 or 15)x2 matrix which can be used as follows:
+##
+## @example
+## @group
+## Second order:  
+## [xrw yrw] = [1 x y x*y x^2 y^2] * Tinv
+## @end group
+## @group
+## Third order:   
+## [xrw yrw] = [1 x y x*y x^2 y^2 y*x^2 x*y^2 x^3 y^3] * Tinv
+## @end group
+## @group
+## Fourth order:  
+## [xrw yrw] = [1 x y x*y x^2 y^2 y*x^2 x*y^2 x^3 y^3 x^3*y x^2*y^2 x*y^3 x^4 y^4] * Tinv
+## @end group
+## @end example
+## This transform is well suited when lines in one space become curves
+## in the other space. 
+## @end table
+## @seealso{tformfwd, tforminv, maketform}
+## @end deftypefn
+
+## Author: Pantxo Diribarne <pantxo@dibona>
+## Created: 2012-09-05
+
+function trans = cp2tform (crw, cap, ttype, opt)
+  if (nargin < 3)
+    print_usage ();
+  endif
+
+  if (! all (size (crw) == size (cap)) ||
+      columns (crw) != 2)
+    error ("cp2tform: expect the 2 first input arguments to be (m x 2) matrices")
+  elseif (! ischar (ttype))
+    error ("cp2tform: expect a string as third input argument")
+  endif
+
+  ttype = lower (ttype);
+  switch ttype
+    case {'nonreflective similarity', 'similarity', 'affine', 'projective'}
+      trans = gettrans (ttype, cap, crw);
+    case 'polynomial'
+      if (nargin < 4)
+        error ("cp2tform: expect a fourth input argument for 'polynomial'")
+      elseif (! isscalar (opt))
+        error ("cp2tform: expect a scalar as fourth argument")
+      endif
+      trans = gettrans (ttype, cap, crw, round (opt));
+    otherwise
+      error ("cp2tform: expect 'nonreflective similarity', 'similarity', 'affine' or 'polynomial' as third input argument")
+  endswitch  
+endfunction
+
+function trans = gettrans (ttype, cap, crw, ord = 0)
+  if (strcmp (ttype, 'nonreflective similarity'))
+    x = cap(:,1);
+    y = cap(:,2);
+    u = crw(:,1);
+    v = crw(:,2);
+    tmp0 = zeros(size(x));
+    tmp1 = ones(size(x));
+    
+    A = [x y tmp1 tmp0 ; y -x tmp0 tmp1];
+    B = [u; v];
+    tmat = A\B;
+    tmat = [tmat(1) -tmat(2);
+            tmat(2)  tmat(1);
+            tmat(3)  tmat(4)];
+    trans = maketform ("affine", tmat);
+  elseif (strcmp (ttype, 'similarity'))
+    #error ("cp2tform: similarity not implemented yet.")
+    x = cap(:,1);
+    y = cap(:,2);
+    u = crw(:,1);
+    v = crw(:,2);
+    tmp0 = zeros(size(x));
+    tmp1 = ones(size(x));
+
+    #without reflection
+    A = [x y tmp1 tmp0 ; y -x tmp0 tmp1];
+    B = [u; v];
+    tmat1 = A\B;
+    resid = norm (A*tmat1 - B);
+    #with reflection
+    A = [x y tmp1 tmp0 ; -y x tmp0 tmp1];
+    B = [u; v];
+    tmat2 = A\B;
+    if (norm (A*tmat2 - B) < resid)
+      tmat = [tmat2(1)  tmat2(2); 
+              tmat2(2) -tmat2(1); 
+              tmat2(3)  tmat2(4)];
+    else
+      tmat = [tmat1(1) -tmat1(2); 
+              tmat1(2)  tmat1(1); 
+              tmat1(3)  tmat1(4)];
+      warning ("cp2tform: reflection not included.")
+    endif
+    trans = maketform ("affine", tmat);
+  elseif (strcmp (ttype, 'affine'))
+    tmat =[cap ones(rows(cap), 1)]\crw;
+    trans = maketform ("affine", tmat);
+  elseif (strcmp (ttype, "projective"))
+    x = cap(:,1);
+    y = cap(:,2);
+    u = crw(:,1);
+    v = crw(:,2);
+    tmp0 = zeros(size(x));
+    tmp1 = ones(size(x));
+    A = [-x -y -tmp1 tmp0 tmp0 tmp0 u.*x u.*y u;
+         tmp0 tmp0 tmp0 -x -y -tmp1 v.*x v.*y v];
+    [U S V] = svd (A);
+    tmat = V(:,end);
+    tmat = reshape (tmat, 3, 3);
+    tmat = tmat./tmat(end,end);
+    trans = maketform ("projective", tmat);
+  elseif (strcmp (ttype, 'polynomial'))
+    x = cap(:,1);
+    y = cap(:,2);
+    u = crw(:,1);
+    v = crw(:,2);
+    tmp1 = ones(size(x));
+    
+    ndims_in = 2;
+    ndims_out = 2;
+    forward_fcn = [];
+    inverse_fcn = @inv_polynomial;
+    A = [tmp1, x, y, x.*y, x.^2, y.^2]; 
+    B = [u v];
+    switch ord
+      case 2
+      case 3
+        A = [A, y.*x.^2 x.*y.^2 x.^3 y.^3]; 
+      case 4
+        A = [A, y.*x.^2 x.*y.^2 x.^3 y.^3];
+        A = [A, x.^3.*y x.^2.*y.^2 x.*y.^3 x.^4 y.^4];
+      otherwise
+        error ("cp2tform: supported polynomial orders are 2, 3 and 4.")
+    endswitch
+    tmat = A\B;
+    trans = maketform ("custom", ndims_in, ndims_out, ...
+                       forward_fcn, inverse_fcn, tmat);
+  endif
+endfunction
+
+function out = inv_polynomial (x, pst)
+  out = [];
+  for ii = 1:2
+    p = pst.tdata(:,ii);
+    
+    if (rows (p) == 6)
+      ## 2nd order
+      out(:,ii) = p(1) + p(2)*x(:,1) + p(3)*x(:,2) + p(4)*x(:,1).*x(:,2) + \
+                  p(5)*x(:,1).^2 + p(6)*x(:,2).^2;
+    elseif (rows (p) == 10)
+      ## 3rd order
+      out(:,ii) = p(1) + p(2)*x(:,1) + p(3)*x(:,2) + p(4)*x(:,1).*x(:,2) + \
+                  p(5)*x(:,1).^2 + p(6)*x(:,2).^2 + p(7)*x(:,2).*x(:,1).^2 + \
+                  p(8)*x(:,1).*x(:,2).^2 + p(9)*x(:,1).^3 + p(10)*x(:,2).^3;
+    elseif (rows (p) == 15)
+      ## 4th order
+      out(:,ii) = p(1) + p(2)*x(:,1) + p(3)*x(:,2) + p(4)*x(:,1).*x(:,2) + \
+                  p(5)*x(:,1).^2 + p(6)*x(:,2).^2 + p(7)*x(:,2).*x(:,1).^2 + \
+                  p(8)*x(:,1).*x(:,2).^2 + p(9)*x(:,1).^3 + p(10)*x(:,2).^3 + \
+                  p(11)*x(:,2).*x(:,1).^3 + p(12)*x(:,2).^2.*x(:,1).^2+ \
+                  p(13)*x(:,1).*x(:,2).^3 + p(14)*x(:,1).^4 + p(15)*x(:,2).^4;
+    endif
+  endfor
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/maketform.m	Thu Jan 03 14:52:10 2013 +0000
@@ -0,0 +1,142 @@
+## Copyright (C) 2012 Pantxo Diribarne
+## 
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {@var{T} =} maketform (@var{ttype}, @var{tmat})
+## @deftypefnx {Function File} {@var{T} =} maketform ("custom", @var{ndims_in}, @var{ndims_out}, @var{forward_fcn}, @var{inverse_fcn}, @var{tdata})
+## Returns a transform structure containing fields @var{ndims_in},
+## @var{ndims_out}, @var{forward_fcn}, @var{inverse_fcn} and @var{tdata}. The content
+## of each field depends on the requested transform type @var{ttype}:
+## @table @asis
+## @item "projective"
+## A ndims_in = N -> ndims_out = N projective transformation structure
+## is returned.
+## The second input argument @var{tmat} must be a (N+1)-by-(N+1)
+## transformation matrix. The
+## (N+1)th column must contain projection coefficients. As an example a two
+## dimentionnal transform from [x y] coordinates to [u v] coordinates
+## is represented by a transformation matrix defined so that:
+## @example
+## [xx yy zz] = [u v 1] * [a d g;
+##                         b e h;
+##                         c f i]
+## [x y] =  [xx./zz yy./zz];
+## @end example
+## @item "affine"
+## Affine is a subset of projective transform (see above). A ndims_in = N ->
+## ndims_out = N  affine transformation structure is returned.
+## The second input argument @var{tmat} must be a (N+1)-by-(N+1) or
+## (N+1)-by-(N) transformation matrix. If present, the (N+1)th column  must
+## contain [zeros(N,1); 1] so that projection is suppressed.
+## @item "custom"
+## For user defined transforms every field of the transform structure
+## must be supplied. The prototype of the transform functions,
+## @var{forward_fcn} and @var{inverse_fcn}, should be X' =
+## transform_fcn (X, T). X and X' are respectively p-by-ndims_in and
+## p-by-ndims_out arrays for forward_fcn and reversed for inverse_fcn.
+## The argument T is the transformation structure which will contain
+## the user supplied transformation matrix @var{tdata}. 
+## @end table
+## @seealso{tformfwd, tforminv, cp2tform}
+## @end deftypefn
+
+## Author: Pantxo Diribarne <pantxo@dibona>
+## Created: 2012-09-05
+
+function T = maketform (ttype, varargin)
+
+  if (nargin < 2 || ! any (strcmp (ttype, {"affine", "projective", "custom"})))
+    print_usage ();
+  endif
+  if (numel (varargin) == 1)
+    tmat = varargin {1};
+    ndin = rows (tmat) - 1;
+    ndout = columns (tmat) - 1;
+    if (ndin < 2);
+      error ("maketform: expect at least 3-by-2 transform matrix")
+    elseif ((ndin-ndout) > 1 || (ndout > ndin))
+      print_usage ();
+    endif
+
+    switch ttype
+      case "affine"
+        if ((ndin - ndout) == 1)
+          tmat = [tmat [zeros(ndin, 1); 1]];
+          ndout += 1;
+        elseif (!all (tmat(:,end) == [zeros(ndin, 1); 1]))
+          error ("maketform: \"affine\" expect [zeros(N,1); 1] as (N+1)th column");
+        endif
+        forward_fcn = @fwd_affine; 
+        inverse_fcn = @inv_affine;
+      case "projective"
+        if ((ndin - ndout) == 1)
+          print_usage ();
+        endif
+        forward_fcn = @fwd_projective;
+        inverse_fcn = @inv_projective;
+    endswitch
+    T.ndims_in = ndin;
+    T.ndims_out = ndout;
+    T.forward_fcn = forward_fcn;
+    T.inverse_fcn = inverse_fcn;
+    T.tdata.T = inv (tmat);
+    T.tdata.Tinv = tmat;
+  elseif (numel (varargin) == 5 && strcmp (ttype, "custom"))
+    if (isscalar (varargin{1}) && isscalar (varargin{2})
+        && varargin{1} > 0 && varargin{2} > 0)
+      T.ndims_in = varargin{1};
+      T.ndims_out = varargin{2};
+    else
+      error ("maketform: expect positive scalars as ndims.")
+    endif
+    if (is_function_handle (varargin{3}) || isempty (varargin{3}))
+      T.forward_fcn = varargin{3};
+    else
+      error ("maketform: expect function handle as forward_fcn.")
+    endif
+    if (is_function_handle (varargin{4}) || isempty (varargin{4}))
+      T.inverse_fcn = varargin{4};
+    else
+      error ("maketform: expect function handle as inverse_fcn.")
+    endif
+    
+    T.tdata = varargin{5};
+  else
+    print_usage ();
+  endif
+endfunction
+
+function X = fwd_affine (U, T)
+  U = [U, ones(rows(U), 1)];
+  X = U * T.tdata.T(:,1:end-1);
+endfunction
+
+function U = inv_affine (X, T)
+  X = [X, ones(rows(X), 1)];
+  U = X * T.tdata.Tinv(:,1:end-1);
+endfunction
+
+function X = fwd_projective (U, T)
+  U = [U, ones(rows(U), 1)];
+  XX = U * T.tdata.T;
+  X = [XX(:,1)./XX(:,3) XX(:,2)./XX(:,3)];
+endfunction
+
+function U = inv_projective (X, T)
+  X = [X, ones(rows(X), 1)];
+  UU = X * T.tdata.Tinv;
+  U = [UU(:,1)./UU(:,3) UU(:,2)./UU(:,3)];
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/tformfwd.m	Thu Jan 03 14:52:10 2013 +0000
@@ -0,0 +1,74 @@
+## Copyright (C) 2012 Pantxo Diribarne
+## 
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {[@var{UV}] =} tformfwd (@var{T}, @var{XY})
+## @deftypefnx {Function File} {[@var{U}, @var{V}] =} tformfwd (@var{T}, @var{X}, @var{Y})
+## 
+## Given to dimensionnal coordinates from one space, returns two 
+## dimensionnal coordinates in the other space, as defined in 
+## the transform structure @var{T}. Input and output coordinates 
+## may be gigen either as a n-by-2 arrays, or as two n-by-1 vectors.
+## @seealso{maketform, cp2tform, tforminv}
+## @end deftypefn
+
+## Author: Pantxo Diribarne <pantxo@dibona>
+
+function varargout = tformfwd (T, varargin)
+
+  if (nargin > 3 || nargin < 2)
+    print_usage ();
+  elseif (! istform (T))
+    error ("tformfwd: expect a transform structure as first argument")
+  elseif (nargin == 2)
+    XX = varargin{1};
+    if (columns (XX) != 2)
+      error ("tformfwd: expect n-by-2 array as second argument")
+    endif
+  else
+    if (!isvector (varargin{1}) || !isvector (varargin{2}))
+      error ("tformfwd: expect vectors as coordinates")
+    elseif (!all (size (varargin{1}) == size (varargin{2})))
+      error ("tformfwd: expect two vectors the same size")
+    elseif (columns (varargin{1}) != 1)
+      error ("tformfwd: expect column vectors")
+    endif
+    XX = [varargin{1} varargin{2}];
+  endif
+  UU = T.forward_fcn(XX, T);
+  if (nargin == 3)
+    varargout{1} = UU(:,1);
+    varargout{2} = UU(:,2);
+  else
+    varargout{1} = UU;
+  endif
+endfunction
+
+
+function out = istform (T)
+  out = true;
+  if (! isstruct (T))
+    out = false;
+  else
+    required = {"ndims_in";"ndims_out"; ...
+                "forward_fcn"; "inverse_fcn"; ...
+                "tdata"};
+    fields = fieldnames (T);
+    if (!all (strcmp (fields, required)))
+      out = false
+    endif
+  endif 
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/image/inst/tforminv.m	Thu Jan 03 14:52:10 2013 +0000
@@ -0,0 +1,72 @@
+## Copyright (C) 2012 Pantxo Diribarne
+## 
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {Function File} {[@var{UV}] =} tforminv (@var{T}, @var{XY})
+## @deftypefnx {Function File} {[@var{U}, @var{V}] =} tforminvfwd (@var{T}, @var{X}, @var{Y})
+## 
+## Given to dimensionnal coordinates from one space, returns two 
+## dimensionnal coordinates in the other space, as defined in 
+## the transform structure @var{T}. Input and output coordinates 
+## may be gigen either as a n-by-2 arrays, or as two n-by-1 vectors.
+## @seealso{maketform, cp2tform, tformfwd}
+## @end deftypefn
+
+## Author: Pantxo Diribarne <pantxo@dibona>
+
+function varargout = tforminv (T, varargin)
+  if (nargin > 3 || nargin < 2)
+    print_usage ();
+  elseif (! istform (T))
+    error ("tforminv: expect a transform structure as first argument")
+  elseif (nargin == 2)
+    XX = varargin{1};
+    if (columns (XX) != 2)
+      error ("tforminv: expect n-by-2 array as second argument")
+    endif
+  else
+    if (!isvector (varargin{1}) || !isvector (varargin{2}))
+      error ("tforminv: expect vectors as coordinates")
+    elseif (!all (size (varargin{1}) == size (varargin{2})))
+      error ("tforminv: expect two vectors the same size")
+    elseif (columns (varargin{1}) != 1)
+      error ("tforminv: expect column vectors")
+    endif
+    XX = [varargin{1} varargin{2}];
+  endif
+  UU = T.inverse_fcn(XX, T);
+  if (nargin == 3)
+    varargout{1} = UU(:,1);
+    varargout{2} = UU(:,2);
+  else
+    varargout{1} = UU;
+  endif
+endfunction
+
+function out = istform (T)
+  out = true;
+  if (!isstruct (T))
+    out = false;
+  else
+    required = {"ndims_in";"ndims_out"; ...
+                "forward_fcn"; "inverse_fcn"; ...
+                "tdata"};
+    fields = fieldnames (T);
+    if (!all (strcmp (fields, required)))
+      out = false
+    endif
+  endif 
+endfunction