├── bmemd.m ├── local_var_img.m ├── IMG ├── source02_0.tif ├── source02_1.tif ├── source02_2.tif ├── source09_1.tif └── source09_2.tif ├── gridfitdir ├── gridfit.pdf ├── ravine.jpg ├── bluff_data.mat ├── doc │ ├── gridfit.pdf │ ├── gridfit.tex │ └── Understanding_gridfit.rtf ├── demo │ ├── gridfit_demo.mlx │ ├── html │ │ ├── gridfit_demo.png │ │ ├── gridfit_demo_01.png │ │ ├── gridfit_demo_02.png │ │ ├── gridfit_demo_03.png │ │ ├── gridfit_demo_04.png │ │ ├── gridfit_demo_05.png │ │ ├── gridfit_demo_06.png │ │ ├── gridfit_demo_07.png │ │ ├── gridfit_demo_08.png │ │ ├── gridfit_demo_09.png │ │ ├── gridfit_demo_10.png │ │ └── gridfit_demo.html │ └── gridfit_demo.m ├── test │ └── test_main.m └── gridfit.m ├── bmemd_fusion.m ├── Texture_Generate.m └── README.md /bmemd.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/bmemd.m -------------------------------------------------------------------------------- /local_var_img.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/local_var_img.m -------------------------------------------------------------------------------- /IMG/source02_0.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/IMG/source02_0.tif -------------------------------------------------------------------------------- /IMG/source02_1.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/IMG/source02_1.tif -------------------------------------------------------------------------------- /IMG/source02_2.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/IMG/source02_2.tif -------------------------------------------------------------------------------- /IMG/source09_1.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/IMG/source09_1.tif -------------------------------------------------------------------------------- /IMG/source09_2.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/IMG/source09_2.tif -------------------------------------------------------------------------------- /gridfitdir/gridfit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/gridfit.pdf -------------------------------------------------------------------------------- /gridfitdir/ravine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/ravine.jpg -------------------------------------------------------------------------------- /gridfitdir/bluff_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/bluff_data.mat -------------------------------------------------------------------------------- /gridfitdir/doc/gridfit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/doc/gridfit.pdf -------------------------------------------------------------------------------- /gridfitdir/demo/gridfit_demo.mlx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/gridfit_demo.mlx -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_01.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_02.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_03.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_04.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_05.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_06.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_07.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_08.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_09.png -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z-bingo/Bidimensional-Multivariate-Empirical-Mode-Decomposition/HEAD/gridfitdir/demo/html/gridfit_demo_10.png -------------------------------------------------------------------------------- /bmemd_fusion.m: -------------------------------------------------------------------------------- 1 | %This is the demo of fusing multiple images by using bmemd 2 | if exist('im', 'var') 3 | clear im 4 | end 5 | 6 | im1=imread('./IMG/source02_0.tif'); 7 | im2=imread('./IMG/source02_1.tif'); 8 | color = size(im1,3); 9 | 10 | final_img1 = uint8(zeros(size(im1))); 11 | final_img2 = uint8(zeros(size(im1))); 12 | 13 | for color_chanel = 1:color 14 | im(:,:,1)=im1(:,:,color_chanel); 15 | im(:,:,2)=im2(:,:,color_chanel); 16 | imf = bmemd(im,8); 17 | n_imf = size(imf,2); 18 | [M, N, dim] = size(imf{1,1}); 19 | cor = zeros(M,N,dim); 20 | var = zeros(M,N,dim); 21 | fusion_img = zeros(M,N); 22 | for imf_i=1:n_imf 23 | if imf_i ~= n_imf 24 | var = local_var_img(imf{1,imf_i},5); 25 | for j = 1:dim 26 | cor(:,:,j) = var(:,:,j) ./ sum(var,3); 27 | end 28 | else 29 | for j = 1:dim 30 | cor(:,:,j) = imf{1,imf_i}(:,:,j) ./ sum(imf{1,imf_i},3); 31 | end 32 | end 33 | fusion_img = fusion_img + sum(imf{1,imf_i} .* cor,3); 34 | end 35 | end 36 | 37 | imshow(fusion_img, []) 38 | 39 | -------------------------------------------------------------------------------- /Texture_Generate.m: -------------------------------------------------------------------------------- 1 | %The code to generate synthetic texture images in paper 2 | clc, close all 3 | f1 = 0.08 4 | f2 = 0.02 5 | n = 0:199; 6 | 7 | mask = mat2gray(normpdf(1:200, 100, 80)); 8 | 9 | s1 = sin(2*pi*f1*n+rand()*pi) + sin(2*pi*f2*n+rand()*pi) + sin(2*pi*f3*n+rand()*pi); 10 | text1 = repmat(s1, 200, 1) + repmat(s1', 1, 200); 11 | text1 = mat2gray(text1); 12 | 13 | s2 = sin(2*pi*f2*n+rand()*pi) + sin(2*pi*f1*n+rand()*pi); 14 | text2 = repmat(s2, 200, 1) + repmat(s2', 1, 200); 15 | text2 = mat2gray(text2); 16 | %figure, imshow(text2) 17 | s3 = sin(2*pi*f1*n+rand()*pi) + sin(2*pi*f2*n+rand()*pi); 18 | text3 = repmat(s3, 200, 1) + repmat(s3', 1, 200); 19 | text3 = mat2gray(text3); 20 | 21 | mask = repmat(normpdf(1:200, 100, 80), 200, 1)+repmat(normpdf(1:200, 100, 80)', 1, 200); 22 | syn1 = mat2gray(text1); 23 | syn2 = mat2gray(text2+sqrt(0.006)*randn(200,200)); 24 | syn3 = mat2gray(text3+sqrt(0.006)*randn(200,200)); 25 | % final multi-channel 26 | final = zeros(200, 200, 3); 27 | final(:,:,1) = syn1; 28 | final(:,:,2) = syn2; 29 | final(:,:,3) = syn3; 30 | figure 31 | subplot(2,3,1), imshow(syn1) 32 | subplot(2,3,2), imshow(syn2) 33 | subplot(2,3,3), imshow(syn3) 34 | 35 | save('texture_syn.mat', 'final') 36 | 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Bidimensional-Multivariate-Empirical-Mode-Decomposition 2 | Matlab codes of Bidimensional Multivariate Empirical Mode Decomposition (BMEMD). 3 | 4 | ### Introduction 5 | BMEMD is a bidimensional and multivariate version of original EMD, which is capable of processing multi-images, such as image fusion, texture analysis and so on. More details about the BMEMD can be referred in our paper [Bidimensional Multivariate Empirical Mode Decomposition with Applications in Multi-Scale Image Fusion](https://ieeexplore.ieee.org/document/8805082?source=authoralert). 6 | 7 | ### Requirements 8 | - Image Processing Toolbox (installed in Matlab) 9 | - gridfitdir (attanched in this repo., add the path to your Matlab environment) 10 | 11 | ### How to use these codes? 12 | #### Files and Directories 13 | - bmemd.m 14 | - main code of proposed BMEMD 15 | - bmemd_fusion.m 16 | - its application on multi-images fusion, several images are provided at path `./IMG` 17 | - Texture_Generate.m 18 | - the code to generate synthetic texture images in paper 19 | 20 | #### Usages (take only decomposition as the example) 21 | ```matlab 22 | x: [n, h, w], a non-int array 23 | q: a cell of length Q, the number of IMFs, and each array in the cell 24 | share the same size with x representing the corresponding IMF of x 25 | ``` 26 | 1. `q=bmemd(x)` 27 | 2. `q=bmemd(x, ndir)`, here, `ndir` is the number of projections 28 | 3. `q=bmemd(x,ndir,stop_crtit)`, `stop_crtit` means stopping conditions, can be choosen from `'stop'` and `'fix_h'`. If `'stop'`, the default parameter is `[0.01, 0.1, 0.01]`, ohtherwise, `fix_h=2` 29 | 4. `q=bmemd(x,ndir,'stop',[x1,x2,x3])`, `[x1,x2,x3]` is parameter of stop criteria 30 | 5. `q=bmemd(x,nir,'fix_h',fix_h)` 31 | 32 | ### Reference 33 | [1] N. Rehman and D. P. Mandic,, "Multivariate empirical mode decomposition," Proc. R. Soc. A, vol.466, no. 2117, pp. 1291-1302, 2010. 34 | [2] T. Tanaka and D. P. Mandic, “Complex empirical mode decomposition,” IEEE Signal Process. Lett., vol. 14, no. 2, pp. 101–104, Feb. 2007. 35 | [3] G. Rilling, P. Flandrin, P. Gonalves, and J. M. Lilly, “Bivariate empirical mode decomposition,” IEEE Signal Process. Lett., vol. 14, no.12, pp. 936–939, Dec. 2007. 36 | [4] N. Rehman and D. P. Mandic, “Empirical mode decomposition for trivariate signals,” IEEE Trans. Signal Process., vol. 58, no. 3, pp. 1059–1068, Mar. 2010. 37 | [5] J. C. Nunes, S. Guyot, and E. Delechelle, “Texture analysis based on local analysis of the bidimensional empirical mode decomposition,” Mach. Vis. Appl., vol. 16, no. 3, pp. 177–188, May 2005. 38 | 39 | ### Citation 40 | ```tex 41 | @article{Xia2019BMEMD, 42 | author = {Y. Xia and B. Zhang and W. Pei and D. P. Mandic}, 43 | journal = {IEEE Access}, 44 | title = {Bidimensional Multivariate Empirical Mode Decomposition With Applications in Multi-Scale Image Fusion}, 45 | year = {2019}, 46 | volume = {7}, 47 | pages = {114261-114270}, 48 | doi = {10.1109/ACCESS.2019.2936030}, 49 | ISSN = {2169-3536}, 50 | month = {Dec.} 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /gridfitdir/demo/gridfit_demo.m: -------------------------------------------------------------------------------- 1 | % Gridfit demo script 2 | 3 | % This script file is designed to be used in cell mode 4 | % from the matlab editor, or best of all, use the publish 5 | % to HTML feature from the matlab editor. Older versions 6 | % of matlab can copy and paste entire blocks of code into 7 | % the Matlab command window. 8 | 9 | %% Topographic data 10 | load bluff_data; 11 | x=bluff_data(:,1); 12 | y=bluff_data(:,2); 13 | z=bluff_data(:,3); 14 | % Two ravines on a hillside. Scanned from a 15 | % topographic map of an area in upstate New York. 16 | plot3(x,y,z,'.') 17 | 18 | %% 19 | 20 | % Turn the scanned point data into a surface 21 | gx=0:4:264; 22 | gy=0:4:400; 23 | g=gridfit(x,y,z,gx,gy); 24 | figure 25 | colormap(hot(256)); 26 | surf(gx,gy,g); 27 | camlight right; 28 | lighting phong; 29 | shading interp 30 | line(x,y,z,'marker','.','markersize',4,'linestyle','none'); 31 | title 'Use topographic contours to recreate a surface' 32 | 33 | %% Fitting a trigonometric surface 34 | clear 35 | 36 | n1 = 15; 37 | n2 = 15; 38 | theta = rand(n1,1)*pi/2; 39 | r = rand(1,n2); 40 | 41 | x = cos(theta)*r; 42 | y = sin(theta)*r; 43 | x=x(:); 44 | y=y(:); 45 | 46 | x = [[0 0 1 1]';x;x;1-x;1-x]; 47 | y = [[0 1 0 1]';y;1-y;y;1-y]; 48 | figure 49 | plot(x,y,'.') 50 | title 'Data locations in the x-y plane' 51 | 52 | %% 53 | z = sin(4*x+5*y).*cos(7*(x-y))+exp(x+y); 54 | 55 | xi = linspace(0,1,51); 56 | [xg,yg]=meshgrid(xi,xi); 57 | zgd = griddata(x,y,z,xg,yg); 58 | 59 | figure 60 | surf(xi,xi,zgd) 61 | colormap(hot(256)) 62 | camlight right 63 | lighting phong 64 | title 'Griddata on trig surface' 65 | % Note the wing-like artifacts along the edges, due 66 | % to the use of a Delaunay triangulation in griddata. 67 | 68 | %% 69 | 70 | zgrid = gridfit(x,y,z,xi,xi); 71 | 72 | figure 73 | surf(xi,xi,zgrid) 74 | colormap(hot(256)) 75 | camlight right 76 | lighting phong 77 | title('Gridfit to trig surface') 78 | 79 | %% The trig surface with highly different scalings on the x and y axes 80 | xs = x/100; 81 | xis = xi/100; 82 | 83 | ys = y*100; 84 | yis = xi*100; 85 | 86 | % griddata has problems with badly scaled data 87 | [xg,yg]=meshgrid(xis,yis); 88 | zgd = griddata(xs,ys,z,xg,yg); 89 | 90 | figure 91 | surf(xg,yg,zgd) 92 | colormap(hot(256)) 93 | camlight right 94 | lighting phong 95 | title 'Serious problems for griddata on badly scaled trig surface' 96 | 97 | % autoscaling on (the default) 98 | zgrids = gridfit(xs,ys,z,xis,yis,'autoscale','on'); 99 | 100 | % plot the autoscaled result 101 | figure 102 | surf(xis,yis,zgrids) 103 | colormap(hot(256)) 104 | camlight right 105 | lighting phong 106 | title 'Gridfit (automatically scaled) on trig surface' 107 | 108 | %% Fitting the "peaks" surface 109 | 110 | clear 111 | 112 | n = 100; 113 | x = (rand(n,1)-.5)*6; 114 | y = (rand(n,1)-.5)*6; 115 | 116 | z = peaks(x,y); 117 | 118 | xi = linspace(-3,3,101); 119 | zpgf = gridfit(x,y,z,xi,xi); 120 | 121 | [xg,yg]=meshgrid(xi,xi); 122 | zpgd = griddata(x,y,z,xg,yg,'cubic'); 123 | 124 | figure 125 | surf(xi,xi,zpgd) 126 | colormap(jet(256)) 127 | camlight right 128 | lighting phong 129 | title 'Griddata (method == cubic) on peaks surface' 130 | 131 | figure 132 | surf(xi,xi,zpgf) 133 | colormap(hsv(256)) 134 | camlight right 135 | lighting phong 136 | title('Gridfit to peaks surface') 137 | 138 | %% Using tiles in gridfit 139 | 140 | % Users of gridfit who have really huge problems now have 141 | % an option. I'll generate a large amount of data, 142 | % and hope to model a fairly large grid - 800 x 800. This 143 | % would normally require gridfit to solve a system of 144 | % equations with 640,000 unknowns. It would probably be too 145 | % large of a problem for my computer, were I to use gridfit 146 | % on the full problem. Gridfit allows you to break the problem 147 | % into smaller tiles if you choose. In this case each tile 148 | % is 120x120, with a 25% (30 element) overlap between tiles. 149 | 150 | % Relax, this demo may take a couple of minutes to run!!!! 151 | 152 | n = 100000; 153 | x = rand(n,1); 154 | y = rand(n,1); 155 | z = x+y+sin((x.^2+y.^2)*10); 156 | 157 | xnodes = 0:.00125:1; 158 | ynodes = xnodes; 159 | 160 | [zg,xg,yg] = gridfit(x,y,z,xnodes,ynodes,'tilesize',120,'overlap',0.25); 161 | 162 | surf(xg,yg,zg) 163 | shading interp 164 | colormap(jet(256)) 165 | camlight right 166 | lighting phong 167 | title 'Tiled gridfit' 168 | 169 | %% 170 | -------------------------------------------------------------------------------- /gridfitdir/test/test_main.m: -------------------------------------------------------------------------------- 1 | % Gridfit test script 2 | 3 | % This script file is designed to be used in cell mode 4 | % from the matlab editor, or best of all, use the publish 5 | % to HTML feature from the matlab editor. Older versions 6 | % of matlab can copy and paste entire blocks of code into 7 | % the Matlab command window. 8 | 9 | %% Topographic data 10 | load bluff_data; 11 | x=bluff_data(:,1); 12 | y=bluff_data(:,2); 13 | z=bluff_data(:,3); 14 | % Two ravines on a hillside. Scanned from a 15 | % topographic map of an area in upstate New York. 16 | plot3(x,y,z,'.') 17 | 18 | %% 19 | 20 | % Turn the scanned point data into a surface 21 | gx=0:4:264; 22 | gy=0:4:400; 23 | g=gridfit(x,y,z,gx,gy); 24 | figure 25 | colormap(hot(256)); 26 | surf(gx,gy,g); 27 | camlight right; 28 | lighting phong; 29 | shading interp 30 | line(x,y,z,'marker','.','markersize',4,'linestyle','none'); 31 | title 'Use topographic contours to recreate a surface' 32 | 33 | %% Fitting a trigonometric surface 34 | clear 35 | 36 | n1 = 15; 37 | n2 = 15; 38 | theta = rand(n1,1)*pi/2; 39 | r = rand(1,n2); 40 | 41 | x = cos(theta)*r; 42 | y = sin(theta)*r; 43 | x=x(:); 44 | y=y(:); 45 | 46 | x = [[0 0 1 1]';x;x;1-x;1-x]; 47 | y = [[0 1 0 1]';y;1-y;y;1-y]; 48 | figure 49 | plot(x,y,'.') 50 | title 'Data locations in the x-y plane' 51 | 52 | %% 53 | z = sin(4*x+5*y).*cos(7*(x-y))+exp(x+y); 54 | 55 | xi = linspace(0,1,51); 56 | [xg,yg]=meshgrid(xi,xi); 57 | zgd = griddata(x,y,z,xg,yg); 58 | 59 | figure 60 | surf(xi,xi,zgd) 61 | colormap(hot(256)) 62 | camlight right 63 | lighting phong 64 | title 'Griddata on trig surface' 65 | % Note the wing-like artifacts along the edges, due 66 | % to the use of a Delaunay triangulation in griddata. 67 | 68 | %% 69 | 70 | zgrid = gridfit(x,y,z,xi,xi); 71 | 72 | figure 73 | surf(xi,xi,zgrid) 74 | colormap(hot(256)) 75 | camlight right 76 | lighting phong 77 | title('Gridfit to trig surface') 78 | 79 | %% The trig surface with highly different scalings on the x and y axes 80 | xs = x/100; 81 | xis = xi/100; 82 | 83 | ys = y*100; 84 | yis = xi*100; 85 | 86 | % griddata has problems with badly scaled data 87 | [xg,yg]=meshgrid(xis,yis); 88 | zgd = griddata(xs,ys,z,xg,yg); 89 | 90 | figure 91 | surf(xg,yg,zgd) 92 | colormap(hot(256)) 93 | camlight right 94 | lighting phong 95 | title 'Serious problems for griddata on badly scaled trig surface' 96 | 97 | %% 98 | 99 | % autoscaling off 100 | zgrid = gridfit(xs,ys,z,xis,yis,'autoscale','off'); 101 | 102 | % autoscaling on (the default) 103 | zgrids = gridfit(xs,ys,z,xis,yis,'autoscale','on'); 104 | 105 | % plot the unscaled result 106 | figure 107 | surf(xis,yis,zgrid) 108 | colormap(hot(256)) 109 | camlight right 110 | lighting phong 111 | title 'Gridfit (unscaled) on trig surface' 112 | 113 | % plot the unscaled result 114 | figure 115 | surf(xis,yis,zgrids) 116 | colormap(hot(256)) 117 | camlight right 118 | lighting phong 119 | title 'Default gridfit (automatically scaled) on trig surface' 120 | 121 | %% Comparison of extrapolation behaviors 122 | % Note: griddata will not extrapolate using most most of 123 | % its methods, and is very slow/memory intensive for the 124 | % 'v4' method 125 | 126 | clear 127 | 128 | n = 50000; 129 | x = min(2,max(-2,randn(n,1))); 130 | y = min(2,max(-2,randn(n,1))); 131 | z = exp(x+y); 132 | nodes = -3:.1:3; 133 | zg = gridfit(x,y,z,nodes,nodes,'reg','gradient','sm',.01); 134 | zl = gridfit(x,y,z,nodes,nodes,'reg','laplacian','sm',.01); 135 | zs = gridfit(x,y,z,nodes,nodes,'reg','springs','sm',.01); 136 | 137 | % plot the results 138 | figure 139 | surf(nodes,nodes,zg) 140 | colormap(bone(256)) 141 | camlight right 142 | lighting phong 143 | hold on 144 | plot3(x,y,z,'r.') 145 | hold off 146 | title 'Extrapolation with gridfit (gradient regularization)' 147 | 148 | figure 149 | surf(nodes,nodes,zl) 150 | colormap(bone(256)) 151 | camlight right 152 | lighting phong 153 | hold on 154 | plot3(x,y,z,'r.') 155 | hold off 156 | title 'Extrapolation with gridfit (Laplacian regularization)' 157 | 158 | figure 159 | surf(nodes,nodes,zs) 160 | colormap(bone(256)) 161 | camlight right 162 | lighting phong 163 | hold on 164 | plot3(x,y,z,'r.') 165 | hold off 166 | title 'Extrapolation with gridfit (gradient regularization)' 167 | 168 | %% Fitting the "peaks" surface with fairly sparse data 169 | 170 | clear 171 | 172 | n = 100; 173 | x = (rand(n,1)-.5)*6; 174 | y = (rand(n,1)-.5)*6; 175 | 176 | z = peaks(x,y); 177 | 178 | xi = linspace(-3,3,101); 179 | zpgf = gridfit(x,y,z,xi,xi); 180 | 181 | [xg,yg]=meshgrid(xi,xi); 182 | zpgd = griddata(x,y,z,xg,yg,'cubic'); 183 | 184 | figure 185 | surf(xi,xi,zpgd) 186 | colormap(jet(256)) 187 | camlight right 188 | lighting phong 189 | title 'Griddata (method == cubic) on peaks surface' 190 | 191 | figure 192 | surf(xi,xi,zpgf) 193 | colormap(hsv(256)) 194 | camlight right 195 | lighting phong 196 | title('Gridfit on peaks surface') 197 | 198 | %% Fitting the seamount surface 199 | 200 | clear 201 | 202 | load seamount 203 | 204 | xg = linspace(min(x),max(x),50); 205 | yg = linspace(min(y),max(y),50); 206 | 207 | zsgf = gridfit(x,y,z,xg,yg); 208 | 209 | figure 210 | surf(xg,yg,zsgf) 211 | colormap(hot(256)) 212 | camlight right 213 | lighting phong 214 | title('Gridfit applied to seamount data') 215 | 216 | % Note that gridfit will extrapolate smoothly to the 217 | % corners of the grid, whereas griddata cannot, unless 218 | % the 'v4' option is chosen. But 'v4' is slow on even 219 | % moderately large problems. 220 | 221 | [xg,yg]=meshgrid(xg,yg); 222 | zsgd = griddata(x,y,z,xg,yg); 223 | 224 | figure 225 | surf(xg,yg,zsgd) 226 | colormap(pink(256)) 227 | camlight right 228 | lighting phong 229 | title 'Griddata' 230 | 231 | %% Using tiles in gridfit 232 | 233 | % Users of gridfit who have really huge problems now have 234 | % an option. I'll generate a large amount of data, 235 | % and hope to model a fairly large grid - 800 x 800. This 236 | % would normally require gridfit to solve a system of 237 | % equations with 640,000 unknowns. It might be a solveable 238 | % (though very slowly so) problem for my computer, were I 239 | % to use gridfit on the full problem. Gridfit allows you to 240 | % break the problem into smaller tiles if you choose. In 241 | % this case each tile is 120x120, with a 25% (30 element) 242 | % overlap between tiles. 243 | 244 | % Relax, this demo may take a couple of minutes to run!!!! 245 | 246 | n = 100000; 247 | x = rand(n,1); 248 | y = rand(n,1); 249 | z = x+y+sin((x.^2+y.^2)*10); 250 | 251 | xnodes = 0:.002:1; 252 | ynodes = xnodes; 253 | 254 | [zg,xg,yg] = gridfit(x,y,z,xnodes,ynodes,'tilesize',120,'overlap',0.25); 255 | 256 | surf(xg,yg,zg) 257 | shading interp 258 | colormap(jet(256)) 259 | camlight right 260 | lighting phong 261 | title 'Tiled gridfit' 262 | 263 | %% 264 | -------------------------------------------------------------------------------- /gridfitdir/doc/gridfit.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,11pt]{article} 2 | 3 | \usepackage{fancyhdr} 4 | \usepackage[dvips]{graphicx} % for eps and MatLab Diagrams 5 | \usepackage{amsmath} 6 | \usepackage{psfrag,color} 7 | \usepackage[framed]{/Applications/TeX/mcode} 8 | 9 | \pagestyle{fancy} 10 | 11 | \newcommand{\G}{\mcode{GRIDFIT}} 12 | 13 | \begin{document} % Begin Document 14 | 15 | % Title 16 | \title{\textsc{Understanding Gridfit}} 17 | 18 | % Authors and Contact Information 19 | \author{\textbf{John R. D'Errico}\\ 20 | Email: woodchips@rochester.rr.com} 21 | 22 | \maketitle 23 | 24 | \section{Introduction} 25 | 26 | \G{} is a surface modeling tool, fitting a surface of the form 27 | $z(x,y)$ to scattered (or regular) data. As it is not an 28 | interpolant, it allows the existence of replicates in your data with 29 | no problems. What do I mean by an "interpolant"? An interpolant is a 30 | code that is designed to always exactly predict all supplied data. 31 | \mcode{GRIDDATA} and \mcode{INTERP1} are examples of interpolants. 32 | \G{} is more accurately described as an approximant. It produces a 33 | surface which represents the behavior of the supplied data as 34 | closely as possible, allowing for noise in the data and for 35 | replicate data. 36 | 37 | A nice feature of \G{} is its ability to smoothly extrapolate beyond 38 | the convex hull of your data, something that \mcode{GRIDDATA} cannot 39 | do.\footnote{except by the slow, memory intensive 'v4' method} 40 | Finally, \G{} does its extrapolation in a well behaved manner, 41 | unlike how polynomial models (for example) might behave in 42 | extrapolation. 43 | 44 | \G{} also allows you to build a gridded surface directly from your 45 | data, rather than interpolating a linear approximation to a surface 46 | from a delaunay triangulation. 47 | 48 | This document describes the ideas behind \G. 49 | 50 | \section{The mechanical and philosophical underpinnings of GRIDFIT} 51 | 52 | How does \G{} work? Imagine a thin, flexible plate, attached to your 53 | data points by elastic bands. Each of these elastic bands draws the 54 | plate towards its corresponding data point, stretching only in the 55 | direction. If your data points were to fall on a simple planar 56 | surface, 57 | \begin{equation} 58 | z(x,y) = a_0 + a_1 x + a_2 y 59 | \end{equation} 60 | then the result will be a least squares plane. This works because 61 | the potential energy stored in a elastic band\footnote{perfect, 62 | linearly elastic} will be proportional to the square of its 63 | extension. 64 | 65 | Now imagine that the data points do not fall on a plane, but arise 66 | from some curved surface $z(x,y)$. While the plate itself is 67 | flexible, I've allowed it some finite and non-zero bending rigidity 68 | that the user can control. The bands connecting the plate to our 69 | data points will pull the plate into a curved shape, while the 70 | bending rigidity of the plate resists deformation. The relative 71 | stiffness of the plate, as compared to the strength of the elastic 72 | bands will cause some tradeoff between fitting the data as well as 73 | possible and a smooth overall surface. It is this plate stiffness 74 | that allows \G{} to smoothly extrapolate beyond the data itself. Of 75 | course, extrapolation beyond the data will be well behaved, since 76 | the plate will tend to become locally planar wherever there is no 77 | data to deform it. 78 | 79 | The tradeoff between stiffness of the plate and the effective spring 80 | constant of the bands connecting the plate to the data allows the 81 | user to choose anything between the extremes of a purely planar fit, 82 | to a crinkly thing that can follow the bumps and hollows of noisy 83 | data. 84 | 85 | 86 | \section{The methodology of GRIDFIT} 87 | 88 | First, lets look at how \G{} predicts a value at any location. Since 89 | the \G{} surface is defined by values at a set of nodes forming a 90 | rectangular lattice, any data point must lie in one of these 91 | rectangular cells of the lattice. (One requirement of \G{} is that 92 | the boundaries of its lattice must form a bounding box for all of 93 | the data supplied in the $(x,y)$ plane.) 94 | 95 | Given a point inside a single rectangular cell of the lattice, there 96 | are three methods supplied in \G{} to impute a value at that point. 97 | They are: 98 | \begin{itemize} 99 | \item Nearest neighbor interpolation 100 | \item Triangle (linear) interpolation 101 | \item Bilinear (tensor product linear) interpolation 102 | \end{itemize} 103 | 104 | These methods were chosen to be simple to generate, as well as 105 | because they are very "local" methods. They require no more than a 106 | few local values from the grid to predict the value at each point. 107 | This local nature makes the sparse linear least squares problem as 108 | efficient as possible, by keeping it maximally sparse. 109 | 110 | If we think of the surface that will result from \G{} as essentially 111 | a low order spline, it is easy to visualize the idea that 112 | interpolation at any point inside the grid is just a linear 113 | combination of the values at the grid nodes in the locality of the 114 | given point. Thus, we can write the interpolation problem in general 115 | as a linear algebra problem 116 | \begin{equation} 117 | A\mathbf{x}=\mathbf{y} 118 | \end{equation} 119 | where the vector $\mathbf{x}$ is of length $nx\cdot{}ny$ ($nx$ is 120 | the number of nodes in the $\mathbf{x}$ direction, and $ny$ is the 121 | number of grid nodes in the $\mathbf{y}$ direction.) Thus $A$ has 122 | $n$ rows, corresponding to each data point supplied by the user, and 123 | $nx\cdot{}ny$ columns. 124 | 125 | It will not be atypical to expect that there will be more columns 126 | than rows in this "regression" problem. For example, a user with 100 127 | data points might choose to build a gridded surface which has 128 | $100\times100$ nodes. The resulting regression problem will be 129 | massively underdetermined. As such, standard regression techniques, 130 | involving a solution like \mcode{x = A$\backslash{}$y} or \mcode{x = 131 | pinv(A)*y}, will be inappropriate. 132 | 133 | The approach that \G{} takes, with its plate-like metaphor, is to 134 | solve a regularized problem. At every node of the surface, 135 | \G{}\footnote{using its default regularization method of 'gradient'} 136 | attempts to force the (first) partial derivatives of the surface in 137 | neighboring cells to be equal. In effect, this results in a second 138 | set of linear equations of the form 139 | \begin{equation} 140 | B\mathbf{x} = \mathbf{0} 141 | \end{equation} 142 | where the derivatives are approximated using finite differences of 143 | the surface at neighboring nodes. 144 | 145 | The main alternative regularization method in \G{} is the 146 | 'laplacian' method. It attempts to force the sum of second partial 147 | derivatives to zero. This is actually closely related to the 148 | gradient method, but with a subtly significant difference. Either 149 | method will work nicely. (The 'springs' regularizer for \G{} is a 150 | flawed one, at least in my humble opinion. I'd generally recommend 151 | not using it without care. I've left it in the code because it has 152 | interesting properties in extrapolation.) 153 | 154 | The result of any of these regularizers is a system of the form 155 | $B\mathbf{x}=\mathbf{0}$. Coupled with the interpolation system, 156 | $A\mathbf{x}=\mathbf{y}$, we solve them in combination using a 157 | simple trick. First, I scale $A$ and $B$ so that each matrix has a 158 | unit 1-norm. This helps to eliminate scaling problems with the 159 | derivatives. Then I solve for the vector $\mathbf{x}$ such that 160 | \begin{equation} 161 | \|(A\mathbf{x}-\mathbf{y})\|^2 + \lambda\|B\mathbf{x}\|^2 162 | \end{equation} 163 | is minimized. Note that the parameter $\lambda$, nominally 1 as a 164 | default in \G, allows the user to control the relative plate 165 | "stiffness". As $\lambda$ approaches zero, the plate becomes as 166 | flexible as possible. Stiffer plates are modeled by larger values, 167 | $\lambda>1$. 168 | 169 | 170 | 171 | \section{Tricks with tiles} 172 | 173 | What can you do when a \G{} problem is simply too large even for the 174 | sparse matrix abilities of MATLAB? This is where the tiling 175 | abilities of \G{} become valuable. What do I mean by tiling? An 176 | example might be appropriate. 177 | 178 | \begin{lstlisting} 179 | >> nodes = linspace(0,1,100); 180 | >> tic,... 181 | zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes);... 182 | toc 183 | Elapsed time is 5.814102 seconds. 184 | >> 185 | >> nodes = linspace(0,1,200); 186 | >> tic,... 187 | zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes);... 188 | toc 189 | Elapsed time is 27.118945 seconds. 190 | \end{lstlisting} 191 | \bigskip 192 | 193 | To solve for a $100\times100$ grid, \G{} must solve for 10000 194 | unknown parameters using sparse linear algebra. A $200\times200$ 195 | grid is nominally 4 times as large, and it took my computer a bit 196 | more than $4\times$ as long to solve. However, a $2000\times2000$ 197 | grid will probably take much more than 400 times as long to solve as 198 | a $100\times100$ grid. It will likely take very much longer, if it 199 | runs at all without exceeding the memory limits, due to the much 200 | slower access times for virtual memory. For example, my computer 201 | took 1020 seconds to solve a $500\times500$ problem. 202 | \begin{lstlisting} 203 | >> nodes = linspace(0,1,500); 204 | >> tic,... 205 | zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes);... 206 | toc 207 | Elapsed time is 1020.657834 seconds. 208 | \end{lstlisting} 209 | \bigskip 210 | 211 | A trick that can work, IF you have enough data to adequately 212 | populate each tile, is to break down the domain of a large grid into 213 | smaller chunks, each of which can be quickly populated by \G. Then 214 | \G{} composes each chunk into the whole surface. To smooth out any 215 | artifacts at the edges of each tile, \G{} allows the tiles some 216 | fractional overlap, interpolating between them in the overlapped 217 | region. Thus the tiled solution below was accomplished with 218 | essentially no dips into virtual memory, so it took far less time to 219 | generate. 220 | 221 | \begin{lstlisting} 222 | >> nodes = linspace(0,1,500); 223 | >> tic,... 224 | zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes,... 225 | 'tilesize',200,'overlap',0.2);... 226 | toc 227 | Elapsed time is 211.946853 seconds. 228 | \end{lstlisting} 229 | \bigskip 230 | 231 | Note: these random surfaces were generated purely to show speed of 232 | estimation. They are of course purely garbage. Also, only 100 data 233 | points may well be inadequate to estimate a tiled surface. \G{} is 234 | not strongly dependent on the number of data points for its speed, 235 | so that number did not have an impact on the speed of estimation. 236 | 237 | A picture: 238 | 239 | \begin{lstlisting} 240 | % Users of gridfit who have really huge problems now have 241 | % an option. I'll generate a large amount of data, 242 | % and hope to model a fairly large grid - 800 x 800. This 243 | % would normally require gridfit to solve a system of 244 | % equations with 640,000 unknowns. It would probably be too 245 | % large of a problem for my computer, were I to use gridfit 246 | % on the full problem. Gridfit allows you to break the problem 247 | % into smaller tiles if you choose. In this case each tile 248 | % is 120x120, with a 25% (30 element) overlap between tiles. 249 | 250 | % Relax, this demo may take a couple of minutes to run!!!! 251 | 252 | n = 100000; 253 | x = rand(n,1); 254 | y = rand(n,1); 255 | z = x+y+sin((x.^2+y.^2)*10); 256 | 257 | xnodes = 0:.00125:1; 258 | ynodes = xnodes; 259 | 260 | [zg,xg,yg] = gridfit(x,y,z,xnodes,ynodes,'tilesize',... 261 | 120,'overlap',0.25); 262 | 263 | surf(xg,yg,zg) 264 | shading interp 265 | colormap(jet(256)) 266 | camlight right 267 | lighting phong 268 | title 'Tiled gridfit' 269 | \end{lstlisting} 270 | 271 | \begin{figure} 272 | \centering 273 | \includegraphics[width=5in]{gfit.eps} 274 | \caption{An Example} 275 | \end{figure} 276 | 277 | 278 | 279 | \end{document} 280 | -------------------------------------------------------------------------------- /gridfitdir/demo/html/gridfit_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | gridfit_demo 10 | 11 | 12 | 70 | 71 |

Contents

72 |
73 | 80 |
% Gridfit demo script
 81 | 
 82 | % This script file is designed to be used in cell mode
 83 | % from the matlab editor, or best of all, use the publish
 84 | % to HTML feature from the matlab editor. Older versions
 85 | % of matlab can copy and paste entire blocks of code into
 86 | % the Matlab command window.
 87 | 

Topographic data

load bluff_data;
 88 | x=bluff_data(:,1);
 89 | y=bluff_data(:,2);
 90 | z=bluff_data(:,3);
 91 | % Two ravines on a hillside. Scanned from a
 92 | % topographic map of an area in upstate New York.
 93 | plot3(x,y,z,'.')
 94 | 
% Turn the scanned point data into a surface
 95 | gx=0:4:264;
 96 | gy=0:4:400;
 97 | g=gridfit(x,y,z,gx,gy);
 98 | figure
 99 | colormap(hot(256));
100 | surf(gx,gy,g);
101 | camlight right;
102 | lighting phong;
103 | shading interp
104 | line(x,y,z,'marker','.','markersize',4,'linestyle','none');
105 | title 'Use topographic contours to recreate a surface'
106 | 

Fitting a trigonometric surface

clear
107 | 
108 | n1 = 15;
109 | n2 = 15;
110 | theta = rand(n1,1)*pi/2;
111 | r = rand(1,n2);
112 | 
113 | x = cos(theta)*r;
114 | y = sin(theta)*r;
115 | x=x(:);
116 | y=y(:);
117 | 
118 | x = [[0 0 1 1]';x;x;1-x;1-x];
119 | y = [[0 1 0 1]';y;1-y;y;1-y];
120 | figure
121 | plot(x,y,'.')
122 | title 'Data locations in the x-y plane'
123 | 
z = sin(4*x+5*y).*cos(7*(x-y))+exp(x+y);
124 | 
125 | xi = linspace(0,1,51);
126 | [xg,yg]=meshgrid(xi,xi);
127 | zgd = griddata(x,y,z,xg,yg);
128 | 
129 | figure
130 | surf(xi,xi,zgd)
131 | colormap(hot(256))
132 | camlight right
133 | lighting phong
134 | title 'Griddata on trig surface'
135 | % Note the wing-like artifacts along the edges, due
136 | % to the use of a Delaunay triangulation in griddata.
137 | 
zgrid = gridfit(x,y,z,xi,xi);
138 | 
139 | figure
140 | surf(xi,xi,zgrid)
141 | colormap(hot(256))
142 | camlight right
143 | lighting phong
144 | title('Gridfit to trig surface')
145 | 

The trig surface with highly different scalings on the x and y axes

xs = x/100;
146 | xis = xi/100;
147 | 
148 | ys = y*100;
149 | yis = xi*100;
150 | 
151 | % griddata has problems with badly scaled data
152 | [xg,yg]=meshgrid(xis,yis);
153 | zgd = griddata(xs,ys,z,xg,yg);
154 | 
155 | figure
156 | surf(xg,yg,zgd)
157 | colormap(hot(256))
158 | camlight right
159 | lighting phong
160 | title 'Serious problems for griddata on badly scaled trig surface'
161 | 
162 | % autoscaling on (the default)
163 | zgrids = gridfit(xs,ys,z,xis,yis,'autoscale','on');
164 | 
165 | % plot the autoscaled result
166 | figure
167 | surf(xis,yis,zgrids)
168 | colormap(hot(256))
169 | camlight right
170 | lighting phong
171 | title 'Gridfit (automatically scaled) on trig surface'
172 | 
Warning: Duplicate x-y data points detected: using average of the z values.
173 | 

Fitting the "peaks" surface

clear
174 | 
175 | n = 100;
176 | x = (rand(n,1)-.5)*6;
177 | y = (rand(n,1)-.5)*6;
178 | 
179 | z = peaks(x,y);
180 | 
181 | xi = linspace(-3,3,101);
182 | zpgf = gridfit(x,y,z,xi,xi);
183 | 
184 | [xg,yg]=meshgrid(xi,xi);
185 | zpgd = griddata(x,y,z,xg,yg,'cubic');
186 | 
187 | figure
188 | surf(xi,xi,zpgd)
189 | colormap(jet(256))
190 | camlight right
191 | lighting phong
192 | title 'Griddata (method == cubic) on peaks surface'
193 | 
194 | figure
195 | surf(xi,xi,zpgf)
196 | colormap(hsv(256))
197 | camlight right
198 | lighting phong
199 | title('Gridfit to peaks surface')
200 | 

Using tiles in gridfit

% Users of gridfit who have really huge problems now have
201 | % an option. I'll generate a large amount of data,
202 | % and hope to model a fairly large grid - 800 x 800. This
203 | % would normally require gridfit to solve a system of
204 | % equations with 640,000 unknowns. It would probably be too
205 | % large of a problem for my computer, were I to use gridfit
206 | % on the full problem. Gridfit allows you to break the problem
207 | % into smaller tiles if you choose. In this case each tile
208 | % is 120x120, with a 25% (30 element) overlap between tiles.
209 | 
210 | % Relax, this demo may take a couple of minutes to run!!!!
211 | 
212 | n = 100000;
213 | x = rand(n,1);
214 | y = rand(n,1);
215 | z = x+y+sin((x.^2+y.^2)*10);
216 | 
217 | xnodes = 0:.00125:1;
218 | ynodes = xnodes;
219 | 
220 | [zg,xg,yg] = gridfit(x,y,z,xnodes,ynodes,'tilesize',120,'overlap',0.25);
221 | 
222 | surf(xg,yg,zg)
223 | shading interp
224 | colormap(jet(256))
225 | camlight right
226 | lighting phong
227 | title 'Tiled gridfit'
228 | 
230 | 404 | 405 | -------------------------------------------------------------------------------- /gridfitdir/doc/Understanding_gridfit.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\mac\ansicpg10000\cocoartf102 2 | {\fonttbl\f0\fswiss\fcharset77 Helvetica;\f1\fswiss\fcharset77 Helvetica-Bold;} 3 | {\colortbl;\red255\green255\blue255;} 4 | \margl1440\margr1440\vieww11400\viewh18880\viewkind0 5 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\qc 6 | 7 | \f0\fs24 \cf0 Understanding Gridfit\ 8 | John R. D'Errico\ 9 | woodchips@rochester.rr.com\ 10 | \ 11 | 8/19/06\ 12 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural 13 | \cf0 \ 14 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural 15 | 16 | \f1\b \cf0 Introduction 17 | \f0\b0 \ 18 | \ 19 | Gridfit is a surface modeling tool, fitting a surface of the form z(x,y) to scattered (or regular) data. As it is not an interpolant, it allows the existence of replicates in your data with no problems. What do I mean by an "interpolant"? An interpolant is a code that is designed to always exactly predict all supplied data. Griddata and interp1 are examples of interpolants. Gridfit is more accurately described as an approximant. It produces a surface which represents the behavior of the supplied data as closely as possible, allowing for noise in the data and for replicate data.\ 20 | \ 21 | A nice feature of gridfit is its ability to smoothly extrapolate beyond the convex hull of your data, something that griddata cannot do (except by the slow, memory intensive 'v4' method.) Finally, gridfit does its extrapolation in a well behaved manner, unlike how polynomial models (for example) might behave in extrapolation. \ 22 | \ 23 | Gridfit also allows you to build a gridded surface directly from your data, rather than interpolating a linear approximation to a surface from a delaunay triangulation.\ 24 | \ 25 | This document describes the ideas behind gridfit.\ 26 | \ 27 | \ 28 | 29 | \f1\b The mechanical and philosophical underpinnings of gridfit 30 | \f0\b0 \ 31 | \ 32 | How does gridfit work? Imagine a thin, flexible plate, attached to your data points by elastic bands. Each of these elastic bands draws the plate towards its corresponding data point, stretching only in the z direction. If your data points were to fall on a simple planar surface,\ 33 | \ 34 | z(x,y) = a_0 + a_1 x + a_2 y\ 35 | \ 36 | then the result will be a least squares plane. This works because the potential energy stored in a (perfect, linearly elastic) elastic band will be proportional to the square of its extension.\ 37 | \ 38 | Now imagine that the data points do not fall on a plane, but arise from some curved surface z(x,y). While the plate itself is flexible, I've allowed it some finite and non-zero bending rigidity that the user can control. The bands connecting the plate to our data points will pull the plate into a curved shape, while the bending rigidity of the plate resists deformation. The relative stiffness of the plate, as compared to the strength of the elastic bands will cause some tradeoff between fitting the data as well as possible and a smooth overall surface. It is this plate stiffness that allows gridfit to smoothly extrapolate beyond the data itself. Of course, extrapolation beyond the data will be well behaved, since the plate will tend to become locally planar wherever there is no data to deform it.\ 39 | \ 40 | The tradeoff between stiffness of the plate and the effective spring constant of the bands connecting the plate to the data allows the user to choose anything between the extremes of a purely planar fit, to a crinkly thing that can follow the bumps and hollows of noisy data.\ 41 | \ 42 | \ 43 | 44 | \f1\b The methodology of gridfit 45 | \f0\b0 \ 46 | \ 47 | First, lets look at how gridfit predicts a value at any location. Since the gridfit surface is defined by values at a set of nodes forming a rectangular lattice, any data point must lie in one of these rectangular cells of the lattice. (One requirement of gridfit is that the boundaries of its lattice must form a bounding box for all of the data supplied in the (x,y) plane.) \ 48 | \ 49 | Given a point inside a single rectangular cell of the lattice, there are three methods supplied in gridfit to impute a value at that point. They are:\ 50 | \ 51 | - Nearest neighbor interpolation\ 52 | \ 53 | - Triangle (linear) interpolation\ 54 | \ 55 | - Bilinear (tensor product linear) interpolation \ 56 | \ 57 | These methods were chosen to be simple to generate, as well as because they are very "local" methods. They require no more than a few local values from the grid to predict the value at each point. This local nature makes the sparse linear least squares problem as efficient as possible, by keeping it maximally sparse.\ 58 | \ 59 | If we think of the surface that will result from gridfit as essentially a low order spline, it is easy to visualize the idea that interpolation at any point inside the grid is just a linear combination of the values at the grid nodes in the locality of the given point. Thus, we can write the interpolation problem in general as a linear algebra problem\ 60 | \ 61 | A*x = y\ 62 | \ 63 | where the vector x is of length nx*ny (nx is the number of nodes in the x direction, and ny is the number of grid nodes in the y direction.) Thus A has n rows, corresponding to each data point supplied by the user, and nx*ny columns.\ 64 | \ 65 | It will not be atypical to expect that there will be more columns than rows in this "regression" problem. For example, a user with 100 data points might choose to build a gridded surface which has 100x100 nodes. The resulting regression problem will be massively underdetermined. As such, standard regression techniques, involving a solution like x = A\\y or x = pinv(A)*y, will be inappropriate. \ 66 | \ 67 | The approach that gridfit takes, with its plate-like metaphor, is to solve a regularized problem. At every node of the surface, gridfit (using its default regularization method of 'gradient') attempts to force the (first) partial derivatives of the surface in neighboring cells to be equal. In effect, this results in a second set of linear equations of the form\ 68 | \ 69 | B*x = 0\ 70 | \ 71 | where the derivatives are approximated using finite differences of the surface at neighboring nodes.\ 72 | \ 73 | The main alternative regularization method in gridit is the 'laplacian' method. It attempts to force the sum of second partial derivatives to zero. This is actually closely related to the gradient method, but with a subtly significant difference. Either method will work nicely. (The 'springs' regularizer for gridfit is a flawed one, at least in my humble opinion. I'd generally recommend not using it without care. I've left it in the code because it has interesting properties in extrapolation.)\ 74 | \ 75 | The result of any of these regularizers is a system of the form B*x=0. Coupled with the interpolation system, A*x=y, we solve them in combination using a simple trick. First, I scale A and B so that each matrix has a unit 1-norm. This helps to eliminate scaling problems with the derivatives. Then I solve for the vector x such that\ 76 | \ 77 | (norm(A*x-y))^2 + (lambda*norm(B*x))^2\ 78 | \ 79 | is minimized. Note that the parameter lambda, nominally 1 as a default in gridfit, allows the user to control the relative plate "stiffness". As lambda approaches zero, the plate becomes as flexible as possible. Stiffer plates are modeled by larger values of lambda than 1.\ 80 | \ 81 | \ 82 | 83 | \f1\b Gridfit specifics, but in one dimension\ 84 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural 85 | 86 | \f0\b0 \cf0 \ 87 | The above discussion was at a high level. Perhaps a specific example, solved in one dimension might help to visualize how gridfit works. I've reduced the problem to 1-d because the 2-d stuff may be more complex than we need to understand the basic ideas. In essence, I'll write a very simple piecewise linear least squares spline code. Note that I'll actually have more degrees of freedom than I have real data points in this example. What, me worry?\ 88 | \ 89 | % First, generate some random data.\ 90 | n = 50;\ 91 | x = sort(rand(n,1));\ 92 | \ 93 | % A simple function, plus some random noise.\ 94 | y = exp(x) + randn(n,1)/10;\ 95 | \ 96 | % Choose a set of knots/nodes for the "spline"\ 97 | xknots = (0:0.01:1)';\ 98 | \ 99 | % Each data points lies in some node interval, between some pair of knots.\ 100 | % Use histc to find out where each point lies. We will find the vector bin such that\ 101 | % xknots(bin) <= x < xknots(bin+1)\ 102 | [junk,bin] = histc(x,xknots);\ 103 | \ 104 | % IFF we knew the values of our unknown function at each knot/node point in x, then we could\ 105 | % interpolate using simple linear interpolation. The problem is that we don't know those nodal\ 106 | % function values. We only have the data. So build a least squares problem to estimate those\ 107 | % unknown function values. The unknowns appear linearly, so this will be a linear least squares\ 108 | % problem. For example, if a data point falls exactly midway between the first two nodes in our\ 109 | % list of nodes, then the corresponding row for the least squares problem will look like:\ 110 | % [0.5 0.5 0 0 0 0 0 0 0 0 0 0 0 0 ... ] \ 111 | % When you multiply this vector by our list of unknown function values at the nodes, it reduces\ 112 | % to 0.5*(f(1) + f(2)), which is just the linear interpolant at that point.\ 113 | %\ 114 | % So now, build the least squares problem.\ 115 | t = (x - xknots(bin))./(xknots(bin+1) - xknots(bin));\ 116 | \ 117 | % This does it in one line.\ 118 | nk = length(xknots);\ 119 | A = sparse(repmat((1:n)',1,2),[bin,bin+1],[1-t,t],n,nk);\ 120 | rhs = y;\ 121 | \ 122 | % We have now generated a sparse least squares spline problem,\ 123 | %\ 124 | % A*f = rhs\ 125 | %\ 126 | % with 101 unknowns and 50 data points. The non-zero elements in A are seen as:\ 127 | spy(A)\ 128 | % Note that there are TWO non-zero elements in each row of A, 50 rows that correspond to 50\ 129 | % data points, and 101 columns, corresponding to 101 unknown function values in our grid of\ 130 | % nodes.\ 131 | \ 132 | % We can't solve this problem smoothly using linear algebra in Matlab, at least not yet. Try it\ 133 | % if you like. Here is what I get:\ 134 | f = A\\y\ 135 | \ 136 | Warning: Rank deficient, rank = 48.\ 137 | \ 138 | f =\ 139 | 0\ 140 | 0\ 141 | 7.2727\ 142 | 0\ 143 | 0\ 144 | 0.64977\ 145 | 1.1866\ 146 | -1.5238\ 147 | 10.843\ 148 | 0\ 149 | 5.0996\ 150 | 1.6849\ 151 | 0\ 152 | 0\ 153 | 3.717\ 154 | 0\ 155 | 1.0612\ 156 | 1.1947\ 157 | 0\ 158 | 0\ 159 | 1.2099\ 160 | 0\ 161 | 0\ 162 | 0\ 163 | 0\ 164 | 0\ 165 | 0\ 166 | 1.0186\ 167 | 2.4521\ 168 | 1.6774\ 169 | 0\ 170 | 0\ 171 | 12.135\ 172 | 0\ 173 | 2.4237\ 174 | 3.934\ 175 | 0\ 176 | 2.9862\ 177 | 0\ 178 | 0\ 179 | 15.946\ 180 | 0\ 181 | 3.179\ 182 | 0\ 183 | 3.5062\ 184 | 1.9515\ 185 | 0\ 186 | 1.4144\ 187 | 1.6008\ 188 | 9.5534\ 189 | 0\ 190 | 0\ 191 | 0\ 192 | 5.0481\ 193 | 0\ 194 | 2.4381\ 195 | 0\ 196 | 7.5175\ 197 | 0\ 198 | 1.4645\ 199 | 2.0286\ 200 | 1.8657\ 201 | 1.6965\ 202 | 2.1905\ 203 | 0\ 204 | 0\ 205 | 0\ 206 | 1.984\ 207 | 0\ 208 | 104.1\ 209 | 2.5044\ 210 | 0\ 211 | 0\ 212 | 0\ 213 | 0\ 214 | 3.7775\ 215 | 0\ 216 | 0\ 217 | 0\ 218 | 2.3394\ 219 | 0\ 220 | 2.2655\ 221 | 2.1796\ 222 | 2.2311\ 223 | 2.5115\ 224 | 11.312\ 225 | 0\ 226 | 0\ 227 | 0\ 228 | 13.56\ 229 | 0\ 230 | 2.7334\ 231 | 0\ 232 | 0\ 233 | 0\ 234 | 4.5913\ 235 | 1.4497\ 236 | 9.0807\ 237 | 0\ 238 | 0\ 239 | 0\ 240 | \ 241 | % Yes, this was a mess. Plot it:\ 242 | plot(x,y,'bo',xknots,f,'r-')\ 243 | \ 244 | % So why does gridfit work? Do you recall that I talked about a regularized solution?\ 245 | % Suppose we decided that the resulting curve should be everywhere "smooth"? Smoothness\ 246 | % is related to the second derivative of our function. So build a matrix that computes the\ 247 | % second derivative everywhere of our unknown function. Assuming the nodes are equally\ 248 | % spaced, an estimate of the second derivative at the second node looks like\ 249 | %\ 250 | % (f(1) - 2*f(2) + f(3)) / dx^2\ 251 | %\ 252 | % At the third node, its just \ 253 | %\ 254 | % (f(2) - 2*f(3) + f(4)) / dx^2\ 255 | % \ 256 | % Etc. So build this as a (tridiagonal) matrix. Assume the nodes are uniform.\ 257 | dx = xknots(2) - xknots(1);\ 258 | ind = (2:(nk - 1))';\ 259 | B = sparse(repmat(ind-1,1,3), [ind-1,ind,ind+1], repmat([1 -2 1],nk-2,1), nk-2,nk);\ 260 | \ 261 | % Convince yourself that we built B properly.\ 262 | spy(B)\ 263 | \ 264 | % B has the property that if we multiply it times a vector of unknown function values at the\ 265 | % nodes in xknots, that it computes something proportional to the second derivative at\ 266 | % each internal node.\ 267 | \ 268 | % Now, solve the related least squares problem,\ 269 | lambda = 1;\ 270 | f = [A; lambda*B] \\ [rhs;zeros(nk-2,1)]\ 271 | \ 272 | % Plot this result\ 273 | plot(x,y,'bo',xknots,f,'r-')\ 274 | \ 275 | % Note that by changing the value of lambda, we can make the curve nicely smooth.\ 276 | lambda = 30;\ 277 | f = [A; lambda*B] \\ [rhs;zeros(nk-2,1)]\ 278 | \ 279 | % Plot this result\ 280 | plot(x,y,'bo',xknots,f,'r-')\ 281 | \ 282 | The actual code in gridfit does something very similar, but it does it in two dimensions. \ 283 | \ 284 | \ 285 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural 286 | 287 | \f1\b \cf0 Tricks with tiles 288 | \f0\b0 \ 289 | \ 290 | What can you do when a gridfit problem is simply too large even for the sparse matrix abilities of matlab? This is where the tiling abilities of gridfit become valuable. What do I mean by tiling?\ 291 | \ 292 | An example might be appropriate. \ 293 | \ 294 | >> nodes = linspace(0,1,100);\ 295 | >> tic,zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes);toc\ 296 | Elapsed time is 5.814102 seconds.\ 297 | \ 298 | >> nodes = linspace(0,1,200);\ 299 | >> tic,zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes);toc\ 300 | Elapsed time is 27.118945 seconds.\ 301 | \ 302 | To solve for a 100x100 grid, gridfit must solve for 10000 unknown parameters using sparse linear algebra. A 200x200 grid is nominally 4 times as large, and it took my computer a bit more than 4x as long to solve. However, a 2000x2000 grid will probably take much more than 400 times as long to solve as a 100x100 grid. It will likely take very much longer, if it runs at all without exceeding the memory limits, due to the much slower access times for virtual memory. For example, my computer took 1020 seconds to solve a 500x500 problem.\ 303 | \ 304 | >> nodes = linspace(0,1,500);\ 305 | >> tic,zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes);toc\ 306 | Elapsed time is 1020.657834 seconds.\ 307 | \ 308 | A trick that can work, IF you have enough data to adequately populate each tile, is to break down the domain of a large grid into smaller chunks, each of which can be quickly populated by gridfit. Then gridfit composes each chunk into the whole surface. To smooth out any artifacts at the edges of each tile, gridfit allows the tiles some fractional overlap, interpolating between them in the overlapped region. Thus the tiled solution below was accomplished with essentially no dips into virtual memory, so it took far less time to generate.\ 309 | \ 310 | >> nodes = linspace(0,1,500);\ 311 | >> tic,zg=gridfit(rand(100,1),rand(100,1),rand(100,1),nodes,nodes,'tilesize',200,'overlap',0.2);toc\ 312 | Elapsed time is 211.946853 seconds.\ 313 | \ 314 | Note: these random surfaces were generated purely to show speed of estimation. They are of course\ 315 | purely garbage. Also, only 100 data points may well be inadequate to estimate a tiled surface. Gridfit is not strongly dependent on the number of data points for its speed, so that number did not have an impact on the speed of estimation.\ 316 | \ 317 | } -------------------------------------------------------------------------------- /gridfitdir/gridfit.m: -------------------------------------------------------------------------------- 1 | function [zgrid,xgrid,ygrid] = gridfit(x,y,z,xnodes,ynodes,varargin) 2 | % gridfit: estimates a surface on a 2d grid, based on scattered data 3 | % Replicates are allowed. All methods extrapolate to the grid 4 | % boundaries. Gridfit uses a modified ridge estimator to 5 | % generate the surface, where the bias is toward smoothness. 6 | % 7 | % Gridfit is not an interpolant. Its goal is a smooth surface 8 | % that approximates your data, but allows you to control the 9 | % amount of smoothing. 10 | % 11 | % usage #1: zgrid = gridfit(x,y,z,xnodes,ynodes); 12 | % usage #2: [zgrid,xgrid,ygrid] = gridfit(x,y,z,xnodes,ynodes); 13 | % usage #3: zgrid = gridfit(x,y,z,xnodes,ynodes,prop,val,prop,val,...); 14 | % 15 | % Arguments: (input) 16 | % x,y,z - vectors of equal lengths, containing arbitrary scattered data 17 | % The only constraint on x and y is they cannot ALL fall on a 18 | % single line in the x-y plane. Replicate points will be treated 19 | % in a least squares sense. 20 | % 21 | % ANY points containing a NaN are ignored in the estimation 22 | % 23 | % xnodes - vector defining the nodes in the grid in the independent 24 | % variable (x). xnodes need not be equally spaced. xnodes 25 | % must completely span the data. If they do not, then the 26 | % 'extend' property is applied, adjusting the first and last 27 | % nodes to be extended as necessary. See below for a complete 28 | % description of the 'extend' property. 29 | % 30 | % If xnodes is a scalar integer, then it specifies the number 31 | % of equally spaced nodes between the min and max of the data. 32 | % 33 | % ynodes - vector defining the nodes in the grid in the independent 34 | % variable (y). ynodes need not be equally spaced. 35 | % 36 | % If ynodes is a scalar integer, then it specifies the number 37 | % of equally spaced nodes between the min and max of the data. 38 | % 39 | % Also see the extend property. 40 | % 41 | % Additional arguments follow in the form of property/value pairs. 42 | % Valid properties are: 43 | % 'smoothness', 'interp', 'regularizer', 'solver', 'maxiter' 44 | % 'extend', 'tilesize', 'overlap' 45 | % 46 | % Any UNAMBIGUOUS shortening (even down to a single letter) is 47 | % valid for property names. All properties have default values, 48 | % chosen (I hope) to give a reasonable result out of the box. 49 | % 50 | % 'smoothness' - scalar or vector of length 2 - determines the 51 | % eventual smoothness of the estimated surface. A larger 52 | % value here means the surface will be smoother. Smoothness 53 | % must be a non-negative real number. 54 | % 55 | % If this parameter is a vector of length 2, then it defines 56 | % the relative smoothing to be associated with the x and y 57 | % variables. This allows the user to apply a different amount 58 | % of smoothing in the x dimension compared to the y dimension. 59 | % 60 | % Note: the problem is normalized in advance so that a 61 | % smoothness of 1 MAY generate reasonable results. If you 62 | % find the result is too smooth, then use a smaller value 63 | % for this parameter. Likewise, bumpy surfaces suggest use 64 | % of a larger value. (Sometimes, use of an iterative solver 65 | % with too small a limit on the maximum number of iterations 66 | % will result in non-convergence.) 67 | % 68 | % DEFAULT: 1 69 | % 70 | % 71 | % 'interp' - character, denotes the interpolation scheme used 72 | % to interpolate the data. 73 | % 74 | % DEFAULT: 'triangle' 75 | % 76 | % 'bilinear' - use bilinear interpolation within the grid 77 | % (also known as tensor product linear interpolation) 78 | % 79 | % 'triangle' - split each cell in the grid into a triangle, 80 | % then linear interpolation inside each triangle 81 | % 82 | % 'nearest' - nearest neighbor interpolation. This will 83 | % rarely be a good choice, but I included it 84 | % as an option for completeness. 85 | % 86 | % 87 | % 'regularizer' - character flag, denotes the regularization 88 | % paradignm to be used. There are currently three options. 89 | % 90 | % DEFAULT: 'gradient' 91 | % 92 | % 'diffusion' or 'laplacian' - uses a finite difference 93 | % approximation to the Laplacian operator (i.e, del^2). 94 | % 95 | % We can think of the surface as a plate, wherein the 96 | % bending rigidity of the plate is specified by the user 97 | % as a number relative to the importance of fidelity to 98 | % the data. A stiffer plate will result in a smoother 99 | % surface overall, but fit the data less well. I've 100 | % modeled a simple plate using the Laplacian, del^2. (A 101 | % projected enhancement is to do a better job with the 102 | % plate equations.) 103 | % 104 | % We can also view the regularizer as a diffusion problem, 105 | % where the relative thermal conductivity is supplied. 106 | % Here interpolation is seen as a problem of finding the 107 | % steady temperature profile in an object, given a set of 108 | % points held at a fixed temperature. Extrapolation will 109 | % be linear. Both paradigms are appropriate for a Laplacian 110 | % regularizer. 111 | % 112 | % 'gradient' - attempts to ensure the gradient is as smooth 113 | % as possible everywhere. Its subtly different from the 114 | % 'diffusion' option, in that here the directional 115 | % derivatives are biased to be smooth across cell 116 | % boundaries in the grid. 117 | % 118 | % The gradient option uncouples the terms in the Laplacian. 119 | % Think of it as two coupled PDEs instead of one PDE. Why 120 | % are they different at all? The terms in the Laplacian 121 | % can balance each other. 122 | % 123 | % 'springs' - uses a spring model connecting nodes to each 124 | % other, as well as connecting data points to the nodes 125 | % in the grid. This choice will cause any extrapolation 126 | % to be as constant as possible. 127 | % 128 | % Here the smoothing parameter is the relative stiffness 129 | % of the springs connecting the nodes to each other compared 130 | % to the stiffness of a spting connecting the lattice to 131 | % each data point. Since all springs have a rest length 132 | % (length at which the spring has zero potential energy) 133 | % of zero, any extrapolation will be minimized. 134 | % 135 | % Note: The 'springs' regularizer tends to drag the surface 136 | % towards the mean of all the data, so too large a smoothing 137 | % parameter may be a problem. 138 | % 139 | % 140 | % 'solver' - character flag - denotes the solver used for the 141 | % resulting linear system. Different solvers will have 142 | % different solution times depending upon the specific 143 | % problem to be solved. Up to a certain size grid, the 144 | % direct \ solver will often be speedy, until memory 145 | % swaps causes problems. 146 | % 147 | % What solver should you use? Problems with a significant 148 | % amount of extrapolation should avoid lsqr. \ may be 149 | % best numerically for small smoothnesss parameters and 150 | % high extents of extrapolation. 151 | % 152 | % Large numbers of points will slow down the direct 153 | % \, but when applied to the normal equations, \ can be 154 | % quite fast. Since the equations generated by these 155 | % methods will tend to be well conditioned, the normal 156 | % equations are not a bad choice of method to use. Beware 157 | % when a small smoothing parameter is used, since this will 158 | % make the equations less well conditioned. 159 | % 160 | % DEFAULT: 'normal' 161 | % 162 | % '\' - uses matlab's backslash operator to solve the sparse 163 | % system. 'backslash' is an alternate name. 164 | % 165 | % 'symmlq' - uses matlab's iterative symmlq solver 166 | % 167 | % 'lsqr' - uses matlab's iterative lsqr solver 168 | % 169 | % 'normal' - uses \ to solve the normal equations. 170 | % 171 | % 172 | % 'maxiter' - only applies to iterative solvers - defines the 173 | % maximum number of iterations for an iterative solver 174 | % 175 | % DEFAULT: min(10000,length(xnodes)*length(ynodes)) 176 | % 177 | % 178 | % 'extend' - character flag - controls whether the first and last 179 | % nodes in each dimension are allowed to be adjusted to 180 | % bound the data, and whether the user will be warned if 181 | % this was deemed necessary to happen. 182 | % 183 | % DEFAULT: 'warning' 184 | % 185 | % 'warning' - Adjust the first and/or last node in 186 | % x or y if the nodes do not FULLY contain 187 | % the data. Issue a warning message to this 188 | % effect, telling the amount of adjustment 189 | % applied. 190 | % 191 | % 'never' - Issue an error message when the nodes do 192 | % not absolutely contain the data. 193 | % 194 | % 'always' - automatically adjust the first and last 195 | % nodes in each dimension if necessary. 196 | % No warning is given when this option is set. 197 | % 198 | % 199 | % 'tilesize' - grids which are simply too large to solve for 200 | % in one single estimation step can be built as a set 201 | % of tiles. For example, a 1000x1000 grid will require 202 | % the estimation of 1e6 unknowns. This is likely to 203 | % require more memory (and time) than you have available. 204 | % But if your data is dense enough, then you can model 205 | % it locally using smaller tiles of the grid. 206 | % 207 | % My recommendation for a reasonable tilesize is 208 | % roughly 100 to 200. Tiles of this size take only 209 | % a few seconds to solve normally, so the entire grid 210 | % can be modeled in a finite amount of time. The minimum 211 | % tilesize can never be less than 3, although even this 212 | % size tile is so small as to be ridiculous. 213 | % 214 | % If your data is so sparse than some tiles contain 215 | % insufficient data to model, then those tiles will 216 | % be left as NaNs. 217 | % 218 | % DEFAULT: inf 219 | % 220 | % 221 | % 'overlap' - Tiles in a grid have some overlap, so they 222 | % can minimize any problems along the edge of a tile. 223 | % In this overlapped region, the grid is built using a 224 | % bi-linear combination of the overlapping tiles. 225 | % 226 | % The overlap is specified as a fraction of the tile 227 | % size, so an overlap of 0.20 means there will be a 20% 228 | % overlap of successive tiles. I do allow a zero overlap, 229 | % but it must be no more than 1/2. 230 | % 231 | % 0 <= overlap <= 0.5 232 | % 233 | % Overlap is ignored if the tilesize is greater than the 234 | % number of nodes in both directions. 235 | % 236 | % DEFAULT: 0.20 237 | % 238 | % 239 | % 'autoscale' - Some data may have widely different scales on 240 | % the respective x and y axes. If this happens, then 241 | % the regularization may experience difficulties. 242 | % 243 | % autoscale = 'on' will cause gridfit to scale the x 244 | % and y node intervals to a unit length. This should 245 | % improve the regularization procedure. The scaling is 246 | % purely internal. 247 | % 248 | % autoscale = 'off' will disable automatic scaling 249 | % 250 | % DEFAULT: 'on' 251 | % 252 | % 253 | % Arguments: (output) 254 | % zgrid - (nx,ny) array containing the fitted surface 255 | % 256 | % xgrid, ygrid - as returned by meshgrid(xnodes,ynodes) 257 | % 258 | % 259 | % Speed considerations: 260 | % Remember that gridfit must solve a LARGE system of linear 261 | % equations. There will be as many unknowns as the total 262 | % number of nodes in the final lattice. While these equations 263 | % may be sparse, solving a system of 10000 equations may take 264 | % a second or so. Very large problems may benefit from the 265 | % iterative solvers or from tiling. 266 | % 267 | % 268 | % Example usage: 269 | % 270 | % x = rand(100,1); 271 | % y = rand(100,1); 272 | % z = exp(x+2*y); 273 | % xnodes = 0:.1:1; 274 | % ynodes = 0:.1:1; 275 | % 276 | % g = gridfit(x,y,z,xnodes,ynodes); 277 | % 278 | % Note: this is equivalent to the following call: 279 | % 280 | % g = gridfit(x,y,z,xnodes,ynodes, ... 281 | % 'smooth',1, ... 282 | % 'interp','triangle', ... 283 | % 'solver','normal', ... 284 | % 'regularizer','gradient', ... 285 | % 'extend','warning', ... 286 | % 'tilesize',inf); 287 | % 288 | % 289 | % Author: John D'Errico 290 | % e-mail address: woodchips@rochester.rr.com 291 | % Release: 2.0 292 | % Release date: 5/23/06 293 | 294 | % set defaults 295 | params.smoothness = 1; 296 | params.interp = 'triangle'; 297 | params.regularizer = 'gradient'; 298 | params.solver = 'backslash'; 299 | params.maxiter = []; 300 | params.extend = 'warning'; 301 | params.tilesize = inf; 302 | params.overlap = 0.20; 303 | params.mask = []; 304 | params.autoscale = 'on'; 305 | params.xscale = 1; 306 | params.yscale = 1; 307 | 308 | % was the params struct supplied? 309 | if ~isempty(varargin) 310 | if isstruct(varargin{1}) 311 | % params is only supplied if its a call from tiled_gridfit 312 | params = varargin{1}; 313 | if length(varargin)>1 314 | % check for any overrides 315 | params = parse_pv_pairs(params,varargin{2:end}); 316 | end 317 | else 318 | % check for any overrides of the defaults 319 | params = parse_pv_pairs(params,varargin); 320 | 321 | end 322 | end 323 | 324 | % check the parameters for acceptability 325 | params = check_params(params); 326 | 327 | % ensure all of x,y,z,xnodes,ynodes are column vectors, 328 | % also drop any NaN data 329 | x=x(:); 330 | y=y(:); 331 | z=z(:); 332 | k = isnan(x) | isnan(y) | isnan(z); 333 | if any(k) 334 | x(k)=[]; 335 | y(k)=[]; 336 | z(k)=[]; 337 | end 338 | xmin = min(x); 339 | xmax = max(x); 340 | ymin = min(y); 341 | ymax = max(y); 342 | 343 | % did they supply a scalar for the nodes? 344 | if length(xnodes)==1 345 | xnodes = linspace(xmin,xmax,xnodes)'; 346 | xnodes(end) = xmax; % make sure it hits the max 347 | end 348 | if length(ynodes)==1 349 | ynodes = linspace(ymin,ymax,ynodes)'; 350 | ynodes(end) = ymax; % make sure it hits the max 351 | end 352 | 353 | xnodes=xnodes(:); 354 | ynodes=ynodes(:); 355 | dx = diff(xnodes); 356 | dy = diff(ynodes); 357 | nx = length(xnodes); 358 | ny = length(ynodes); 359 | ngrid = nx*ny; 360 | 361 | % set the scaling if autoscale was on 362 | if strcmpi(params.autoscale,'on') 363 | params.xscale = mean(dx); 364 | params.yscale = mean(dy); 365 | params.autoscale = 'off'; 366 | end 367 | 368 | % check to see if any tiling is necessary 369 | if (params.tilesize < max(nx,ny)) 370 | % split it into smaller tiles. compute zgrid and ygrid 371 | % at the very end if requested 372 | zgrid = tiled_gridfit(x,y,z,xnodes,ynodes,params); 373 | else 374 | % its a single tile. 375 | 376 | % mask must be either an empty array, or a boolean 377 | % aray of the same size as the final grid. 378 | nmask = size(params.mask); 379 | if ~isempty(params.mask) && ((nmask(2)~=nx) || (nmask(1)~=ny)) 380 | if ((nmask(2)==ny) || (nmask(1)==nx)) 381 | error 'Mask array is probably transposed from proper orientation.' 382 | else 383 | error 'Mask array must be the same size as the final grid.' 384 | end 385 | end 386 | if ~isempty(params.mask) 387 | params.maskflag = 1; 388 | else 389 | params.maskflag = 0; 390 | end 391 | 392 | % default for maxiter? 393 | if isempty(params.maxiter) 394 | params.maxiter = min(10000,nx*ny); 395 | end 396 | 397 | % check lengths of the data 398 | n = length(x); 399 | if (length(y)~=n) || (length(z)~=n) 400 | error 'Data vectors are incompatible in size.' 401 | end 402 | if n<3 403 | error 'Insufficient data for surface estimation.' 404 | end 405 | 406 | % verify the nodes are distinct 407 | if any(diff(xnodes)<=0) || any(diff(ynodes)<=0) 408 | error 'xnodes and ynodes must be monotone increasing' 409 | end 410 | 411 | % do we need to tweak the first or last node in x or y? 412 | if xminxnodes(end) 424 | switch params.extend 425 | case 'always' 426 | xnodes(end) = xmax; 427 | case 'warning' 428 | warning('GRIDFIT:extend',['xnodes(end) was increased by: ',num2str(xmax-xnodes(end)),', new node = ',num2str(xmax)]) 429 | xnodes(end) = xmax; 430 | case 'never' 431 | error(['Some x (',num2str(xmax),') falls above xnodes(end) by: ',num2str(xmax-xnodes(end))]) 432 | end 433 | end 434 | if yminynodes(end) 446 | switch params.extend 447 | case 'always' 448 | ynodes(end) = ymax; 449 | case 'warning' 450 | warning('GRIDFIT:extend',['ynodes(end) was increased by: ',num2str(ymax-ynodes(end)),', new node = ',num2str(ymax)]) 451 | ynodes(end) = ymax; 452 | case 'never' 453 | error(['Some y (',num2str(ymax),') falls above ynodes(end) by: ',num2str(ymax-ynodes(end))]) 454 | end 455 | end 456 | 457 | % determine which cell in the array each point lies in 458 | [junk,indx] = histc(x,xnodes); %#ok 459 | [junk,indy] = histc(y,ynodes); %#ok 460 | % any point falling at the last node is taken to be 461 | % inside the last cell in x or y. 462 | k=(indx==nx); 463 | indx(k)=indx(k)-1; 464 | k=(indy==ny); 465 | indy(k)=indy(k)-1; 466 | ind = indy + ny*(indx-1); 467 | 468 | % Do we have a mask to apply? 469 | if params.maskflag 470 | % if we do, then we need to ensure that every 471 | % cell with at least one data point also has at 472 | % least all of its corners unmasked. 473 | params.mask(ind) = 1; 474 | params.mask(ind+1) = 1; 475 | params.mask(ind+ny) = 1; 476 | params.mask(ind+ny+1) = 1; 477 | end 478 | 479 | % interpolation equations for each point 480 | tx = min(1,max(0,(x - xnodes(indx))./dx(indx))); 481 | ty = min(1,max(0,(y - ynodes(indy))./dy(indy))); 482 | % Future enhancement: add cubic interpolant 483 | switch params.interp 484 | case 'triangle' 485 | % linear interpolation inside each triangle 486 | k = (tx > ty); 487 | L = ones(n,1); 488 | L(k) = ny; 489 | 490 | t1 = min(tx,ty); 491 | t2 = max(tx,ty); 492 | A = sparse(repmat((1:n)',1,3),[ind,ind+ny+1,ind+L], ... 493 | [1-t2,t1,t2-t1],n,ngrid); 494 | 495 | case 'nearest' 496 | % nearest neighbor interpolation in a cell 497 | k = round(1-ty) + round(1-tx)*ny; 498 | A = sparse((1:n)',ind+k,ones(n,1),n,ngrid); 499 | 500 | case 'bilinear' 501 | % bilinear interpolation in a cell 502 | A = sparse(repmat((1:n)',1,4),[ind,ind+1,ind+ny,ind+ny+1], ... 503 | [(1-tx).*(1-ty), (1-tx).*ty, tx.*(1-ty), tx.*ty], ... 504 | n,ngrid); 505 | 506 | end 507 | rhs = z; 508 | 509 | % do we have relative smoothing parameters? 510 | if numel(params.smoothness) == 1 511 | % it was scalar, so treat both dimensions equally 512 | smoothparam = params.smoothness; 513 | xyRelativeStiffness = [1;1]; 514 | else 515 | % It was a vector, so anisotropy reigns. 516 | % I've already checked that the vector was of length 2 517 | smoothparam = sqrt(prod(params.smoothness)); 518 | xyRelativeStiffness = params.smoothness(:)./smoothparam; 519 | end 520 | 521 | % Build regularizer. Add del^4 regularizer one day. 522 | switch params.regularizer 523 | case 'springs' 524 | % zero "rest length" springs 525 | [i,j] = meshgrid(1:nx,1:(ny-1)); 526 | ind = j(:) + ny*(i(:)-1); 527 | m = nx*(ny-1); 528 | stiffness = 1./(dy/params.yscale); 529 | Areg = sparse(repmat((1:m)',1,2),[ind,ind+1], ... 530 | xyRelativeStiffness(2)*stiffness(j(:))*[-1 1], ... 531 | m,ngrid); 532 | 533 | [i,j] = meshgrid(1:(nx-1),1:ny); 534 | ind = j(:) + ny*(i(:)-1); 535 | m = (nx-1)*ny; 536 | stiffness = 1./(dx/params.xscale); 537 | Areg = [Areg;sparse(repmat((1:m)',1,2),[ind,ind+ny], ... 538 | xyRelativeStiffness(1)*stiffness(i(:))*[-1 1],m,ngrid)]; 539 | 540 | [i,j] = meshgrid(1:(nx-1),1:(ny-1)); 541 | ind = j(:) + ny*(i(:)-1); 542 | m = (nx-1)*(ny-1); 543 | stiffness = 1./sqrt((dx(i(:))/params.xscale/xyRelativeStiffness(1)).^2 + ... 544 | (dy(j(:))/params.yscale/xyRelativeStiffness(2)).^2); 545 | 546 | Areg = [Areg;sparse(repmat((1:m)',1,2),[ind,ind+ny+1], ... 547 | stiffness*[-1 1],m,ngrid)]; 548 | 549 | Areg = [Areg;sparse(repmat((1:m)',1,2),[ind+1,ind+ny], ... 550 | stiffness*[-1 1],m,ngrid)]; 551 | 552 | case {'diffusion' 'laplacian'} 553 | % thermal diffusion using Laplacian (del^2) 554 | [i,j] = meshgrid(1:nx,2:(ny-1)); 555 | ind = j(:) + ny*(i(:)-1); 556 | dy1 = dy(j(:)-1)/params.yscale; 557 | dy2 = dy(j(:))/params.yscale; 558 | 559 | Areg = sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ... 560 | xyRelativeStiffness(2)*[-2./(dy1.*(dy1+dy2)), ... 561 | 2./(dy1.*dy2), -2./(dy2.*(dy1+dy2))],ngrid,ngrid); 562 | 563 | [i,j] = meshgrid(2:(nx-1),1:ny); 564 | ind = j(:) + ny*(i(:)-1); 565 | dx1 = dx(i(:)-1)/params.xscale; 566 | dx2 = dx(i(:))/params.xscale; 567 | 568 | Areg = Areg + sparse(repmat(ind,1,3),[ind-ny,ind,ind+ny], ... 569 | xyRelativeStiffness(1)*[-2./(dx1.*(dx1+dx2)), ... 570 | 2./(dx1.*dx2), -2./(dx2.*(dx1+dx2))],ngrid,ngrid); 571 | 572 | case 'gradient' 573 | % Subtly different from the Laplacian. A point for future 574 | % enhancement is to do it better for the triangle interpolation 575 | % case. 576 | [i,j] = meshgrid(1:nx,2:(ny-1)); 577 | ind = j(:) + ny*(i(:)-1); 578 | dy1 = dy(j(:)-1)/params.yscale; 579 | dy2 = dy(j(:))/params.yscale; 580 | 581 | Areg = sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ... 582 | xyRelativeStiffness(2)*[-2./(dy1.*(dy1+dy2)), ... 583 | 2./(dy1.*dy2), -2./(dy2.*(dy1+dy2))],ngrid,ngrid); 584 | 585 | [i,j] = meshgrid(2:(nx-1),1:ny); 586 | ind = j(:) + ny*(i(:)-1); 587 | dx1 = dx(i(:)-1)/params.xscale; 588 | dx2 = dx(i(:))/params.xscale; 589 | 590 | Areg = [Areg;sparse(repmat(ind,1,3),[ind-ny,ind,ind+ny], ... 591 | xyRelativeStiffness(1)*[-2./(dx1.*(dx1+dx2)), ... 592 | 2./(dx1.*dx2), -2./(dx2.*(dx1+dx2))],ngrid,ngrid)]; 593 | 594 | end 595 | nreg = size(Areg,1); 596 | 597 | % Append the regularizer to the interpolation equations, 598 | % scaling the problem first. Use the 1-norm for speed. 599 | NA = norm(A,1); 600 | NR = norm(Areg,1); 601 | A = [A;Areg*(smoothparam*NA/NR)]; 602 | rhs = [rhs;zeros(nreg,1)]; 603 | % do we have a mask to apply? 604 | if params.maskflag 605 | unmasked = find(params.mask); 606 | end 607 | % solve the full system, with regularizer attached 608 | switch params.solver 609 | case {'\' 'backslash'} 610 | if params.maskflag 611 | % there is a mask to use 612 | zgrid=nan(ny,nx); 613 | zgrid(unmasked) = A(:,unmasked)\rhs; 614 | else 615 | % no mask 616 | zgrid = reshape(A\rhs,ny,nx); 617 | end 618 | 619 | case 'normal' 620 | % The normal equations, solved with \. Can be faster 621 | % for huge numbers of data points, but reasonably 622 | % sized grids. The regularizer makes A well conditioned 623 | % so the normal equations are not a terribly bad thing 624 | % here. 625 | if params.maskflag 626 | % there is a mask to use 627 | Aunmasked = A(:,unmasked); 628 | zgrid=nan(ny,nx); 629 | zgrid(unmasked) = (Aunmasked'*Aunmasked)\(Aunmasked'*rhs); 630 | else 631 | zgrid = reshape((A'*A)\(A'*rhs),ny,nx); 632 | end 633 | 634 | case 'symmlq' 635 | % iterative solver - symmlq - requires a symmetric matrix, 636 | % so use it to solve the normal equations. No preconditioner. 637 | tol = abs(max(z)-min(z))*1.e-13; 638 | if params.maskflag 639 | % there is a mask to use 640 | zgrid=nan(ny,nx); 641 | [zgrid(unmasked),flag] = symmlq(A(:,unmasked)'*A(:,unmasked), ... 642 | A(:,unmasked)'*rhs,tol,params.maxiter); 643 | else 644 | [zgrid,flag] = symmlq(A'*A,A'*rhs,tol,params.maxiter); 645 | zgrid = reshape(zgrid,ny,nx); 646 | end 647 | % display a warning if convergence problems 648 | switch flag 649 | case 0 650 | % no problems with convergence 651 | case 1 652 | % SYMMLQ iterated MAXIT times but did not converge. 653 | warning('GRIDFIT:solver',['Symmlq performed ',num2str(params.maxiter), ... 654 | ' iterations but did not converge.']) 655 | case 3 656 | % SYMMLQ stagnated, successive iterates were the same 657 | warning('GRIDFIT:solver','Symmlq stagnated without apparent convergence.') 658 | otherwise 659 | warning('GRIDFIT:solver',['One of the scalar quantities calculated in',... 660 | ' symmlq was too small or too large to continue computing.']) 661 | end 662 | 663 | case 'lsqr' 664 | % iterative solver - lsqr. No preconditioner here. 665 | tol = abs(max(z)-min(z))*1.e-13; 666 | if params.maskflag 667 | % there is a mask to use 668 | zgrid=nan(ny,nx); 669 | [zgrid(unmasked),flag] = lsqr(A(:,unmasked),rhs,tol,params.maxiter); 670 | else 671 | [zgrid,flag] = lsqr(A,rhs,tol,params.maxiter); 672 | zgrid = reshape(zgrid,ny,nx); 673 | end 674 | 675 | % display a warning if convergence problems 676 | switch flag 677 | case 0 678 | % no problems with convergence 679 | case 1 680 | % lsqr iterated MAXIT times but did not converge. 681 | warning('GRIDFIT:solver',['Lsqr performed ', ... 682 | num2str(params.maxiter),' iterations but did not converge.']) 683 | case 3 684 | % lsqr stagnated, successive iterates were the same 685 | warning('GRIDFIT:solver','Lsqr stagnated without apparent convergence.') 686 | case 4 687 | warning('GRIDFIT:solver',['One of the scalar quantities calculated in',... 688 | ' LSQR was too small or too large to continue computing.']) 689 | end 690 | 691 | end % switch params.solver 692 | 693 | end % if params.tilesize... 694 | 695 | % only generate xgrid and ygrid if requested. 696 | if nargout>1 697 | [xgrid,ygrid]=meshgrid(xnodes,ynodes); 698 | end 699 | 700 | % ============================================ 701 | % End of main function - gridfit 702 | % ============================================ 703 | 704 | % ============================================ 705 | % subfunction - parse_pv_pairs 706 | % ============================================ 707 | function params=parse_pv_pairs(params,pv_pairs) 708 | % parse_pv_pairs: parses sets of property value pairs, allows defaults 709 | % usage: params=parse_pv_pairs(default_params,pv_pairs) 710 | % 711 | % arguments: (input) 712 | % default_params - structure, with one field for every potential 713 | % property/value pair. Each field will contain the default 714 | % value for that property. If no default is supplied for a 715 | % given property, then that field must be empty. 716 | % 717 | % pv_array - cell array of property/value pairs. 718 | % Case is ignored when comparing properties to the list 719 | % of field names. Also, any unambiguous shortening of a 720 | % field/property name is allowed. 721 | % 722 | % arguments: (output) 723 | % params - parameter struct that reflects any updated property/value 724 | % pairs in the pv_array. 725 | % 726 | % Example usage: 727 | % First, set default values for the parameters. Assume we 728 | % have four parameters that we wish to use optionally in 729 | % the function examplefun. 730 | % 731 | % - 'viscosity', which will have a default value of 1 732 | % - 'volume', which will default to 1 733 | % - 'pie' - which will have default value 3.141592653589793 734 | % - 'description' - a text field, left empty by default 735 | % 736 | % The first argument to examplefun is one which will always be 737 | % supplied. 738 | % 739 | % function examplefun(dummyarg1,varargin) 740 | % params.Viscosity = 1; 741 | % params.Volume = 1; 742 | % params.Pie = 3.141592653589793 743 | % 744 | % params.Description = ''; 745 | % params=parse_pv_pairs(params,varargin); 746 | % params 747 | % 748 | % Use examplefun, overriding the defaults for 'pie', 'viscosity' 749 | % and 'description'. The 'volume' parameter is left at its default. 750 | % 751 | % examplefun(rand(10),'vis',10,'pie',3,'Description','Hello world') 752 | % 753 | % params = 754 | % Viscosity: 10 755 | % Volume: 1 756 | % Pie: 3 757 | % Description: 'Hello world' 758 | % 759 | % Note that capitalization was ignored, and the property 'viscosity' 760 | % was truncated as supplied. Also note that the order the pairs were 761 | % supplied was arbitrary. 762 | 763 | npv = length(pv_pairs); 764 | n = npv/2; 765 | 766 | if n~=floor(n) 767 | error 'Property/value pairs must come in PAIRS.' 768 | end 769 | if n<=0 770 | % just return the defaults 771 | return 772 | end 773 | 774 | if ~isstruct(params) 775 | error 'No structure for defaults was supplied' 776 | end 777 | 778 | % there was at least one pv pair. process any supplied 779 | propnames = fieldnames(params); 780 | lpropnames = lower(propnames); 781 | for i=1:n 782 | p_i = lower(pv_pairs{2*i-1}); 783 | v_i = pv_pairs{2*i}; 784 | 785 | ind = strmatch(p_i,lpropnames,'exact'); 786 | if isempty(ind) 787 | ind = find(strncmp(p_i,lpropnames,length(p_i))); 788 | if isempty(ind) 789 | error(['No matching property found for: ',pv_pairs{2*i-1}]) 790 | elseif length(ind)>1 791 | error(['Ambiguous property name: ',pv_pairs{2*i-1}]) 792 | end 793 | end 794 | p_i = propnames{ind}; 795 | 796 | % override the corresponding default in params 797 | params = setfield(params,p_i,v_i); %#ok 798 | 799 | end 800 | 801 | 802 | % ============================================ 803 | % subfunction - check_params 804 | % ============================================ 805 | function params = check_params(params) 806 | 807 | % check the parameters for acceptability 808 | % smoothness == 1 by default 809 | if isempty(params.smoothness) 810 | params.smoothness = 1; 811 | else 812 | if (numel(params.smoothness)>2) || any(params.smoothness<=0) 813 | error 'Smoothness must be scalar (or length 2 vector), real, finite, and positive.' 814 | end 815 | end 816 | 817 | % regularizer - must be one of 4 options - the second and 818 | % third are actually synonyms. 819 | valid = {'springs', 'diffusion', 'laplacian', 'gradient'}; 820 | if isempty(params.regularizer) 821 | params.regularizer = 'diffusion'; 822 | end 823 | ind = find(strncmpi(params.regularizer,valid,length(params.regularizer))); 824 | if (length(ind)==1) 825 | params.regularizer = valid{ind}; 826 | else 827 | error(['Invalid regularization method: ',params.regularizer]) 828 | end 829 | 830 | % interp must be one of: 831 | % 'bilinear', 'nearest', or 'triangle' 832 | % but accept any shortening thereof. 833 | valid = {'bilinear', 'nearest', 'triangle'}; 834 | if isempty(params.interp) 835 | params.interp = 'triangle'; 836 | end 837 | ind = find(strncmpi(params.interp,valid,length(params.interp))); 838 | if (length(ind)==1) 839 | params.interp = valid{ind}; 840 | else 841 | error(['Invalid interpolation method: ',params.interp]) 842 | end 843 | 844 | % solver must be one of: 845 | % 'backslash', '\', 'symmlq', 'lsqr', or 'normal' 846 | % but accept any shortening thereof. 847 | valid = {'backslash', '\', 'symmlq', 'lsqr', 'normal'}; 848 | if isempty(params.solver) 849 | params.solver = '\'; 850 | end 851 | ind = find(strncmpi(params.solver,valid,length(params.solver))); 852 | if (length(ind)==1) 853 | params.solver = valid{ind}; 854 | else 855 | error(['Invalid solver option: ',params.solver]) 856 | end 857 | 858 | % extend must be one of: 859 | % 'never', 'warning', 'always' 860 | % but accept any shortening thereof. 861 | valid = {'never', 'warning', 'always'}; 862 | if isempty(params.extend) 863 | params.extend = 'warning'; 864 | end 865 | ind = find(strncmpi(params.extend,valid,length(params.extend))); 866 | if (length(ind)==1) 867 | params.extend = valid{ind}; 868 | else 869 | error(['Invalid extend option: ',params.extend]) 870 | end 871 | 872 | % tilesize == inf by default 873 | if isempty(params.tilesize) 874 | params.tilesize = inf; 875 | elseif (length(params.tilesize)>1) || (params.tilesize<3) 876 | error 'Tilesize must be scalar and > 0.' 877 | end 878 | 879 | % overlap == 0.20 by default 880 | if isempty(params.overlap) 881 | params.overlap = 0.20; 882 | elseif (length(params.overlap)>1) || (params.overlap<0) || (params.overlap>0.5) 883 | error 'Overlap must be scalar and 0 < overlap < 1.' 884 | end 885 | 886 | % ============================================ 887 | % subfunction - tiled_gridfit 888 | % ============================================ 889 | function zgrid=tiled_gridfit(x,y,z,xnodes,ynodes,params) 890 | % tiled_gridfit: a tiled version of gridfit, continuous across tile boundaries 891 | % usage: [zgrid,xgrid,ygrid]=tiled_gridfit(x,y,z,xnodes,ynodes,params) 892 | % 893 | % Tiled_gridfit is used when the total grid is far too large 894 | % to model using a single call to gridfit. While gridfit may take 895 | % only a second or so to build a 100x100 grid, a 2000x2000 grid 896 | % will probably not run at all due to memory problems. 897 | % 898 | % Tiles in the grid with insufficient data (<4 points) will be 899 | % filled with NaNs. Avoid use of too small tiles, especially 900 | % if your data has holes in it that may encompass an entire tile. 901 | % 902 | % A mask may also be applied, in which case tiled_gridfit will 903 | % subdivide the mask into tiles. Note that any boolean mask 904 | % provided is assumed to be the size of the complete grid. 905 | % 906 | % Tiled_gridfit may not be fast on huge grids, but it should run 907 | % as long as you use a reasonable tilesize. 8-) 908 | 909 | % Note that we have already verified all parameters in check_params 910 | 911 | % Matrix elements in a square tile 912 | tilesize = params.tilesize; 913 | % Size of overlap in terms of matrix elements. Overlaps 914 | % of purely zero cause problems, so force at least two 915 | % elements to overlap. 916 | overlap = max(2,floor(tilesize*params.overlap)); 917 | 918 | % reset the tilesize for each particular tile to be inf, so 919 | % we will never see a recursive call to tiled_gridfit 920 | Tparams = params; 921 | Tparams.tilesize = inf; 922 | 923 | nx = length(xnodes); 924 | ny = length(ynodes); 925 | zgrid = zeros(ny,nx); 926 | 927 | % linear ramp for the bilinear interpolation 928 | rampfun = inline('(t-t(1))/(t(end)-t(1))','t'); 929 | 930 | % loop over each tile in the grid 931 | h = waitbar(0,'Relax and have a cup of JAVA. Its my treat.'); 932 | warncount = 0; 933 | xtind = 1:min(nx,tilesize); 934 | while ~isempty(xtind) && (xtind(1)<=nx) 935 | 936 | xinterp = ones(1,length(xtind)); 937 | if (xtind(1) ~= 1) 938 | xinterp(1:overlap) = rampfun(xnodes(xtind(1:overlap))); 939 | end 940 | if (xtind(end) ~= nx) 941 | xinterp((end-overlap+1):end) = 1-rampfun(xnodes(xtind((end-overlap+1):end))); 942 | end 943 | 944 | ytind = 1:min(ny,tilesize); 945 | while ~isempty(ytind) && (ytind(1)<=ny) 946 | % update the waitbar 947 | waitbar((xtind(end)-tilesize)/nx + tilesize*ytind(end)/ny/nx) 948 | 949 | yinterp = ones(length(ytind),1); 950 | if (ytind(1) ~= 1) 951 | yinterp(1:overlap) = rampfun(ynodes(ytind(1:overlap))); 952 | end 953 | if (ytind(end) ~= ny) 954 | yinterp((end-overlap+1):end) = 1-rampfun(ynodes(ytind((end-overlap+1):end))); 955 | end 956 | 957 | % was a mask supplied? 958 | if ~isempty(params.mask) 959 | submask = params.mask(ytind,xtind); 960 | Tparams.mask = submask; 961 | end 962 | 963 | % extract data that lies in this grid tile 964 | k = (x>=xnodes(xtind(1))) & (x<=xnodes(xtind(end))) & ... 965 | (y>=ynodes(ytind(1))) & (y<=ynodes(ytind(end))); 966 | k = find(k); 967 | 968 | if length(k)<4 969 | if warncount == 0 970 | warning('GRIDFIT:tiling','A tile was too underpopulated to model. Filled with NaNs.') 971 | end 972 | warncount = warncount + 1; 973 | 974 | % fill this part of the grid with NaNs 975 | zgrid(ytind,xtind) = NaN; 976 | 977 | else 978 | % build this tile 979 | zgtile = gridfit(x(k),y(k),z(k),xnodes(xtind),ynodes(ytind),Tparams); 980 | 981 | % bilinear interpolation (using an outer product) 982 | interp_coef = yinterp*xinterp; 983 | 984 | % accumulate the tile into the complete grid 985 | zgrid(ytind,xtind) = zgrid(ytind,xtind) + zgtile.*interp_coef; 986 | 987 | end 988 | 989 | % step to the next tile in y 990 | if ytind(end)=ny 994 | % extend this tile to the edge 995 | ytind = ytind(1):ny; 996 | end 997 | else 998 | ytind = ny+1; 999 | end 1000 | 1001 | end % while loop over y 1002 | 1003 | % step to the next tile in x 1004 | if xtind(end)=nx 1008 | % extend this tile to the edge 1009 | xtind = xtind(1):nx; 1010 | end 1011 | else 1012 | xtind = nx+1; 1013 | end 1014 | 1015 | end % while loop over x 1016 | 1017 | % close down the waitbar 1018 | close(h) 1019 | 1020 | if warncount>0 1021 | warning('GRIDFIT:tiling',[num2str(warncount),' tiles were underpopulated & filled with NaNs']) 1022 | end 1023 | 1024 | 1025 | 1026 | --------------------------------------------------------------------------------