1use super::*;
2use std::fmt::Write;
3
4pub fn fit_similarity_2d(points: &[[f32; 2]], template: &[[f32; 2]]) -> Result<[[f32; 3]; 2]> {
8 if points.len() < 2
9 || points.len() != template.len()
10 || !points
11 .iter()
12 .chain(template)
13 .flatten()
14 .all(|v| v.is_finite())
15 {
16 return Err(Error::Message(
17 "similarity needs matching finite point sets".into(),
18 ));
19 }
20 let mean = |points: &[[f32; 2]]| {
21 [
22 points.iter().map(|p| p[0]).sum::<f32>() / points.len() as f32,
23 points.iter().map(|p| p[1]).sum::<f32>() / points.len() as f32,
24 ]
25 };
26 let p = mean(points);
27 let q = mean(template);
28 let (mut den, mut a, mut b) = (0., 0., 0.);
29 for (s, d) in points.iter().zip(template) {
30 let (x, y, u, v) = (s[0] - p[0], s[1] - p[1], d[0] - q[0], d[1] - q[1]);
31 den += x * x + y * y;
32 a += x * u + y * v;
33 b += x * v - y * u;
34 }
35 a /= den;
36 b /= den;
37 let result = [
38 [a, -b, q[0] - a * p[0] + b * p[1]],
39 [b, a, q[1] - b * p[0] - a * p[1]],
40 ];
41 invert_affine(result)?;
42 Ok(result)
43}
44
45impl ImageOps {
46 pub fn prepare_similarity_2d(
54 &self,
55 points: &TensorDesc,
56 template: &[[f32; 2]],
57 ) -> Result<Arc<PreparedModel>> {
58 let key = (
59 points.clone(),
60 template.iter().flatten().map(|v| v.to_bits()).collect(),
61 );
62 self.similarity_plans.get_or_prepare(key, || {
63 self.similarity_2d_fragment(points, template)?.prepare(3)
64 })
65 }
66
67 pub fn similarity_2d_fragment(
69 &self,
70 points: &TensorDesc,
71 template: &[[f32; 2]],
72 ) -> Result<ModelFragment> {
73 let shape = points.shape();
74 if points.dtype() != DType::F32
75 || points.layout() != Layout::General
76 || shape.len() != 3
77 || shape[2] != 2
78 || shape[0] == 0
79 || !(2..=64).contains(&template.len())
80 || shape[1] != template.len()
81 || points.bytes() / 4 > i32::MAX as usize
82 || !template.iter().flatten().all(|v| v.is_finite())
83 {
84 return Err(Error::Message(
85 "similarity needs F32 [batch,points,2] and a finite template".into(),
86 ));
87 }
88 let batch = shape[0];
89 let count = template.len();
90 let elements = points.bytes() / 4;
91 let matrices = TensorDesc::new(DType::F32, vec![batch, 2, 3])?;
92 let status = TensorDesc::new(DType::I32, vec![batch])?;
93 if matrices.elements() > i32::MAX as usize {
94 return Err(Error::Message("similarity output too large".into()));
95 }
96 let q = [0, 1].map(|axis| template.iter().map(|p| p[axis]).sum::<f32>() / count as f32);
97 if !q.iter().all(|v| v.is_finite()) {
98 return Err(Error::Message("template mean overflow".into()));
99 }
100 let mut body = String::new();
101 for (i, point) in template.iter().enumerate() {
102 for (axis, letter) in [(0, "x"), (1, "y")] {
103 let offset = i * points.strides()[1] + axis * points.strides()[2];
104 writeln!(
105 body,
106 r#"
107 %off{letter}{i} = index.constant {offset} : index
108 %raw{letter}{i} = index.add %inputbase, %off{letter}{i} : index
109 %at{letter}{i} = index.assume %raw{letter}{i} [range(%raw{letter}{i}, 0, {last})] : index
110 %{letter}{i} = view.load %src[%at{letter}{i}] : view<{elements}xf32> -> f32
111 %abs{letter}{i} = scalar.absf %{letter}{i} : f32
112 %finite{letter}{i} = scalar.cmpf ole, %abs{letter}{i}, %max : f32
113 %sum{letter}{next} = scalar.addf %sum{letter}{i}, %{letter}{i} : f32
114"#,
115 last = elements - 1,
116 next = i + 1
117 )
118 .unwrap();
119 }
120 writeln!(body, " %xyfinite{i} = scalar.andi %finitex{i}, %finitey{i} : i1\n %finite{next} = scalar.andi %finite{i}, %xyfinite{i} : i1",next=i+1).unwrap();
121 for (axis, letter) in [(0, "u"), (1, "v")] {
122 writeln!(
123 body,
124 " %{letter}{i} = scalar.constant {:.9e} : f32",
125 point[axis] - q[axis]
126 )
127 .unwrap();
128 }
129 }
130 writeln!(body," %px = scalar.divf %sumx{count}, %n : f32\n %py = scalar.divf %sumy{count}, %n : f32").unwrap();
131 for i in 0..count {
132 writeln!(
133 body,
134 r#"
135 %sx{i} = scalar.subf %x{i}, %px : f32
136 %sy{i} = scalar.subf %y{i}, %py : f32
137 %xx{i} = scalar.mulf %sx{i}, %sx{i} : f32
138 %yy{i} = scalar.mulf %sy{i}, %sy{i} : f32
139 %xy{i} = scalar.addf %xx{i}, %yy{i} : f32
140 %den{next} = scalar.addf %den{i}, %xy{i} : f32
141 %xu{i} = scalar.mulf %sx{i}, %u{i} : f32
142 %yv{i} = scalar.mulf %sy{i}, %v{i} : f32
143 %dot{i} = scalar.addf %xu{i}, %yv{i} : f32
144 %dot{next}sum = scalar.addf %dot{i}sum, %dot{i} : f32
145 %xv{i} = scalar.mulf %sx{i}, %v{i} : f32
146 %yu{i} = scalar.mulf %sy{i}, %u{i} : f32
147 %cross{i} = scalar.subf %xv{i}, %yu{i} : f32
148 %cross{next}sum = scalar.addf %cross{i}sum, %cross{i} : f32
149"#,
150 next = i + 1
151 )
152 .unwrap();
153 }
154 let mut source = include_str!("similarity_2d.loom").replace("@BODY@", &body);
155 for (name, value) in [
156 ("BATCH", batch),
157 ("BATCH_LAST", batch - 1),
158 ("GRID", batch.div_ceil(64)),
159 ("INPUT", elements),
160 ("STRIDE", points.strides()[0]),
161 ("COUNT", count),
162 ("OUTPUT", batch * 6),
163 ("OUTPUT_LAST", batch * 6 - 1),
164 ] {
165 source = source.replace(&format!("@{name}@"), &value.to_string());
166 }
167 source = source
168 .replace("@QX@", &format!("{:.9e}", q[0]))
169 .replace("@QY@", &format!("{:.9e}", q[1]));
170 let mut model = ModelSession::in_context(&self.context)?;
171 let input = model.allocate(points.bytes())?;
172 let forward = model.allocate(matrices.bytes())?;
173 let inverse = model.allocate(matrices.bytes())?;
174 let flags = model.allocate_shared(status.bytes())?;
175 let kernel =
176 unsafe { model.compile(&[(&source, Specialization::new("similarity_2d"))])? }[0];
177 unsafe {
178 model.freeze(&self.context)?.fragment(
179 &[Command::Dispatch(Dispatch::indices(
180 kernel,
181 [0],
182 [batch.div_ceil(64) as u32, 1, 1],
183 vec![
184 input.read(),
185 forward.write(),
186 inverse.write(),
187 flags.write(),
188 ],
189 ))],
190 &[(input, points.clone())],
191 &[
192 (forward, matrices.clone()),
193 (inverse, matrices),
194 (flags, status),
195 ],
196 )
197 }
198 }
199
200 pub fn similarity_2d(&self, points: &DeviceTensor, template: &[[f32; 2]]) -> Result<Inference> {
202 self.context.validate(points)?;
203 self.prepare_similarity_2d(points.desc(), template)?
204 .submit(std::slice::from_ref(points))
205 }
206}