mhe / pynrrd

Simple pure-python module for reading and writing nrrd files.
https://pynrrd.readthedocs.io/
MIT License
116 stars 51 forks source link

Error when writing nrrd data with a vector list header #162

Closed eleftherioszisis closed 2 weeks ago

eleftherioszisis commented 2 weeks ago

With the changes in #157 the following repro that was working before:

import nrrd
import numpy as np

header = { 'space directions': [None, np.array([10.,  0.], dtype=np.float32), np. array([ 0., 20.], dtype=np.float32)],
          'encoding': 'gzip'}
nrrd_data = np.array([[[11, 21]],
                      [[12, 22]],
                      [[13, 23]]])
nrrd.write('/tmp/a.nrrd', nrrd_data, header=header)

throws with the v1.1.1 release:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], line 9
      4 header = { 'space directions': [None, np.array([10.,  0.], dtype=np.float32), np. array([ 0., 20.], dtype=np.float32)],
      5           'encoding': 'gzip'}
      6 nrrd_data = np.array([[[11, 21]],
      7                       [[12, 22]],
      8                       [[13, 23]]])
----> 9 nrrd.write('/tmp/a.nrrd', nrrd_data, header=header)

File ~/.pyenv/versions/3.12-dev/envs/v312/lib/python3.12/site-packages/nrrd/writer.py:372, in write(file, data, header, detached_header, relative_data_path, custom_field_map, compression_level, index_order)
    369     detached_header = False
    371 with open(file, 'wb') as fh:
--> 372     _write_header(fh, header, custom_field_map)
    374     # If header & data in the same file is desired, write data in the file
    375     if not detached_header:

File ~/.pyenv/versions/3.12-dev/envs/v312/lib/python3.12/site-packages/nrrd/writer.py:177, in _write_header(file, header, custom_field_map)
    173 for x, (field, value) in enumerate(ordered_options.items()):
    174     # Get the field_type based on field and then get corresponding
    175     # value as a str using _format_field_value
    176     field_type = _get_field_type(field, custom_field_map)
--> 177     value_str = _format_field_value(value, field_type)
    179     # Custom fields are written as key/value pairs with a := instead of : delimiter
    180     if x >= custom_field_start_index:

File ~/.pyenv/versions/3.12-dev/envs/v312/lib/python3.12/site-packages/nrrd/writer.py:99, in _format_field_value(value, field_type)
     97     return format_matrix(value)
     98 elif field_type == 'double matrix':
---> 99     return format_optional_matrix(value)
    100 elif field_type == 'int vector list':
    101     return format_optional_vector_list(value)

File ~/.pyenv/versions/3.12-dev/envs/v312/lib/python3.12/site-packages/nrrd/formatters.py:141, in format_optional_matrix(x)
    119 """Format a (M,N) :class:`numpy.ndarray` of :class:`float` into a NRRD optional matrix string
    120
    121 Function converts a (M,N) :class:`numpy.ndarray` of :class:`float` into a string using the NRRD matrix format. For
   (...)
    138     String containing NRRD matrix
    139 """
    140 # Convert to float dtype to convert None to NaN
--> 141 x = np.asarray(x, dtype=float)
    143 return ' '.join([format_optional_vector(y) for y in x])

ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (3,) + inhomogeneous part.

Given that this is similar to how the vector list header is described in https://pynrrd.readthedocs.io/en/stable/background/datatypes.html#double-vector-list , this shouldn't fail. Isn't that right?

addisonElliott commented 2 weeks ago

If you do nrrd.SPACE_DIRECTIONS_TYPE = 'double vector list', it should work.

For your code to work as it is, you have to do [None, None], ... so that it's converted to NaN when massaging into a Numpy array. This is a np.asarray issue from what I recall.

See below snippet for what will work (I think).

header = { 'space directions': [[None, None], np.array([10.,  0.], dtype=np.float32), np. array([ 0., 20.], dtype=np.float32)],
          'encoding': 'gzip'}
eleftherioszisis commented 2 weeks ago

I can verify that the suggested snippet does indeed work with the default configuration. Thanks a lot for the quick reply!