Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mincGetWorldVoxel is really mincGetWorldVoxelNeighbour in disguise, but only sometimes #316

Open
yohanyee opened this issue Aug 9, 2023 · 1 comment

Comments

@yohanyee
Copy link
Contributor

yohanyee commented Aug 9, 2023

Issue

Basically, mincGetWorldVoxel() sometimes returns the value of a neighbouring voxel, as compared to what Display renders. Given world coordinates that lie between image sampling points, the output of mincGetWorldVoxel() is also sometimes inconsistent with the output of mincConvertWorldToVoxel() %>% mincGetVoxel(...).

It appears that mincGetWorldVoxel() follows the convention where voxels fall between sampling points. For example, consider an image that contains three voxels, starting at the origin and with 1 mm separations, with values: 21, 10, and 28. mincGetWorldVoxel() sees this as:

# World coordinates : ... 0.8---0.9---1.0---1.1---1.2---1.3---1.4---1.5---1.6---1.7---1.8---1.9---2.0---2.1 ...
# Sampling          :                  |                                                           |
# Voxel             : ...  0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2  ...
# Value             : ...  21 21 21 21 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 28 28 28 ...

On the other hand, mincConvertWorldToVoxel() and Display follow the convention where voxels are centered at sampling points:

# World coordinates : ... 0.8---0.9---1.0---1.1---1.2---1.3---1.4---1.5---1.6---1.7---1.8---1.9---2.0---2.1 ...
# Sampling          :                  |                                                           |
# Voxel             : ...  1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2  ...
# Value             : ...  10 10 10 10 10 10 10 10 10 10 10 10 10 10 28 28 28 28 28 28 28 28 28 28 28 28 28 ...

Test data

Below is an R script to reproduce the issue, and a Python script to generate a test minc file for this:

Generate test minc file (python)

#!/usr/bin/env python3

from pyminc.volumes.factory import volumeFromDescription
import numpy as np

img = np.array([[[21, 10, 28]]], dtype=np.int16)

vol = volumeFromDescription("test_file.mnc", dimnames=["zspace", "yspace", "xspace"], sizes=[1,1,3], starts=[0,0,0], steps=[1,1,1])
vol.data = img
vol.writeFile()
vol.closeVolume()

R code that demonstrates the issue

# R version: 4.1.3
# RMINC version: 1.5.3.0

library(RMINC)

# Test file
# This image contains three voxels along the xspace dimension, starting at the origin and with 1 mm separations
# Values are: 21, 10, 28
test_file <- "test_file.mnc"

# Confirm shape
# z, y, x
minc.dimensions.sizes(test_file)

# Confirm intensities given voxel coordinates
# z, y, x; starts at 0
as.integer(round(mincGetVoxel(test_file, 0, 0, 0)))==21
as.integer(round(mincGetVoxel(test_file, 0, 0, 1)))==10
as.integer(round(mincGetVoxel(test_file, 0, 0, 2)))==28

# Confirm intensities given world coordinates
# x, y, z; starts at 0
as.integer(round(mincGetWorldVoxel(test_file, 0, 0, 0)))==21
as.integer(round(mincGetWorldVoxel(test_file, 1, 0, 0)))==10
as.integer(round(mincGetWorldVoxel(test_file, 2, 0, 0)))==28

# mincGetWorldVoxel follows the convention where voxels fall between sampling points
# World coordinates : ... 0.8---0.9---1.0---1.1---1.2---1.3---1.4---1.5---1.6---1.7---1.8---1.9---2.0---2.1 ...
# Sampling          :                  |                                                           |
# Voxel             : ...  0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2  ...
# Value             : ...  21 21 21 21 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 28 28 28 ...
as.integer(round(mincGetWorldVoxel(test_file, 1, 0, 0)))   # Returns: 10
as.integer(round(mincGetWorldVoxel(test_file, 0.9, 0, 0))) # Returns: 21
as.integer(round(mincGetWorldVoxel(test_file, 1.9, 0, 0))) # Returns: 10
as.integer(round(mincGetWorldVoxel(test_file, 1.6, 0, 0))) # Returns: 10
as.integer(round(mincGetWorldVoxel(test_file, 1.5, 0, 0))) # Returns: 10

# But this is inconsistent with the conversion to voxel coordinates, which centers voxels at sampling points
# World coordinates : ... 0.8---0.9---1.0---1.1---1.2---1.3---1.4---1.5---1.6---1.7---1.8---1.9---2.0---2.1 ...
# Sampling          :                  |                                                           |
# Voxel             : ...  1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2  ...
# Value             : ...  10 10 10 10 10 10 10 10 10 10 10 10 10 10 28 28 28 28 28 28 28 28 28 28 28 28 28 ...
attr(mincGetWorldVoxel(test_file, 1, 0, 0), "voxelCoord")
attr(mincGetWorldVoxel(test_file, 0.9, 0, 0), "voxelCoord")
attr(mincGetWorldVoxel(test_file, 1.9, 0, 0), "voxelCoord")
attr(mincGetWorldVoxel(test_file, 1.6, 0, 0), "voxelCoord")
attr(mincGetWorldVoxel(test_file, 1.5, 0, 0), "voxelCoord")

# Using mincGetVoxel on voxel coordinates taken from the mincGetWorldVoxel attributes (which in turn relies on
# mincConvertWorldToVoxel) returns different values as compared to a direct mincGetWorldVoxel call, due to this
# mismatch in conventions
mincConvertWorldToVoxel(test_file, 1, 0, 0)   # Voxel coord: 0 0 1
mincConvertWorldToVoxel(test_file, 0.9, 0, 0) # Voxel coord: 0 0 1
mincConvertWorldToVoxel(test_file, 1.9, 0, 0) # Voxel coord: 0 0 2
mincConvertWorldToVoxel(test_file, 1.6, 0, 0) # Voxel coord: 0 0 2
mincConvertWorldToVoxel(test_file, 1.5, 0, 0) # Voxel coord: 0 0 2
mincGetVoxel(filenames = test_file, 0, 0, 1) # Returns: 10
mincGetVoxel(filenames = test_file, 0, 0, 1) # Returns: 10
mincGetVoxel(filenames = test_file, 0, 0, 2) # Returns: 28
mincGetVoxel(filenames = test_file, 0, 0, 2) # Returns: 28
mincGetVoxel(filenames = test_file, 0, 0, 2) # Returns: 28

# Note: Display renders the voxels based on this second convention that centers voxels at sampling points
@gdevenyi
Copy link
Contributor

The official MINC standard is coordinates of a voxel are the center, see slide 14
https://www.bic.mni.mcgill.ca/software/workshop2003/02-2003MINC-meeting_js.pdf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants