Move SSD detector cell mapping to typed Kotlin

ober

17ef88451537afe65487b65cbf49259036e2e63f

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 2a394c9..06d30f7 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2284,6 +2284,7 @@
     (typed-kotlin-file "com/sfb/ssdreview/VisionArrays.kt"
       (typed-library (com sfb ssdreview)
         (export grayAt edgeAt edgeMask sideCoverage darkSideCoverage contourCell toneCell
+                detectorComponentCells detectorToneCells detectorContourCells
                 shrinkMaskBounds)
         (type Int32)
         (type IntArray)
@@ -2721,7 +2722,41 @@
                                   (float32 (Component-y1 component))
                                   (float32 w)
                                   (float32 h)
-                                  "android-contour")))))))))))))))
+                                  "android-contour")))))))))))))
+        (def (detectorComponentCells (components : (List Component))
+                                     (detector : String)) : (MutableList SsdCell)
+          (for/fold ((out (mutable-list-empty SsdCell)))
+                    ((i (in-range (int32 0) (list-size components))))
+            (let ((cell (componentCell (list-ref components i) detector)))
+              (if (nullable-null? cell)
+                  out
+                  (begin
+                    (mutable-list-add! out (nullable-get cell))
+                    out)))))
+        (def (detectorToneCells (components : (List Component))
+                                (gray : IntArray)
+                                (width : Int32)
+                                (height : Int32)) : (MutableList SsdCell)
+          (for/fold ((out (mutable-list-empty SsdCell)))
+                    ((i (in-range (int32 0) (list-size components))))
+            (let ((cell (toneCell (list-ref components i) gray width height)))
+              (if (nullable-null? cell)
+                  out
+                  (begin
+                    (mutable-list-add! out (nullable-get cell))
+                    out)))))
+        (def (detectorContourCells (components : (List Component))
+                                   (edge : BooleanArray)
+                                   (width : Int32)
+                                   (height : Int32)) : (MutableList SsdCell)
+          (for/fold ((out (mutable-list-empty SsdCell)))
+                    ((i (in-range (int32 0) (list-size components))))
+            (let ((cell (contourCell (list-ref components i) edge width height)))
+              (if (nullable-null? cell)
+                  out
+                  (begin
+                    (mutable-list-add! out (nullable-get cell))
+                    out)))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/SsdVisionJson.kt"
       (kotlin-imports (org json JSONArray) (org json JSONObject))
@@ -4930,14 +4965,21 @@
        "        val gray = grayscale(pixels)"
        "        val cells = mutableListOf<SsdCell>()"
        ""
-       "        cells += detectMask(width, height, pixels, \"android-color\") { index ->"
-       "            isColorMask(pixels[index])"
-       "        }.filter { componentCell(it, \"android-color\") != null }"
-       "            .mapNotNull { componentCell(it, \"android-color\") }"
+       "        cells += detectorComponentCells("
+       "            detectMask(width, height, pixels, \"android-color\") { index ->"
+       "                isColorMask(pixels[index])"
+       "            },"
+       "            \"android-color\""
+       "        )"
        ""
-       "        cells += detectMask(width, height, pixels, \"android-tone\") { index ->"
-       "            gray[index] in 70..245"
-       "        }.mapNotNull { toneCell(it, gray, width, height) }"
+       "        cells += detectorToneCells("
+       "            detectMask(width, height, pixels, \"android-tone\") { index ->"
+       "                gray[index] in 70..245"
+       "            },"
+       "            gray,"
+       "            width,"
+       "            height"
+       "        )"
        ""
        "        var merged = mergeDuplicateCells(cells)"
        "        if (merged.size < 50) {"
@@ -5026,8 +5068,12 @@
        ""
        "    private fun contourCells(width: Int, height: Int, gray: IntArray): List<SsdCell> {"
        "        val edge = edgeMask(gray, width, height)"
-       "        return detectMask(width, height, IntArray(width * height), \"android-contour\") { edge[it] }"
-       "            .mapNotNull { contourCell(it, edge, width, height) }"
+       "        return detectorContourCells("
+       "            detectMask(width, height, IntArray(width * height), \"android-contour\") { edge[it] },"
+       "            edge,"
+       "            width,"
+       "            height"
+       "        )"
        "    }"
        ""
        "    private fun mergeDuplicateCells(cells: List<SsdCell>): MutableList<SsdCell> ="