pysnirf2


Namepysnirf2 JSON
Version 0.7.3 PyPI version JSON
download
home_pagehttps://github.com/BUNPC/pysnirf2
SummaryInterface and validator for SNIRF files
upload_time2022-08-03 21:19:57
maintainer
docs_urlNone
author
requires_python>=3.6.0
licenseGPLv3
keywords
VCS
bugtrack_url
requirements h5py numpy setuptools pip termcolor colorama
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
<h1 style="text-align:center">

pysnirf2

`pip install snirf`

</h1>


![testing](https://github.com/BUNPC/pysnirf2/actions/workflows/test.yml/badge.svg)
![lazydocs](https://github.com/BUNPC/pysnirf2/actions/workflows/lazydocs.yml/badge.svg)
[![PyPI version](https://badge.fury.io/py/pysnirf2.svg)](https://badge.fury.io/py/pysnirf2)
[![DOI](https://zenodo.org/badge/426339949.svg)](https://zenodo.org/badge/latestdoi/426339949)

Dynamically generated Python library for reading, writing, and validating [Shared Near Infrared Spectroscopy Format (SNIRF) files](https://github.com/fNIRS/snirf).

Developed and maintained by the [Boston University Neurophotonics Center](https://www.bu.edu/neurophotonics/).

## Documentation

[Documentation](https://github.com/BUNPC/pysnirf2/tree/main/docs) is generated from source using [lazydocs](https://github.com/ml-tooling/lazydocs)

## Installation
`pip install snirf`

pysnirf2 requires Python > 3.6.

# Features

The library generated via metaprogramming, but the resulting classes explicitly implement each and every specified SNIRF field so as to provide an extensible object-oriented foundation for SNIRF applications.

## Open a SNIRF file
`Snirf(<path>, <mode>)` opens a SNIRF file at `<path>` _or creates a new one if it doesn't exist._ Use mode 'w' to create a new file, 'r' to read a file, and 'r+' to edit an existing file.
```python
from snirf import Snirf
snirf = Snirf(r'some\path\subj1_run01.snirf', 'r+')
```
## Create a SNIRF file object
`Snirf()` with no arguments creates a temporary file which can be written later using `save()`.
```python
snirf = Snirf()
```

## Closing a SNIRF file
A `Snirf` instance wraps a file on disk. It should be closed when you're done reading from it or saving.
```python
snirf.close()
```
Use a `with` statement to ensure that the file is closed when you're done with it:
```python
with Snirf(r'some\path\subj1_run01.snirf', 'r+') as snirf:
     # Read/write
     snirf.save()
```

## Copy a SNIRF file object
Any `Snirf` object can be copied to a new instance in memory, after which the original can be closed.
```python
snirf2 = snirf.copy()
snirf.close()
# snirf2 is free for manipulation
```

## View or retrieve a file's contents
```python
>>> snirf

Snirf at /
filename: 
C:\Users\you\some\path\subj1_run01.snirf
formatVersion: v1.0
nirs: <iterable of 2 <class 'pysnirf2.NirsElement'>>
```
```python
>>> snirf.nirs[0].probe

Probe at /nirs1/probe
correlationTimeDelayWidths: [0.]
correlationTimeDelays: [0.]
detectorLabels: ['D1' 'D2']
detectorPos2D: [[30.  0.]
 [ 0. 30.]]
detectorPos3D: [[30.  0.  0.]
 [ 0. 30.  0.]]
filename: 
C:\Users\you\some\path\subj1_run01.snirf
frequencies: [1.]
landmarkLabels: None
landmarkPos2D: None
landmarkPos3D: None
location: /nirs/probe
momentOrders: None
sourceLabels: ['S1']
sourcePos2D: [[0. 0.]]
sourcePos3D: [[0.]
 [0.]
 [0.]]
timeDelayWidths: [0.]
timeDelays: [0.]
useLocalIndex: None
wavelengths: [690. 830.]
wavelengthsEmission: None
```
## Edit a SNIRF file
Assign a new value to a field
```python
>>> snirf.nirs[0].metaDataTags.SubjectID = 'subj1'
>>> snirf.nirs[0].metaDataTags.SubjectID

'subj1'
```
```python
>>> snirf.nirs[0].probe.detectorPos3D[0, :] = [90, 90, 90]
>>> snirf.nirs[0].probe.detectorPos3D

array([[90.,  90.,  90.],
      [  0.,  30.,   0.]])
```
> Note: assignment via slicing is not possible in `dynamic_loading` mode. 
## Indexed groups
Indexed groups are defined by the SNIRF file format as groups of the same type which are indexed via their name + a 1-based index, i.e.  `data1`, `data2`, ... or `stim1`, `stim2`, `stim3`, ...

pysnirf2 provides an iterable interface for these groups using Pythonic 0-based indexing, i.e. `data[0]`, `data[1]`, ... or `stim[0]`, `stim[1]]`, `stim[2]`, ...

```python
>>> snirf.nirs[0].stim


<iterable of 0 <class 'pysnirf2.StimElement'>>

>>> len(nirs[0].stim)

0
```
To add an indexed group, use the `appendGroup()` method of any `IndexedGroup` class. Indexed groups are created automatically. `nirs` is an indexed group.
```python
>>> snirf.nirs[0].stim.appendGroup()
>>> len(nirs[0].stim)

1

>>> snirf.nirs[0].stim[0]

StimElement at /nirs/stim2
data: None
dataLabels: None
filename: 
C:\Users\you\some\path\subj1_run01.snirf
name: None
```
To remove an indexed group
```python
del snirf.nirs[0].stim[0]
```
## Save a SNIRF file
Overwrite the open file
```python
snirf.save()
```
Save As in a new location
```python
snirf.save(r'some\new\path\subj1_run01_edited.snirf')
```
The `save()` function can be called for any group or indexed group:
```python
snirf.nirs[0].metaDataTags.save('subj1_run01_edited_metadata_only.snirf')
```
## Dynamic loading mode
For larger files, it may be useful to load data dynamically: data will only be loaded on access, and only changed datasets will be written on `save()`. When creating a new `Snirf` instance, set `dynamic_loading` to `True` (Default `False`).
```python
snirf = Snirf(r'some\path\subj1_run01.snirf', 'r+', dynamic_loading=True)
```
> Note: in dynamic loading mode, array data cannot be modified with indices like in the example above:
> ```python
> >>> snirf = Snirf(TESTPATH, 'r+', dynamic_loading=True)
> >>> snirf.nirs[0].probe.detectorPos3D
> 
> array([[30.,  0.,  0.],
>       [ 0., 30.,  0.]])
> 
> >>> snirf.nirs[0].probe.detectorPos3D[0, :] = [90, 90, 90]
> >>> snirf.nirs[0].probe.detectorPos3D
> 
> array([[30.,  0.,  0.],
>        [ 0., 30.,  0.]])
> ```
> To modify an array in `dynamic_loading` mode, assign it, modify it, and assign it back to the Snirf object.
> ```python
> >>> detectorPos3D = snirf.nirs[0].probe.detectorPos3D
> >>> detectorPos3D[0, :] = [90, 90, 90]
> >>> snirf.nirs[0].probe.detectorPos3D = detectorPos3D
> 
> array([[90.,  90.,  90.],
>        [ 0.,   30.,  0.]])

# Validating a SNIRF file
pysnirf2 features functions for validating SNIRF files against the specification and generating detailed error reports.
## Validate a Snirf object you have created
```python
result = snirf.validate()
```
## Validate a SNIRF file on disk
To validate a SNIRF file on disk
```python
from snirf import validateSnirf
result = validateSnirf(r'some\path\subj1_run01.snirf')
assert result, 'Invalid SNIRF file!\n' + result.display()  # Crash and display issues if the file is invalid.
```
## Validation results
The validation functions return a [`ValidationResult`](https://github.com/BUNPC/pysnirf2/blob/main/docs/pysnirf2.md#class-validationresult) instance which contains details about the SNIRF file.
To view the validation result:
```python
>>> result.display(severity=3)  # Display all fatal errors

<pysnirf2.pysnirf2.ValidationResult object at 0x000001C0CCF05A00>
/nirs1/data1/measurementList103/dataType                 FATAL   REQUIRED_DATASET_MISSING
/nirs1/data1/measurementList103/dataTypeIndex            FATAL   REQUIRED_DATASET_MISSING
/nirs1/data1                                             FATAL   INVALID_MEASUREMENTLIST 

Found 668 OK      (hidden)
Found 635 INFO    (hidden)
Found 204 WARNING (hidden)
Found 3 FATAL  

File is INVALID
```
To look at a particular result:
```python
>>> result.errors[2]

<pysnirf2.pysnirf2.ValidationIssue object at 0x000001C0CB502F70>
location: /nirs1/data1
severity: 3   FATAL  
name:     8   INVALID_MEASUREMENTLIST
message:  The number of measurementList elements does not match the second dimension of dataTimeSeries
```
The full list of validation results `result.issues` can be explored programatically.
# Code generation

The interface and validator are generated via metacode that downloads and parses [the latest SNIRF specification](https://raw.githubusercontent.com/fNIRS/snirf/master/snirf_specification.md). 

See [\gen](https://github.com/BUNPC/pysnirf2/tree/main/gen) for details.







            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/BUNPC/pysnirf2",
    "name": "pysnirf2",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "",
    "author_email": "sstucker@bu.edu",
    "download_url": "https://files.pythonhosted.org/packages/1b/d3/b0a2947ce101ab675126f3e79c44ac007cdf0362e5ed3c7fe9d523e5dc85/pysnirf2-0.7.3.tar.gz",
    "platform": null,
    "description": "\n<h1 style=\"text-align:center\">\n\npysnirf2\n\n`pip install snirf`\n\n</h1>\n\n\n![testing](https://github.com/BUNPC/pysnirf2/actions/workflows/test.yml/badge.svg)\n![lazydocs](https://github.com/BUNPC/pysnirf2/actions/workflows/lazydocs.yml/badge.svg)\n[![PyPI version](https://badge.fury.io/py/pysnirf2.svg)](https://badge.fury.io/py/pysnirf2)\n[![DOI](https://zenodo.org/badge/426339949.svg)](https://zenodo.org/badge/latestdoi/426339949)\n\nDynamically generated Python library for reading, writing, and validating [Shared Near Infrared Spectroscopy Format (SNIRF) files](https://github.com/fNIRS/snirf).\n\nDeveloped and maintained by the [Boston University Neurophotonics Center](https://www.bu.edu/neurophotonics/).\n\n## Documentation\n\n[Documentation](https://github.com/BUNPC/pysnirf2/tree/main/docs) is generated from source using [lazydocs](https://github.com/ml-tooling/lazydocs)\n\n## Installation\n`pip install snirf`\n\npysnirf2 requires Python > 3.6.\n\n# Features\n\nThe library generated via metaprogramming, but the resulting classes explicitly implement each and every specified SNIRF field so as to provide an extensible object-oriented foundation for SNIRF applications.\n\n## Open a SNIRF file\n`Snirf(<path>, <mode>)` opens a SNIRF file at `<path>` _or creates a new one if it doesn't exist._ Use mode 'w' to create a new file, 'r' to read a file, and 'r+' to edit an existing file.\n```python\nfrom snirf import Snirf\nsnirf = Snirf(r'some\\path\\subj1_run01.snirf', 'r+')\n```\n## Create a SNIRF file object\n`Snirf()` with no arguments creates a temporary file which can be written later using `save()`.\n```python\nsnirf = Snirf()\n```\n\n## Closing a SNIRF file\nA `Snirf` instance wraps a file on disk. It should be closed when you're done reading from it or saving.\n```python\nsnirf.close()\n```\nUse a `with` statement to ensure that the file is closed when you're done with it:\n```python\nwith Snirf(r'some\\path\\subj1_run01.snirf', 'r+') as snirf:\n     # Read/write\n     snirf.save()\n```\n\n## Copy a SNIRF file object\nAny `Snirf` object can be copied to a new instance in memory, after which the original can be closed.\n```python\nsnirf2 = snirf.copy()\nsnirf.close()\n# snirf2 is free for manipulation\n```\n\n## View or retrieve a file's contents\n```python\n>>> snirf\n\nSnirf at /\nfilename: \nC:\\Users\\you\\some\\path\\subj1_run01.snirf\nformatVersion: v1.0\nnirs: <iterable of 2 <class 'pysnirf2.NirsElement'>>\n```\n```python\n>>> snirf.nirs[0].probe\n\nProbe at /nirs1/probe\ncorrelationTimeDelayWidths: [0.]\ncorrelationTimeDelays: [0.]\ndetectorLabels: ['D1' 'D2']\ndetectorPos2D: [[30.  0.]\n [ 0. 30.]]\ndetectorPos3D: [[30.  0.  0.]\n [ 0. 30.  0.]]\nfilename: \nC:\\Users\\you\\some\\path\\subj1_run01.snirf\nfrequencies: [1.]\nlandmarkLabels: None\nlandmarkPos2D: None\nlandmarkPos3D: None\nlocation: /nirs/probe\nmomentOrders: None\nsourceLabels: ['S1']\nsourcePos2D: [[0. 0.]]\nsourcePos3D: [[0.]\n [0.]\n [0.]]\ntimeDelayWidths: [0.]\ntimeDelays: [0.]\nuseLocalIndex: None\nwavelengths: [690. 830.]\nwavelengthsEmission: None\n```\n## Edit a SNIRF file\nAssign a new value to a field\n```python\n>>> snirf.nirs[0].metaDataTags.SubjectID = 'subj1'\n>>> snirf.nirs[0].metaDataTags.SubjectID\n\n'subj1'\n```\n```python\n>>> snirf.nirs[0].probe.detectorPos3D[0, :] = [90, 90, 90]\n>>> snirf.nirs[0].probe.detectorPos3D\n\narray([[90.,  90.,  90.],\n      [  0.,  30.,   0.]])\n```\n> Note: assignment via slicing is not possible in `dynamic_loading` mode. \n## Indexed groups\nIndexed groups are defined by the SNIRF file format as groups of the same type which are indexed via their name + a 1-based index, i.e.  `data1`, `data2`, ... or `stim1`, `stim2`, `stim3`, ...\n\npysnirf2 provides an iterable interface for these groups using Pythonic 0-based indexing, i.e. `data[0]`, `data[1]`, ... or `stim[0]`, `stim[1]]`, `stim[2]`, ...\n\n```python\n>>> snirf.nirs[0].stim\n\n\n<iterable of 0 <class 'pysnirf2.StimElement'>>\n\n>>> len(nirs[0].stim)\n\n0\n```\nTo add an indexed group, use the `appendGroup()` method of any `IndexedGroup` class. Indexed groups are created automatically. `nirs` is an indexed group.\n```python\n>>> snirf.nirs[0].stim.appendGroup()\n>>> len(nirs[0].stim)\n\n1\n\n>>> snirf.nirs[0].stim[0]\n\nStimElement at /nirs/stim2\ndata: None\ndataLabels: None\nfilename: \nC:\\Users\\you\\some\\path\\subj1_run01.snirf\nname: None\n```\nTo remove an indexed group\n```python\ndel snirf.nirs[0].stim[0]\n```\n## Save a SNIRF file\nOverwrite the open file\n```python\nsnirf.save()\n```\nSave As in a new location\n```python\nsnirf.save(r'some\\new\\path\\subj1_run01_edited.snirf')\n```\nThe `save()` function can be called for any group or indexed group:\n```python\nsnirf.nirs[0].metaDataTags.save('subj1_run01_edited_metadata_only.snirf')\n```\n## Dynamic loading mode\nFor larger files, it may be useful to load data dynamically: data will only be loaded on access, and only changed datasets will be written on `save()`. When creating a new `Snirf` instance, set `dynamic_loading` to `True` (Default `False`).\n```python\nsnirf = Snirf(r'some\\path\\subj1_run01.snirf', 'r+', dynamic_loading=True)\n```\n> Note: in dynamic loading mode, array data cannot be modified with indices like in the example above:\n> ```python\n> >>> snirf = Snirf(TESTPATH, 'r+', dynamic_loading=True)\n> >>> snirf.nirs[0].probe.detectorPos3D\n> \n> array([[30.,  0.,  0.],\n>       [ 0., 30.,  0.]])\n> \n> >>> snirf.nirs[0].probe.detectorPos3D[0, :] = [90, 90, 90]\n> >>> snirf.nirs[0].probe.detectorPos3D\n> \n> array([[30.,  0.,  0.],\n>        [ 0., 30.,  0.]])\n> ```\n> To modify an array in `dynamic_loading` mode, assign it, modify it, and assign it back to the Snirf object.\n> ```python\n> >>> detectorPos3D = snirf.nirs[0].probe.detectorPos3D\n> >>> detectorPos3D[0, :] = [90, 90, 90]\n> >>> snirf.nirs[0].probe.detectorPos3D = detectorPos3D\n> \n> array([[90.,  90.,  90.],\n>        [ 0.,   30.,  0.]])\n\n# Validating a SNIRF file\npysnirf2 features functions for validating SNIRF files against the specification and generating detailed error reports.\n## Validate a Snirf object you have created\n```python\nresult = snirf.validate()\n```\n## Validate a SNIRF file on disk\nTo validate a SNIRF file on disk\n```python\nfrom snirf import validateSnirf\nresult = validateSnirf(r'some\\path\\subj1_run01.snirf')\nassert result, 'Invalid SNIRF file!\\n' + result.display()  # Crash and display issues if the file is invalid.\n```\n## Validation results\nThe validation functions return a [`ValidationResult`](https://github.com/BUNPC/pysnirf2/blob/main/docs/pysnirf2.md#class-validationresult) instance which contains details about the SNIRF file.\nTo view the validation result:\n```python\n>>> result.display(severity=3)  # Display all fatal errors\n\n<pysnirf2.pysnirf2.ValidationResult object at 0x000001C0CCF05A00>\n/nirs1/data1/measurementList103/dataType                 FATAL   REQUIRED_DATASET_MISSING\n/nirs1/data1/measurementList103/dataTypeIndex            FATAL   REQUIRED_DATASET_MISSING\n/nirs1/data1                                             FATAL   INVALID_MEASUREMENTLIST \n\nFound 668 OK      (hidden)\nFound 635 INFO    (hidden)\nFound 204 WARNING (hidden)\nFound 3 FATAL  \n\nFile is INVALID\n```\nTo look at a particular result:\n```python\n>>> result.errors[2]\n\n<pysnirf2.pysnirf2.ValidationIssue object at 0x000001C0CB502F70>\nlocation: /nirs1/data1\nseverity: 3   FATAL  \nname:     8   INVALID_MEASUREMENTLIST\nmessage:  The number of measurementList elements does not match the second dimension of dataTimeSeries\n```\nThe full list of validation results `result.issues` can be explored programatically.\n# Code generation\n\nThe interface and validator are generated via metacode that downloads and parses [the latest SNIRF specification](https://raw.githubusercontent.com/fNIRS/snirf/master/snirf_specification.md). \n\nSee [\\gen](https://github.com/BUNPC/pysnirf2/tree/main/gen) for details.\n\n\n\n\n\n\n",
    "bugtrack_url": null,
    "license": "GPLv3",
    "summary": "Interface and validator for SNIRF files",
    "version": "0.7.3",
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "md5": "bc10d7b1e047237ca43793085528f7f7",
                "sha256": "338a081630c9f189228dfc4558e838156218127c9f828c8790858182ac7dd85a"
            },
            "downloads": -1,
            "filename": "pysnirf2-0.7.3-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "bc10d7b1e047237ca43793085528f7f7",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": ">=3.6.0",
            "size": 55516,
            "upload_time": "2022-08-03T21:19:55",
            "upload_time_iso_8601": "2022-08-03T21:19:55.894570Z",
            "url": "https://files.pythonhosted.org/packages/8e/73/cd2850f18baf099870b253b36102e1915cbf5aa473f8f426b29c512751ed/pysnirf2-0.7.3-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "md5": "65d9a1f1dcae41d9b816976b1e320caf",
                "sha256": "68d51d921af12f3ae2ab544443cc2c91cbdf3e2e001901b58860b49a1acd4632"
            },
            "downloads": -1,
            "filename": "pysnirf2-0.7.3.tar.gz",
            "has_sig": false,
            "md5_digest": "65d9a1f1dcae41d9b816976b1e320caf",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6.0",
            "size": 58045,
            "upload_time": "2022-08-03T21:19:57",
            "upload_time_iso_8601": "2022-08-03T21:19:57.661391Z",
            "url": "https://files.pythonhosted.org/packages/1b/d3/b0a2947ce101ab675126f3e79c44ac007cdf0362e5ed3c7fe9d523e5dc85/pysnirf2-0.7.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-08-03 21:19:57",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "BUNPC",
    "github_project": "pysnirf2",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "h5py",
            "specs": [
                [
                    "==",
                    "3.1.0"
                ]
            ]
        },
        {
            "name": "numpy",
            "specs": [
                [
                    "==",
                    "1.19.5"
                ]
            ]
        },
        {
            "name": "setuptools",
            "specs": [
                [
                    "==",
                    "40.8.0"
                ]
            ]
        },
        {
            "name": "pip",
            "specs": [
                [
                    "==",
                    "21.3.1"
                ]
            ]
        },
        {
            "name": "termcolor",
            "specs": [
                [
                    "==",
                    "1.1.0"
                ]
            ]
        },
        {
            "name": "colorama",
            "specs": [
                [
                    "==",
                    "0.4.4"
                ]
            ]
        }
    ],
    "lcname": "pysnirf2"
}
        
Elapsed time: 0.02106s