nx_photogram_hbench.nx source
↩ module page · 230 lines · 7990 B
1// nx_photogram_hbench.nx -- run the Nishi front end on a REAL photograph pair and emit its matches.
2//
3// This organ deliberately does NOT score itself. It reads two images, detects/describes/matches, and
4// prints the matched coordinate pairs. The ground-truth homography is never opened here and the
5// correctness rule is never implemented here. A single external scorer grades this organ, OpenCV SIFT
6// and OpenCV ORB from identical output formats, so all three are judged by exactly the same code
7// against exactly the same ground truth. Scoring yourself with your own scorer is the oldest way to
8// win a benchmark that means nothing.
9//
10// Usage: nx_photogram_hbench <img1.pgm|ppm> <img2.pgm|ppm> [maxkp]
11// Output: one "M <x1> <y1> <x2> <y2>" line per match, then a "SUMMARY ..." line.
12// license_tier: ORIGINAL expect_exit: 0
13import "nx_photogram_front.nx"
14const HB_MAGIC_262144: i64 = 262144
15
16const HB_MAXBYTES: i64 = 8388608
17const HB_DEFAULT_KP: i64 = 500
18
19func hw(s: *u8) -> i64 {
20 var n: i64 = 0
21 while s[n] != (0 as u8) { n = n + 1 }
22 sys_write(1, s, n)
23 return 0
24}
25
26func hn(v: i64) -> i64 {
27 let bb: *u8 = sys_mmap(32)
28 let t: *u8 = sys_mmap(32)
29 var m: i64 = v
30 var neg: i64 = 0
31 if m < 0 { neg = 1; m = 0 - m }
32 var k: i64 = 0
33 if m == 0 { t[0] = 48 as u8; k = 1 }
34 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
35 var i: i64 = 0
36 if neg == 1 { bb[0] = 45 as u8; i = 1 }
37 var j: i64 = 0
38 while j < k { bb[i + j] = t[k - 1 - j]; j = j + 1 }
39 sys_write(1, bb, i + k)
40 return 0
41}
42
43func hb_isdigit(c: i64) -> i64 {
44 if c < 48 { return 0 }
45 if c > 57 { return 0 }
46 return 1
47}
48func hb_isspace(c: i64) -> i64 {
49 if c == 32 { return 1 }
50 if c == 10 { return 1 }
51 if c == 13 { return 1 }
52 if c == 9 { return 1 }
53 return 0
54}
55
56// Netpbm header fields are whitespace-separated with '#' comments running to end of line. pos is a
57// one-element cursor so the caller can advance through magic/width/height/maxval in sequence.
58// ⚠Every loop here exits by a FLAG, never by assigning the cursor past the end. The obvious
59// pseudo-break `p = n + 1` does leave the loop, but it also destroys p -- and p is precisely what the
60// next field depends on. Written that way, width parsed correctly and then height and maxval both read
61// 0 because the cursor had been slammed to the end of the buffer, so every image failed to load with
62// no indication of where. The flag form preserves the cursor.
63func hb_next_int(buf: *u8, n: i64, pos: *i64) -> i64 {
64 var p: i64 = pos[0]
65 var scanning: i64 = 1
66 while scanning == 1 {
67 if p >= n { scanning = 0 } else {
68 let c: i64 = buf[p] as i64
69 if hb_isspace(c) == 1 { p = p + 1 } else {
70 if c == 35 {
71 var incomment: i64 = 1
72 while incomment == 1 {
73 if p >= n { incomment = 0 } else {
74 if (buf[p] as i64) == 10 { incomment = 0 } else { p = p + 1 }
75 }
76 }
77 } else { scanning = 0 }
78 }
79 }
80 }
81 var v: i64 = 0
82 var indigits: i64 = 1
83 while indigits == 1 {
84 if p >= n { indigits = 0 } else {
85 let d: i64 = buf[p] as i64
86 if hb_isdigit(d) == 1 {
87 v = v * 10 + (d - 48)
88 p = p + 1
89 } else { indigits = 0 }
90 }
91 }
92 pos[0] = p
93 return v
94}
95
96// Read a binary Netpbm image (P5 grayscale or P6 RGB) into a grayscale byte buffer.
97// Returns 1 on success; wh[0]=width wh[1]=height, gray = caller-supplied buffer.
98func hb_load_pnm(path: *u8, gray: *u8, wh: *i64) -> i64 {
99 let fd: i64 = sys_openat_rd(path)
100 if fd < 0 { return 0 }
101 let buf: *u8 = sys_mmap(HB_MAXBYTES)
102 var total: i64 = 0
103 var reading: i64 = 1
104 while reading == 1 {
105 let got: i64 = sys_read(fd, buf + total, HB_MAGIC_262144)
106 if got <= 0 { reading = 0 } else {
107 total = total + got
108 if total >= HB_MAXBYTES - HB_MAGIC_262144 { reading = 0 }
109 }
110 }
111 sys_close(fd)
112 if total < 16 { return 0 }
113 if (buf[0] as i64) != 80 { return 0 }
114 let kind: i64 = buf[1] as i64
115 let pos: *i64 = sys_mmap(64) as *i64
116 pos[0] = 2
117 let w: i64 = hb_next_int(buf, total, pos)
118 let h: i64 = hb_next_int(buf, total, pos)
119 let mx: i64 = hb_next_int(buf, total, pos)
120 if w <= 0 { return 0 }
121 if h <= 0 { return 0 }
122 if mx <= 0 { return 0 }
123 // exactly one whitespace byte separates the header from the raster
124 var dstart: i64 = pos[0] + 1
125 var i: i64 = 0
126 if kind == 53 {
127 while i < w * h {
128 gray[i] = buf[dstart + i]
129 i = i + 1
130 }
131 } else {
132 if kind == 54 {
133 while i < w * h {
134 let o: i64 = dstart + i * 3
135 let r: i64 = buf[o] as i64
136 let g: i64 = buf[o + 1] as i64
137 let b: i64 = buf[o + 2] as i64
138 // Rec.601 luma, integer: the same conversion OpenCV's COLOR_BGR2GRAY applies, so the
139 // baselines and this organ see the same pixels rather than differently-greyed ones.
140 gray[i] = ((r * 299 + g * 587 + b * 114) / 1000) as u8
141 i = i + 1
142 }
143 } else { return 0 }
144 }
145 wh[0] = w
146 wh[1] = h
147 return 1
148}
149
150func main(argc: i64, argv: *i64) -> i64 {
151 if argc < 3 {
152 hw("usage: nx_photogram_hbench <img1> <img2> [maxkp]\n" as *u8)
153 return 2
154 }
155 let p1v: i64 = argv[1]
156 let p2v: i64 = argv[2]
157 let p1: *u8 = p1v as *u8
158 let p2: *u8 = p2v as *u8
159 var maxkp: i64 = HB_DEFAULT_KP
160 if argc >= 4 {
161 let p3v: i64 = argv[3]
162 let p3: *u8 = p3v as *u8
163 let pos: *i64 = sys_mmap(64) as *i64
164 pos[0] = 0
165 let m: i64 = hb_next_int(p3, 12, pos)
166 if m > 0 { maxkp = m }
167 }
168 pf_init()
169 let g1: *u8 = sys_mmap(HB_MAXBYTES)
170 let g2: *u8 = sys_mmap(HB_MAXBYTES)
171 let wh1: *i64 = sys_mmap(64) as *i64
172 let wh2: *i64 = sys_mmap(64) as *i64
173 if hb_load_pnm(p1, g1, wh1) == 0 { hw("ERROR cannot load image 1\n" as *u8); return 3 }
174 if hb_load_pnm(p2, g2, wh2) == 0 { hw("ERROR cannot load image 2\n" as *u8); return 3 }
175 let w1: i64 = wh1[0]
176 let h1: i64 = wh1[1]
177 let w2: i64 = wh2[0]
178 let h2: i64 = wh2[1]
179
180 let kx1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
181 let ky1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
182 let kl1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
183 let kx2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
184 let ky2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
185 let kl2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
186 let d1: *i64 = sys_mmap(maxkp * PF_NWORD * 8 + 64) as *i64
187 let d2: *i64 = sys_mmap(maxkp * PF_NWORD * 8 + 64) as *i64
188 let a1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
189 let a2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
190 // pyramid detect+describe: the baselines (SIFT, ORB) are both multi-scale, so a single-scale run
191 // here would not be a like-for-like comparison
192 let n1: i64 = pf_pyramid(g1, w1, h1, maxkp, kx1, ky1, kl1, d1, a1)
193 let n2: i64 = pf_pyramid(g2, w2, h2, maxkp, kx2, ky2, kl2, d2, a2)
194
195 let ma: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
196 let mb: *i64 = sys_mmap(maxkp * 8 + 64) as *i64
197 let nm: i64 = pf_match(d1, n1, d2, n2, ma, mb, maxkp)
198
199 var i: i64 = 0
200 while i < nm {
201 let ia: i64 = ma[i]
202 let ib: i64 = mb[i]
203 hw("M " as *u8)
204 hn(kx1[ia])
205 hw(" " as *u8)
206 hn(ky1[ia])
207 hw(" " as *u8)
208 hn(kx2[ib])
209 hw(" " as *u8)
210 hn(ky2[ib])
211 hw("\n" as *u8)
212 i = i + 1
213 }
214 hw("SUMMARY kp1=" as *u8)
215 hn(n1)
216 hw(" kp2=" as *u8)
217 hn(n2)
218 hw(" matches=" as *u8)
219 hn(nm)
220 hw(" w1=" as *u8)
221 hn(w1)
222 hw(" h1=" as *u8)
223 hn(h1)
224 hw(" w2=" as *u8)
225 hn(w2)
226 hw(" h2=" as *u8)
227 hn(h2)
228 hw("\n" as *u8)
229 return 0
230}