| 202 | // --------------------------------------------------------------------------- |
| 203 | |
| 204 | Image::Pointer ImageFromNumpy(py::array array, |
| 205 | std::optional<std::array<double, 3>> spacing, |
| 206 | std::optional<std::array<double, 3>> origin, |
| 207 | std::optional<py::array_t<double>> direction, |
| 208 | bool copy) |
| 209 | { |
| 210 | if (!copy) |
| 211 | throw py::value_error("from_numpy(copy=False) is not yet supported"); |
| 212 | |
| 213 | // Make C-contiguous (forcecast to ensure compatible dtype passes through) |
| 214 | auto arr = py::array::ensure(array, py::array::c_style); |
| 215 | |
| 216 | if (!arr) |
| 217 | throw py::type_error("from_numpy: input is not array-like"); |
| 218 | |
| 219 | const auto ndim = arr.ndim(); |
| 220 | |
| 221 | if (ndim < 2 || ndim > 4) |
| 222 | throw py::value_error("from_numpy: only 2D, 3D, and 4D arrays are supported"); |
| 223 | |
| 224 | // numpy is C-order with the slowest dimension first; MITK expects |
| 225 | // dimensions in (x, y, z[, t]) order with x as the fastest dimension. |
| 226 | // Reverse the numpy shape to obtain the MITK dimensions vector. |
| 227 | std::vector<unsigned int> dims(ndim); |
| 228 | for (py::ssize_t i = 0; i < ndim; ++i) |
| 229 | dims[i] = static_cast<unsigned int>(arr.shape(ndim - 1 - i)); |
| 230 | |
| 231 | auto pixelType = MakePixelType(py::object(arr.dtype())); |
| 232 | |
| 233 | auto img = Image::New(); |
| 234 | img->Initialize(pixelType, static_cast<unsigned int>(ndim), dims.data()); |
| 235 | |
| 236 | // Copy the buffer one volume at a time. For 2D/3D arrays this is a |
| 237 | // single SetVolume call; for 4D the slowest numpy dim becomes time. |
| 238 | if (ndim < 4) |
| 239 | { |
| 240 | img->SetVolume(arr.data()); |
| 241 | } |
| 242 | else |
| 243 | { |
| 244 | const auto bytesPerVolume = static_cast<size_t>(arr.nbytes()) / dims[3]; |
| 245 | const auto* base = static_cast<const unsigned char*>(arr.data()); |
| 246 | for (unsigned int t = 0; t < dims[3]; ++t) |
| 247 | img->SetVolume(base + static_cast<size_t>(t) * bytesPerVolume, static_cast<int>(t)); |
| 248 | } |
| 249 | |
| 250 | // Apply geometry overrides to every time step so a 4D image is not left |
| 251 | // with inconsistent per-time-step geometries. |
| 252 | auto* tg = img->GetTimeGeometry(); |
| 253 | const auto numSteps = tg->CountTimeSteps(); |
| 254 | |
| 255 | for (mitk::TimeStepType t = 0; t < numSteps; ++t) |
| 256 | { |
| 257 | if (spacing.has_value()) |
| 258 | SetSpacing(tg, *spacing, t); |
| 259 | |
| 260 | if (origin.has_value()) |
| 261 | SetOrigin(tg, *origin, t); |
no test coverage detected