tests/testthat/test-onnx-chain-fpn.R

# Chain tests: Feature Pyramid Network (FPN) patterns
# Conv → Resize → Concat → Conv
#
# Covers: Resize/Upsample, Concat

run_onnx <- function(path, inputs, device = "cpu") {
  m <- onnx_load(path, device = device)
  res <- onnx_run(m, inputs)
  res[[1]]
}

# ── Minimal (2 ops): Conv → Resize ──────────────────────────

test_that("chain fpn: Conv→Resize (minimal)", {
  # Input: [1, 1, 2, 2], Conv 1→1, 1x1 → [1, 1, 2, 2], Resize 2x → [1, 1, 4, 4]
  inp <- .onnx_value_info("X", 1L, c(1L, 1L, 2L, 2L))
  outp <- .onnx_value_info("Y", 1L, c(1L, 1L, 4L, 4L))

  w_raw <- .float_bytes(1.0)
  w_t  <- .onnx_tensor("W", c(1L, 1L, 1L, 1L), 1L, w_raw)
  w_vi <- .onnx_value_info("W", 1L, c(1L, 1L, 1L, 1L))

  # Resize scales: [1.0, 1.0, 2.0, 2.0] (N, C, H, W)
  scales_data <- c(1.0, 1.0, 2.0, 2.0)
  scales_raw <- unlist(lapply(scales_data, .float_bytes))
  scales_t  <- .onnx_tensor("scales", c(4L), 1L, scales_raw)
  scales_vi <- .onnx_value_info("scales", 1L, c(4L))

  # Empty roi tensor (required by Resize but unused for "nearest")
  roi_t  <- .onnx_tensor("roi", c(0L), 1L, raw(0))
  roi_vi <- .onnx_value_info("roi", 1L, integer(0))

  conv_node   <- .onnx_node("Conv", c("X", "W"), "conv_out",
                             attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
  resize_node <- .onnx_node("Resize", c("conv_out", "roi", "scales"), "Y",
                             attrs = list())

  graph <- .onnx_graph("test",
                        list(conv_node, resize_node),
                        list(inp, w_vi, roi_vi, scales_vi),
                        list(outp),
                        list(w_t, roi_t, scales_t))
  path <- tempfile(fileext = ".onnx")
  writeBin(.onnx_model(graph), path)

  x <- c(1, 2, 3, 4)
  result <- run_onnx(path, list(X = x))
  r <- as.numeric(result)
  expect_equal(length(r), 16)
  # Nearest upsample 2x: each pixel duplicated to 2x2 block
  expect_true(all(is.finite(r)))
})

# ── Real (4 ops): Conv → Resize → Concat → Conv ─────────────

test_that("chain fpn: Conv→Resize→Concat→Conv (FPN merge)", {
  # Two branches:
  # Branch A: X[1,1,2,2] → Conv 1→2, 1x1 → [1,2,2,2] → Resize 2x → [1,2,4,4]
  # Branch B: X[1,1,2,2] → Conv 1→2, 1x1 → [1,2,2,2] → Resize 2x → [1,2,4,4]
  # Concat(A, B, axis=1) → [1,4,4,4]
  # Conv 4→1, 1x1 → [1,1,4,4]

  inp <- .onnx_value_info("X", 1L, c(1L, 1L, 2L, 2L))
  outp <- .onnx_value_info("Y", 1L, c(1L, 1L, 4L, 4L))

  # Conv A: [2, 1, 1, 1]
  wa_data <- c(1.0, 0.5)
  wa_raw <- unlist(lapply(wa_data, .float_bytes))
  wa_t  <- .onnx_tensor("WA", c(2L, 1L, 1L, 1L), 1L, wa_raw)
  wa_vi <- .onnx_value_info("WA", 1L, c(2L, 1L, 1L, 1L))

  # Conv B: [2, 1, 1, 1]
  wb_data <- c(0.5, 1.0)
  wb_raw <- unlist(lapply(wb_data, .float_bytes))
  wb_t  <- .onnx_tensor("WB", c(2L, 1L, 1L, 1L), 1L, wb_raw)
  wb_vi <- .onnx_value_info("WB", 1L, c(2L, 1L, 1L, 1L))

  # Conv merge: [1, 4, 1, 1]
  wm_data <- rep(0.25, 4)
  wm_raw <- unlist(lapply(wm_data, .float_bytes))
  wm_t  <- .onnx_tensor("WM", c(1L, 4L, 1L, 1L), 1L, wm_raw)
  wm_vi <- .onnx_value_info("WM", 1L, c(1L, 4L, 1L, 1L))

  # Scales
  scales_data <- c(1.0, 1.0, 2.0, 2.0)
  scales_raw <- unlist(lapply(scales_data, .float_bytes))
  scales_t  <- .onnx_tensor("scales", c(4L), 1L, scales_raw)
  scales_vi <- .onnx_value_info("scales", 1L, c(4L))
  roi_t  <- .onnx_tensor("roi", c(0L), 1L, raw(0))
  roi_vi <- .onnx_value_info("roi", 1L, integer(0))

  conv_a  <- .onnx_node("Conv", c("X", "WA"), "ca",
                         attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
  conv_b  <- .onnx_node("Conv", c("X", "WB"), "cb",
                         attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))
  res_a   <- .onnx_node("Resize", c("ca", "roi", "scales"), "ra")
  res_b   <- .onnx_node("Resize", c("cb", "roi", "scales"), "rb")
  cat_node <- .onnx_node("Concat", c("ra", "rb"), "cat",
                          attrs = list(.onnx_attr_int("axis", 1L)))
  conv_m  <- .onnx_node("Conv", c("cat", "WM"), "Y",
                         attrs = list(.onnx_attr_ints("kernel_shape", c(1L, 1L))))

  graph <- .onnx_graph("test",
                        list(conv_a, conv_b, res_a, res_b, cat_node, conv_m),
                        list(inp, wa_vi, wb_vi, wm_vi, roi_vi, scales_vi),
                        list(outp),
                        list(wa_t, wb_t, wm_t, roi_t, scales_t))
  path <- tempfile(fileext = ".onnx")
  writeBin(.onnx_model(graph), path)

  x <- c(1, 2, 3, 4)
  result <- run_onnx(path, list(X = x))
  r <- as.numeric(result)
  expect_equal(length(r), 16)
  expect_true(all(is.finite(r)))
})

# ── Boundary: 1x1 spatial resize ─────────────────────────────

test_that("chain fpn: 1x1 spatial resize to 2x2 (boundary)", {
  inp <- .onnx_value_info("X", 1L, c(1L, 1L, 1L, 1L))
  outp <- .onnx_value_info("Y", 1L, c(1L, 1L, 2L, 2L))

  scales_data <- c(1.0, 1.0, 2.0, 2.0)
  scales_raw <- unlist(lapply(scales_data, .float_bytes))
  scales_t  <- .onnx_tensor("scales", c(4L), 1L, scales_raw)
  scales_vi <- .onnx_value_info("scales", 1L, c(4L))
  roi_t  <- .onnx_tensor("roi", c(0L), 1L, raw(0))
  roi_vi <- .onnx_value_info("roi", 1L, integer(0))

  resize_node <- .onnx_node("Resize", c("X", "roi", "scales"), "Y")

  graph <- .onnx_graph("test", list(resize_node),
                        list(inp, roi_vi, scales_vi), list(outp),
                        list(roi_t, scales_t))
  path <- tempfile(fileext = ".onnx")
  writeBin(.onnx_model(graph), path)

  result <- run_onnx(path, list(X = c(5.0)))
  r <- as.numeric(result)
  expect_equal(length(r), 4)
  # Nearest: all 4 pixels = 5.0
  expect_equal(r, rep(5.0, 4), tolerance = 1e-4)
})

Try the ggmlR package in your browser

Any scripts or data that you put into this service are public.

ggmlR documentation built on July 14, 2026, 1:08 a.m.