1## Copyright (C) 2018 Martin Janda <janda.martin1@gmail.com>
2##
3## This program is free software: you can redistribute it and/or modify it
4## under the terms of the GNU General Public License as published by
5## the Free Software Foundation, either version 3 of the License, or
6## (at your option) any later version.
7##
8## This program is distributed in the hope that it will be useful, but
9## WITHOUT ANY WARRANTY; without even the implied warranty of
10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11## GNU General Public License for more details.
12##
13## You should have received a copy of the GNU General Public License
14## along with this program.  If not, see
15## <https://www.gnu.org/licenses/>.
16
17## -*- texinfo -*-
18## @deftypefn {} {@var{r} =} imref2d
19## @deftypefnx {} {@var{r} =} imref2d (@var{imageSize})
20## @deftypefnx {} {@var{r} =} imref2d (@var{imageSize}, @var{pixelExtentInWorldX}, @var{pixelExtentInWorldY})
21## @deftypefnx {} {@var{r} =} imref2d (@var{imageSize}, @var{xWorldLimits}, @var{yWorldLimits})
22## Reference 2-D image to world coordinates.
23##
24## Creates an imref2d object referencing a 2-D m-by-n image with the size
25## @var{imageSize} to world coordinates.  The world extent is either given
26## by @var{xWorldLimits} and @var{yWorldLimits} or computed from
27## @var{pixelExtentInWorldX} and @var{pixelExtentInWorldY}.
28## @var{imageSize} is [2, 2] by default.
29##
30## Intrinsic coordinates are x = 1.0, y = 1.0 in the center of the top left
31## pixel and x = n, y = m in the center of the bottom right pixel.
32## Spatial resolution in each dimension can be different.
33##
34## imref2d object has the following properties:
35##
36## ImageSize - two element integer vector with image height and width
37## in pixels.
38##
39## XWorldLimits - limits of the image along the x-axis in world units
40## specified as a two element real vector @code{[xMin, xMax]}.
41##
42## YWorldLimits - limits of the image along the y-axis in world units
43## specified as a two element real vector @code{[yMin, yMax]}.
44##
45## PixelExtentInWorldX - pixel extent along the x-axis in world units
46## specified as a real scalar.
47##
48## PixelExtentInWorldY - pixel extent along the y-axis in world units
49## specified as a real scalar.
50##
51## ImageExtentInWorldX - image extent along the x-axis in world units
52## specified as a real scalar.
53##
54## ImageExtentInWorldY - image extent along the y-axis in world units
55## specified as a real scalar.
56##
57## XIntrinsicLimits - limits of the image along the x-axis in intrinsic
58## units, equals to @code{[n - 0.5, n + 0.5]}.
59##
60## YIntrinsicLimits - limits of the image along the y-axis in intrinsic
61## units, equals to @code{[m - 0.5, m + 0.5]}.
62##
63## @seealso{imref3d}
64## @end deftypefn
65
66function r = imref2d (imageSize, varargin)
67  if (nargin > 1 && nargin != 3)
68    print_usage();
69  endif
70
71  if (nargin == 0)
72    imageSize = [2, 2];
73  endif
74
75  if (nargin > 0)
76    if (length (imageSize) < 2)
77      error ("Octave:invalid-input-arg", ...
78      "ImageSize must have at least two elements");
79    endif
80
81    validateattributes (imageSize, {"numeric"}, ...
82    {"positive", "integer", "vector"}, "imref2d", "imageSize");
83  endif
84
85  imageSize = imageSize(1:2);
86  m = imageSize(1);
87  n = imageSize(2);
88
89  if (numel (varargin) == 0)
90    xWorldLimits = [0.5, n + 0.5];
91    yWorldLimits = [0.5, m + 0.5];
92  elseif (numel (varargin) == 2)
93    if (isscalar (varargin{1}))
94      validateattributes (varargin{1}, {"numeric"}, ...
95      {"real", "positive", "scalar"}, "imref2d", "pixelExtentInWorldX");
96      validateattributes (varargin{2}, {"numeric"}, ...
97      {"real", "positive", "scalar"}, "imref2d", "pixelExtentInWorldY");
98      pixelExtentInWorldX = varargin{1};
99      pixelExtentInWorldY = varargin{2};
100    else
101      validateattributes (varargin{1}, {"numeric"}, ...
102      {"real", "increasing", "vector", "size", [1, 2]}, "imref2d", ...
103      "xWorldLimits");
104      validateattributes (varargin{2}, {"numeric"}, ...
105      {"real", "increasing", "vector", "size", [1, 2]}, "imref2d", ...
106      "yWorldLimits");
107      xWorldLimits = varargin{1};
108      yWorldLimits = varargin{2};
109    endif
110  endif
111
112  if (exist ("pixelExtentInWorldX") && exist ("pixelExtentInWorldY"))
113    imageExtentInWorldX = pixelExtentInWorldX * n;
114    imageExtentInWorldY = pixelExtentInWorldY * m;
115    xWorldLimits = [pixelExtentInWorldX / 2, imageExtentInWorldX + ...
116    pixelExtentInWorldX / 2];
117    yWorldLimits = [pixelExtentInWorldY / 2, imageExtentInWorldY + ...
118    pixelExtentInWorldY / 2];
119  elseif (exist ("xWorldLimits") && exist ("yWorldLimits"))
120    imageExtentInWorldX = xWorldLimits(2) - xWorldLimits(1);
121    imageExtentInWorldY = yWorldLimits(2) - yWorldLimits(1);
122    pixelExtentInWorldX = imageExtentInWorldX / n;
123    pixelExtentInWorldY = imageExtentInWorldY / m;
124  endif
125
126  xIntrinsicLimits = [0.5, n + 0.5];
127  yIntrinsicLimits = [0.5, m + 0.5];
128
129  r.ImageSize = imageSize;
130  r.XWorldLimits = xWorldLimits;
131  r.YWorldLimits = yWorldLimits;
132  r.PixelExtentInWorldX = pixelExtentInWorldX;
133  r.PixelExtentInWorldY = pixelExtentInWorldY;
134  r.ImageExtentInWorldX = imageExtentInWorldX;
135  r.ImageExtentInWorldY = imageExtentInWorldY;
136  r.XIntrinsicLimits = xIntrinsicLimits;
137  r.YIntrinsicLimits = yIntrinsicLimits;
138  r = class (r, "imref2d");
139endfunction
140
141%!error id=Octave:invalid-fun-call imref2d (1, 2, 3, 4)
142%!error id=Octave:invalid-input-arg imref2d (42)
143%!error id=Octave:invalid-input-arg imref2d ([42])
144%!error id=Octave:expected-integer imref2d ([4.2, 42])
145%!error id=Octave:expected-positive imref2d ([0, 0])
146%!error id=Octave:expected-positive imref2d ([-4, 2])
147%!error id=Octave:expected-positive imref2d ([4, 2], 0, 2)
148%!error id=Octave:expected-positive imref2d ([4, 2], 2, 0)
149%!error id=Octave:expected-real imref2d ([4, 2], j, 2)
150%!error id=Octave:expected-real imref2d ([4, 2], 2, j)
151%!error id=Octave:expected-real imref2d ([4, 2], [j, 2], [3, 4])
152%!error id=Octave:expected-real imref2d ([4, 2], [1, 2], [j, 4])
153%!error id=Octave:expected-vector imref2d ([4, 2], [], [])
154%!error id=Octave:expected-vector imref2d ([4, 2], [], [1])
155%!error id=Octave:expected-scalar imref2d ([4, 2], [1], [])
156%!error id=Octave:incorrect-size imref2d ([4, 2], [1, 2], [0])
157%!error id=Octave:incorrect-size imref2d ([4, 2], [1, 2], [1, 2, 3])
158%!error id=Octave:incorrect-size imref2d ([4, 2], [1, 2, 3], [1, 2])
159%!error id=Octave:incorrect-size imref2d ([4, 2], [1; 2], [1, 2])
160%!error id=Octave:incorrect-size imref2d ([4, 2], [1, 2], [1; 2])
161%!error id=Octave:invalid-indexing imref2d().InvalidProperty
162%!error id=Octave:expected-increasing imref2d ([100 200], [1.5 0.5], [2.5 3.5])
163%!error id=Octave:expected-increasing imref2d ([100 200], [1.5 2.5], [2.5 1.5])
164
165%!test
166%! r = imref2d;
167%! assert (r.XWorldLimits, [0.5, 2.5])
168%! assert (r.YWorldLimits, [0.5, 2.5])
169%! assert (r.ImageSize, [2, 2])
170%! assert (r.PixelExtentInWorldX, 1)
171%! assert (r.PixelExtentInWorldY, 1)
172%! assert (r.ImageExtentInWorldX, 2)
173%! assert (r.ImageExtentInWorldY, 2)
174%! assert (r.XIntrinsicLimits, [0.5, 2.5])
175%! assert (r.YIntrinsicLimits, [0.5, 2.5])
176
177%!test
178%! r = imref2d ([100, 200]);
179%! assert (r.XWorldLimits, [0.5, 200.5])
180%! assert (r.YWorldLimits, [0.5, 100.5])
181%! assert (r.ImageSize, [100, 200])
182%! assert (r.PixelExtentInWorldX, 1)
183%! assert (r.PixelExtentInWorldY, 1)
184%! assert (r.ImageExtentInWorldX, 200)
185%! assert (r.ImageExtentInWorldY, 100)
186%! assert (r.XIntrinsicLimits, [0.5, 200.5])
187%! assert (r.YIntrinsicLimits, [0.5, 100.5])
188
189%!test
190%! xWorldLimits = [2, 5];
191%! yWorldLimits = [3, 6];
192%! r = imref2d ([291, 240], xWorldLimits, yWorldLimits);
193%! assert (r.XWorldLimits, [2, 5])
194%! assert (r.YWorldLimits, [3, 6])
195%! assert (r.ImageSize, [291, 240])
196%! assert (r.PixelExtentInWorldX, 0.0125)
197%! assert (r.PixelExtentInWorldY, 0.0103, 1e-3)
198%! assert (r.ImageExtentInWorldX, 3)
199%! assert (r.ImageExtentInWorldY, 3)
200%! assert (r.XIntrinsicLimits, [0.5, 240.5])
201%! assert (r.YIntrinsicLimits, [0.5, 291.5])
202
203%!test
204%! pixelExtentInWorldX = 0.3125;
205%! pixelExtentInWorldY = 0.3125;
206%! r = imref2d ([512, 512], pixelExtentInWorldX, pixelExtentInWorldY);
207%! assert (r.XWorldLimits, [0.15625, 160.1562], 1e-4)
208%! assert (r.YWorldLimits, [0.15625, 160.1562], 1e-4)
209%! assert (r.ImageSize, [512, 512])
210%! assert (r.PixelExtentInWorldX, 0.3125)
211%! assert (r.PixelExtentInWorldY, 0.3125)
212%! assert (r.ImageExtentInWorldX, 160)
213%! assert (r.ImageExtentInWorldY, 160)
214%! assert (r.XIntrinsicLimits, [0.5, 512.5])
215%! assert (r.YIntrinsicLimits, [0.5, 512.5])
216
217%!test
218%! pixelExtentInWorldX = 0.1;
219%! pixelExtentInWorldY = 0.4;
220%! r = imref2d ([100, 200], pixelExtentInWorldX, pixelExtentInWorldY);
221%! assert (r.XWorldLimits, [0.05, 20.05], 1e-4)
222%! assert (r.YWorldLimits, [0.2, 40.2], 1e-4)
223%! assert (r.ImageSize, [100, 200])
224%! assert (r.PixelExtentInWorldX, 0.1)
225%! assert (r.PixelExtentInWorldY, 0.4)
226%! assert (r.ImageExtentInWorldX, 20)
227%! assert (r.ImageExtentInWorldY, 40)
228%! assert (r.XIntrinsicLimits, [0.5, 200.5])
229%! assert (r.YIntrinsicLimits, [0.5, 100.5])
230
231## changing ImageSize
232%!test
233%! r = imref2d;
234%! assert (r.XWorldLimits, [0.5, 2.5])
235%! assert (r.YWorldLimits, [0.5, 2.5])
236%! assert (r.ImageSize, [2, 2])
237%! assert (r.PixelExtentInWorldX, 1)
238%! assert (r.PixelExtentInWorldY, 1)
239%! assert (r.ImageExtentInWorldX, 2)
240%! assert (r.ImageExtentInWorldY, 2)
241%! assert (r.XIntrinsicLimits, [0.5, 2.5])
242%! assert (r.YIntrinsicLimits, [0.5, 2.5])
243%! r.ImageSize = [800, 600];
244%! assert (r.XWorldLimits, [0.5, 2.5])
245%! assert (r.YWorldLimits, [0.5, 2.5])
246%! assert (r.ImageSize, [800, 600])
247%! assert (r.PixelExtentInWorldX, 0.003333, 1e-5)
248%! assert (r.PixelExtentInWorldY, 0.0025)
249%! assert (r.ImageExtentInWorldX, 2)
250%! assert (r.ImageExtentInWorldY, 2)
251%! assert (r.XIntrinsicLimits, [0.5, 600.5])
252%! assert (r.YIntrinsicLimits, [0.5, 800.5])
253
254## changing XWorldLimits and YWorldLimits
255%!test
256%! r = imref2d;
257%! assert (r.XWorldLimits, [0.5, 2.5])
258%! assert (r.YWorldLimits, [0.5, 2.5])
259%! assert (r.ImageSize, [2, 2])
260%! assert (r.PixelExtentInWorldX, 1)
261%! assert (r.PixelExtentInWorldY, 1)
262%! assert (r.ImageExtentInWorldX, 2)
263%! assert (r.ImageExtentInWorldY, 2)
264%! assert (r.XIntrinsicLimits, [0.5, 2.5])
265%! assert (r.YIntrinsicLimits, [0.5, 2.5])
266%! r.XWorldLimits = [-60, 13.33];
267%! r.YWorldLimits = [-900.8, -560.26];
268%! assert (r.XWorldLimits, [-60, 13.33])
269%! assert (r.YWorldLimits, [-900.8, -560.26])
270%! assert (r.PixelExtentInWorldX, 36.6650)
271%! assert (r.PixelExtentInWorldY, 170.27, 1e-5)
272%! assert (r.ImageExtentInWorldX, 73.33, 1e-5)
273%! assert (r.ImageExtentInWorldY, 340.54, 1e-5)
274%! assert (r.XIntrinsicLimits, [0.5, 2.5])
275%! assert (r.YIntrinsicLimits, [0.5, 2.5])
276
277%!test
278%! r = imref2d;
279%! fail ("r.XWorldLimits = []", "")
280%! fail ("r.XWorldLimits = [1]", "")
281%! fail ("r.XWorldLimits = [j]", "")
282%! fail ("r.XWorldLimits = [1; 2]", "")
283%! fail ("r.YWorldLimits = []", "")
284%! fail ("r.YWorldLimits = [1]", "")
285%! fail ("r.YWorldLimits = [j]", "")
286%! fail ("r.YWorldLimits = [1; 2]", "")
287
288%!assert (imref2d ([4, 2, 3]).ImageSize, [4, 2]);