BUNPC / pysnirf2

Python package for reading, writing and validating Shared Near Infrared Spectroscopy Format (SNIRF) files
GNU General Public License v3.0
16 stars 12 forks source link

snirf files not readable by Homer3 and MNE #35

Closed Carlafrenzel closed 2 years ago

Carlafrenzel commented 2 years ago

Hi, I am trying to create a snirf file from fNIRS data we have collected. I then want to use the created snirf file with Homer3 and/or MNE. In case of Homer3, the created snirf files are validated correctly but once loaded in Homer show that the probe group is empty. If I create an empty snirf file and only add the probe group data, it is working. I am a bit confused now as I have absolutely no idea why or how this is possible? I can also not append the probe group manually. snirf.nirs[0].stim.appendGroup() works but snirf.nirs[0].probe.appendGroup() shows: AttributeError: 'Probe' object has no attribute 'appendGroup'

As for MNE, none of the snirf files are even readable as all of them result in an error: KeyError: "Unable to open object (object 'nirs' doesn't exist)"

All the files are validated with validateSnirf and show no error.

sstucker commented 2 years ago

Hi,

I am not particularly familiar with MNE's SNIRF parser, but hopefully I can help you create a SNIRF file.

Just to clarify, you have data and want to create a SNIRF file from scratch using pysnirf2?

snirf.nirs[0].probe.appendGroup() shows: AttributeError: 'Probe' object has no attribute 'appendGroup'

This is expected, as Probe is not an indexed Group, it is just a Group. It is required, so creating a new nirs element will create one automatically

You can add data to the probe with assignment ie

snirf.nirs[0].probe.sourcePos2D = <your array>
sstucker commented 2 years ago

Feel free to share the code you are using to create the SNIRF file

Carlafrenzel commented 2 years ago

Hi,

Thanks so much for the reply. Yes, we collected data with LSL and have a bunch of xdf files. The idea is now to extract the relevant information and save it as a snirf file.

snirf.nirs[0].probe.sourcePos2D =

This is exactly what I did the first time. And it seems to be alright. The problem appears when I add the remaining data and then reload the snirf file after saving. In that case the probe data is missing again? Thats why I tried appending the probe group.

The code looks like this:

snirf_file = Snirf() snirf_file.nirs.appendGroup()

now the probe data I used another snirf file for testing if I can actually create a snirf file.

snirf_file.nirs[0].probe.coordinateSystem = None snirf_file.nirs[0].probe.coordinateSystemDescription = None snirf_file.nirs[0].probe.correlationTimeDelayWidths = None snirf_file.nirs[0].probe.correlationTimeDelays = None snirf_file.nirs[0].probe.detectorLabels = None snirf_file.nirs[0].probe.detectorPos2D = snirf.nirs[0].probe.detectorPos2D snirf_file.nirs[0].probe.detectorPos3D = snirf.nirs[0].probe.detectorPos3D snirf_file.nirs[0].probe.sourcePos2D = snirf.nirs[0].probe.sourcePos2D snirf_file.nirs[0].probe.sourcePos3D = snirf.nirs[0].probe.sourcePos3D snirf_file.nirs[0].probe.wavelengths = snirf.nirs[0].probe.wavelengths

All Pos2D and Pos3D are numpy.ndarray, so I think the datatype is alright.

So up until this point everything is great. I can save it and re load the file and the probe data is there. As soon as I add other data the probe group shows no data if I load the file in Matlab/ Homer3. So I was wondering if I made a mistake somewhere.

I also attached the full code with the output, in case that makes a difference

create_snirf-2.md

sstucker commented 2 years ago

But you see the second data element when loading the file with pysnirf?

Your code looks great, aside from the formatVersion actually being 1.1 :) You are also not using measurementList which is required.

I don't see how this could be your issue based on the code you sent, but note that Homer3 has uncertain support for SNIRF files with multiple data elements. That may be the issue, Homer3 expects each run to be in its own file. This is apparently also true for MNE in the output you attached:

---> 96 if 'data2' in dat['nirs']: 97 warn("File contains multiple recordings. " 98 "MNE does not support this feature. "

Carlafrenzel commented 2 years ago

Hi,

I think my previous response got lost. Sorry for that!

But you see the second data element when loading the file with pysnirf?

No, I do not. And to be honest I didn't intend to have a second data element at all. I also have no idea where it is coming from? I think it very well is possible and likely that that is the cause for all my problems. Is there a way to get rid of it?

sstucker commented 2 years ago

I am confused by what you are doing. I don't see in your attachment a place where you append nirs groups, but I thought that was your goal.

Post here the complete code used to generate the file (not as attachment) and attach the file itself, thanks

Carlafrenzel commented 2 years ago
nirs = Snirf()
nirs.nirs.appendGroup()
nirs.nirs[0].data.appendGroup()
for i in range(0,34):
    nirs.nirs[0].data[0].measurementList.appendGroup()

nirs.nirs[0].stim.appendGroup()
nirs.nirs[0].metaDataTags.SubjectID = 'default'
nirs.nirs[0].metaDataTags.FrequencyUnit = 'Hz'
nirs.nirs[0].metaDataTags.LengthUnit = 'mm'
nirs.nirs[0].metaDataTags.MeasurementDate = '2021-06-24'
nirs.nirs[0].metaDataTags.TimeUnit = 's'
nirs.nirs[0].metaDataTags.SubjectID = 'default'
nirs.nirs[0].probe.coordinateSystem = None
nirs.nirs[0].probe.coordinateSystemDescription = None
nirs.nirs[0].probe.correlationTimeDelayWidths = None
nirs.nirs[0].probe.correlationTimeDelays = None
nirs.nirs[0].probe.detectorLabels = None
nirs.nirs[0].probe.detectorPos2D = snirf.nirs[0].probe.detectorPos2D
nirs.nirs[0].probe.detectorPos3D = snirf.nirs[0].probe.detectorPos3D
nirs.nirs[0].probe.sourcePos2D = snirf.nirs[0].probe.sourcePos2D
nirs.nirs[0].probe.sourcePos3D = snirf.nirs[0].probe.sourcePos3D
nirs.nirs[0].probe.wavelengths = snirf.nirs[0].probe.wavelengths
nirs.nirs[0].data[0].dataTimeSeries = snirf.nirs[0].data[0].dataTimeSeries
nirs.nirs[0].data[0].time = snirf.nirs[0].data[0].time

for i in range(0,34):
    nirs.nirs[0].data[0].measurementList[i].dataType = snirf.nirs[0].data[0].measurementList[i].dataType`
    nirs.nirs[0].data[0].measurementList[i].dataTypeIndex = snirf.nirs[0].data[0].measurementList[i].dataTypeIndex
    nirs.nirs[0].data[0].measurementList[i].dataTypeLabel = snirf.nirs[0].data[0].measurementList[i].dataTypeLabel
    nirs.nirs[0].data[0].measurementList[i].dataUnit=snirf.nirs[0].data[0].measurementList[i].dataUnit
    nirs.nirs[0].data[0].measurementList[i].detectorGain = snirf.nirs[0].data[0].measurementList[i].detectorGain
    nirs.nirs[0].data[0].measurementList[i].detectorIndex=snirf.nirs[0].data[0].measurementList[i].detectorIndex
    nirs.nirs[0].data[0].measurementList[i].detectorModuleIndex = snirf.nirs[0].data[0].measurementList[i].detectorModuleIndex
    nirs.nirs[0].data[0].measurementList[i].moduleIndex=snirf.nirs[0].data[0].measurementList[i].moduleIndex
    nirs.nirs[0].data[0].measurementList[i].sourceIndex = snirf.nirs[0].data[0].measurementList[i].sourceIndex
    nirs.nirs[0].data[0].measurementList[i].sourceModuleIndex = snirf.nirs[0].data[0].measurementList[i].sourceModuleIndex
    nirs.nirs[0].data[0].measurementList[i].sourcePower = snirf.nirs[0].data[0].measurementList[i].sourcePower
    nirs.nirs[0].data[0].measurementList[i].wavelengthActual = snirf.nirs[0].data[0].measurementList[i].wavelengthActual
    nirs.nirs[0].data[0].measurementList[i].wavelengthEmissionActual =  snirf.nirs[0].data[0].measurementList[i].wavelengthEmissionActual
    nirs.nirs[0].data[0].measurementList[i].wavelengthIndex = snirf.nirs[0].data[0].measurementList[i].wavelengthIndex

nirs.formatVersion = '1.0'
nirs.nirs[0].metaDataTags.MeasurementTime = '16:07:58'
nirs.save(r'Desktop/Homer/nirs_file.snirf')
nirs.close()

from pysnirf2 import validateSnirf
result = validateSnirf(r'Desktop/Homer/nirs_file.snirf')
assert result, 'Invalid SNIRF file!\n' + result.display()

Just be sure:

nirs_file.nirs[0].data[0]

will result in

DataElement at /nirs1/data1
dataTimeSeries: <(3819, 34) array of float64>
filename: Desktop/Homer/nirs_file.snirf
location: /nirs1/data1
measurementList: 
<iterable of 34 <class 'pysnirf2.pysnirf2.MeasurementListElement'>>
time: <(3819,) array of float64>

But:

nirs_file.nirs[0].data[1]

leads to

IndexError: list index out of range

So this is why I can't see where there would be a second data element.

nirs_file.snirf.zip

sstucker commented 2 years ago

This file looks great. I was able to validate it on my end and open it in Homer3.

I don't think your issue is with pysnirf2. image

Carlafrenzel commented 2 years ago

Thank you so much for taking the time to go through it!

One last question. Could you let me know which version of Matlab you are using and possibly share the code you used in Homer3 for this file?

sstucker commented 2 years ago

I am using the latest version of master branch of https://github.com/BUNPC/Homer3

MATLAB 2019b

Carlafrenzel commented 2 years ago

Thank you so much again!