NeurodataWithoutBorders / pynwb

A Python API for working with Neurodata stored in the NWB Format
https://pynwb.readthedocs.io
Other
178 stars 84 forks source link

[Bug]: float ROI positions lose precision due to uint32 conversion during export #1922

Closed RR-N closed 4 months ago

RR-N commented 5 months ago

What happened?

I have a float32 array describing 3D positions (x, y, z). I'm trying to save these to an existing NWB file as such:

NeuroPALTracks = ImageSegmentation(
    name='TrackedNeurons',
)

trackIDs = PlaneSegmentation(
    name='TrackedNeuronROIs',
    description='Neuron centers as tracked throughout GCaMP video. Formatted as (x, y, z, t)',
    imaging_plane=calc_imaging_vol,
)

for eachNeuron in range(len(wlid)):
    trackIDs.add_roi(voxel_mask=[[x[eachNeuron], y[eachNeuron], z[eachNeuron], t[eachNeuron]]])

label = []
for eachNeuron in range(len(wlid)):
    label.append(name[wlid[eachNeuron]])

trackIDs.add_column(
    name='TrackedNeuronIDs',
    description='Neuron ID labels for tracked neuron rois.',
    data=label,
    index=False,
)

NeuroPALTracks.add_plane_segmentation(trackIDs)
nwbfile.processing['CalciumActivity'].add(NeuroPALTracks)

nwbfile.set_modified()
with NWBHDF5IO(output_path, mode='w') as export_io:
    export_io.export(src_io=read_io, nwbfile=nwbfile)

When I then try to load the updated file, I find that all values have been converted into uint32 and thus floored to 0. I've tried explicitly converting values to native float first as such:

for eachNeuron in range(len(wlid)):
    trackIDs.add_roi(voxel_mask=[[float(x[eachNeuron]), float(y[eachNeuron]), float(z[eachNeuron]), float(t[eachNeuron])]])

But that yielded no results. I noticed that I seem to be able to write floats to the file is by passing its value directly, i.e.

for eachNeuron in range(len(wlid)):
    trackIDs.add_roi(voxel_mask=[[float(x[eachNeuron]), float(y[eachNeuron]), float(z[eachNeuron]), 0.053589]])

Will result in (0, 0, 0, 0.053589.) in the updated file. This made me think that maybe the bug stems from an issue referencing the array, but no matter what combination of the above I try, if I specify a variable, I run into this bug:

for eachNeuron in range(len(x)):
    a = float(str(x[eachNeuron]))
    b = float(y[eachNeuron])
    trackIDs.add_roi(voxel_mask=[[a, b, float(z[eachNeuron]), t[eachNeuron]]])

Yields (0, 0, 0, 0.). I've copied minimum reproducible code into a jupyter notebook and uploaded that alongside a copy of my NWB file (stripped of unpublished data) here.

Steps to Reproduce

https://github.com/RR-N/issue-demo/blob/main/conversion_bug_demo.ipynb

Traceback

No response

Operating System

Windows

Python Executable

Python

Python Version

3.10

Package Versions

2to3==1.0 absl-py==1.4.0 accessible-pygments==0.0.4 acvl-utils==0.2 aiobotocore==2.13.0 aiohttp==3.9.5 aioitertools==0.11.0 aiosignal==1.3.1 alabaster==0.7.13 alembic==1.10.3 altgraph==0.17.3 annotated-types==0.7.0 anyio==3.6.2 appdirs==1.4.4 argon2-cffi==21.3.0 argon2-cffi-bindings==21.2.0 arrow==1.2.3 asciitree==0.3.3 asgiref==3.6.0 astor==0.8.1 asttokens==2.2.1 astunparse==1.6.3 async-timeout==4.0.3 attrs==21.4.0 Babel==2.12.1 backcall==0.2.0 batchgenerators==0.25 bcrypt==3.2.2 beautifulsoup4==4.10.0 bidsschematools==0.7.2 bleach==6.0.0 blosc2==2.0.0 boto3==1.26.118 botocore==1.34.106 bqplot==0.12.40 cachetools==5.0.0 certifi==2021.10.8 cffi==1.15.1 chardet==3.0.4 charset-normalizer==2.0.12 ci-info==0.3.0 clearml==1.10.3 click==8.1.3 click-didyoumean==0.3.0 cloudpickle==2.2.1 cmaes==0.9.1 colorama==0.4.6 colorlog==6.7.0 comm==0.1.3 connected-components-3d==3.10.5 contextlib2==21.6.0 contourpy==1.0.7 cryptography==40.0.2 csbdeep==0.7.4 cx-Freeze==6.14.9 cx-Logging==3.1.0 cycler==0.11.0 Cython==3.0.2 dandi==0.62.2 dandischema==0.10.1 databricks-cli==0.17.6 dataclasses==0.6 debugpy==1.6.7 decorator==5.1.1 defusedxml==0.7.1 dicom2nifti==2.4.8 dicomweb-client==0.56.2 discord==1.7.3 discord-ui==5.1.6 discord.py==2.3.0 diskcache==5.6.1 distlib==0.3.6 dnspython==2.3.0 docker==6.0.1 docopt==0.6.2 docutils==0.18.1 dynamic-network-architectures==0.2 ecdsa==0.18.0 einops==0.6.1 email-validator==2.0.0.post2 entrypoints==0.4 et-xmlfile==1.1.0 etelemetry==0.3.0 executing==1.2.0 expiring-dict==1.1.0 expiringdict==1.2.1 fastapi==0.78.0 fasteners==0.18 fastjsonschema==2.16.3 filelock==3.7.1 fire==0.5.0 Flask==2.2.3 flatbuffers==23.3.3 fonttools==4.39.3 fqdn==1.5.1 frozenlist==1.3.3 fscacher==0.3.0 fsspec==2024.6.0 furl==2.1.3 future==0.18.3 gast==0.4.0 gdown==4.7.1 gevent==22.10.2 ghp-import==2.1.0 girder-client==3.1.14 gitdb==4.0.10 GitPython==3.1.31 google==3.0.0 google-api-core==2.7.1 google-api-python-client==2.42.0 google-auth==2.6.2 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 google-cloud-core==2.3.3 google-pasta==0.2.0 googleapis-common-protos==1.56.0 graphviz==0.20.1 greenlet==2.0.2 grpcio==1.54.0 gspread==5.3.0 h11==0.12.0 h5py==3.10.0 hdmf==3.13.0 highdicom==0.21.1 httpcore==0.15.0 httplib2==0.20.4 httpx==0.23.0 huggingface-hub==0.16.4 humanize==4.6.0 idna==3.3 imagecodecs==2023.3.16 imageio==2.27.0 imagesize==1.4.1 importlib-metadata==6.5.0 interleave==0.2.1 ipydatawidgets==4.3.5 ipykernel==6.22.0 ipython==8.12.0 ipython-genutils==0.2.0 ipyvolume==0.6.3 ipyvue==1.9.2 ipyvuetify==1.8.10 ipywebrtc==0.6.0 ipywidgets==8.0.6 isodate==0.6.1 isoduration==20.11.0 itk==5.3.0 itk-core==5.3.0 itk-filtering==5.3.0 itk-io==5.3.0 itk-numerics==5.3.0 itk-registration==5.3.0 itk-segmentation==5.3.0 itsdangerous==2.1.2 jaraco.classes==3.2.3 jedi==0.18.2 Jinja2==3.1.2 jmespath==1.0.1 joblib==1.2.0 json-tricks==3.16.1 jsonpointer==2.3 jsonschema==3.2.0 jupyter==1.0.0 jupyter-book==0.15.1 jupyter-cache==0.6.1 jupyter-console==6.6.3 jupyter-events==0.6.3 jupyter-http-over-ws==0.0.8 jupyter_client==8.2.0 jupyter_core==5.3.0 jupyter_server==2.5.0 jupyter_server_terminals==0.4.4 jupyterlab-pygments==0.2.2 jupyterlab-widgets==3.0.7 keras==2.10.0 Keras-Preprocessing==1.1.2 keyring==23.13.1 keyrings.alt==4.2.0 kiwisolver==1.4.4 latexcodec==2.0.1 Levenshtein==0.21.0 lib==4.0.0 libclang==16.0.0 libs==0.0.10 lief==0.13.0 lightning-utilities==0.8.0 linecache2==1.0.0 linkify-it-py==2.0.2 llvmlite==0.40.0 lmdb==1.4.1 Mako==1.2.4 Markdown==3.4.3 markdown-it-py==2.2.0 MarkupSafe==2.1.2 matplotlib==3.8.3 matplotlib-inline==0.1.6 mccabe==0.7.0 mdit-py-plugins==0.3.5 mdurl==0.1.2 MedPy==0.4.0 mistune==2.0.5 mlflow==2.3.0 monai==1.1.0 monai-deploy-app-sdk==0.5.0 monai-weekly==1.2.dev2316 monailabel==0.6.0 more-itertools==9.1.0 mpmath==1.3.0 msgpack==1.0.5 multidict==6.0.2 munch==2.5.0 munkres==1.1.4 myst-nb==0.17.2 myst-parser==0.18.1 mystmd==1.1.49 natsort==8.3.1 nbclassic==0.5.5 nbclient==0.7.3 nbconvert==7.6.0 nbdiff==1.0.3 nbformat==5.8.0 nd2reader==3.3.0 ndx-multichannel-volume==0.1.12 nest-asyncio==1.5.6 networkx==3.1 nibabel==5.1.0 ninja==1.10.2.3 nltk==3.8.1 nni==2.10 nnunetv2==2.1 notebook==6.5.4 notebook_shim==0.2.2 numba==0.57.0 numcodecs==0.11.0 numexpr==2.8.5 numpy==1.23.4 numpymaxflow==0.0.5 nwbinspector==0.4.37 oauth2client==4.1.3 oauthlib==3.2.0 opencv-python==4.5.5.64 opencv-python-headless==4.5.5.64 openpyxl==3.1.2 openslide-python==1.1.2 opt-einsum==3.3.0 optuna==3.1.1 orderedmultidict==1.0.1 packaging==23.1 pandas==1.4.2 pandocfilters==1.5.0 parameterized==0.9.0 parso==0.8.3 passlib==1.7.4 pathlib2==2.3.7.post1 pefile==2023.2.7 pickleshare==0.7.5 Pillow==9.4.0 pillow-jpls==1.2.0 PIMS==0.6.1 pims-nd2==1.1 pipreqs==0.4.13 pipreqsnb==0.2.4 platformdirs==3.2.0 plotext==5.2.8 plotly==5.15.0 pretrainedmodels==0.7.4 prettytable==3.7.0 prometheus-client==0.16.0 prompt-toolkit==3.0.38 protobuf==3.19.6 psutil==5.9.6 psycopg2==2.9.6 pure-eval==0.2.2 py-cpuinfo==9.0.0 py2exe==0.13.0.0 pyarrow==11.0.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pybtex==0.24.0 pybtex-docutils==1.0.2 pycodestyle==2.10.0 pycparser==2.21 pycryptodomex==3.17 pydantic==2.7.4 pydantic_core==2.18.4 pydata-sphinx-theme==0.13.3 pydicom==2.3.0 pydicom-seg==0.4.0 pyflakes==3.0.1 Pygments==2.15.1 pyinstaller==6.6.0 pyinstaller-hooks-contrib==2024.5 PyJWT==2.4.0 pymongo==4.4.1 pynetdicom==2.0.2 pynrrd==0.4.3 pynwb==2.6.0 pyout==0.7.3 pyparsing==3.0.7 PyQt5==5.15.9 pyqt5-plugins==5.15.9.2.3 PyQt5-Qt5==5.15.2 PyQt5-sip==12.13.0 pyqt5-tools==5.15.9.3.3 pyreadline3==3.4.1 pyreqs==0.1.1 pyrsistent==0.19.3 PySocks==1.7.1 python-dateutil==2.8.2 python-dotenv==0.20.0 python-gdcm==3.0.21 python-jose==3.3.0 python-json-logger==2.0.7 python-Levenshtein==0.21.0 python-multipart==0.0.5 PythonWebHDFS==0.2.3 pythreejs==2.4.2 pytorch-ignite==0.4.10 pytorch-lightning==1.9.5 pytz==2022.1 PyWavelets==1.4.1 pywin32==306 pywin32-ctypes==0.2.2 pywinpty==2.0.10 PyYAML==6.0 pyzmq==25.0.2 qgrid==1.3.1 qt5-applications==5.15.2.2.3 qt5-tools==5.15.2.1.3 qtconsole==5.4.4 QtPy==2.4.1 querystring-parser==1.2.4 rapidfuzz==3.0.0 regex==2023.3.23 requests==2.27.1 requests-oauthlib==1.3.1 requests-toolbelt==0.9.1 responses==0.23.1 retrying==1.3.4 rfc3339-validator==0.1.4 rfc3986==1.5.0 rfc3986-validator==0.1.1 rfc3987==1.3.8 rsa==4.8 ruamel.yaml==0.17.21 ruamel.yaml.clib==0.2.7 s3fs==2024.6.0 s3transfer==0.6.0 safetensors==0.3.2 schedule==1.1.0 schema==0.7.5 scikit-image==0.19.3 scikit-learn==1.2.1 scipy==1.10.0 seaborn==0.12.2 semantic-version==2.10.0 Send2Trash==1.8.0 setuptools-scm==7.1.0 sh==2.0.3 Shapely==1.8.2 SimpleITK==2.2.1 simplejson==3.19.1 six==1.16.0 slicerator==1.1.0 smmap==5.0.0 sniffio==1.3.0 snowballstemmer==2.2.0 sortedcontainers==2.4.0 soupsieve==2.3.1 Sphinx==5.0.2 sphinx-book-theme==1.0.1 sphinx-comments==0.0.3 sphinx-copybutton==0.5.2 sphinx-jupyterbook-latex==0.5.2 sphinx-multitoc-numbering==0.1.3 sphinx-thebe==0.2.1 sphinx-togglebutton==0.3.2 sphinx_design==0.3.0 sphinx_external_toc==0.3.1 sphinxcontrib-applehelp==1.0.4 sphinxcontrib-bibtex==2.5.0 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 SQLAlchemy==2.0.9 sqlparse==0.4.4 stack-data==0.6.2 stardist==0.8.5 starlette==0.19.1 strict-rfc3339==0.7 sympy==1.11.1 tables==3.8.0 tabulate==0.9.0 tdqm==0.0.1 tenacity==8.2.2 tensorboard==2.10.1 tensorboard-data-server==0.6.1 tensorboard-plugin-wit==1.8.1 tensorboardX==2.6 tensorflow==2.10.0 tensorflow-estimator==2.10.0 tensorflow-io-gcs-filesystem==0.31.0 tensortools==0.4 termcolor==2.2.0 terminado==0.17.1 threadpoolctl==3.1.0 tifffile==2023.4.12 timeloop==1.0.2 tinycss2==1.2.1 tokenizers==0.12.1 tomli==2.0.1 tomlkit==0.11.7 torch==2.0.0+cu118 torchaudio==2.0.1+cu118 torchmetrics==0.11.4 torchvision==0.15.1+cu118 tornado==6.3 tqdm==4.65.0 traceback2==1.4.0 traitlets==5.9.0 traittypes==0.2.1 transformers==4.31.0 typeguard==3.0.2 types-pkg-resources==0.1.3 types-PyYAML==6.0.12.9 typing_extensions==4.7.1 tzdata==2023.3 uc-micro-py==1.0.2 unittest2==1.1.0 uri-template==1.2.0 uritemplate==4.1.1 urllib3==1.26.9 uvicorn==0.17.6 virtualenv==20.21.0 waitress==2.1.2 watchdog==2.1.8 wcwidth==0.2.6 webcolors==1.13 webencodings==0.5.1 websocket-client==1.5.1 websockets==11.0.2 Werkzeug==2.2.3 widgetsnbextension==4.0.7 WMI==1.5.1 wordcloud==1.9.2 wrapt==1.15.0 xmltodict==0.13.0 yacs==0.1.8 yarg==0.1.9 yarl==1.9.4 zarr==2.14.2 zarr-checksum==0.4.0 zephir==1.0.4 zipp==3.15.0 zope.event==4.6 zope.interface==6.0

Code of Conduct

stephprince commented 5 months ago

Hi @RR-N, thanks for submitting this issue and for sharing a great minimal working example and notebook!

In the nwb schema, a voxel mask is specified to be a list of indices and weights for the ROI (x, y, z, weight) as (uint32, uint32, uint32, float32):

So pynwb is expecting that your (x, y, z) coordinates are describing your ROI in terms of indices of your image data and is automatically converting them to uint32 when writing the file.

# e.g. a 3 x 1 x 1 voxel region of weight '0.5'
 voxel_mask = [
 (0, 0, 0, 0.5),
 (1, 0, 0, 0.5),
 (2, 0, 0, 0.5)
]
trackIDs.add_roi(voxel_mask=voxel_mask)

If you convert the float32 coordinates you have into the corresponding voxel indices of your images you should be able to add a voxel_mask with those values.

stephprince commented 4 months ago

I think this has been addressed so will close this issue for now. Please reopen if needed!