1 /*
2 Copyright (c) 2019- Ferhat Kurtulmuş
3 Boost Software License - Version 1.0 - August 17th, 2003
4 Permission is hereby granted, free of charge, to any person or organization
5 obtaining a copy of the software and accompanying documentation covered by
6 this license (the "Software") to use, reproduce, display, distribute,
7 execute, and transmit the Software, and to prepare derivative works of the
8 Software, and to permit third-parties to whom the Software is furnished to
9 do so, all subject to the following:
10 The copyright notices in the Software and this entire statement, including
11 the above license grant, this restriction and the following disclaimer,
12 must be included in all copies of the Software, in whole or in part, and
13 all derivative works of the Software, unless such copies or derivative
14 works are solely in the form of machine-executable object code generated by
15 a source language processor.
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
19 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
20 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
21 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 DEALINGS IN THE SOFTWARE.
23 */
24 
25 module measure.types;
26 
27 import std.math;
28 import std.algorithm;
29 import std.algorithm.searching;
30 
31 struct XYList {
32     // this is useful for many situation instead of using Point[]
33     int[] xs;
34     int[] ys;
35 }
36 
37 struct Rectangle{
38     int x;
39     int y;
40     int width;
41     int height;
42 }
43 
44 struct Ellipse{
45     double angle;
46     double center_x; 
47     double center_y;
48     double maj;
49     double min;
50 }
51 
52 struct Point {
53     int x;
54     int y;
55  
56     int opCmp(Point rhs) {
57         if (x < rhs.x) return -1;
58         if (rhs.x < x) return 1;
59         return 0;
60     }
61  
62     void toString(scope void delegate(const(char)[]) sink) const {
63         import std.format;
64         sink("(");
65         formattedWrite(sink, "%d", x);
66         sink(",");
67         formattedWrite(sink, "%d", y);
68         sink(")");
69     }
70 }
71 
72 class Region{
73     Mat2D!ubyte image;
74     
75     // spatial raw moments
76     double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
77     
78     ulong area;
79     double areaFromContour;
80     double perimeter;
81     Point centroid;
82     double aspect_Ratio;
83     Rectangle bBox;
84     XYList convexHull;
85     double convexArea;
86     Ellipse ellipse;
87     double extent;
88     double solidity;
89     double majorAxisLength;
90     double minorAxisLength;
91     double orientation;
92     double eccentricity;
93     double equivalentDiameter;
94     XYList contourPixelList; // chain sorted!
95     XYList pixelList;
96     
97     this(){}
98 }
99 
100 struct Mat2D(T)
101 {
102     T[] data;
103     size_t rows;
104     size_t cols;
105     
106     alias height = rows;
107     alias width = cols;
108     
109     this(T)(T[] array, size_t r, size_t c)
110     {
111         data = array;
112         rows = r;
113         cols = c;
114         
115         assert (data.length == rows* cols,
116             "array length does not match with rows * cols!");
117     }
118     
119     this(size_t r, size_t c)
120     {
121         data = new T[r*c];
122         rows = r;
123         cols = c;
124         data[0..$] = 0;
125     }
126     
127     this(size_t n){
128         data = new T[n*n];
129         rows = n;
130         cols = n;
131         data[0..$] = 0;
132     }
133     
134     T opIndex( size_t i, size_t j)
135     {
136         assert((i < rows) && (j < cols), "index out of bounds!");
137         return data[i * cols + j];
138     }
139     
140     void opIndexAssign(T val, size_t i, size_t j)
141     {
142         assert((i < rows) && (j < cols), "index out of bounds!");
143         data[i * cols + j] = val;
144     }
145     
146     bool opEquals(Mat2D!T other)
147     {
148         return data == other.data;
149     }
150     
151     T max()
152     {
153         return data.maxElement;
154     }
155     
156     T min()
157     {
158         return data.minElement;
159     }
160     
161     int[2] argMax()
162     {
163         const size_t flatInd = maxIndex(data);
164         int yy = cast(int)(flatInd % cols);
165         int xx = cast(int)(flatInd / cols);
166         return [xx, yy];
167     }
168     int[2] argMin()
169     {
170         const size_t flatInd = minIndex(data);
171         int yy = cast(int)(flatInd % cols);
172         int xx = cast(int)(flatInd / cols);
173         return [xx, yy];
174     }
175     
176     Mat2D!T opMul(S)(Mat2D!S b)
177     {
178         const size_t r1 = rows;
179         const size_t c1 = cols;
180 
181         const size_t r2 = b.rows;
182         const size_t c2 = b.cols;
183         
184         assert ((c1 == r2), "column of first matrix in not equal to row of second matrix"); 
185         
186         Mat2D!T mult = Mat2D!T(r1, c2);
187         
188         foreach(i; 0..r1)
189             foreach(j; 0..c2){
190                 T tempSum = 0;
191                 foreach(k; 0..c1)
192                     tempSum += this[i, k] * b[k, j];
193                 mult[i, j] = tempSum;
194             }
195                 
196         
197         return mult;
198     }
199     
200     Mat2D!T opMul(T scalar)
201     {
202         Mat2D!T mult = Mat2D!T(rows, cols);
203         foreach(i, val; data) mult.data[i] = cast(T)(val * scalar);
204         return mult;
205     }
206     
207     Mat2D!T opMulAssign (Mat2D!T mat)
208     {
209         this = this * mat;
210         return this;
211     }
212     
213     Mat2D!T opMulAssign(T scalar)
214     {
215         this = this * scalar;
216         return this;
217     }
218     
219     static Mat2D!T diagFromArray(T)(T[] v){
220         size_t len = v.length;
221         Mat2D!T d = Mat2D!T(len, len);
222         
223         foreach(y; 0..len)
224             foreach(x; 0..len){
225                     d[y, x] = 0;
226                     if (y == x){
227                         d[y, x] = v[x];
228                     }
229                     else
230                         d[y, x] = 0;
231             }
232         
233         return d;
234     }
235     
236     T[] diag()
237     {
238         assert(rows == cols, "The matrix is not square! Diagonal cannot be created!");
239         
240         T[] d; d.length = cols;
241         
242         foreach(y; 0..rows)
243             foreach(x; 0..rows){
244                 if (y == x) d[x] = this[y, x];
245             }
246         return d;
247     }
248     
249     Mat2D!T transpose()
250     {
251 
252         Mat2D!T transp = Mat2D!T(cols, rows);
253 
254         foreach(i; 0..rows)
255             foreach(j; 0..cols)
256                 transp[j, i] = this[i, j];
257         return transp;
258     }
259     
260     Mat2D!double inverse() 
261     { 
262         // https://www.geeksforgeeks.org/adjoint-inverse-matrix/
263         
264         assert(rows == cols, "The matrix is not square, cannot inverse!");
265         
266         size_t N = rows;
267         Mat2D!double inv = Mat2D!double(rows, rows);
268 
269         const double det = determinant(this, N);
270         assert (det != 0); //  Singular matrix, can't find its inverse
271       
272         // Find adjoint 
273         Mat2D!double adj = Mat2D!double(rows, rows);
274         adjoint(this, adj);
275         
276         for (int i=0; i<N; i++) 
277             for (int j=0; j<N; j++) 
278                 inv[i, j] = adj[i, j]/cast(double)det; 
279       
280         return inv; 
281     }
282 }
283 
284 /+ planned for performance, but it badly affects the speed for some reason?
285 struct ROIViewOfMat2D(T)
286 {
287     T* data;
288     size_t rows;
289     size_t cols;
290     
291     alias height = rows;
292     alias width = cols;
293     
294     Rectangle roi;
295     
296     this(T)(Mat2D!T parentMat, Rectangle _roi)
297     {
298         data = parentMat.data.ptr;
299         rows = parentMat.rows;
300         cols = parentMat.cols;
301         roi = _roi;
302     }
303     
304     T opIndex( size_t _i, size_t _j)
305     {
306         size_t i = _i + roi.y;
307         size_t j = _j + roi.x;
308         
309         assert((i < rows) && (j < cols), "index out of bounds!");
310         return data[i * cols + j];
311     }
312 }
313 +/
314 
315 void getCofactor(T)(Mat2D!T A, Mat2D!double temp, int p, int q, ulong n) { 
316     int i = 0, j = 0; 
317   
318     for (int row = 0; row < n; row++) 
319     { 
320         for (int col = 0; col < n; col++) 
321         {  
322             if (row != p && col != q) 
323             { 
324                 temp[i, j++] = A[row, col]; 
325   
326                 if (j == n - 1) 
327                 { 
328                     j = 0; 
329                     i++; 
330                 } 
331             } 
332         } 
333     } 
334 } 
335 
336 double determinant(T)(Mat2D!T A, ulong n)
337 { 
338     size_t N = A.rows;
339     double D = 0;
340     
341     if (n == 1) 
342         return A[0, 0]; 
343   
344     Mat2D!double temp = Mat2D!double(N, N);
345   
346     int sign = 1;
347     
348     for (int f = 0; f < n; f++) 
349     {
350         getCofactor(A, temp, 0, f, n); 
351         D += sign * A[0, f] * determinant(temp, n - 1); 
352         sign = -sign; 
353     } 
354   
355     return D; 
356 }
357 
358 void adjoint(T)(Mat2D!T A, Mat2D!double adj) 
359 { 
360     size_t N = A.rows;
361     
362     if (N == 1) 
363     { 
364         adj[0, 0] = 1; 
365         return; 
366     } 
367   
368     int sign = 1;
369     Mat2D!double temp = Mat2D!double(N, N); 
370   
371     for (int i=0; i<N; i++) 
372     { 
373         for (int j=0; j<N; j++) 
374         {
375             getCofactor(A, temp, i, j, N); 
376             sign = ((i+j)%2==0)? 1: -1; 
377             adj[j, i] = (sign)*(determinant(temp, N-1)); 
378         } 
379     } 
380 }
381 
382 unittest
383 {
384     double[] aa = [1,2,3,4,5,6];
385     Mat2D!double maa = Mat2D!double(aa, 2, 3);
386     
387     double[] bb = [7,8,9,10,11,12];
388     Mat2D!double mbb = Mat2D!double(bb, 3, 2);
389     
390     auto mul = maa*mbb;
391     assert(maa*mbb == Mat2D!double([58.0, 64.0, 139.0, 154.0], 2, 2));
392     
393     Mat2D!int cc = Mat2D!int([1,2,3,0,1,5,5,6,0], 3, 3);
394     assert(cc.inverse().data == [-6.0, 3.6, 1.4, 5.0, -3.0, -1.0, -1.0, 0.8, 0.2] );
395 }