diff --git a/.github/workflows/publish_schema.yml b/.github/workflows/publish_schema.yml
new file mode 100644
index 0000000000..842c513eab
--- /dev/null
+++ b/.github/workflows/publish_schema.yml
@@ -0,0 +1,91 @@
+name: "Publish schema"
+
+on:
+ push:
+ branches:
+ - "master"
+ tags:
+ - "schema-*"
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+defaults:
+ run:
+ shell: bash
+
+env:
+ GIT_AUTHOR_NAME: BIDS CI
+ GIT_AUTHOR_EMAIL: bids.maintenance@gmail.com
+ GIT_COMMITTER_NAME: BIDS CI
+ GIT_COMMITTER_EMAIL: bids.maintenance@gmail.com
+
+permissions:
+ contents: write
+ id-token: write
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ filter: "blob:none"
+ - uses: actions/setup-python@v5
+ with:
+ python-version: 3
+ - name: Install bidsschematools
+ run: |
+ pip install --upgrade tools/schemacode
+ git clean -fxd tools/schemacode
+ - name: Checkout jsr-dist
+ run: |
+ git checkout -t origin/jsr-dist
+ - name: Regenerate schema
+ run: bst export > schema.json
+ - name: Regenerate context types
+ run: |
+ jq .meta.context schema.json \
+ | npx quicktype --src-lang schema --lang ts -t Context --just-types \
+ > context.ts
+ - name: Regenerate metaschema types
+ run: |
+ # Name the file schema so the type will be named Schema
+ bst export-metaschema > /tmp/schema.json
+ npx --package=json-schema-to-typescript json2ts --unknownAny /tmp/schema.json > metaschema.ts
+ - name: Determine next version
+ run: |
+ BASE=$( jq -r .schema_version schema.json )
+ if [[ "$BASE" =~ ^[0-9]*.[0-9]*.[0-9]*$ ]]; then
+ # Release, so unconditionally update version
+ VERSION=$BASE
+ jq ".version = \"$VERSION\"" jsr.json > tmp.json && mv tmp.json jsr.json
+ else
+ DENOVER=$( jq -r .version jsr.json )
+ # Get the reference of the latest commit to touch the schema directory
+ HASH=$( git log -n 1 --pretty=%h $REF -- src/schema )
+ if [[ $DENOVER =~ ^"$BASE".[0-9] ]]; then
+ PREFIX=${DENOVER%+*}
+ let SERIAL=1+${PREFIX#$BASE.}
+ else
+ SERIAL=1
+ fi
+ VERSION="$BASE.$SERIAL+$HASH"
+ fi
+ echo VERSION=$VERSION | tee -a $GITHUB_ENV
+ env:
+ REF: ${{ github.ref }}
+ - name: Check for changes, set version and commit
+ run: |
+ if ! git diff -s --exit-code; then
+ jq ".version = \"$VERSION\"" jsr.json > tmp.json && mv tmp.json jsr.json
+ git add jsr.json schema.json context.ts metaschema.ts
+ git commit -m "Update schema JSR distribution"
+ git push
+ fi
+ - name: Publish to JSR
+ if: success()
+ run: |
+ npx jsr publish
diff --git a/.github/workflows/schemacode_ci.yml b/.github/workflows/schemacode_ci.yml
index 14746534db..46c437826c 100644
--- a/.github/workflows/schemacode_ci.yml
+++ b/.github/workflows/schemacode_ci.yml
@@ -33,7 +33,7 @@ jobs:
- name: "Install build dependencies"
run: pip install --upgrade build twine
- name: "Install test dependencies on tag"
- run: pip install --upgrade tools/schemacode[test]
+ run: pip install --upgrade tools/schemacode[tests]
if: ${{ startsWith(github.ref, 'refs/tags/schema-') }}
- name: "Build archive on tag"
run: pytest tools/schemacode/bidsschematools -k make_archive
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 82122e77c7..277632e355 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,7 +15,7 @@ repos:
- id: check-added-large-files
- id: check-case-conflict
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.29.1
+ rev: 0.29.2
hooks:
- id: check-dependabot
- id: check-github-workflows
@@ -25,7 +25,7 @@ repos:
- id: check-readthedocs
files: readthedocs.yml
- repo: https://github.com/psf/black
- rev: 24.4.2
+ rev: 24.8.0
hooks:
- id: black
files: ^tools/(?!schemacode)
@@ -45,7 +45,7 @@ repos:
files: tools/schemacode
args: ["--settings-file", "tools/schemacode/pyproject.toml"]
- repo: https://github.com/pyCQA/flake8
- rev: 7.1.0
+ rev: 7.1.1
hooks:
- id: flake8
args: [--config=tools/schemacode/setup.cfg]
@@ -67,7 +67,7 @@ repos:
- id: codespell
args: ["--config=.codespellrc", "--dictionary=-", "--dictionary=.codespell_dict"]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.11.0
+ rev: v1.11.2
hooks:
- id: mypy
# Sync with project.optional-dependencies.typing
diff --git a/README.md b/README.md
index 6474f470a1..40c29b6b81 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ BIDS currently supports the following data modalities with more to come in the f
- microscopy
- NIRS
- motion
+- MRS
# Formatting your data with BIDS
diff --git a/mkdocs.yml b/mkdocs.yml
index 114e05c135..cfbdba2a49 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -19,6 +19,7 @@ nav:
- Microscopy: modality-specific-files/microscopy.md
- Near-Infrared Spectroscopy: modality-specific-files/near-infrared-spectroscopy.md
- Motion: modality-specific-files/motion.md
+ - Magnetic Resonance Spectroscopy: modality-specific-files/magnetic-resonance-spectroscopy.md
- Derivatives:
- BIDS Derivatives: derivatives/introduction.md
- Common data types and metadata: derivatives/common-data-types.md
diff --git a/src/appendices/arterial-spin-labeling.md b/src/appendices/arterial-spin-labeling.md
index 4d1253eaab..842bde8d80 100644
--- a/src/appendices/arterial-spin-labeling.md
+++ b/src/appendices/arterial-spin-labeling.md
@@ -79,7 +79,7 @@ For (P)CASL, specifying the `LabelingDuration` and the `PostLabelingDelay` is re
The `LabelingDuration` is defined as the total duration of the labeling pulse train in seconds.
`PostLabelingDelay` is the time in seconds after the end of the labeling until the middle of the excitation pulse applied
to the imaging slab (for 3D acquisition) or first slice (for 2D acquisition).
-Additionally, the `BackgroundSuppressionPulseTime`'s is required in case `BackgroundSuppression` was applied.
+Additionally, the `BackgroundSuppressionPulseTime` is RECOMMENDED if `BackgroundSuppression` was applied.
This an array of numbers containing the timing in seconds of the background suppression pulses
with respect to the start of the labeling.
In the case of `PCASL`, the recommended `PCASLType` field defines the type of the gradient pulses
diff --git a/src/appendices/contributors.md b/src/appendices/contributors.md
index f48586f04d..a359956c8e 100644
--- a/src/appendices/contributors.md
+++ b/src/appendices/contributors.md
@@ -55,6 +55,7 @@ If you contributed to the BIDS ecosystem and your name is not listed, please add
| Alexander Jones | 💻🐛 |
| Alexander L. Cohen | 🐛💻📖💬 |
| Alexander von Lautz | 📖 |
+| Alexandre D'Astous | 📖 |
| Alexandre Gramfort | 📖💡 |
| Alexandre Hutton | 📖 |
| Alexandre Routier | 📖 |
diff --git a/src/appendices/cross-modality-correspondence.md b/src/appendices/cross-modality-correspondence.md
index c71a1c73f5..6dba22b325 100644
--- a/src/appendices/cross-modality-correspondence.md
+++ b/src/appendices/cross-modality-correspondence.md
@@ -12,3 +12,10 @@ The reason for this is that the MRI needs to be corrected for nonlinear gradient
in order to fit the accompanying PET scans for co-registration
(Knudsen et al. 2020, [doi:10.1177/0271678X20905433](https://doi.org/10.1177/0271678X20905433);
Norgaard et al. 2019, [doi:10.1016/j.neuroimage.2019.05.055](https://doi.org/10.1016/j.neuroimage.2019.05.055)).
+
+## MRS-MRI correspondence
+
+It is typical to acquire high-resolution 3D anatomical MR images alongside MRS data for
+voxel/slab placement, co-registration, and partial-volume tissue correction of metabolite concentrations.
+To avoid incorrectly matching an MRS dataset with a corresponding anatomical MR image,
+it is RECOMMENDED that the field `AnatomicalImage` be included in the MRS sidecar JSON files.
diff --git a/src/appendices/qmri.md b/src/appendices/qmri.md
index 8aad465f24..d0991be72e 100644
--- a/src/appendices/qmri.md
+++ b/src/appendices/qmri.md
@@ -607,10 +607,15 @@ The nominal FA value of the SE pulse is twice this value.
Note that the following metadata fields MUST be defined in the accompanying JSON
files:
-| **Field name** | **Definition** |
-| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
-| `TotalReadoutTime` | The effective readout length defined as `EffectiveEchoSpacing * PEReconMatrix`, with `EffectiveEchoSpacing = TrueEchoSpacing / PEacceleration` |
-| `MixingTime` | Time interval between the SE and STE pulses |
+
+{{ MACROS___make_sidecar_table("fmap.TB1EPI") }}
To properly identify constituents of this particular method, values of the `echo`
entity MUST index the images as follows:
diff --git a/src/appendices/units.md b/src/appendices/units.md
index 4e4355483f..078db3b64e 100644
--- a/src/appendices/units.md
+++ b/src/appendices/units.md
@@ -83,30 +83,51 @@ Examples for CMIXF-12 (including the five unicode symbols mentioned above):
### Multiples
-| **Prefix name** | **Prefix symbol** | **Factor** |
-| ------------------------------------------- | ----------------- | --------------- |
-| [deca](https://www.wikiwand.com/en/Deca-) | da | 101 |
-| [hecto](https://www.wikiwand.com/en/Hecto-) | h | 102 |
-| [kilo](https://www.wikiwand.com/en/Kilo-) | k | 103 |
-| [mega](https://www.wikiwand.com/en/Mega-) | M | 106 |
-| [giga](https://www.wikiwand.com/en/Giga-) | G | 109 |
-| [tera](https://www.wikiwand.com/en/Tera-) | T | 1012 |
-| [peta](https://www.wikiwand.com/en/Peta-) | P | 1015 |
-| [exa](https://www.wikiwand.com/en/Exa-) | E | 1018 |
-| [zetta](https://www.wikiwand.com/en/Zetta-) | Z | 1021 |
-| [yotta](https://www.wikiwand.com/en/Yotta-) | Y | 1024 |
+| **Prefix name** | **Prefix symbol** | **Factor** |
+| --------------- | ----------------- | --------------- |
+| [deca][] | da | 101 |
+| [hecto][] | h | 102 |
+| [kilo][] | k | 103 |
+| [mega][] | M | 106 |
+| [giga][] | G | 109 |
+| [tera][] | T | 1012 |
+| [peta][] | P | 1015 |
+| [exa][] | E | 1018 |
+| [zetta][] | Z | 1021 |
+| [yotta][] | Y | 1024 |
### Submultiples
-| **Prefix name** | **Prefix symbol** | **Factor** |
-| ------------------------------------------- | ----------------- | ---------------- |
-| [deci](https://www.wikiwand.com/en/Deci-) | d | 10-1 |
-| [centi](https://www.wikiwand.com/en/Centi-) | c | 10-2 |
-| [milli](https://www.wikiwand.com/en/Milli-) | m | 10-3 |
-| [micro](https://www.wikiwand.com/en/Micro-) | u | 10-6 |
-| [nano](https://www.wikiwand.com/en/Nano-) | n | 10-9 |
-| [pico](https://www.wikiwand.com/en/Pico-) | p | 10-12 |
-| [femto](https://www.wikiwand.com/en/Femto-) | f | 10-15 |
-| [atto](https://www.wikiwand.com/en/Atto-) | a | 10-18 |
-| [zepto](https://www.wikiwand.com/en/Zepto-) | z | 10-21 |
-| [yocto](https://www.wikiwand.com/en/Yocto-) | y | 10-24 |
+| **Prefix name** | **Prefix symbol** | **Factor** |
+| --------------- | ----------------- | ---------------- |
+| [deci][] | d | 10-1 |
+| [centi][] | c | 10-2 |
+| [milli][] | m | 10-3 |
+| [micro][] | u | 10-6 |
+| [nano][] | n | 10-9 |
+| [pico][] | p | 10-12 |
+| [femto][] | f | 10-15 |
+| [atto][] | a | 10-18 |
+| [zepto][] | z | 10-21 |
+| [yocto][] | y | 10-24 |
+
+[deca]: https://en.wikipedia.org/wiki/Deca-
+[hecto]: https://en.wikipedia.org/wiki/Hecto-
+[kilo]: https://en.wikipedia.org/wiki/Kilo-
+[mega]: https://en.wikipedia.org/wiki/Mega-
+[giga]: https://en.wikipedia.org/wiki/Giga-
+[tera]: https://en.wikipedia.org/wiki/Tera-
+[peta]: https://en.wikipedia.org/wiki/Peta-
+[exa]: https://en.wikipedia.org/wiki/Exa-
+[zetta]: https://en.wikipedia.org/wiki/Zetta-
+[yotta]: https://en.wikipedia.org/wiki/Yotta-
+[deci]: https://en.wikipedia.org/wiki/Deci-
+[centi]: https://en.wikipedia.org/wiki/Centi-
+[milli]: https://en.wikipedia.org/wiki/Milli-
+[micro]: https://en.wikipedia.org/wiki/Micro-
+[nano]: https://en.wikipedia.org/wiki/Nano-
+[pico]: https://en.wikipedia.org/wiki/Pico-
+[femto]: https://en.wikipedia.org/wiki/Femto-
+[atto]: https://en.wikipedia.org/wiki/Atto-
+[zepto]: https://en.wikipedia.org/wiki/Zepto-
+[yocto]: https://en.wikipedia.org/wiki/Yocto-
diff --git a/src/introduction.md b/src/introduction.md
index 4315720e3a..a07b654b2c 100644
--- a/src/introduction.md
+++ b/src/introduction.md
@@ -184,6 +184,10 @@ For example:
PsyArXiv.
[doi:10.31234/osf.io/w6z79](https://doi.org/10.31234/osf.io/w6z79)
+#### MRS
+
+- (publication forthcoming)
+
### Research Resource Identifier (RRID)
BIDS has also a
diff --git a/src/metaschema.json b/src/metaschema.json
index c0462edcaf..c00481b5e8 100644
--- a/src/metaschema.json
+++ b/src/metaschema.json
@@ -68,6 +68,7 @@
"versions": {
"type": "array",
"items": {
+ "type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
}
}
@@ -315,7 +316,7 @@
"$ref": "#/definitions/ruleTypes/expressionList"
}
},
- "required": ["checks", "selectors"],
+ "required": ["checks", "selectors", "issue"],
"additionalProperties": false
}
}
@@ -386,19 +387,25 @@
"required": ["common", "deriv", "raw"],
"additionalProperties": false
},
+ "json": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/json"
+ }
+ },
"sidecars": {
"type": "object",
"patternProperties": {
"^derivatives$": {
"type": "object",
"properties": {
- "common_derivatives": { "$ref": "#/definitions/sidecar" }
+ "common_derivatives": { "$ref": "#/definitions/json" }
},
"required": ["common_derivatives"],
"additionalProperties": false
},
"^(?!derivatives$)[a-z_]+$": {
- "$ref": "#/definitions/sidecar"
+ "$ref": "#/definitions/json"
},
"additionalProperties": false
},
@@ -464,7 +471,7 @@
"properties": {
"datatypes": {
"type": "array",
- "items": { "pattern": "^[a-z]+$" }
+ "items": { "type": "string", "pattern": "^[a-z]+$" }
}
},
"required": ["datatypes"],
@@ -476,6 +483,7 @@
"required": [
"entities",
"files",
+ "json",
"sidecars",
"tabular_data",
"common_principles",
@@ -586,7 +594,7 @@
}
}
},
- "sidecar": {
+ "json": {
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9_]+$": {
@@ -654,7 +662,7 @@
"level": { "enum": ["optional", "recommended", "required"] },
"datatypes": {
"type": "array",
- "items": { "pattern": "^[a-z]+$" }
+ "items": { "type": "string", "pattern": "^[a-z]+$" }
},
"stem": { "type": "string" },
"extensions": { "type": "array", "items": { "type": "string" } }
@@ -668,11 +676,11 @@
"level": { "enum": ["optional", "recommended", "required"] },
"datatypes": {
"type": "array",
- "items": { "pattern": "^[a-z]+$" }
+ "items": { "type": "string", "pattern": "^[a-z]+$" }
},
"suffixes": {
"type": "array",
- "items": { "pattern": "^[a-zA-Z0-9]+$" }
+ "items": { "type": "string", "pattern": "^[a-zA-Z0-9]+$" }
},
"extensions": { "type": "array", "items": { "type": "string" } },
"entities": {
diff --git a/src/modality-specific-files/electroencephalography.md b/src/modality-specific-files/electroencephalography.md
index 8a584bb7a0..782012f3ea 100644
--- a/src/modality-specific-files/electroencephalography.md
+++ b/src/modality-specific-files/electroencephalography.md
@@ -390,7 +390,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("eeg.EEGCoordsystemGeneral") }}
+{{ MACROS___make_json_table("json.eeg.EEGCoordsystemGeneral") }}
Fields relating to the EEG electrode positions:
@@ -402,7 +402,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("eeg.EEGCoordsystemPositions") }}
+{{ MACROS___make_json_table("json.eeg.EEGCoordsystemPositions") }}
Fields relating to the position of fiducials measured during an EEG session/run:
@@ -414,7 +414,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("eeg.EEGCoordsystemFiducials") }}
+{{ MACROS___make_json_table("json.eeg.EEGCoordsystemFiducials") }}
Fields relating to the position of anatomical landmark measured during an EEG session/run:
@@ -426,7 +426,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table(["eeg.EEGCoordsystemLandmark", "eeg.EEGCoordsystemLandmarkDescriptionRec"]) }}
+{{ MACROS___make_json_table(["json.eeg.EEGCoordsystemLandmark", "json.eeg.EEGCoordsystemLandmarkDescriptionRec"]) }}
If the position of anatomical landmarks is measured using the same system or
device used to measure electrode positions, and if thereby the anatomical
diff --git a/src/modality-specific-files/intracranial-electroencephalography.md b/src/modality-specific-files/intracranial-electroencephalography.md
index 096b96f97d..dc15d9f92d 100644
--- a/src/modality-specific-files/intracranial-electroencephalography.md
+++ b/src/modality-specific-files/intracranial-electroencephalography.md
@@ -389,7 +389,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("ieeg.iEEGCoordsystemGeneral") }}
+{{ MACROS___make_json_table("json.ieeg.iEEGCoordsystemGeneral") }}
Fields relating to the iEEG electrode positions:
@@ -401,7 +401,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("ieeg.iEEGCoordsystemPositions") }}
+{{ MACROS___make_json_table("json.ieeg.iEEGCoordsystemPositions") }}
### Recommended 3D coordinate systems
diff --git a/src/modality-specific-files/magnetic-resonance-imaging-data.md b/src/modality-specific-files/magnetic-resonance-imaging-data.md
index 113d77be73..fc1424d102 100644
--- a/src/modality-specific-files/magnetic-resonance-imaging-data.md
+++ b/src/modality-specific-files/magnetic-resonance-imaging-data.md
@@ -723,37 +723,46 @@ within the `[*_]dwi.bval` and `[*_]dwi.bvec` files) MAY change across DWI runs.
**Gradient orientation file formats**.
The `[*_]dwi.bval` and `[*_]dwi.bvec` files MUST follow the
-[FSL format](https://fsl.fmrib.ox.ac.uk/fsl/docs/#/diffusion/index?id=diffusion-data-in-fsl):
-The `[*_]dwi.bvec` file contains 3 rows with *N* space-delimited floating-point numbers
-(corresponding to the *N* volumes in the corresponding NIfTI file.)
-The first row contains the *x* elements, the second row contains the *y* elements and
-the third row contains the *z* elements of a unit vector in the direction of the applied
-diffusion gradient, where the *i*-th elements in each row correspond together to
-the *i*-th volume, with `[0,0,0]` for *non-diffusion-weighted* (also called *b*=0 or *low-b*)
-volumes.
-Following the FSL format for the `[*_]dwi.bvec` specification, the coordinate system of
-the *b* vectors MUST be defined with respect to the coordinate system defined by
-the header of the corresponding `_dwi` NIfTI file and not the scanner's device
-coordinate system (see [Coordinate systems](../appendices/coordinate-systems.md)).
-The most relevant limitation imposed by this choice is that the gradient information cannot
-be directly stored in this format if the scanner generates *b*-vectors in *scanner coordinates*.
-
-Example of `[*_]dwi.bvec` file, with *N*=6, with two *b*=0 volumes in the beginning:
-
-```Text
-0 0 0.021828 -0.015425 -0.70918 -0.2465
-0 0 0.80242 0.22098 -0.00063106 0.1043
-0 0 -0.59636 0.97516 -0.70503 -0.96351
-```
-
-The `[*_]dwi.bval` file contains the *b*-values (in s/mm2 ) corresponding to the
-volumes in the relevant NIfTI file), with 0 designating *b*=0 volumes, space-delimited.
-
-Example of `[*_]dwi.bval` file, corresponding to the previous `[*_]dwi.bvec` example:
-
-```Text
-0 0 2000 2000 1000 1000
-```
+[FSL format](https://fsl.fmrib.ox.ac.uk/fsl/docs/#/diffusion/index?id=diffusion-data-in-fsl).
+
+The `[*_]dwi.bvec` file contains 3 rows with *N* space-delimited floating-point numbers,
+corresponding to the *N* volumes in the corresponding NIfTI file.
+Across these three rows,
+each column encodes three elements of a 3-vector for the corresponding image volume;
+each vector MUST be either of unit norm,
+or optionally the vector `[0.0,0.0,0.0]`
+for *non-diffusion-weighted* (also called *b*=0 or *low-b*) volumes.
+These values are to be interpreted as cosine values with respect to the image axis orientations
+as defined by the corresponding NIfTI image header transformation;
+*unless* the image axes defined in the corresponding NIfTI image header
+form a right-handed coordinate system
+(that is, the 3x3 matrix of direction cosines has a positive determinant),
+in which case the sign of the first element of each 3-vector must be inverted
+for this interpretation to be valid.
+Note that this definition of orientations with respect to the NIfTI image axes
+is *not* equivalent to the DICOM convention,
+where orientations are instead defined with respect to the scanner device's coordinate system
+(see [Coordinate systems](../appendices/coordinate-systems.md)).
+
+The `[*_]dwi.bval` file contains the *b*-values (in s/mm2 )
+corresponding to the volumes in the relevant NIfTI file,
+with 0 designating *b*=0 volumes; space-delimited.
+
+Examples of `[*_]dwi.bvec` and `[*_]dwi.bval` files,
+corresponding to a NIfTI image with 6 volumes
+with the first two volumes having no diffusion sensitization:
+
+- `[*_]dwi.bvec`:
+ ```Text
+ 0 0 0.021828 -0.015425 -0.70918 -0.2465
+ 0 0 0.80242 0.22098 -0.00063106 0.1043
+ 0 0 -0.59636 0.97516 -0.70503 -0.96351
+ ```
+
+- `[_]dwi.bval`:
+ ```Text
+ 0 0 2000 2000 1000 1000
+ ```
### Multipart (split) DWI schemes
diff --git a/src/modality-specific-files/magnetic-resonance-spectroscopy.md b/src/modality-specific-files/magnetic-resonance-spectroscopy.md
new file mode 100644
index 0000000000..22ec6540e0
--- /dev/null
+++ b/src/modality-specific-files/magnetic-resonance-spectroscopy.md
@@ -0,0 +1,260 @@
+# Magnetic Resonance Spectroscopy
+
+Support for Magnetic Resonance Spectroscopy (MRS) was developed as a
+[BIDS Extension Proposal](../extensions.md#bids-extension-proposals).
+Please see [Citing BIDS](../introduction.md#citing-bids)
+on how to appropriately credit this extension when referring to it in the
+context of the academic literature.
+
+!!! example "Example datasets"
+
+ Several [example MRS datasets](https://github.com/bids-standard/bids-examples/pull/425) have
+ been formatted using this specification and can be used for practical guidance when curating a new
+ dataset.
+
+## MRS data
+
+
+{{ MACROS___make_filename_template("raw", datatypes=["mrs"]) }}
+
+MRS is a spectroscopic technique based on the phenomenon of nuclear magnetic resonance
+that allows for the noninvasive detection and quantification of molecules in biochemical samples, such as brain tissue.
+It can be conducted in humans using conventional MRI systems.
+
+Due to the diversity in manufacturers' MRS data file formats, source data MUST be converted into the
+[NIfTI-MRS format](https://wtclarke.github.io/mrs_nifti_standard/) (`*.nii[.gz]`) ([doi:10.1002/mrm.29418](https://doi.org/10.1002/mrm.29418)).
+This format is based on the NIfTI framework and is designed to accommodate the nuances of raw MRS data.
+All necessary information to parse this `*.nii[.gz]` file (for example, spectrometer frequency, echo time,
+repetition time, and so on) are stored in a JSON header extension.
+Conversion of proprietary MRS file formats to NIfTI-MRS and extraction of some (but not all) BIDS-compliant metadata can be performed
+using [spec2nii](https://github.com/wtclarke/spec2nii).
+Note that the "rawness" of data stored in the NIfTI-MRS file will depend on the format of the source data.
+It is RECOMMENDED that users export their source data from the scanner in an appropriately raw format prior to conversion.
+
+For MRSI data, "raw" signifies spatially reconstructed data (that is, data in image space rather than (*k*,*t*)-space),
+given the complexity and diversity of sampling approaches.
+Note that NIfTI-MRS is not designed to store data that has not been spatially reconstructed.
+
+Regarding source data, each manufacturer has its own file format (sometimes multiple formats) for exporting MRS data from
+the MRI scanner console for offline processing.
+GE exports a P-file (`*.7`) that stores unprocessed, un-coil-combined data with metadata embedded
+in a proprietary data header.
+Philips has multiple export formats, the most common being the SDAT/SPAR format.
+The `*.sdat` file contains either each coil-combined transient stored separately
+or all transients summed into a signal average.
+The `*.spar` file is a plaintext file describing acquisition parameters.
+It is also possible to export raw data as `*.data`/`*.list` or DICOM files.
+Siemens scanners allow data export in four formats: i) a proprietary DICOM-structured file known as IMA (`*.ima`);
+ii) a conventional DICOM MR Spectroscopy Storage format (`*.dcm`); iii) RDA (`*.rda`),
+a proprietary file format with a text-formatted header followed by the binary data points;
+and iv) TWIX (`*.dat`), a proprietary file format designed for storing unreconstructed, unprocessed MRS data
+from each individual coil element.
+The IMA, DICOM MRS, and RDA formats are typically used to export reconstructed and processed data;
+however, the sequence designer may choose to also allow the export of un-averaged transients
+or data from individual coil elements.
+Bruker data are are exported as two binary files: one file stores each transient separately,
+while the other stores the sum of the transients.
+A separate plaintext file stores the sequence name, voxel position, voxel orientation, and other metadata.
+All of these files are considered source data and, if present, MUST be stored in the
+[`sourcedata`](../common-principles.md#source-vs-raw-vs-derived-data) directory.
+
+### Single-voxel spectroscopy and MRS imaging
+
+| **Name** | **`suffix`** | **Description** |
+| ---------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Single-voxel spectroscopy | svs | MRS acquisitions where the detected MR signal is spatially localized to a single volume. |
+| Magnetic resonance spectroscopic imaging | mrsi | MRS acquisitions where additional imaging gradients are used to detect the MR signal from 1, 2, or 3 spatial dimensions. |
+| Unlocalized spectroscopy | unloc | MRS acquisitions run without localization. This includes signals detected using coil sensitivity only. |
+| Concentration or calibration reference | mrsref | An MRS acquisition collected to serve as a concentration reference for absolute quantification or as a calibration reference for preprocessing (for example, eddy-current correction). |
+
+A major distinction between MRS acquisitions is whether the acquisition technique probes spectral
+information from a single volume (single-voxel spectroscopy, SVS) or encodes this information along
+1, 2, or 3 spatial dimensions resulting in multiple sub-volumes (MRS imaging, MRSI).
+To avoid confusion, the suffixes `svs` and `mrsi` MUST be used to distinguish the two techniques.
+For cases where localization is not used, the suffix `unloc` MUST be used.
+
+Furthermore, it is common to acquire an additional MRS dataset that may serve as a reference for
+scaling metabolite signal levels (for example, to obtain concentrations) and/or for preprocessing steps (such as
+eddy-current correction, RF coil combination, phasing, and frequency calibration).
+This could be either an external reference (for example, a phantom or a synthetic signal) or, more typically,
+an internal tissue water reference.
+For such datasets, the suffix `mrsref` MUST be used.
+Should multiple references exist for a given dataset, the user MAY use the `acq-` entity to distinguish the files.
+For example, `sub-01_acq-conc_mrsref.nii.gz` and `sub-01_acq-ecc_mrsref.nii.gz` could be used to name
+two references to be used for concentration scaling and eddy-current correction, respectively.
+
+### MRS sequences
+
+Given the large variety of MRS sequences, there will be times when providing sufficient detail of
+acquisition parameters in filenames is helpful or necessary to distinguish datasets in a given study.
+
+Here we present a set of labels that can be used when using the `acq-` entity in the filename.
+These are based on the most commonly used in vivo MRS sequences/techniques, and are OPTIONAL to use.
+Users are free to choose any label they wish as long as they are consistent across participants
+and sessions and use only legal label characters.
+If used, the chosen label SHOULD also be described in the `PulseSequenceType` field in the sidecar JSON file.
+
+| **Name** | **`label`** | **Description** |
+| ------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| PRESS | press | A double spin-echo sequence that achieves spatial localization by employing three slice-selective RF pulses: 90°–180°–180°–acq. |
+| STEAM | steam | A stimulated-echo sequence that uses three 90° slice-selective pulses for spatial localization. |
+| LASER | laser | LASER uses three pairs of slice-selective 180° adiabatic full-passage (AFP) refocusing pulses for localization. These are preceded by a non-slice-selective adiabatic half-passage (AHP) excitation pulse. |
+| sLASER | slaser | sLASER is a modification of LASER where the AHP and first pair of AFP pulses are replaced with a non-adiabatic slice-selective 90° excitation pulse, typically employed to reduce the minimum TE. |
+| SPECIAL | special | SPECIAL is a two-shot experiment. In the first shot, a pre-excitation slice-selective 180° AFP inversion pulse precedes a spin-echo acquisition with slice selection (90°–180°–acq). In the second shot, the adiabatic pulse is not applied. The 3D localized signal is derived by subtracting the two shots. |
+| MEGA | mega | MEGA is a spectral editing technique that applies narrowband frequency-selective 180° pulses to refocus *J*-coupled spins at a specific frequency without affecting the spins of metabolites with resonances beyond the frequency range. Applying these pulses in alternating scans (for example, edit ON and edit OFF) and then subtracting the ON/OFF pairs results in a *J*-difference-edited spectrum that removes the unedited signals, leaving only those signals that were affected by the editing pulses. |
+| HERMES | hermes | HERMES is an extension of MEGA editing whereby the two-step experiment becomes a four-step experiment. This permits multiple metabolites to be edited in a multiplexed manner. By employing Hadamard combination of the four edited sub-spectra, HERMES can reveal several metabolites unambiguously. |
+| HERCULES | hercules | HERCULES is a different flavor of HERMES that targets more metabolites using the same four-step experiment. |
+| Multiple quantum coherence (MQC) editing | mqc | MQC editing targets *J*-coupled resonances by selecting desired coherence pathways using MQ gradients and frequency-selective RF pulses. |
+| Localized correlation spectroscopy (L-COSY) | lcosy | L-COSY is a 2D MRS technique whereby one of the interpulse durations is changed sequentially. A 2D Fourier transform produces a 2D spectrum that displays singlets on the diagonal and *J*-coupled metabolites on the off-diagonal, with the offsets equal to the *J*-coupling constants. |
+| *J*-resolved spectroscopy | j | Another 2D technique, where in a *J*-resolved acquisition, a series of transients are collected at different TEs. A 2D Fourier transform is applied to generate a 2D spectrum where one dimension characterizes both chemical shift and *J*-coupling and the other only *J*-coupling. |
+| Diffusion-weighted (DW) spectroscopy | dw | The diffusion of intracellular metabolites can be characterized using DW spectroscopy. In such acquisitions, the strength of gradients in a conventional MRS sequence is modulated to sensitize the metabolite signals to diffusion. |
+| FID spectroscopy | fid | FID spectroscopy is a pulse-acquire acquisition where an excitation pulse is followed by direct acquisition of the FID. This approach is most often used in MRSI (that is, FID-MRSI) when combined with slice- or slab-selection. |
+| Metabolite-cycled (MC) spectroscopy | mc | MC spectroscopy involves the use of asymmetric adiabatic inversion of the upfield and downfield parts of the MR spectrum, allowing for simultaneous acquisition of water and metabolite spectra. |
+| Spin-echo spectroscopy | spinecho | An MRS experiment whereby the MR signal is detected using a spin-echo acquisition: 90°–180°–acq. |
+
+Each `` in the table above MAY be combined with another to better describe the acquisition used.
+For example, `megaspecial`, `jpress`, `dwslaser`, `mcdwsteam`, and so on.
+
+The OPTIONAL `nuc-` entity can be used to distinguish acquisitions tuned to detect different nuclei.
+The label is the name of the nucleus or nuclei, which corresponds to DICOM Tag 0018, 9100.
+For example, `nuc-1H`, `nuc-31P`, `nuc-1H13C`.
+If used, the field `ResonantNucleus` MUST also be included in the corresponding sidecar JSON file, using the same label.
+
+Similarly, the OPTIONAL `voi-` entity can be used to distinguish between
+acquisitions localized to different regions (that is, acquisitions with different VOI).
+The label SHOULD be the name of the body region or part scanned.
+If used, the fields `BodyPart` and `BodyPartDetails` MUST also be included in the corresponding sidecar JSON file.
+`BodyPartDetailsOntology` is OPTIONAL to also include.
+
+## Sidecar JSON
+
+MRS data files MUST be described by metadata fields, stored in sidecar JSON files (`*.json`).
+
+### Common metadata fields
+
+Metadata described in the following sections are shared with other MR modalities that SHOULD
+or MAY be present in the sidecar JSON files.
+
+#### Scanner hardware
+
+
+{{ MACROS___make_sidecar_table("mrs.MRSScannerHardware") }}
+
+#### Sequence specifics
+
+
+{{ MACROS___make_sidecar_table("mrs.MRSSequenceSpecifics") }}
+
+#### Tissue description
+
+
+{{ MACROS___make_sidecar_table("mrs.MRSSample") }}
+
+### MRS-relevant fields
+
+Metadata fields that MUST be present:
+
+
+{{ MACROS___make_sidecar_table([
+ "mrs.MRSRequiredFields",
+ ])
+}}
+
+Metadata fields that SHOULD be present:
+
+
+{{ MACROS___make_sidecar_table([
+ "mrs.MRSRecommendedFields",
+ "mrs.MRSRepetitionTime",
+ "mrs.MRSVolumeTiming",
+ "mrs.MRSConditionalNumTransients",
+ "mrs.MRSIRecommendedFields",
+ "mrs.MRSConditionalInversionTime",
+ "mrs.MRSConditionalAnatomicalImage",
+ ])
+}}
+
+Metadata fields that MAY be present:
+
+
+{{ MACROS___make_sidecar_table([
+ "mrs.MRSOptionalFields",
+ ])
+}}
+
+### Example `*_svs.json`
+
+```JSON
+{
+ "InstitutionName": "Weill Cornell Medicine",
+ "InstitutionAddress": "1300 York Avenue, New York, NY 10065, USA",
+ "Manufacturer": "GE",
+ "ManufacturersModelName": "Discovery MR750",
+ "MagneticFieldStrength": 3,
+ "PulseSequenceType": "PRESS",
+ "ResonantNucleus": "1H",
+ "SpectrometerFrequency": 127.771,
+ "SpectralWidth": 2000,
+ "EchoTime": 0.035,
+ "NumberOfSpectralPoints": 2048,
+ "NumberOfTransients": 64,
+ "RepetitionTime": 2,
+ "AcquisitionVoxelSize": [40, 20, 30],
+ "BodyPart": "BRAIN",
+ "BodyPartDetails": "Anterior cingulate cortex",
+ "ReferenceSignal": "bids::sub-01/mrs/sub-01_acq-press_mrsref.nii.gz",
+ "AnatomicalImage": "bids::sub-01/anat/sub-01_T1w.nii.gz"
+}
+```
+
+## Combining MRS with anatomical MRI
+
+For combining MRS data with anatomical MRI data, see [MRS-MRI correspondence](../appendices/cross-modality-correspondence.md#mrs-mri-correspondence) in the Appendix.
diff --git a/src/modality-specific-files/magnetoencephalography.md b/src/modality-specific-files/magnetoencephalography.md
index c3896a908e..babc92be7c 100644
--- a/src/modality-specific-files/magnetoencephalography.md
+++ b/src/modality-specific-files/magnetoencephalography.md
@@ -343,7 +343,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("meg.MEGCoordsystemWithEEG") }}
+{{ MACROS___make_json_table("json.meg.MEGCoordsystemWithEEG") }}
Head localization coils:
@@ -355,7 +355,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("meg.MEGCoordsystemHeadLocalizationCoils") }}
+{{ MACROS___make_json_table("json.meg.MEGCoordsystemHeadLocalizationCoils") }}
Digitized head points:
@@ -367,7 +367,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("meg.MEGCoordsystemDigitizedHeadPoints") }}
+{{ MACROS___make_json_table("json.meg.MEGCoordsystemDigitizedHeadPoints") }}
Anatomical MRI:
@@ -379,7 +379,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("meg.MEGCoordsystemAnatomicalMRI") }}
+{{ MACROS___make_json_table("json.meg.MEGCoordsystemAnatomicalMRI") }}
Anatomical landmarks:
@@ -391,7 +391,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("meg.MEGCoordsystemAnatomicalLandmarks") }}
+{{ MACROS___make_json_table("json.meg.MEGCoordsystemAnatomicalLandmarks") }}
It is also RECOMMENDED that the MRI voxel coordinates of the actual anatomical
landmarks for co-registration of MEG with structural MRI are stored in the
@@ -419,7 +419,7 @@ The definitions of the fields specified in these tables may be found in
A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("meg.MEGCoordsystemFiducialsInformation") }}
+{{ MACROS___make_json_table("json.meg.MEGCoordsystemFiducialsInformation") }}
For more information on the definition of anatomical landmarks, please visit:
[How are the Left and Right Pre-Auricular (LPA and RPA) points defined? - FieldTrip Toolbox](https://www.fieldtriptoolbox.org/faq/how_are_the_lpa_and_rpa_points_defined/)
diff --git a/src/modality-specific-files/near-infrared-spectroscopy.md b/src/modality-specific-files/near-infrared-spectroscopy.md
index 7382560a43..b423b4c2dd 100644
--- a/src/modality-specific-files/near-infrared-spectroscopy.md
+++ b/src/modality-specific-files/near-infrared-spectroscopy.md
@@ -386,7 +386,7 @@ A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table("nirs.CoordsystemGeneral") }}
+{{ MACROS___make_json_table("json.nirs.CoordsystemGeneral") }}
Fields relating to the NIRS optode positions:
@@ -399,7 +399,7 @@ A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table(["nirs.CoordinateSystem", "nirs.CoordinateSystemDescriptionRec"]) }}
+{{ MACROS___make_json_table(["json.nirs.CoordinateSystem", "json.nirs.CoordinateSystemDescriptionRec"]) }}
Fields relating to the position of fiducials measured during an NIRS session/run:
@@ -412,7 +412,7 @@ A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table(["nirs.Fiducials", "nirs.FiducialsCoordinateSystemDescriptionRec"]) }}
+{{ MACROS___make_json_table(["json.nirs.Fiducials", "json.nirs.FiducialsCoordinateSystemDescriptionRec"]) }}
Fields relating to the position of anatomical landmarks measured during an NIRS session/run:
@@ -425,7 +425,7 @@ A guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_sidecar_table(["nirs.AnatomicalLandmark", "nirs.AnatomicalLandmarkCoordinateSystemDescriptionRec"]) }}
+{{ MACROS___make_json_table(["json.nirs.AnatomicalLandmark", "json.nirs.AnatomicalLandmarkCoordinateSystemDescriptionRec"]) }}
### Example `*_coordsystem.json`
diff --git a/src/schema/README.md b/src/schema/README.md
index 68be4f2a9b..6d2225bf69 100644
--- a/src/schema/README.md
+++ b/src/schema/README.md
@@ -259,20 +259,36 @@ The following operators should be defined by an interpreter:
The following functions should be defined by an interpreter:
-| Function | Definition | Example | Note |
-| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
-| `count(arg: array, val: any)` | Number of elements in an array equal to `val` | `count(columns.type, "EEG")` | The number of times "EEG" appears in the column "type" of the current TSV file |
-| `exists(arg: str \| array, rule: str) -> int` | Count of files in an array that exist in the dataset. String is array with length 1. Rules include `"bids-uri"`, `"dataset"`, `"subject"` and `"stimuli"`. | `exists(sidecar.IntendedFor, "subject")` | True if all files in `IntendedFor` exist, relative to the subject directory. |
-| `index(arg: array, val: any)` | Index of first element in an array equal to `val`, `null` if not found | `index(["i", "j", "k"], axis)` | The number, from 0-2 corresponding to the string `axis` |
-| `intersects(a: array, b: array) -> bool` | `true` if arguments contain any shared elements | `intersects(dataset.modalities, ["pet", "mri"])` | True if either PET or MRI data is found in dataset |
-| `allequal(a: array, b: array) -> bool` | `true` if arrays have the same length and paired elements are equal | `intersects(dataset.modalities, ["pet", "mri"])` | True if either PET or MRI data is found in dataset |
-| `length(arg: array) -> int` | Number of elements in an array | `length(columns.onset) > 0` | True if there is at least one value in the onset column |
-| `match(arg: str, pattern: str) -> bool` | `true` if `arg` matches the regular expression `pattern` (anywhere in string) | `match(extension, ".gz$")` | True if the file extension ends with `.gz` |
-| `max(arg: array) -> number` | The largest non-`n/a` value in an array | `max(columns.onset)` | The time of the last onset in an events.tsv file |
-| `min(arg: array) -> number` | The smallest non-`n/a` value in an array | `min(sidecar.SliceTiming) == 0` | A check that the onset of the first slice is 0s |
-| `sorted(arg: array, method: str) -> array` | The sorted values of the input array; defaults to type-determined sort. If method is "lexical", or "numeric" use lexical or numeric sort. | `sorted(sidecar.VolumeTiming) == sidecar.VolumeTiming` | True if `sidecar.VolumeTiming` is sorted |
-| `substr(arg: str, start: int, end: int) -> str` | The portion of the input string spanning from start position to end position | `substr(path, 0, length(path) - 3)` | `path` with the last three characters dropped |
-| `type(arg: Any) -> str` | The name of the type, including `"array"`, `"object"`, `"null"` | `type(datatypes)` | Returns `"array"` |
+| Function | Definition | Example | Note |
+| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
+| `count(arg: array, val: any) -> int` | Number of elements in an array equal to `val` | `count(columns.type, "EEG")` | The number of times "EEG" appears in the column "type" of the current TSV file |
+| `exists(arg: str \| array, rule: str) -> int` | Count of files in an array that exist in the dataset. String is array with length 1. See following section for the meanings of rules. | `exists(sidecar.IntendedFor, "subject")` | True if all files in `IntendedFor` exist, relative to the subject directory. |
+| `index(arg: array, val: any) -> int` | Index of first element in an array equal to `val`, `null` if not found | `index(["i", "j", "k"], axis)` | The number, from 0-2 corresponding to the string `axis` |
+| `intersects(a: array, b: array) -> bool` | `true` if arguments contain any shared elements | `intersects(dataset.modalities, ["pet", "mri"])` | True if either PET or MRI data is found in dataset |
+| `allequal(a: array, b: array) -> bool` | `true` if arrays have the same length and paired elements are equal | `intersects(dataset.modalities, ["pet", "mri"])` | True if either PET or MRI data is found in dataset |
+| `length(arg: array) -> int` | Number of elements in an array | `length(columns.onset) > 0` | True if there is at least one value in the onset column |
+| `match(arg: str, pattern: str) -> bool` | `true` if `arg` matches the regular expression `pattern` (anywhere in string) | `match(extension, ".gz$")` | True if the file extension ends with `.gz` |
+| `max(arg: array) -> number` | The largest non-`n/a` value in an array | `max(columns.onset)` | The time of the last onset in an events.tsv file |
+| `min(arg: array) -> number` | The smallest non-`n/a` value in an array | `min(sidecar.SliceTiming) == 0` | A check that the onset of the first slice is 0s |
+| `sorted(arg: array, method: str) -> array` | The sorted values of the input array; defaults to type-determined sort. If method is "lexical", or "numeric" use lexical or numeric sort. | `sorted(sidecar.VolumeTiming) == sidecar.VolumeTiming` | True if `sidecar.VolumeTiming` is sorted |
+| `substr(arg: str, start: int, end: int) -> str` | The portion of the input string spanning from start position to end position | `substr(path, 0, length(path) - 3)` | `path` with the last three characters dropped |
+| `type(arg: Any) -> str` | The name of the type, including `"array"`, `"object"`, `"null"` | `type(datatypes)` | Returns `"array"` |
+
+#### The `exists()` function
+
+In various places, BIDS datasets may declare links between files.
+In order to validate these links,
+the `exists()` function returns a count of files that can be found within the dataset.
+To accommodate the various ways of declaring these links,
+the following rules are defined:
+
+| `rule` | Definition | Example |
+| ------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
+| `"dataset"` | A path relative to the root of the dataset. | `exists('participants.tsv', 'dataset')` |
+| `"subject"` | A path relative to the current subject directory. | `exists('ses-1/anat/sub-01_ses-1_T1w.nii.gz', 'subject')` |
+| `"stimuli"` | A path relative to the `/stimuli` directory. | For `events.tsv`: `exists(columns.stim_file, 'stimuli') == length(columns.stim_file)` |
+| `"file"` | A path relative to the directory containing the current file. | For `scans.tsv`: `exists(columns.filename, 'file') == length(columns.stim_file)` |
+| `"bids-uri"` | A URI of the form `bids::`. If `` is empty, the current dataset is used. | `exists('bids::participants.tsv', 'bids-uri')` |
#### The special value `null`
diff --git a/src/schema/SCHEMA_VERSION b/src/schema/SCHEMA_VERSION
index 539f9fc668..72ab694d37 100644
--- a/src/schema/SCHEMA_VERSION
+++ b/src/schema/SCHEMA_VERSION
@@ -1 +1 @@
-0.10.1-dev
+0.11.1-dev
diff --git a/src/schema/meta/context.yaml b/src/schema/meta/context.yaml
index 280ae7b933..8783b46371 100644
--- a/src/schema/meta/context.yaml
+++ b/src/schema/meta/context.yaml
@@ -18,6 +18,14 @@
#
---
type: object
+required:
+ - schema
+ - dataset
+ - path
+ - size
+ - sidecar
+ - associations
+additionalProperties: false
properties:
schema:
description: 'The BIDS specification schema'
@@ -25,31 +33,45 @@ properties:
dataset:
description: 'Properties and contents of the entire dataset'
type: object
+ required:
+ - dataset_description
+ - tree
+ - ignored
+ - datatypes
+ - modalities
+ - subjects
+ additionalProperties: false
properties:
dataset_description:
description: 'Contents of /dataset_description.json'
type: object
- files:
- description: 'List of all files in dataset'
- type: array
tree:
description: 'Tree view of all files in dataset'
type: object
ignored:
description: 'Set of ignored files'
type: array
+ items:
+ type: string
datatypes:
description: 'Data types present in the dataset'
type: array
+ items:
+ type: string
modalities:
description: 'Modalities present in the dataset'
type: array
+ items:
+ type: string
subjects:
description: 'Collections of subjects in dataset'
type: object
+ required:
+ - sub_dirs
+ additionalProperties: false
properties:
sub_dirs:
- description: 'Subjects as determined by sub-*/ directories'
+ description: 'Subjects as determined by sub-* directories'
type: array
items:
type: string
@@ -66,13 +88,19 @@ properties:
subject:
description: 'Properties and contents of the current subject'
type: object
+ required:
+ - sessions
+ additionalProperties: false
properties:
sessions:
description: 'Collections of sessions in subject'
type: object
+ required:
+ - ses_dirs
+ additionalProperties: false
properties:
ses_dirs:
- description: 'Sessions as determined by ses-*/ directories'
+ description: 'Sessions as determined by ses-* directories'
type: array
items:
type: string
@@ -97,6 +125,8 @@ properties:
entities:
description: 'Entities parsed from the current filename'
type: object
+ additionalProperties:
+ type: string
datatype:
description: 'Datatype of current file, for examples, anat'
type: string
@@ -120,10 +150,13 @@ properties:
description: |
Associated files, indexed by suffix, selected according to the inheritance principle
type: object
+ additionalProperties: false
properties:
events:
description: 'Events file'
type: object
+ required: [path]
+ additionalProperties: false
properties:
path:
description: 'Path to associated events file'
@@ -136,6 +169,8 @@ properties:
aslcontext:
description: 'ASL context file'
type: object
+ required: [path, n_rows]
+ additionalProperties: false
properties:
path:
description: 'Path to associated aslcontext file'
@@ -151,6 +186,8 @@ properties:
m0scan:
description: 'M0 scan file'
type: object
+ required: [path]
+ additionalProperties: false
properties:
path:
description: 'Path to associated M0 scan file'
@@ -158,6 +195,8 @@ properties:
magnitude:
description: 'Magnitude image file'
type: object
+ required: [path]
+ additionalProperties: false
properties:
path:
description: 'Path to associated magnitude file'
@@ -165,6 +204,8 @@ properties:
magnitude1:
description: 'Magnitude1 image file'
type: object
+ required: [path]
+ additionalProperties: false
properties:
path:
description: 'Path to associated magnitude1 file'
@@ -172,6 +213,8 @@ properties:
bval:
description: 'B value file'
type: object
+ required: [path, n_cols, n_rows, values]
+ additionalProperties: false
properties:
path:
description: 'Path to associated bval file'
@@ -190,6 +233,8 @@ properties:
bvec:
description: 'B vector file'
type: object
+ required: [path, n_cols, n_rows]
+ additionalProperties: false
properties:
path:
description: 'Path to associated bvec file'
@@ -203,6 +248,8 @@ properties:
channels:
description: 'Channels file'
type: object
+ required: [path]
+ additionalProperties: false
properties:
path:
description: 'Path to associated channels file'
@@ -212,9 +259,21 @@ properties:
type: array
items:
type: string
+ short_channel:
+ description: 'Contents of the short_channel column'
+ type: array
+ items:
+ type: string
+ sampling_frequency:
+ description: 'Contents of the sampling_frequency column'
+ type: array
+ items:
+ type: string
coordsystem:
description: 'Coordinate system file'
type: object
+ required: [path]
+ additionalProperties: false
properties:
path:
description: 'Path to associated coordsystem file'
@@ -225,12 +284,16 @@ properties:
type: object
additionalProperties:
type: array
+ items:
+ type: string
json:
description: 'Contents of the current JSON file'
type: object
gzip:
description: 'Parsed contents of gzip header'
type: object
+ required: [timestamp]
+ additionalProperties: false
properties:
timestamp:
description: 'Modification time, unix timestamp'
@@ -245,11 +308,23 @@ properties:
name: 'NIfTI Header'
description: 'Parsed contents of NIfTI header referenced elsewhere in schema.'
type: object
+ required:
+ - dim_info
+ - dim
+ - pixdim
+ - shape
+ - voxel_sizes
+ - xyzt_units
+ - qform_code
+ - sform_code
+ additionalProperties: false
properties:
dim_info:
name: 'Dimension Information'
description: 'Metadata about dimensions data.'
type: object
+ required: [freq, phase, slice]
+ additionalProperties: false
properties:
freq:
name: 'Frequency'
@@ -299,6 +374,8 @@ properties:
name: 'XYZT Units'
description: 'Units of pixdim[1..4]'
type: object
+ required: [xyz, t]
+ additionalProperties: false
properties:
xyz:
name: 'XYZ Units'
@@ -328,10 +405,15 @@ properties:
name: 'sform code'
description: 'Use of the affine fields.'
type: integer
+ mrs:
+ name: 'NIfTI-MRS extension'
+ description: 'NIfTI-MRS JSON fields'
+ type: object
ome:
name: 'Open Microscopy Environment fields'
description: 'Parsed contents of OME-XML header, which may be found in OME-TIFF or OME-ZARR files'
type: object
+ additionalProperties: false
properties:
PhysicalSizeX:
name: 'PhysicalSizeX'
@@ -361,6 +443,9 @@ properties:
name: 'TIFF'
description: 'TIFF file format metadata'
type: object
+ required:
+ - version
+ additionalProperties: false
properties:
version:
name: 'Version'
diff --git a/src/schema/objects/columns.yaml b/src/schema/objects/columns.yaml
index d93145430e..16422d7beb 100644
--- a/src/schema/objects/columns.yaml
+++ b/src/schema/objects/columns.yaml
@@ -89,18 +89,13 @@ component:
- $ref: objects.enums.quat_y.value
- $ref: objects.enums.quat_z.value
- $ref: objects.enums.quat_w.value
- - n/a
detector__channels:
name: detector
display_name: Detector Name
description: |
Name of the detector as specified in the `*_optodes.tsv` file.
`n/a` for channels that do not contain NIRS signals (for example, acceleration).
- anyOf:
- - type: string
- - type: string
- enum:
- - n/a
+ type: string
detector_type:
name: detector_type
display_name: Detector Type
@@ -164,13 +159,9 @@ duration:
Must always be either zero or positive (or `n/a` if unavailable).
A "duration" value of zero implies that the delta function or event is so
short as to be effectively modeled as an impulse.
- anyOf:
- - type: number
- unit: s
- minimum: 0
- - type: string
- enum:
- - n/a
+ type: number
+ unit: s
+ minimum: 0
filename:
name: filename
display_name: Filename
@@ -238,13 +229,9 @@ high_cutoff:
If no low-pass filter applied, use `n/a`.
Note that hardware anti-aliasing in A/D conversion of all MEG/EEG electronics
applies a low-pass filter; specify its frequency here if applicable.
- anyOf:
- - type: number
- unit: Hz
- minimum: 0
- - type: string
- enum:
- - n/a
+ type: number
+ unit: Hz
+ minimum: 0
hplc_recovery_fractions:
name: hplc_recovery_fractions
display_name: HPLC recovery fractions
@@ -271,12 +258,8 @@ low_cutoff:
description: |
Frequencies used for the high-pass filter applied to the channel in Hz.
If no high-pass filter applied, use `n/a`.
- anyOf:
- - type: number
- unit: Hz
- - type: string
- enum:
- - n/a
+ type: number
+ unit: Hz
manufacturer:
name: manufacturer
display_name: Manufacturer
@@ -417,11 +400,7 @@ reference__ieeg:
description: |
Specification of the reference (for example, `mastoid`, `ElectrodeName01`, `intracranial`, `CAR`, `other`, `n/a`).
If the channel is not an electrode channel (for example, a microphone channel) use `n/a`.
- anyOf:
- - type: string
- - type: string
- enum:
- - n/a
+ type: string
reference_frame:
name: reference_frame
display_name: Reference frame
@@ -429,11 +408,7 @@ reference_frame:
Specification of a reference frame in which the motion data are to be interpreted.
The description of the levels in `*_channels.json` SHOULD use `RotationRule`,
`RotationOrder`, and `SpatialAxes`, and MAY use `Description` for the specification.
- anyOf:
- - type: string
- - type: string
- enum:
- - n/a
+ type: string
respiratory:
name: respiratory
display_name: Respiratory measurement
@@ -450,12 +425,8 @@ response_time:
Response time measured in seconds.
A negative response time can be used to represent preemptive responses and
`n/a` denotes a missed response.
- anyOf:
- - type: number
- unit: s
- - type: string
- enum:
- - n/a
+ type: number
+ unit: s
sample_id:
name: sample_id
display_name: Sample ID
@@ -551,22 +522,14 @@ software_filters:
(for example, `SSS`, `SpatialCompensation`).
Note that parameters should be defined in the general MEG sidecar .json file.
Indicate `n/a` in the absence of software filters applied.
- anyOf:
- - type: string
- - type: string
- enum:
- - n/a
+ type: string
source__channels:
name: source
display_name: Source name
description: |
Name of the source as specified in the `*_optodes.tsv` file.
`n/a` for channels that do not contain fNIRS signals (for example, acceleration).
- anyOf:
- - type: string
- - type: string
- enum:
- - n/a
+ type: string
source__optodes:
name: source_type
display_name: Source type
@@ -599,7 +562,6 @@ status:
enum:
- $ref: objects.enums.good.value
- $ref: objects.enums.bad.value
- - n/a
status_description:
name: status_description
display_name: Channel status description
@@ -746,7 +708,6 @@ type__optodes:
enum:
- $ref: objects.enums.source.value
- $ref: objects.enums.detector.value
- - n/a
units:
name: units
display_name: Units
@@ -801,11 +762,7 @@ wavelength_nominal:
Specified wavelength of light in nm.
`n/a` for channels that do not contain raw NIRS signals (for example, acceleration).
This field is equivalent to `/nirs(i)/probe/wavelengths` in the SNIRF specification.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
wavelength_actual:
name: wavelength_actual
display_name: Wavelength actual
@@ -846,71 +803,43 @@ z:
display_name: Z position
description: |
Recorded position along the z-axis.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
x__optodes:
name: x
display_name: X position
description: |
Recorded position along the x-axis.
`"n/a"` if not available.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
y__optodes:
name: y
display_name: Y position
description: |
Recorded position along the y-axis.
`"n/a"` if not available.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
z__optodes:
name: z
display_name: Z position
description: |
Recorded position along the z-axis.
`"n/a"` if not available.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
template_x:
name: template_x
display_name: X template position
description: |
Assumed or ideal position along the x axis.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
template_y:
name: template_y
display_name: Y template position
description: |
Assumed or ideal position along the y axis.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
template_z:
name: template_z
display_name: Z template position
description: |
Assumed or ideal position along the z axis.
- anyOf:
- - type: number
- - type: string
- enum:
- - n/a
+ type: number
diff --git a/src/schema/objects/common_principles.yaml b/src/schema/objects/common_principles.yaml
index 7e669efb52..06a1fa53d0 100644
--- a/src/schema/objects/common_principles.yaml
+++ b/src/schema/objects/common_principles.yaml
@@ -41,6 +41,9 @@ data_type:
12. `nirs` (near infrared spectroscopy)
13. `motion` (motion)
+
+ 14. `mrs` (magnetic resonance spectroscopy)
+
dataset:
display_name: Dataset
description: |
diff --git a/src/schema/objects/datatypes.yaml b/src/schema/objects/datatypes.yaml
index c51fa41382..e84f1a6ba6 100644
--- a/src/schema/objects/datatypes.yaml
+++ b/src/schema/objects/datatypes.yaml
@@ -47,6 +47,10 @@ motion:
value: motion
display_name: Motion
description: Motion data from a tracking system
+mrs:
+ value: mrs
+ display_name: Magnetic Resonance Spectroscopy
+ description: Magnetic resonance spectroscopy data
perf:
value: perf
display_name: Perfusion imaging
diff --git a/src/schema/objects/entities.yaml b/src/schema/objects/entities.yaml
index 6facc34c0d..ee5ab67534 100644
--- a/src/schema/objects/entities.yaml
+++ b/src/schema/objects/entities.yaml
@@ -179,6 +179,17 @@ mtransfer:
enum:
- $ref: objects.enums.on__mtransfer.value
- $ref: objects.enums.off__mtransfer.value
+nucleus:
+ name: nuc
+ display_name: Nucleus
+ description: |
+ The `nuc-` entity can be used to distinguish acquisitions tuned
+ to detect different nuclei.
+ The label is the name of the nucleus or nuclei, which corresponds to DICOM Tag `0018, 9100`.
+ If present in the filename, `"ResonantNucleus"` MUST also be included in
+ the associated metadata.
+ type: string
+ format: label
part:
name: part
display_name: Part
@@ -408,9 +419,19 @@ tracer:
Please note that the `` does not need to match the actual value of the field.
type: string
format: label
+volume:
+ name: voi
+ display_name: Volume of Interest
+ description: |
+ The `voi-` entity can be used to distinguish acquisitions localized to different regions.
+ The label SHOULD be the name of the body region or part scanned.
+ If used, the fields `"BodyPart"` and `"BodyPartDetails"` MUST be defined in the JSON file.
+ `BodyPartDetailsOntology` is OPTIONAL to also include.
+ type: string
+ format: label
tracksys:
name: tracksys
- display_name: Tracking system
+ display_name: Tracking System
description: |
The `tracksys-` entity can be used as a key-value pair
to label *_motion.tsv and *_motion.json files.
diff --git a/src/schema/objects/enums.yaml b/src/schema/objects/enums.yaml
index 2f63006460..6c43592c9f 100644
--- a/src/schema/objects/enums.yaml
+++ b/src/schema/objects/enums.yaml
@@ -120,6 +120,11 @@ Absent:
display_name: Absent
description: |
No specific M0 information is present.
+OneD:
+ value: 1D
+ display_name: One-dimensional
+ description: |
+ One-dimensional MR acquisition.
TwoD:
value: 2D
display_name: Two-dimensional
diff --git a/src/schema/objects/extensions.yaml b/src/schema/objects/extensions.yaml
index db38cf55b2..d482f8849d 100644
--- a/src/schema/objects/extensions.yaml
+++ b/src/schema/objects/extensions.yaml
@@ -30,21 +30,26 @@ bvec:
description: |
A space-delimited file containing gradient directions (b-vectors) of diffusion measurement.
- This file contains 3 rows with *N* space-delimited floating-point numbers,
+ The `bvec` file contains 3 rows with *N* space-delimited floating-point numbers,
corresponding to the *N* volumes in the corresponding NIfTI file.
- The first row contains the *x* elements, the second row contains the *y* elements and
- the third row contains the *z* elements of a unit vector in the direction of the applied
- diffusion gradient, where the *i*-th elements in each row correspond together to
- the *i*-th volume, with `[0,0,0]` for *non-diffusion-weighted* (also called *b*=0 or *low-b*)
- volumes.
+ Across these three rows,
+ each column encodes three elements of a 3-vector for the corresponding image volume;
+ each vector MUST be either of unit norm,
+ or optionally the vector `[0.0,0.0,0.0]`
+ for *non-diffusion-weighted* (also called *b*=0 or *low-b*) volumes.
+ These values are to be interpreted as cosine values with respect to the image axis orientations
+ as defined by the corresponding NIfTI image header transformation;
+ *unless* the image axes defined in the corresponding NIfTI image header
+ form a right-handed coordinate system
+ (that is, the 3x3 matrix of direction cosines has a positive determinant),
+ in which case the sign of the first element of each 3-vector must be inverted
+ for this interpretation to be valid.
- Following the FSL format for the `bvec` specification, the coordinate system of
- the *b* vectors MUST be defined with respect to the coordinate system defined by
- the header of the corresponding `_dwi` NIfTI file and not the scanner's device
- coordinate system (see [Coordinate systems](SPEC_ROOT/appendices/coordinate-systems.md)).
- The most relevant limitation imposed by this choice is that the gradient information cannot
- be directly stored in this format if the scanner generates *b*-vectors in *scanner coordinates*.
+ Note that this definition of orientations with respect to the NIfTI image axes
+ is *not* equivalent to the DICOM convention,
+ where orientations are instead defined with respect to the scanner device's coordinate system
+ (see [Coordinate systems](SPEC_ROOT/appendices/coordinate-systems.md)).
chn:
value: .chn
display_name: KRISS CHN
diff --git a/src/schema/objects/metadata.yaml b/src/schema/objects/metadata.yaml
index 5614a37c3f..d99fcc599b 100644
--- a/src/schema/objects/metadata.yaml
+++ b/src/schema/objects/metadata.yaml
@@ -37,7 +37,7 @@ AcquisitionVoxelSize:
display_name: Acquisition Voxel Size
description: |
An array of numbers with a length of 3, in millimeters.
- This parameter denotes the original acquisition voxel size,
+ This field denotes the original acquisition voxel size,
excluding any inter-slice gaps and before any interpolation or resampling
within reconstruction or image processing.
Any point spread function effects, for example due to T2-blurring,
@@ -70,6 +70,20 @@ AnalyticalApproach:
- type: array
items:
type: string
+AnatomicalImage:
+ name: AnatomicalImage
+ display_name: Anatomical Image
+ description: |
+ The path(s) to the anatomical MR image file(s), if present,
+ to which the associated MRS data file corresponds.
+ Contains one or more [BIDS URIs](SPEC_ROOT/common-principles.md#bids-uri).
+ anyOf:
+ - type: string
+ format: bids_uri
+ - type: array
+ items:
+ type: string
+ format: bids_uri
AnatomicalLandmarkCoordinateSystem:
name: AnatomicalLandmarkCoordinateSystem
display_name: Anatomical Landmark Coordinate System
@@ -236,14 +250,14 @@ B0ShimmingTechnique:
name: B0ShimmingTechnique
display_name: B0 Shimming Technique
description: |
- The technique used to shim the *B0 * field (for example, `"Dynamic shim updating"`
+ The technique used to shim the *B*0 field (for example, `"Dynamic shim updating"`
or `"FASTMAP"`).
type: string
B1ShimmingTechnique:
name: B1ShimmingTechnique
display_name: B1 Shimming Technique
description: |
- The technique used to shim the *B1 * field (for example, `"Simple phase align"`
+ The technique used to shim the *B*1 field (for example, `"Simple phase align"`
or `"Pre-saturated TurboFLASH"`).
type: string
BIDSVersion:
@@ -402,6 +416,22 @@ CellType:
Values SHOULD come from the
[cell ontology](https://obofoundry.org/ontology/cl.html).
type: string
+ChemicalShiftOffset:
+ name: ChemicalShiftOffset
+ display_name: Chemical Shift Offset
+ description: |
+ The chemical shift at the center of `SpectralWidth` corresponding to 0 Hz,
+ specified in ppm (for example, `4.65`).
+ type: number
+ unit: ppm
+ChemicalShiftReference:
+ name: ChemicalShiftReference
+ display_name: Chemical Shift Reference
+ description: |
+ The chemical shift at the transmitter frequency, specified in ppm (for example, `2.68`).
+ Corresponds to DICOM Tag 0018, 9053 `Chemical Shift Reference`.
+ type: number
+ unit: ppm
ChunkTransformationMatrix:
name: ChunkTransformationMatrix
display_name: Chunk Transformation Matrix
@@ -844,6 +874,13 @@ EOGChannelCount:
Number of EOG channels.
type: integer
minimum: 0
+EchoAcquisition:
+ name: EchoAcquisition
+ display_name: Echo Acquisition
+ description: |
+ How the detected echo was acquired when the analog-to-digital converter was turned on.
+ For example, `"Half echo"`, `"Full echo"`.
+ type: string
EchoTime:
name: EchoTime
display_name: Echo Time
@@ -856,12 +893,6 @@ EchoTime:
[file collection](SPEC_ROOT/appendices/file-collections.md)
where the value of this field is iterated using the
[`echo` entity](SPEC_ROOT/appendices/entities.md#echo).
- The data type array provides a value for each volume in a 4D dataset and
- should only be used when the volume timing is critical for interpretation
- of the data, such as in
- [ASL](SPEC_ROOT/modality-specific-files/magnetic-resonance-imaging-data.md#\
- arterial-spin-labeling-perfusion-data)
- or variable echo time fMRI sequences.
anyOf:
- type: number
unit: s
@@ -896,6 +927,55 @@ EchoTime__fmap:
type: number
unit: s
exclusiveMinimum: 0
+EditCondition:
+ name: EditCondition
+ display_name: Editing Condition
+ description: |
+ If spectral editing was applied, this lists the application order of `"EditPulse"`.
+ For example, `["ON", "OFF"]` for a MEGA-edited experiment or
+ `["A", "B", "C", "D"]` for a HERMES-edited experiment.
+ anyOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+EditPulse:
+ name: EditPulse
+ display_name: Editing Pulse
+ description: |
+ If spectral editing was applied, this details the editing parameters.
+ For example:
+ `{"ON": {"FrequencyOffset": 1.9, "PulseDuration": 16},
+ "OFF": {"FrequencyOffset": 7.5, "PulseDuration": 16}}`.
+ type: object
+ additionalProperties:
+ type: object
+ recommended_fields: [FrequencyOffset, PulseDuration]
+ properties:
+ FrequencyOffset:
+ anyOf:
+ - type: number
+ unit: ppm
+ - type: array
+ items:
+ type: number
+ unit: ppm
+ PulseDuration:
+ type: number
+ unit: ms
+EditTarget:
+ name: EditTarget
+ display_name: Editing Target
+ description: |
+ If spectral editing was applied, this describes the metabolites that were selectively targeted
+ (for example, `"GABA"` or `"Lac"`).
+ If multiple metabolites were targeted (for example, in a HERMES acquisition),
+ an array can be used: `["GABA", "GSH"]`.
+ anyOf:
+ - type: string
+ - type: array
+ items:
+ type: string
EffectiveEchoSpacing:
name: EffectiveEchoSpacing
display_name: Effective Echo Spacing
@@ -945,6 +1025,14 @@ ElectrodeManufacturersModelName:
If different electrode types are used,
please use the corresponding table in the `_electrodes.tsv` file.
type: string
+EncodingTechnique:
+ name: EncodingTechnique
+ display_name: Encoding Technique
+ description: |
+ The encoding technique used during readout.
+ For example, `"Cartesian"`, `"EPSI"`, `"Spiral"`,
+ or `"Density-weighted concentric ring trajectory"`.
+ type: string
EpochLength:
name: EpochLength
display_name: Epoch Length
@@ -1050,12 +1138,6 @@ FlipAngle:
[file collection](SPEC_ROOT/appendices/file-collections.md)
where the value of this field is iterated using the
[`flip` entity](SPEC_ROOT/appendices/entities.md#flip).
- The data type array provides a value for each volume in a 4D dataset and
- should only be used when the volume timing is critical for interpretation of
- the data, such as in
- [ASL](SPEC_ROOT/modality-specific-files/magnetic-resonance-imaging-data.md#\
- arterial-spin-labeling-perfusion-data)
- or variable flip angle fMRI sequences.
anyOf:
- type: number
unit: degree
@@ -1875,6 +1957,7 @@ MRAcquisitionType:
Corresponds to DICOM Tag 0018, 0023 `MR Acquisition Type`.
type: string
enum:
+ - $ref: objects.enums.OneD.value
- $ref: objects.enums.TwoD.value
- $ref: objects.enums.ThreeD.value
MRTransmitCoilSequence:
@@ -1980,6 +2063,19 @@ MatrixCoilMode:
There are typically different default modes when using un-accelerated or
accelerated (for example, `"GRAPPA"`, `"SENSE"`) imaging.
type: string
+MatrixSize:
+ name: MatrixSize
+ display_name: Matrix Size
+ description: |
+ An array of integers with a length of 3 denoting the matrix size of the acquisition slab.
+ This should be specified as, for example, `[32, 32, 1]` for a 2D acquisition
+ or `[32, 1, 1]` for a 1D acquisition.
+ type: array
+ minItems: 3
+ maxItems: 3
+ items:
+ type: integer
+ minimum: 1
MaxMovement:
name: MaxMovement
display_name: Max Movement
@@ -2044,7 +2140,8 @@ MixingTime:
name: MixingTime
display_name: Mixing Time
description: |
- In the context of a stimulated- and spin-echo 3D EPI sequence for B1+ mapping,
+ In the context of a stimulated- and spin-echo 3D EPI sequence for B1+ mapping
+ or a stimulated-echo MRS sequence,
corresponds to the interval between spin- and stimulated-echo pulses.
In the context of a diffusion-weighted double spin-echo sequence,
corresponds to the interval between two successive diffusion sensitizing
@@ -2199,6 +2296,19 @@ NonlinearGradientCorrection:
Boolean stating if the image saved has been corrected for gradient
nonlinearities by the scanner sequence.
type: boolean
+NumberOfSpectralPoints:
+ name: NumberOfSpectralPoints
+ display_name: Number Of Spectral Points
+ description: |
+ The number of complex data points in each recorded transient of the detected time-domain MR
+ signal, equivalent to the number of points in a single spectrum.
+ type: integer
+NumberOfTransients:
+ name: NumberOfTransients
+ display_name: Number Of Transients
+ description: |
+ The number of single applications of the pulse sequence recorded during an MRS acquisition.
+ type: integer
NumberOfVolumesDiscardedByScanner:
name: NumberOfVolumesDiscardedByScanner
display_name: Number Of Volumes Discarded By Scanner
@@ -2287,6 +2397,12 @@ OtherAcquisitionParameters:
description: |
Description of other relevant image acquisition parameters.
type: string
+OuterVolumeSuppression:
+ name: OuterVolumeSuppression
+ display_name: Outer-Volume Suppression
+ description: |
+ Boolean indicating whether outer-volume suppression was used prior to acquisition.
+ type: boolean
PASLType:
name: PASLType
display_name: PASL Type
@@ -2536,13 +2652,34 @@ PulseSequenceDetails:
`"Siemens WIP ### version #.##,"` or
`"Sequence written by X using a version compiled on MM/DD/YYYY"`).
type: string
+PulseSequencePulses:
+ name: PulseSequencePulses
+ display_name: Pulse Sequence Pulses
+ description: |
+ The list of pulses used in the pulse sequence.
+ If this field is specified, the array size MUST equal the array size of `"PulseSequenceTiming"`.
+ The strings MAY be of the format `_R`.
+ For example, a sLASER sequence may be described as such:
+ `["P10_R6", "HS4_R25", "HS4_R25", "HS4_R25", "HS4_R25"]`.
+ type: array
+ items:
+ type: string
+PulseSequenceTiming:
+ name: PulseSequenceTiming
+ display_name: Pulse Sequence Timing
+ description: |
+ The time when each RF pulse of the pulse sequence was played out relative to
+ the beginning of the pulse sequence (that is, the top of the excitation RF pulse),
+ specified in seconds.
+ type: array
+ items:
+ type: number
+ unit: s
PulseSequenceType:
name: PulseSequenceType
display_name: Pulse Sequence Type
description: |
- A general description of the pulse sequence used for the scan
- (for example, `"MPRAGE"`, `"Gradient Echo EPI"`, `"Spin Echo EPI"`,
- `"Multiband gradient echo EPI"`).
+ A general description of the pulse sequence used for the scan.
type: string
Purity:
name: Purity
@@ -2591,6 +2728,18 @@ ReceiveCoilName:
in which case this field can be derived from an appropriate
private DICOM field.
type: string
+ReceiveGain:
+ name: ReceiveGain
+ display_name: Receive Gain
+ description: |
+ The gain of the receive coil.
+ anyOf:
+ - type: number
+ unit: dB
+ - type: array
+ items:
+ type: number
+ unit: dB
ReconFilterSize:
name: ReconFilterSize
display_name: Recon Filter Size
@@ -2679,6 +2828,20 @@ ReferencesAndLinks:
items:
type: string
type: array
+ReferenceSignal:
+ name: ReferenceSignal
+ display_name: Reference Signal
+ description: |
+ The path(s) to the MRS reference file(s), if present, to which the associated
+ MRS data file corresponds.
+ Contains one or more [BIDS URIs](SPEC_ROOT/common-principles.md#bids-uri).
+ anyOf:
+ - type: string
+ format: bids_uri
+ - type: array
+ items:
+ type: string
+ format: bids_uri
RepetitionTime:
name: RepetitionTime
display_name: Repetition Time
@@ -2757,6 +2920,17 @@ Resolution:
- type: object
additionalProperties:
type: string
+ResonantNucleus:
+ name: ResonantNucleus
+ display_name: Resonant Nucleus
+ description: |
+ The isotope of interest of an MR experiment (for example, `"1H"`, `"13C"`, `"31P"`).
+ For multi-nuclei experiments such as 1 H-[13 C] MR,
+ an array can be used: `["1H", "13C"]`.
+ Corresponds to DICOM Tag 0018, 9100 `Resonant Nucleus`.
+ type: array
+ items:
+ type: string
RotationOrder:
name: RotationOrder
display_name: RotationOrder
@@ -2966,6 +3140,16 @@ ScanningSequence:
- type: array
items:
type: string
+ScanningSequence__mrs:
+ name: ScanningSequence
+ display_name: Scanning Sequence
+ description: |
+ Description of the type of data acquired.
+ type: string
+ enum:
+ - SVS
+ - MRSI
+ - Unlocalized MRS
ScatterFraction:
name: ScatterFraction
display_name: Scatter Fraction
@@ -3287,6 +3471,27 @@ SpecificRadioactivityUnits:
- type: string
enum:
- n/a
+SpectralWidth:
+ name: SpectralWidth
+ display_name: Spectral Width
+ description: |
+ The spectral bandwidth of the MR signal that is sampled, specified in Hz.
+ Corresponds to DICOM Tag 0018, 9052 `Spectral Width`.
+ type: number
+ unit: Hz
+SpectrometerFrequency:
+ name: SpectrometerFrequency
+ display_name: Spectrometer Frequency
+ description: |
+ The frequency of the spectrometer, specified in MHz.
+ For example, this could be `127.764` for a 3T scanner tuned
+ to the resonant frequency of 1 H.
+ For multi-nuclei experiments such as 1 H-[13 C] MR at 3T,
+ an array can be used: `[127.731, 32.125]`.
+ type: array
+ items:
+ type: number
+ unit: MHz
SpoilingGradientDuration:
name: SpoilingGradientDuration
display_name: Spoiling Gradient Duration
@@ -3392,6 +3597,23 @@ SubjectArtefactDescription:
If this field is set to `"n/a"`, it will be interpreted as absence of major
source of artifacts except cardiac and blinks.
type: string
+TablePosition:
+ name: TablePosition
+ display_name: Table Position
+ description: |
+ The table position, relative to an implementation-specific reference point,
+ often the isocenter. Values must be an array (1x3) of three distances in
+ millimeters in absolute coordinates (world coordinates). If an observer
+ stands in front of the scanner looking at it, a table moving to the left,
+ up or into the scanner (from the observer's point of view) will increase
+ the 1st, 2nd and 3rd value in the array respectively. The origin is defined
+ by the image affine.
+ type: array
+ minItems: 3
+ maxItems: 3
+ items:
+ type: number
+ unit: mm
TaskDescription:
name: TaskDescription
display_name: Task Description
@@ -3589,6 +3811,27 @@ VascularCrushingVENC:
items:
type: number
unit: cm/s
+VolumeAffineMatrix:
+ name: VolumeAffineMatrix
+ display_name: Volume Affine Matrix
+ description: |
+ A 4-by-4 matrix using identical conventions and coordinate system to
+ the *{qs}form* NIfTI affine matrix to define the orientation,
+ position, and size of an additional VOI.
+ This VOI defines a spatial region in addition to the primary method of localization
+ (encoded in the NIfTI header *{qs}form*).
+ Typically not defined for data stored with a single spatial voxel or FID-MRSI.
+ For example: `[[30, 0, 0, -30], [0, 30, -2.27, -72.67], [0, 2.27, 29.91, 5.47],
+ [0, 0, 0, 1]]`.
+ type: array
+ minItems: 4
+ maxItems: 4
+ items:
+ type: array
+ minItems: 4
+ maxItems: 4
+ items:
+ type: number
VELChannelCount:
name: VELChannelCount
display_name: Velocity Channel Count
@@ -3609,12 +3852,10 @@ VolumeTiming:
description: |
The time at which each volume was acquired during the acquisition.
It is described using a list of times referring to the onset of each volume
- in the BOLD series.
- The list must have the same length as the BOLD series,
+ in the series.
+ The list must have the same length as the series,
and the values must be non-negative and monotonically increasing.
- This field is mutually exclusive with `"RepetitionTime"` and `"DelayTime"`.
- If defined, this requires acquisition time (TA) be defined via either
- `"SliceTiming"` or `"AcquisitionDuration"` be defined.
+ This field is mutually exclusive with `"RepetitionTime"`.
type: array
minItems: 1
items:
diff --git a/src/schema/objects/modalities.yaml b/src/schema/objects/modalities.yaml
index 4809e380df..b80c73d0e1 100644
--- a/src/schema/objects/modalities.yaml
+++ b/src/schema/objects/modalities.yaml
@@ -35,3 +35,6 @@ motion:
nirs:
display_name: Near-Infrared Spectroscopy
description: Data acquired with NIRS.
+mrs:
+ display_name: Magnetic Resonance Spectroscopy
+ description: Data acquired with MRS.
diff --git a/src/schema/objects/suffixes.yaml b/src/schema/objects/suffixes.yaml
index f11740b65b..d49789a7bc 100644
--- a/src/schema/objects/suffixes.yaml
+++ b/src/schema/objects/suffixes.yaml
@@ -716,6 +716,18 @@ motion:
display_name: Motion
description: |
Data recorded from a tracking system store.
+mrsi:
+ value: mrsi
+ display_name: Magnetic resonance spectroscopy imaging
+ description: |
+ MRS acquisitions where additional imaging gradients are used
+ to detect the MR signal from 1, 2, or 3 spatial dimensions.
+mrsref:
+ value: mrsref
+ display_name: MRS reference acquisition
+ description: |
+ An MRS acquisition collected to serve as a concentration reference for absolute quantification
+ or as a calibration reference for preprocessing (for example, eddy-current correction).
nirs:
value: nirs
display_name: Near Infrared Spectroscopy
@@ -842,6 +854,11 @@ stim:
display_name: Continuous recording
description: |
Continuous measures, such as parameters of a film or audio stimulus.
+svs:
+ value: svs
+ display_name: Single-voxel spectroscopy
+ description: |
+ MRS acquisitions where the detected MR signal is spatially localized to a single volume.
trace:
value: trace
display_name: Trace-weighted diffusion image
@@ -852,3 +869,9 @@ uCT:
display_name: Micro-CT
description: |
Micro-CT imaging data
+unloc:
+ value: unloc
+ display_name: Unlocalized spectroscopy
+ description: |
+ MRS acquisitions run without localization.
+ This includes signals detected using coil sensitivity only.
diff --git a/src/schema/rules/checks/common_derivatives.yaml b/src/schema/rules/checks/common_derivatives.yaml
index 723ddf0e59..6d9cd2eda7 100644
--- a/src/schema/rules/checks/common_derivatives.yaml
+++ b/src/schema/rules/checks/common_derivatives.yaml
@@ -1,18 +1,32 @@
---
ResInSidecar:
+ issue:
+ code: MISSING_RESOLUTION_DESCRIPTION
+ message: |
+ The Resolution metadata object does not contain an entry for the file's
+ res- entity.
+ level: error
selectors:
- dataset.dataset_description.DatasetType == "derivative"
- intersects([modality], ["mri", "pet"])
- match(extension, '^\.nii(\.gz)?$')
+ - type(entities.resolution) != 'null'
- type(sidecar.Resolution) == "object"
checks:
- entities.resolution in sidecar.Resolution
DenInSidecar:
+ issue:
+ code: MISSING_DENSITY_DESCRIPTION
+ message: |
+ The Density metadata object does not contain an entry for the file's
+ res- entity.
+ level: error
selectors:
- dataset.dataset_description.DatasetType == "derivative"
- intersects([modality], ["mri", "pet"])
- match(extension, '^\.nii(\.gz)?$')
+ - type(entities.density) != 'null'
- type(sidecar.Density) == "object"
checks:
- entities.density in sidecar.Density
diff --git a/src/schema/rules/checks/dataset.yaml b/src/schema/rules/checks/dataset.yaml
index 91704d32e4..69444aed9e 100644
--- a/src/schema/rules/checks/dataset.yaml
+++ b/src/schema/rules/checks/dataset.yaml
@@ -51,7 +51,7 @@ SamplesTSVMissing:
- path == '/dataset_description.json'
- '"micr" in dataset.modalities'
checks:
- - "'samples.tsv' in dataset.files"
+ - exists('samples.tsv', 'dataset')
UnknownVersion:
issue:
@@ -91,3 +91,15 @@ SingleSourceCitationFields:
- '!("HowToAcknowledge" in dataset.dataset_description)'
- '!("License" in dataset.dataset_description)'
- '!("ReferencesAndLinks" in dataset.dataset_description)'
+
+ScansTSVScans:
+ issue:
+ code: SCANS_FILENAME_NOT_MATCH_DATASET
+ level: error
+ message: |
+ Filenames in scans.tsv file do not match what is present in the BIDS dataset.
+ selectors:
+ - suffix == 'scans'
+ - extension == '.tsv'
+ checks:
+ - exists(columns.filename, "file") == length(columns.filename)
diff --git a/src/schema/rules/checks/events.yaml b/src/schema/rules/checks/events.yaml
index a5fa9ac5fd..4c4e143afd 100644
--- a/src/schema/rules/checks/events.yaml
+++ b/src/schema/rules/checks/events.yaml
@@ -10,9 +10,10 @@ EventsMissing:
level: warning # could be an error with the proper selectors, I think
selectors:
- dataset.dataset_description.DatasetType == "raw"
+ - datatype != "beh"
- '"task" in entities'
- '!match(entities.task, "rest")'
- - suffix != "events"
+ - '!intersects([suffix], ["events", "beh"])'
- extension != ".json"
checks:
- '"events" in associations'
diff --git a/src/schema/rules/checks/func.yaml b/src/schema/rules/checks/func.yaml
index a908bfa345..03570a058d 100644
--- a/src/schema/rules/checks/func.yaml
+++ b/src/schema/rules/checks/func.yaml
@@ -40,7 +40,9 @@ RepetitionTimeMismatch:
- type(sidecar.RepetitionTime) != "null"
- type(nifti_header) != "null"
checks:
- - sidecar.RepetitionTime == nifti_header.pixdim[4]
+ # Implement millisecond rounding via AND
+ - sidecar.RepetitionTime - nifti_header.pixdim[4] < 0.001
+ - sidecar.RepetitionTime - nifti_header.pixdim[4] > -0.001
# 54
BoldNot4d:
@@ -99,7 +101,7 @@ RepetitionTimeAcquisitionDurationMutex:
(RepetitionTime - AcquisitionDuration).
level: error
selectors:
- - type(sidecar.AcquistionDuration) != "null"
+ - type(sidecar.AcquisitionDuration) != "null"
checks:
- type(sidecar.RepetitionTime) == "null"
@@ -115,19 +117,6 @@ VolumeTimingDelayTimeMutex:
checks:
- type(sidecar.DelayTime) == "null"
-SliceTimingAcquisitionDurationMutex:
- issue:
- code: SLICE_TIMING_AND_DURATION_MUTUALLY_EXCLUSIVE
- message: |
- 'SliceTiming' is defined for this file.
- Neither 'DelayTime' nor 'AcquisitionDuration' may be defined in addition.
- level: error
- selectors:
- - type(sidecar.SliceTiming) != "null"
- checks:
- - type(sidecar.AcquisitionDuration) == "null"
- - type(sidecar.DelayTime) == "null"
-
VolumeTimingMissingAcquisitionDuration:
issue:
code: VOLUME_TIMING_MISSING_ACQUISITION_DURATION
diff --git a/src/schema/rules/checks/mrs.yaml b/src/schema/rules/checks/mrs.yaml
new file mode 100644
index 0000000000..598a59c94f
--- /dev/null
+++ b/src/schema/rules/checks/mrs.yaml
@@ -0,0 +1,29 @@
+---
+MRSNiftiConsistency:
+ issue:
+ code: MRS_NIFTI_CONSISTENCY
+ message: |
+ ResonantNucleus and/or SpectrometerFrequency fields are inconsistent
+ between the NIfTI-MRS header extension and the BIDS sidecar.
+ level: error
+ selectors:
+ - datatype == "mrs"
+ - type(nifti_header.mrs) != "null"
+ checks:
+ - sidecar.ResonantNucleus == nifti_header.mrs.ResonantNucleus
+ - sidecar.SpectrometerFrequency == nifti_header.mrs.SpectrometerFrequency
+
+MRSMatrixSize:
+ issue:
+ code: MRS_MATRIX_SIZE
+ message: |
+ MatrixSize metadata must match NIfTI header field `dim[1:4]`.
+ level: error
+ selectors:
+ - datatype == "mrs"
+ - type(nifti_header) != "null"
+ - type(sidecar.MatrixSize) != "null"
+ checks:
+ - sidecar.MatrixSize[0] == nifti_header.dim[1]
+ - sidecar.MatrixSize[1] == nifti_header.dim[2]
+ - sidecar.MatrixSize[2] == nifti_header.dim[3]
diff --git a/src/schema/rules/checks/nirs.yaml b/src/schema/rules/checks/nirs.yaml
index e4cabe3291..b4038b9d34 100644
--- a/src/schema/rules/checks/nirs.yaml
+++ b/src/schema/rules/checks/nirs.yaml
@@ -1,5 +1,11 @@
---
NASamplingFreq:
+ issue:
+ code: NIRS_SAMPLING_FREQUENCY
+ message: |
+ Sampling frequency must be defined in the sidecar, or else as a sampling_frequency
+ column of channels.tsv.
+ level: error
selectors:
- suffix == "nirs"
- sidecar.SamplingFrequency == "n/a"
@@ -7,10 +13,17 @@ NASamplingFreq:
- associations.channels.sampling_frequency != null
NIRSChannelCount:
+ issue:
+ code: NIRS_CHANNEL_COUNT
+ message: |
+ NIRSChannelCount metadata must equal the number of channels with type NIRS*,
+ as listed in channels.tsv.
+ level: error
selectors:
- datatype == "nirs"
- suffix == "nirs"
- match(extension, '\.nii(\.gz)?$')
+ - type(sidecar.NIRSChannelCount) != 'null'
checks:
- |
sidecar.NIRSChannelCount
@@ -22,6 +35,12 @@ NIRSChannelCount:
+ count(associations.channels.type, "NIRSCWMUA")
ACCELChannelCountReq:
+ issue:
+ code: ACCEL_CHANNEL_COUNT
+ message: |
+ ACCELChannelCount metadata must equal the number of channels with type ACCEL,
+ as listed in channels.tsv.
+ level: error
selectors:
- suffix == "nirs"
- count(associations.channels.type, "ACCEL") > 0
@@ -29,6 +48,12 @@ ACCELChannelCountReq:
- sidecar.ACCELChannelCount == count(associations.channels.type, "ACCEL")
GYROChannelCountReq:
+ issue:
+ code: GYRO_CHANNEL_COUNT
+ message: |
+ GYROChannelCount metadata must equal the number of channels with type GYRO,
+ as listed in channels.tsv.
+ level: error
selectors:
- suffix == "nirs"
- count(associations.channels.type, "GYRO") > 0
@@ -36,6 +61,12 @@ GYROChannelCountReq:
- sidecar.GYROChannelCount == count(associations.channels.type, "GYRO")
MAGNChannelCountReq:
+ issue:
+ code: MAGN_CHANNEL_COUNT
+ message: |
+ MAGNChannelCount metadata must equal the number of channels with type MAGN,
+ as listed in channels.tsv.
+ level: error
selectors:
- suffix == "nirs"
- count(associations.channels.type, "MAGN") > 0
@@ -43,6 +74,12 @@ MAGNChannelCountReq:
- sidecar.MAGNChannelCount == count(associations.channels.type, "MAGN")
ShortChannelCountReq:
+ issue:
+ code: SHORT_CHANNEL_COUNT
+ message: |
+ ShortChannelCount metadata must equal the number of channels with the value `true`
+ in the `short_channel` column of channels.tsv.
+ level: error
selectors:
- suffix == "nirs"
- '"ShortChannelCount" in sidecar'
@@ -50,6 +87,11 @@ ShortChannelCountReq:
- sidecar.ShortChannelCount == count(associations.channels.short_channel, "true")
Component:
+ issue:
+ code: COMPONENT_COLUMN_REQUIRED
+ message: |
+ ACCEL, GYRO, and MAGN columns require a `component` column in channels.tsv.
+ level: error
selectors:
- datatype == "nirs"
- suffix == "channels"
@@ -71,6 +113,11 @@ RecommendedChannels:
- associations.channels != null
RequiredTemplateX:
+ issue:
+ code: REQUIRED_TEMPLATE_X
+ message: |
+ The `template_x` column MUST be defined if the `x` column is `n/a`.
+ level: error
selectors:
- datatype == "nirs"
- suffix == "optodes"
@@ -80,6 +127,11 @@ RequiredTemplateX:
- columns.template_x != null
RequiredTemplateY:
+ issue:
+ code: REQUIRED_TEMPLATE_Y
+ message: |
+ The `template_y` column MUST be defined if the `y` column is `n/a`.
+ level: error
selectors:
- datatype == "nirs"
- suffix == "optodes"
@@ -89,6 +141,11 @@ RequiredTemplateY:
- columns.template_y != null
RequiredTemplateZ:
+ issue:
+ code: REQUIRED_TEMPLATE_Z
+ message: |
+ The `template_z` column MUST be defined if the `z` column is `n/a`.
+ level: error
selectors:
- datatype == "nirs"
- suffix == "optodes"
@@ -98,6 +155,11 @@ RequiredTemplateZ:
- columns.template_z != null
RequiredCoordsystem:
+ issue:
+ code: REQUIRED_COORDSYSTEM
+ message: |
+ If an optodes.tsv file is provided, an associated coordsystem.json must also be present.
+ level: error
selectors:
- datatype == "nirs"
- suffix == "optodes"
diff --git a/src/schema/rules/checks/pet.yaml b/src/schema/rules/checks/pet.yaml
new file mode 100644
index 0000000000..e5e10b71af
--- /dev/null
+++ b/src/schema/rules/checks/pet.yaml
@@ -0,0 +1,16 @@
+---
+# Rules for PET data that are not defined in tables.
+
+PETFrameConsistency:
+ issue:
+ code: PET_FRAME_CONSISTENCY
+ message: |
+ The number of frames in this scan does not match the number of frames (as defined by FrameDuration) in the
+ associated '.json' file.
+ level: error
+ selectors:
+ - suffix == 'pet'
+ - type(nifti_header) != "null"
+ - sidecar.FrameDuration
+ checks:
+ - length(sidecar.FrameDuration) == nifti_header.dim[4]
diff --git a/src/schema/rules/checks/privacy.yaml b/src/schema/rules/checks/privacy.yaml
index c9e2ecf0cb..f21f25b167 100644
--- a/src/schema/rules/checks/privacy.yaml
+++ b/src/schema/rules/checks/privacy.yaml
@@ -1,17 +1,41 @@
---
-GzipHeaderFields:
+GzipHeaderMtime:
issue:
- code: GZIP_HEADER_DATA
+ code: GZIP_HEADER_MTIME
message: |
- The gzip header contains a non-zero timestamp or a non-empty filename and/or comment field.
- These may leak sensitive information or indicate a non-reproducible conversion process.
+ The gzip header contains a non-zero timestamp.
+ This may leak sensitive information or indicate a non-reproducible conversion process.
level: warning
selectors:
- match(extension, ".gz$")
- gzip != null
checks:
- gzip.timestamp == 0
+
+GzipHeaderFilename:
+ issue:
+ code: GZIP_HEADER_FILENAME
+ message: |
+ The gzip header contains a non-empty filename.
+ This may leak sensitive information or indicate a non-reproducible conversion process.
+ level: warning
+ selectors:
+ - match(extension, ".gz$")
+ - gzip.filename
+ checks:
- gzip.filename == ""
+
+GzipHeaderComment:
+ issue:
+ code: GZIP_HEADER_COMMENT
+ message: |
+ The gzip header contains a non-empty comment field.
+ This may leak sensitive information or indicate a non-reproducible conversion process.
+ level: warning
+ selectors:
+ - match(extension, ".gz$")
+ - gzip.comment
+ checks:
- gzip.comment == ""
CheckAge89:
diff --git a/src/schema/rules/checks/references.yaml b/src/schema/rules/checks/references.yaml
index b0f1dd6e9d..191dcb6d4f 100644
--- a/src/schema/rules/checks/references.yaml
+++ b/src/schema/rules/checks/references.yaml
@@ -1,5 +1,11 @@
---
SubjectRelativeIntendedForString:
+ issue:
+ code: INTENDED_FOR
+ message: |
+ 'IntendedFor' field needs to point to an existing file.
+ Files must be subject-relative paths or BIDS URIs.
+ level: error
selectors:
- datatype != "ieeg"
- type(sidecar.IntendedFor) == "string"
@@ -7,6 +13,12 @@ SubjectRelativeIntendedForString:
- exists(sidecar.IntendedFor, "bids-uri") + exists(sidecar.IntendedFor, "subject") == 1
SubjectRelativeIntendedForArray:
+ issue:
+ code: INTENDED_FOR
+ message: |
+ 'IntendedFor' field needs to point to an existing file.
+ Files must be subject-relative paths or BIDS URIs.
+ level: error
selectors:
- datatype != "ieeg"
- type(sidecar.IntendedFor) == "array"
@@ -14,6 +26,12 @@ SubjectRelativeIntendedForArray:
- exists(sidecar.IntendedFor, "bids-uri") + exists(sidecar.IntendedFor, "subject") == length(sidecar.IntendedFor)
DatasetRelativeIntendedForString:
+ issue:
+ code: INTENDED_FOR
+ message: |
+ 'IntendedFor' field needs to point to an existing file.
+ Files must be dataset-relative paths or BIDS URIs.
+ level: error
selectors:
- datatype == "ieeg"
- type(sidecar.IntendedFor) == "string"
@@ -21,6 +39,12 @@ DatasetRelativeIntendedForString:
- exists(sidecar.IntendedFor, "bids-uri") + exists(sidecar.IntendedFor, "dataset") == 1
DatasetRelativeIntendedForArray:
+ issue:
+ code: INTENDED_FOR
+ message: |
+ 'IntendedFor' field needs to point to an existing file.
+ Files must be dataset-relative paths or BIDS URIs.
+ level: error
selectors:
- datatype == "ieeg"
- type(sidecar.IntendedFor) == "array"
@@ -28,6 +52,12 @@ DatasetRelativeIntendedForArray:
- exists(sidecar.IntendedFor, "bids-uri") + exists(sidecar.IntendedFor, "dataset") == length(sidecar.IntendedFor)
AssociatedEmptyRoomString:
+ issue:
+ code: ASSOCIATED_EMPTY_ROOM
+ message: |
+ 'AssociatedEmptyRoom' field needs to point to an existing file.
+ Files must be dataset-relative paths or BIDS URIs.
+ level: error
selectors:
- datatype == "meg"
- type(sidecar.AssociatedEmptyRoom) == "string"
@@ -35,6 +65,12 @@ AssociatedEmptyRoomString:
- exists(sidecar.AssociatedEmptyRoom, "bids-uri") + exists(sidecar.AssociatedEmptyRoom, "dataset") == 1
AssociatedEmptyRoomArray:
+ issue:
+ code: ASSOCIATED_EMPTY_ROOM
+ message: |
+ 'AssociatedEmptyRoom' field needs to point to an existing file.
+ Files must be dataset-relative paths or BIDS URIs.
+ level: error
selectors:
- datatype == "meg"
- type(sidecar.AssociatedEmptyRoom) == "array"
@@ -44,6 +80,12 @@ AssociatedEmptyRoomArray:
== length(sidecar.AssociatedEmptyRoom)
Sources:
+ issue:
+ code: SOURCE_FILE_EXIST
+ message: |
+ 'Sources' field needs to point to existing files.
+ Files must be dataset-relative paths or BIDS URIs.
+ level: error
selectors:
- dataset.dataset_description.DatasetType == "derivatives"
- type(sidecar.Sources) != "null"
diff --git a/src/schema/rules/dataset_metadata.yaml b/src/schema/rules/dataset_metadata.yaml
index d0a92081a0..1a702baa37 100644
--- a/src/schema/rules/dataset_metadata.yaml
+++ b/src/schema/rules/dataset_metadata.yaml
@@ -34,7 +34,7 @@ derivative_description:
dataset_description_with_genetics:
selectors:
- path == "/dataset_description.json"
- - intersects(dataset.files, ["/genetic_info.json"])
+ - exists('genetic_info.json', 'dataset')
fields:
Genetics: required
diff --git a/src/schema/rules/entities.yaml b/src/schema/rules/entities.yaml
index 2194a61a92..44174d333e 100644
--- a/src/schema/rules/entities.yaml
+++ b/src/schema/rules/entities.yaml
@@ -7,6 +7,8 @@
- task
- tracksys
- acquisition
+- nucleus
+- volume
- ceagent
- tracer
- stain
diff --git a/src/schema/rules/files/raw/mrs.yaml b/src/schema/rules/files/raw/mrs.yaml
new file mode 100644
index 0000000000..fe95f08f3a
--- /dev/null
+++ b/src/schema/rules/files/raw/mrs.yaml
@@ -0,0 +1,24 @@
+---
+mrs:
+ suffixes:
+ - svs
+ - mrsi
+ - unloc
+ - mrsref
+ extensions:
+ - .nii.gz
+ - .nii
+ - .json
+ datatypes:
+ - mrs
+ entities:
+ subject: required
+ session: optional
+ task: optional
+ acquisition: optional
+ reconstruction: optional
+ run: optional
+ echo: optional
+ inversion: optional
+ nucleus: optional
+ volume: optional
diff --git a/src/schema/rules/files/raw/task.yaml b/src/schema/rules/files/raw/task.yaml
index b7171e568f..812765a063 100644
--- a/src/schema/rules/files/raw/task.yaml
+++ b/src/schema/rules/files/raw/task.yaml
@@ -91,6 +91,16 @@ events__pet:
reconstruction: optional
run: optional
+events__mrs:
+ $ref: rules.files.raw.task.events
+ datatypes:
+ - mrs
+ entities:
+ $ref: rules.files.raw.task.events.entities
+ reconstruction: optional
+ nucleus: optional
+ volume: optional
+
timeseries__func:
$ref: rules.files.raw.task.timeseries
datatypes:
diff --git a/src/schema/rules/json/eeg.yaml b/src/schema/rules/json/eeg.yaml
new file mode 100644
index 0000000000..f42956671f
--- /dev/null
+++ b/src/schema/rules/json/eeg.yaml
@@ -0,0 +1,91 @@
+#
+# Groups of related metadata fields
+#
+# Assumptions: never need disjunction of selectors
+# Assumptions: top-to-bottom overrides is sufficient logic
+
+---
+# General fields
+EEGCoordsystemGeneral:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ fields:
+ IntendedFor:
+ level: optional
+ description_addendum: |
+ This identifies the MRI or CT scan associated with the electrodes,
+ landmarks, and fiducials.
+
+# Fields relating to the EEG electrode positions
+EEGCoordsystemPositions:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ fields:
+ EEGCoordinateSystem: required
+ EEGCoordinateUnits: required
+ EEGCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if `EEGCoordinateSystem` is `"Other"`
+
+EEGCoordsystemOther:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ - '"EEGCoordinateSystem" in json'
+ - json.EEGCoordinateSystem == "Other"
+ fields:
+ EEGCoordinateSystemDescription: required
+
+# Fields relating to the position of fiducials measured during an EEG session/run
+EEGCoordsystemFiducials:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ fields:
+ FiducialsDescription: optional
+ FiducialsCoordinates: recommended
+ FiducialsCoordinateSystem: recommended
+ FiducialsCoordinateUnits: recommended
+ FiducialsCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if `FiducialsCoordinateSystem` is `"Other"`
+
+EEGCoordsystemOtherFiducialCoordinateSystem:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ - json.FiducialsCoordinateSystem == "Other"
+ fields:
+ FiducialsCoordinateSystemDescription: required
+
+# Fields relating to the position of anatomical landmark measured during an EEG session/run
+EEGCoordsystemLandmark:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ fields:
+ AnatomicalLandmarkCoordinates: recommended
+ AnatomicalLandmarkCoordinateSystem:
+ level: recommended
+ description_addendum: Preferably the same as the `EEGCoordinateSystem`.
+ AnatomicalLandmarkCoordinateUnits: recommended
+
+EEGCoordsystemLandmarkDescriptionRec:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ - json.AnatomicalLandmarkCoordinateSystem != "Other"
+ fields:
+ AnatomicalLandmarkCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if `AnatomicalLandmarkCoordinateSystem` is `"Other"`
+
+EEGCoordsystemLandmarkDescriptionReq:
+ selectors:
+ - datatype == "eeg"
+ - suffix == "coordsystem"
+ - json.AnatomicalLandmarkCoordinateSystem == "Other"
+ fields:
+ AnatomicalLandmarkCoordinateSystemDescription: required
diff --git a/src/schema/rules/json/ieeg.yaml b/src/schema/rules/json/ieeg.yaml
new file mode 100644
index 0000000000..4271cfe952
--- /dev/null
+++ b/src/schema/rules/json/ieeg.yaml
@@ -0,0 +1,50 @@
+#
+# Groups of related metadata fields
+#
+# Assumptions: never need disjunction of selectors
+# Assumptions: top-to-bottom overrides is sufficient logic
+
+---
+# General fields
+iEEGCoordsystemGeneral:
+ selectors:
+ - datatype == "ieeg"
+ - suffix == "coordsystem"
+ fields:
+ IntendedFor__ds_relative:
+ level: optional
+ description_addendum: |
+ If only a surface reconstruction is available, this should point to
+ the surface reconstruction file.
+ Note that this file should have the same coordinate system
+ specified in `iEEGCoordinateSystem`.
+ For example, **T1**: `'bids::sub-/ses-/anat/sub-01_T1w.nii.gz'`
+ **Surface**: `'bids::derivatives/surfaces/sub-/ses-/anat/
+ sub-01_hemi-R_desc-T1w_pial.surf.gii'`
+ **Operative photo**: `'bids::sub-/ses-/ieeg/
+ sub-0001_ses-01_acq-photo1_photo.jpg'`
+ **Talairach**: `'bids::derivatives/surfaces/sub-Talairach/ses-01/anat/
+ sub-Talairach_hemi-R_pial.surf.gii'`
+
+# Fields relating to the iEEG electrode positions
+iEEGCoordsystemPositions:
+ selectors:
+ - datatype == "ieeg"
+ - suffix == "coordsystem"
+ fields:
+ iEEGCoordinateSystem: required
+ iEEGCoordinateUnits: required
+ iEEGCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if `iEEGCoordinateSystem` is `"Other"`
+ iEEGCoordinateProcessingDescription: recommended
+ iEEGCoordinateProcessingReference: recommended
+
+iEEGCoordsystemOther:
+ selectors:
+ - datatype == "ieeg"
+ - suffix == "coordsystem"
+ - '"iEEGCoordinateSystem" in json'
+ - json.iEEGCoordinateSystem == "Other"
+ fields:
+ iEEGCoordinateSystemDescription: required
diff --git a/src/schema/rules/json/meg.yaml b/src/schema/rules/json/meg.yaml
new file mode 100644
index 0000000000..4c18d99eaa
--- /dev/null
+++ b/src/schema/rules/json/meg.yaml
@@ -0,0 +1,141 @@
+#
+# Groups of related metadata fields
+#
+# Assumptions: never need disjunction of selectors
+# Assumptions: top-to-bottom overrides is sufficient logic
+
+---
+# MEG and EEG sensors
+MEGCoordsystemWithEEG:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ fields:
+ MEGCoordinateSystem: required
+ MEGCoordinateUnits: required
+ MEGCoordinateSystemDescription:
+ level: optional
+ level_addendum: required if `MEGCoordinateSystem` is `Other`
+ EEGCoordinateSystem:
+ level: optional
+ description_addendum: |
+ See [Recording EEG simultaneously with MEG]
+ (/modality-specific-files/magnetoencephalography.html#recording-eeg-simultaneously-with-meg).
+ Preferably the same as the `MEGCoordinateSystem`.
+ EEGCoordinateUnits: optional
+ EEGCoordinateSystemDescription:
+ level: optional
+ level_addendum: required if `EEGCoordinateSystem` is `Other`
+ description_addendum: |
+ See [Recording EEG simultaneously with MEG]
+ (/modality-specific-files/magnetoencephalography.html#recording-eeg-simultaneously-with-meg).
+
+MEGCoordsystemWithEEGMEGCoordinateSystem:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ - '"MEGCoordinateSystem" in json'
+ - json.MEGCoordinateSystem == "Other"
+ fields:
+ MEGCoordinateSystemDescription: required
+
+# NOTE: Not sure if this requires simult. EEG
+MEGCoordsystemWithEEGEEGCoordinateSystem:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ - '"EEGCoordinateSystem" in json'
+ - json.EEGCoordinateSystem == "Other"
+ fields:
+ EEGCoordinateSystemDescription: required
+
+# Head localization coils
+MEGCoordsystemHeadLocalizationCoils:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ fields:
+ HeadCoilCoordinates: optional
+ HeadCoilCoordinateSystem: optional
+ HeadCoilCoordinateUnits: optional
+ HeadCoilCoordinateSystemDescription:
+ level: optional
+ level_addendum: required if `HeadCoilCoordinateSystem` is `Other`
+
+MEGCoordsystemHeadLocalizationCoilsHeadCoilCoordinateSystem:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ - '"HeadCoilCoordinateSystem" in json'
+ - json.HeadCoilCoordinateSystem == "Other"
+ fields:
+ HeadCoilCoordinateSystemDescription: required
+
+# Digitized head points
+MEGCoordsystemDigitizedHeadPoints:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ fields:
+ DigitizedHeadPoints__coordsystem: optional
+ DigitizedHeadPointsCoordinateSystem: optional
+ DigitizedHeadPointsCoordinateUnits: optional
+ DigitizedHeadPointsCoordinateSystemDescription:
+ level: optional
+ level_addendum: required if `DigitizedHeadPointsCoordinateSystem` is `Other`
+
+MEGCoordsystemDigitizedHeadPointsDigitizedHeadPointsCoordinateSystem:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ - '"DigitizedHeadPointsCoordinateSystem" in json'
+ - json.DigitizedHeadPointsCoordinateSystem == "Other"
+ fields:
+ DigitizedHeadPointsCoordinateSystemDescription: required
+
+# Anatomical MRI
+MEGCoordsystemAnatomicalMRI:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ - intersects(dataset.datatypes, ["anat"])
+ fields:
+ IntendedFor:
+ level: optional
+ description_addendum: |
+ This is used to identify the structural MRI(s),
+ possibly of different types if a list is specified,
+ to be used with the MEG recording.
+
+# Anatomical landmarks
+MEGCoordsystemAnatomicalLandmarks:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ fields:
+ AnatomicalLandmarkCoordinates: optional
+ AnatomicalLandmarkCoordinateSystem:
+ level: optional
+ description_addendum: |
+ Preferably the same as the `MEGCoordinateSystem`.
+ AnatomicalLandmarkCoordinateUnits: optional
+ AnatomicalLandmarkCoordinateSystemDescription:
+ level: optional
+ level_addendum: required if `AnatomicalLandmarkCoordinateSystem` is `Other`
+
+MEGCoordsystemAnatomicalLandmarksAnatomicalLandmarkCoordinateSystem:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ - '"AnatomicalLandmarkCoordinateSystem" in json'
+ - json.AnatomicalLandmarkCoordinateSystem == "Other"
+ fields:
+ AnatomicalLandmarkCoordinateSystemDescription: required
+
+# Fiducials information
+MEGCoordsystemFiducialsInformation:
+ selectors:
+ - datatype == "meg"
+ - suffix == "coordsystem"
+ fields:
+ FiducialsDescription: optional
diff --git a/src/schema/rules/json/nirs.yaml b/src/schema/rules/json/nirs.yaml
new file mode 100644
index 0000000000..c44492d8a4
--- /dev/null
+++ b/src/schema/rules/json/nirs.yaml
@@ -0,0 +1,93 @@
+---
+CoordinateSystem:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ fields:
+ NIRSCoordinateSystem: required
+ NIRSCoordinateUnits: required
+ NIRSCoordinateProcessingDescription: recommended
+
+Fiducials:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ fields:
+ FiducialsDescription: optional
+ FiducialsCoordinates: recommended
+ FiducialsCoordinateUnits: recommended
+ FiducialsCoordinateSystem: recommended
+
+AnatomicalLandmark:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ fields:
+ AnatomicalLandmarkCoordinates: recommended
+ AnatomicalLandmarkCoordinateSystem: recommended
+ AnatomicalLandmarkCoordinateUnits: recommended
+
+CoordsystemGeneral:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ fields:
+ IntendedFor:
+ level: optional
+ description_addendum: |
+ This identifies the MRI or CT scan associated with the optodes,
+ landmarks, and fiducials.
+
+CoordinateSystemDescriptionRec:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ - json.NIRSCoordinateSystem != "other"
+ fields:
+ NIRSCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if NIRSCoordinateSystem is "other"
+
+CoordinateSystemDescriptionReq:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ - json.NIRSCoordinateSystem == "other"
+ fields:
+ NIRSCoordinateSystemDescription: required
+
+AnatomicalLandmarkCoordinateSystemDescriptionRec:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ - json.AnatomicalLandmarkCoordinateSystem != "other"
+ fields:
+ AnatomicalLandmarkCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if NIRSCoordinateSystem is "other"
+
+AnatomicalLandmarkCoordinateSystemDescriptionReq:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ - json.AnatomicalLandmarkCoordinateSystem == "other"
+ fields:
+ AnatomicalLandmarkCoordinateSystemDescription: required
+
+FiducialsCoordinateSystemDescriptionRec:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ - json.FiducialsCoordinateSystem != "other"
+ fields:
+ FiducialsCoordinateSystemDescription:
+ level: recommended
+ level_addendum: required if FiducialsCoordinateSystem is "other"
+
+FiducialsCoordinateSystemDescriptionReq:
+ selectors:
+ - datatype == "nirs"
+ - suffix == "coordsystem"
+ - json.FiducialsCoordinateSystem == "other"
+ fields:
+ FiducialsCoordinateSystemDescription: required
diff --git a/src/schema/rules/modalities.yaml b/src/schema/rules/modalities.yaml
index 2f290ca014..ccba1c3ef1 100644
--- a/src/schema/rules/modalities.yaml
+++ b/src/schema/rules/modalities.yaml
@@ -31,3 +31,6 @@ motion:
nirs:
datatypes:
- nirs
+mrs:
+ datatypes:
+ - mrs
diff --git a/src/schema/rules/sidecars/beh.yaml b/src/schema/rules/sidecars/beh.yaml
index 1221bf84d7..f2d8410914 100644
--- a/src/schema/rules/sidecars/beh.yaml
+++ b/src/schema/rules/sidecars/beh.yaml
@@ -8,6 +8,7 @@
# Metadata for either beh or events files
BEHTaskInformation:
selectors:
+ - datatype == "beh"
- intersects([suffix], ["beh", "events"])
fields:
TaskName: recommended
@@ -18,6 +19,7 @@ BEHTaskInformation:
BEHInstitutionInformation:
selectors:
+ - datatype == "beh"
- intersects([suffix], ["beh", "events"])
fields:
InstitutionName: recommended
diff --git a/src/schema/rules/sidecars/eeg.yaml b/src/schema/rules/sidecars/eeg.yaml
index 7a7252d69b..a30195758b 100644
--- a/src/schema/rules/sidecars/eeg.yaml
+++ b/src/schema/rules/sidecars/eeg.yaml
@@ -89,88 +89,3 @@ EEGOptional:
fields:
ElectricalStimulation: optional
ElectricalStimulationParameters: optional
-
-# General fields
-EEGCoordsystemGeneral:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- fields:
- IntendedFor:
- level: optional
- description_addendum: |
- This identifies the MRI or CT scan associated with the electrodes,
- landmarks, and fiducials.
-
-# Fields relating to the EEG electrode positions
-EEGCoordsystemPositions:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- fields:
- EEGCoordinateSystem: required
- EEGCoordinateUnits: required
- EEGCoordinateSystemDescription:
- level: recommended
- level_addendum: required if `EEGCoordinateSystem` is `"Other"`
-
-EEGCoordsystemOther:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- - '"EEGCoordinateSystem" in sidecar'
- - sidecar.EEGCoordinateSystem == "Other"
- fields:
- EEGCoordinateSystemDescription: required
-
-# Fields relating to the position of fiducials measured during an EEG session/run
-EEGCoordsystemFiducials:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- fields:
- FiducialsDescription: optional
- FiducialsCoordinates: recommended
- FiducialsCoordinateSystem: recommended
- FiducialsCoordinateUnits: recommended
- FiducialsCoordinateSystemDescription:
- level: recommended
- level_addendum: required if `FiducialsCoordinateSystem` is `"Other"`
-
-EEGCoordsystemOtherFiducialCoordinateSystem:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- - sidecar.FiducialsCoordinateSystem == "Other"
- fields:
- FiducialsCoordinateSystemDescription: required
-
-# Fields relating to the position of anatomical landmark measured during an EEG session/run
-EEGCoordsystemLandmark:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- fields:
- AnatomicalLandmarkCoordinates: recommended
- AnatomicalLandmarkCoordinateSystem:
- level: recommended
- description_addendum: Preferably the same as the `EEGCoordinateSystem`.
- AnatomicalLandmarkCoordinateUnits: recommended
-
-EEGCoordsystemLandmarkDescriptionRec:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- - sidecar.AnatomicalLandmarkCoordinateSystem != "Other"
- fields:
- AnatomicalLandmarkCoordinateSystemDescription:
- level: recommended
- level_addendum: required if `AnatomicalLandmarkCoordinateSystem` is `"Other"`
-
-EEGCoordsystemLandmarkDescriptionReq:
- selectors:
- - datatype == "eeg"
- - suffix == "coordsystem"
- - sidecar.AnatomicalLandmarkCoordinateSystem == "Other"
- fields:
- AnatomicalLandmarkCoordinateSystemDescription: required
diff --git a/src/schema/rules/sidecars/entity_rules.yaml b/src/schema/rules/sidecars/entity_rules.yaml
index be8f5603af..54b3c256a0 100644
--- a/src/schema/rules/sidecars/entity_rules.yaml
+++ b/src/schema/rules/sidecars/entity_rules.yaml
@@ -10,12 +10,14 @@
EntitiesTaskMetadata:
selectors:
- '"task" in entities'
+ - suffix != 'events'
fields:
TaskName: recommended
EntitiesCeMetadata:
selectors:
- '"ce" in entities'
+ - match(extension, "^\.nii(\.gz)?$")
fields:
ContrastBolusIngredient: optional
@@ -30,24 +32,28 @@ EntitiesStainMetadata:
EntitiesEchoMetadata:
selectors:
- '"echo" in entities'
+ - match(extension, "^\.nii(\.gz)?$")
fields:
EchoTime: required
EntitiesFlipMetadata:
selectors:
- '"flip" in entities'
+ - match(extension, "^\.nii(\.gz)?$")
fields:
FlipAngle: required
EntitiesInvMetadata:
selectors:
- '"inv" in entities'
+ - match(extension, "^\.nii(\.gz)?$")
fields:
InversionTime: required
EntitiesMTMetadata:
selectors:
- '"mt" in entities'
+ - match(extension, "^\.nii(\.gz)?$")
fields:
MTState: required
diff --git a/src/schema/rules/sidecars/fmap.yaml b/src/schema/rules/sidecars/fmap.yaml
index c642394764..a866973f96 100644
--- a/src/schema/rules/sidecars/fmap.yaml
+++ b/src/schema/rules/sidecars/fmap.yaml
@@ -69,3 +69,13 @@ MRIFieldmapPepolar:
fields:
PhaseEncodingDirection: required
TotalReadoutTime: required
+
+TB1EPI:
+ selectors:
+ - datatype == "fmap"
+ - suffix == "TB1EPI"
+ fields:
+ EchoTime: required
+ FlipAngle: required
+ TotalReadoutTime: required
+ MixingTime: required
diff --git a/src/schema/rules/sidecars/func.yaml b/src/schema/rules/sidecars/func.yaml
index 7192886407..67ec04e84f 100644
--- a/src/schema/rules/sidecars/func.yaml
+++ b/src/schema/rules/sidecars/func.yaml
@@ -41,12 +41,17 @@ MRIFuncVolumeTiming:
VolumeTiming:
level: required
level_addendum: mutually exclusive with `RepetitionTime`
+ description_addendum: |
+ This field is mutually exclusive with `"DelayTime"`.
+ If defined, this requires acquisition time (TA) be defined via either
+ `"SliceTiming"` or `"AcquisitionDuration"`.
# Timing Parameters
MRIFuncTimingParameters:
selectors:
- datatype == "func"
- suffix == "bold"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
NumberOfVolumesDiscardedByScanner: recommended
NumberOfVolumesDiscardedByUser: recommended
@@ -64,6 +69,7 @@ MRIFuncTaskInformation:
selectors:
- datatype == "func"
- suffix == "bold"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
Instructions:
level: recommended
diff --git a/src/schema/rules/sidecars/ieeg.yaml b/src/schema/rules/sidecars/ieeg.yaml
index 066e17a8bf..457c409a3b 100644
--- a/src/schema/rules/sidecars/ieeg.yaml
+++ b/src/schema/rules/sidecars/ieeg.yaml
@@ -94,47 +94,3 @@ iEEGOptional:
fields:
ElectricalStimulation: optional
ElectricalStimulationParameters: optional
-
-# General fields
-iEEGCoordsystemGeneral:
- selectors:
- - datatype == "ieeg"
- - suffix == "coordsystem"
- fields:
- IntendedFor__ds_relative:
- level: optional
- description_addendum: |
- If only a surface reconstruction is available, this should point to
- the surface reconstruction file.
- Note that this file should have the same coordinate system
- specified in `iEEGCoordinateSystem`.
- For example, **T1**: `'bids::sub-/ses-/anat/sub-01_T1w.nii.gz'`
- **Surface**: `'bids::derivatives/surfaces/sub-/ses-/anat/
- sub-01_hemi-R_desc-T1w_pial.surf.gii'`
- **Operative photo**: `'bids::sub-/ses-/ieeg/
- sub-0001_ses-01_acq-photo1_photo.jpg'`
- **Talairach**: `'bids::derivatives/surfaces/sub-Talairach/ses-01/anat/
- sub-Talairach_hemi-R_pial.surf.gii'`
-
-# Fields relating to the iEEG electrode positions
-iEEGCoordsystemPositions:
- selectors:
- - datatype == "ieeg"
- - suffix == "coordsystem"
- fields:
- iEEGCoordinateSystem: required
- iEEGCoordinateUnits: required
- iEEGCoordinateSystemDescription:
- level: recommended
- level_addendum: required if `iEEGCoordinateSystem` is `"Other"`
- iEEGCoordinateProcessingDescription: recommended
- iEEGCoordinateProcessingReference: recommended
-
-iEEGCoordsystemOther:
- selectors:
- - datatype == "ieeg"
- - suffix == "coordsystem"
- - '"iEEGCoordinateSystem" in sidecar'
- - sidecar.iEEGCoordinateSystem == "Other"
- fields:
- iEEGCoordinateSystemDescription: required
diff --git a/src/schema/rules/sidecars/meg.yaml b/src/schema/rules/sidecars/meg.yaml
index 2390766900..b921db19de 100644
--- a/src/schema/rules/sidecars/meg.yaml
+++ b/src/schema/rules/sidecars/meg.yaml
@@ -131,139 +131,3 @@ MEGwithEEG:
CapManufacturer: optional
CapManufacturersModelName: optional
EEGReference: optional
-
-# MEG and EEG sensors
-MEGCoordsystemWithEEG:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- fields:
- MEGCoordinateSystem: required
- MEGCoordinateUnits: required
- MEGCoordinateSystemDescription:
- level: optional
- level_addendum: required if `MEGCoordinateSystem` is `Other`
- EEGCoordinateSystem:
- level: optional
- description_addendum: |
- See [Recording EEG simultaneously with MEG]
- (/modality-specific-files/magnetoencephalography.html#recording-eeg-simultaneously-with-meg).
- Preferably the same as the `MEGCoordinateSystem`.
- EEGCoordinateUnits: optional
- EEGCoordinateSystemDescription:
- level: optional
- level_addendum: required if `EEGCoordinateSystem` is `Other`
- description_addendum: |
- See [Recording EEG simultaneously with MEG]
- (/modality-specific-files/magnetoencephalography.html#recording-eeg-simultaneously-with-meg).
-
-# NOTE: The JSON isn't really a sidecar, so "sidecar.MEGCoordinateSystem" is misleading.
-MEGCoordsystemWithEEGMEGCoordinateSystem:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- - '"MEGCoordinateSystem" in sidecar'
- - sidecar.MEGCoordinateSystem == "Other"
- fields:
- MEGCoordinateSystemDescription: required
-
-# NOTE: Not sure if this requires simult. EEG
-MEGCoordsystemWithEEGEEGCoordinateSystem:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- - '"EEGCoordinateSystem" in sidecar'
- - sidecar.EEGCoordinateSystem == "Other"
- fields:
- EEGCoordinateSystemDescription: required
-
-# Head localization coils
-MEGCoordsystemHeadLocalizationCoils:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- fields:
- HeadCoilCoordinates: optional
- HeadCoilCoordinateSystem: optional
- HeadCoilCoordinateUnits: optional
- HeadCoilCoordinateSystemDescription:
- level: optional
- level_addendum: required if `HeadCoilCoordinateSystem` is `Other`
-
-MEGCoordsystemHeadLocalizationCoilsHeadCoilCoordinateSystem:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- - '"HeadCoilCoordinateSystem" in sidecar'
- - sidecar.HeadCoilCoordinateSystem == "Other"
- fields:
- HeadCoilCoordinateSystemDescription: required
-
-# Digitized head points
-MEGCoordsystemDigitizedHeadPoints:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- fields:
- DigitizedHeadPoints__coordsystem: optional
- DigitizedHeadPointsCoordinateSystem: optional
- DigitizedHeadPointsCoordinateUnits: optional
- DigitizedHeadPointsCoordinateSystemDescription:
- level: optional
- level_addendum: required if `DigitizedHeadPointsCoordinateSystem` is `Other`
-
-MEGCoordsystemDigitizedHeadPointsDigitizedHeadPointsCoordinateSystem:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- - '"DigitizedHeadPointsCoordinateSystem" in sidecar'
- - sidecar.DigitizedHeadPointsCoordinateSystem == "Other"
- fields:
- DigitizedHeadPointsCoordinateSystemDescription: required
-
-# Anatomical MRI
-MEGCoordsystemAnatomicalMRI:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- - intersects(dataset.datatypes, ["anat"])
- fields:
- IntendedFor:
- level: optional
- description_addendum: |
- This is used to identify the structural MRI(s),
- possibly of different types if a list is specified,
- to be used with the MEG recording.
-
-# Anatomical landmarks
-MEGCoordsystemAnatomicalLandmarks:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- fields:
- AnatomicalLandmarkCoordinates: optional
- AnatomicalLandmarkCoordinateSystem:
- level: optional
- description_addendum: |
- Preferably the same as the `MEGCoordinateSystem`.
- AnatomicalLandmarkCoordinateUnits: optional
- AnatomicalLandmarkCoordinateSystemDescription:
- level: optional
- level_addendum: required if `AnatomicalLandmarkCoordinateSystem` is `Other`
-
-MEGCoordsystemAnatomicalLandmarksAnatomicalLandmarkCoordinateSystem:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- - '"AnatomicalLandmarkCoordinateSystem" in sidecar'
- - sidecar.AnatomicalLandmarkCoordinateSystem == "Other"
- fields:
- AnatomicalLandmarkCoordinateSystemDescription: required
-
-# Fiducials information
-MEGCoordsystemFiducialsInformation:
- selectors:
- - datatype == "meg"
- - suffix == "coordsystem"
- fields:
- FiducialsDescription: optional
diff --git a/src/schema/rules/sidecars/mri.yaml b/src/schema/rules/sidecars/mri.yaml
index 1dce5f67af..d57a053bec 100644
--- a/src/schema/rules/sidecars/mri.yaml
+++ b/src/schema/rules/sidecars/mri.yaml
@@ -9,6 +9,7 @@
MRIHardware:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
Manufacturer:
level: recommended
@@ -37,10 +38,27 @@ MRIHardware:
MatrixCoilMode: recommended
CoilCombinationMethod: recommended
NumberTransmitCoilActiveElements: optional
+ TablePosition:
+ level: optional
+ level_addendum: recommended if `chunk` entity is present
+
+MRIChunkPosition:
+ selectors:
+ - modality == "mri"
+ - entities.chunk
+ - match(extension, '\.nii(\.gz)?$')
+ fields:
+ TablePosition:
+ level: recommended
+ issue:
+ code: TABLE_POSITION_RECOMMENDED
+ message: |
+ TablePosition is RECOMMENDED if the chunk entity is present.
MRISample:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
BodyPart:
level: optional
@@ -53,15 +71,23 @@ MRIScannerHardwareASL:
- datatype == "perf"
- suffix == "asl"
- intersects([suffix], ["asl", "m0scan"])
+ - match(extension, "^\.nii(\.gz)?$")
fields:
MagneticFieldStrength: required
MRISequenceSpecifics:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
- PulseSequenceType: recommended
- ScanningSequence: recommended
+ PulseSequenceType:
+ level: recommended
+ description_addendum: |
+ For example, `"MPRAGE"`, `"Gradient Echo EPI"`, `"Spin Echo EPI"`, `"Multiband gradient
+ echo EPI"`.
+ ScanningSequence:
+ level: recommended
+ description_addendum: Corresponds to DICOM Tag 0018, 0020 `Scanning Sequence`.
SequenceVariant: recommended
ScanOptions: recommended
SequenceName: recommended
@@ -89,6 +115,7 @@ PETMRISequenceSpecifics:
selectors:
- modality == "mri"
- intersects(dataset.modalities, ["pet"])
+ - match(extension, "^\.nii(\.gz)?$")
fields:
NonlinearGradientCorrection: required
@@ -96,6 +123,7 @@ ASLMRISequenceSpecifics:
selectors:
- datatype == "perf"
- suffix == "asl"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
MRAcquisitionType: required
@@ -131,6 +159,7 @@ SpoilingGradient:
MRISpatialEncoding:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
NumberShots: recommended
ParallelReductionFactorInPlane: recommended
@@ -148,6 +177,7 @@ PhaseEncodingDirectionRec:
selectors:
- modality == "mri"
- suffix != "epi"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
PhaseEncodingDirection:
level: recommended
@@ -185,27 +215,31 @@ PhaseEncodingDirectionReq:
MRITimingParameters:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
EchoTime:
level: recommended
level_addendum: |
required if corresponding fieldmap data is present,
or the data comes from a multi-echo sequence or Arterial Spin Labeling.
- issue:
- code: ECHO_TIME_NOT_DEFINED
- message: |
- You must define 'EchoTime' for this file. 'EchoTime' is the echo time (TE)
- for the acquisition, specified in seconds. Corresponds to DICOM Tag
- 0018, 0081 Echo Time (please note that the DICOM term is in milliseconds
- not seconds). The data type number may apply to files from any MRI modality
- concerned with a single value for this field, or to the files in a file
- collection where the value of this field is iterated using the echo entity.
- The data type array provides a value for each volume in a 4D dataset and
- should only be used when the volume timing is critical for interpretation
- of the data, such as in ASL or variable echo time fMRI sequences.
+ description_addendum: |
+ The data type array provides a value for each volume in a 4D dataset and
+ should only be used when the volume timing is critical for interpretation
+ of the data, such as in
+ [ASL](SPEC_ROOT/modality-specific-files/magnetic-resonance-imaging-data.md#\
+ arterial-spin-labeling-perfusion-data)
+ or variable echo time fMRI sequences.
InversionTime: recommended
DwellTime: recommended
+EchoTimeRequiredASL:
+ selectors:
+ - modality == "mri"
+ - datatype == "perf"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ EchoTime: required
+
SliceTimingMRI:
selectors:
- modality == "mri"
@@ -224,7 +258,6 @@ SliceTimingASL:
- intersects([suffix], ["asl", "m0scan"])
- sidecar.MRAcquisitionType == "2D"
fields:
- EchoTime: required
SliceTiming:
level: required
issue:
@@ -244,16 +277,10 @@ SliceTimingASL:
final entry in the `SliceTiming` list is the time of acquisition of slice 0.
Without this parameter slice time correction will not be possible.
-# This is technically for sparse sequences only, but I don't know how to encode that.
-# SliceTimingSparse:
-# selectors:
-# - modality == "mri"
-# fields:
-# SliceTiming: required
-
MRIRFandContrast:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
NegativeContrast: optional
@@ -265,6 +292,13 @@ MRIFlipAngleLookLockerFalse:
FlipAngle:
level: recommended
level_addendum: required if LookLocker is set to `true`
+ description_addendum: |
+ The data type array provides a value for each volume in a 4D dataset and
+ should only be used when the volume timing is critical for interpretation of
+ the data, such as in
+ [ASL](SPEC_ROOT/modality-specific-files/magnetic-resonance-imaging-data.md#\
+ arterial-spin-labeling-perfusion-data)
+ or variable flip angle fMRI sequences.
MRIFlipAngleLookLockerTrue:
selectors:
@@ -273,6 +307,13 @@ MRIFlipAngleLookLockerTrue:
fields:
FlipAngle:
level: required
+ description_addendum: |
+ The data type array provides a value for each volume in a 4D dataset and
+ should only be used when the volume timing is critical for interpretation of
+ the data, such as in
+ [ASL](SPEC_ROOT/modality-specific-files/magnetic-resonance-imaging-data.md#\
+ arterial-spin-labeling-perfusion-data)
+ or variable flip angle fMRI sequences.
issue:
code: LOOK_LOCKER_FLIP_ANGLE_MISSING
message: |
@@ -291,6 +332,7 @@ MRIFlipAngleLookLockerTrue:
MRISliceAcceleration:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
MultibandAccelerationFactor: recommended
@@ -298,6 +340,7 @@ MRIAnatomicalLandmarks:
selectors:
- datatype == "anat"
- intersects(dataset.datatypes, ["meg"])
+ - match(extension, "^\.nii(\.gz)?$")
fields:
AnatomicalLandmarkCoordinates__mri: recommended
@@ -312,12 +355,14 @@ MRIEchoPlanarImagingAndB0FieldSource:
selectors:
- intersects(datatype, ['dwi', 'func', 'perf'])
- intersects(dataset.datatypes, ['fmap'])
+ - match(extension, "^\.nii(\.gz)?$")
fields:
B0FieldSource: recommended
MRIInstitutionInformation:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
InstitutionName:
level: recommended
diff --git a/src/schema/rules/sidecars/mrs.yaml b/src/schema/rules/sidecars/mrs.yaml
new file mode 100644
index 0000000000..28796eb5e4
--- /dev/null
+++ b/src/schema/rules/sidecars/mrs.yaml
@@ -0,0 +1,175 @@
+#
+# Groups of related metadata fields
+#
+# Assumptions: never need disjunction of selectors
+# Assumptions: top-to-bottom overrides is sufficient logic
+
+---
+# MRS Common metadata fields
+MRSScannerHardware:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ InstitutionName: recommended
+ InstitutionAddress: recommended
+ InstitutionalDepartmentName: recommended
+ Manufacturer: recommended
+ ManufacturersModelName: recommended
+ DeviceSerialNumber: recommended
+ StationName: recommended
+ SoftwareVersions: recommended
+ MagneticFieldStrength: recommended
+ ReceiveCoilName: recommended
+ ReceiveCoilActiveElements: recommended
+ NumberReceiveCoilActiveElements: optional
+ NumberTransmitCoilActiveElements: optional
+
+MRSSample:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ BodyPart:
+ level: optional
+ level_addendum: required if `voi` entity is present
+ description_addendum: Corresponds to DICOM Tag 0018, 0015 `Body Part Examined`.
+ BodyPartDetails:
+ level: optional
+ level_addendum: required if `voi` entity is present
+ BodyPartDetailsOntology: optional
+
+MRSSampleVOI:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - '"volume" in entities'
+ fields:
+ BodyPart: required
+ BodyPartDetails: required
+
+MRSSequenceSpecifics:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ PulseSequenceType:
+ level: recommended
+ description_addendum: |
+ For example, `"sLASER"`, `"MEGA-PRESS"`, `"EPSI"`, `"Metabolite-cycled MRSI"`.
+ ScanningSequence__mrs: recommended
+ SequenceName: recommended
+ PulseSequenceDetails: recommended
+ WaterSuppression: recommended
+ WaterSuppressionTechnique: optional
+ OuterVolumeSuppression: optional
+ B0ShimmingTechnique: optional
+ B1ShimmingTechnique: optional
+
+MRSRequiredFields:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ ResonantNucleus: required
+ SpectrometerFrequency: required
+ SpectralWidth: required
+ EchoTime: required
+
+MRSRecommendedFields:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ NumberOfSpectralPoints: recommended
+ MixingTime: recommended
+ FlipAngle: recommended
+ AcquisitionVoxelSize: recommended
+ ReferenceSignal: recommended
+
+MRSRepetitionTime:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - '!("VolumeTiming" in sidecar)'
+ fields:
+ RepetitionTime:
+ level: recommended
+ level_addendum: mutually exclusive with `VolumeTiming`
+
+MRSVolumeTiming:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - '!("RepetitionTime" in sidecar)'
+ fields:
+ VolumeTiming:
+ level: recommended
+ level_addendum: mutually exclusive with `RepetitionTime`
+
+MRSConditionalInversionTime:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - entities.inversion
+ fields:
+ InversionTime:
+ level: recommended
+ level_addendum: if `inv` entity is present
+
+MRSConditionalAnatomicalImage:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - intersects(dataset.datatypes, ["anat"])
+ fields:
+ AnatomicalImage:
+ level: recommended
+ level_addendum: if anatomical MRI data are present
+
+MRSConditionalNumTransients:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - intersects([suffix], ["svs", "unloc"])
+ fields:
+ NumberOfTransients:
+ level: recommended
+ level_addendum: for SVS and unlocalized acquisitions
+
+MRSIRecommendedFields:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ - suffix == "mrsi"
+ fields:
+ MRAcquisitionType:
+ level: recommended
+ level_addendum: for MRSI
+ MatrixSize:
+ level: recommended
+ level_addendum: for MRSI
+ VolumeAffineMatrix:
+ level: recommended
+ level_addendum: for MRSI
+ EncodingTechnique:
+ level: recommended
+ level_addendum: for MRSI
+
+MRSOptionalFields:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ ChemicalShiftOffset: optional
+ ChemicalShiftReference: optional
+ EditTarget: optional
+ EditPulse: optional
+ EditCondition: optional
+ EchoAcquisition: optional
+ ParallelReductionFactorInPlane: optional
+ ParallelAcquisitionTechnique: optional
+ MultibandAccelerationFactor: optional
+ PulseSequenceTiming: optional
+ PulseSequencePulses: optional
+ ReceiveGain: optional
diff --git a/src/schema/rules/sidecars/nirs.yaml b/src/schema/rules/sidecars/nirs.yaml
index de84a85536..43b32545d5 100644
--- a/src/schema/rules/sidecars/nirs.yaml
+++ b/src/schema/rules/sidecars/nirs.yaml
@@ -1,97 +1,4 @@
---
-CoordinateSystem:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- fields:
- NIRSCoordinateSystem: required
- NIRSCoordinateUnits: required
- NIRSCoordinateProcessingDescription: recommended
-
-Fiducials:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- fields:
- FiducialsDescription: optional
- FiducialsCoordinates: recommended
- FiducialsCoordinateUnits: recommended
- FiducialsCoordinateSystem: recommended
-
-AnatomicalLandmark:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- fields:
- AnatomicalLandmarkCoordinates: recommended
- AnatomicalLandmarkCoordinateSystem: recommended
- AnatomicalLandmarkCoordinateUnits: recommended
-
-CoordsystemGeneral:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- fields:
- IntendedFor:
- level: optional
- description_addendum: |
- This identifies the MRI or CT scan associated with the optodes,
- landmarks, and fiducials.
-
-CoordinateSystemDescriptionRec:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- - json.NIRSCoordinateSystem != "other"
- fields:
- NIRSCoordinateSystemDescription:
- level: recommended
- level_addendum: required if NIRSCoordinateSystem is "other"
-
-CoordinateSystemDescriptionReq:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- - json.NIRSCoordinateSystem == "other"
- fields:
- NIRSCoordinateSystemDescription: required
-
-AnatomicalLandmarkCoordinateSystemDescriptionRec:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- - json.AnatomicalLandmarkCoordinateSystem != "other"
- fields:
- AnatomicalLandmarkCoordinateSystemDescription:
- level: recommended
- level_addendum: required if NIRSCoordinateSystem is "other"
-
-AnatomicalLandmarkCoordinateSystemDescriptionReq:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- - json.AnatomicalLandmarkCoordinateSystem == "other"
- fields:
- AnatomicalLandmarkCoordinateSystemDescription: required
-
-FiducialsCoordinateSystemDescriptionRec:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- - json.FiducialsCoordinateSystem != "other"
- fields:
- FiducialsCoordinateSystemDescription:
- level: recommended
- level_addendum: required if FiducialsCoordinateSystem is "other"
-
-FiducialsCoordinateSystemDescriptionReq:
- selectors:
- - datatype == "nirs"
- - suffix == "coordsystem"
- - json.FiducialsCoordinateSystem == "other"
- fields:
- FiducialsCoordinateSystemDescription: required
-
NirsHardware:
selectors:
- datatype == "nirs"
diff --git a/tools/mkdocs_macros_bids/macros.py b/tools/mkdocs_macros_bids/macros.py
index e43e8449d3..18d295a195 100644
--- a/tools/mkdocs_macros_bids/macros.py
+++ b/tools/mkdocs_macros_bids/macros.py
@@ -233,6 +233,31 @@ def make_metadata_table(field_info, src_path=None):
return table
+def make_json_table(table_name, src_path=None):
+ """Generate a markdown table of metadata field information.
+
+ Parameters
+ ----------
+ table_name : str or list of str
+ Qualified name(s) in schema.rules.sidecars
+ src_path : str or None
+ The file where this macro is called, which may be explicitly provided
+ by the "page.file.src_path" variable.
+
+ Returns
+ -------
+ table : str
+ A Markdown-format table containing the corresponding table for
+ the requested fields.
+ """
+ if src_path is None:
+ src_path = _get_source_path()
+
+ schema_obj = schema.load_schema()
+ table = render.make_json_table(schema_obj, table_name, src_path=src_path)
+ return table
+
+
def make_sidecar_table(table_name, src_path=None):
"""Generate a markdown table of metadata field information.
diff --git a/tools/mkdocs_macros_bids/main.py b/tools/mkdocs_macros_bids/main.py
index 13bb5caf44..f927ae2699 100644
--- a/tools/mkdocs_macros_bids/main.py
+++ b/tools/mkdocs_macros_bids/main.py
@@ -39,6 +39,7 @@ def define_env(env):
env.macro(macros.make_glossary, "MACROS___make_glossary")
env.macro(macros.make_suffix_table, "MACROS___make_suffix_table")
env.macro(macros.make_metadata_table, "MACROS___make_metadata_table")
+ env.macro(macros.make_json_table, "MACROS___make_json_table")
env.macro(macros.make_sidecar_table, "MACROS___make_sidecar_table")
env.macro(macros.make_subobject_table, "MACROS___make_subobject_table")
env.macro(macros.make_columns_table, "MACROS___make_columns_table")
diff --git a/tools/schemacode/bidsschematools/__main__.py b/tools/schemacode/bidsschematools/__main__.py
index c8d554cf0a..a250e37cab 100644
--- a/tools/schemacode/bidsschematools/__main__.py
+++ b/tools/schemacode/bidsschematools/__main__.py
@@ -1,8 +1,15 @@
import logging
import os
+import sys
import click
+if sys.version_info < (3, 9):
+ from importlib_resources import files
+else:
+ from importlib.resources import files
+
+
from .schema import export_schema, load_schema
@@ -32,5 +39,19 @@ def export(ctx, schema, output):
fobj.write(text)
+@cli.command()
+@click.option("--output", default="-")
+@click.pass_context
+def export_metaschema(ctx, output):
+ """Export BIDS schema to JSON document"""
+ metaschema = files("bidsschematools.data").joinpath("metaschema.json").read_text()
+ if output == "-":
+ print(metaschema, end="")
+ else:
+ output = os.path.abspath(output)
+ with open(output, "w") as fobj:
+ fobj.write(metaschema)
+
+
if __name__ == "__main__":
cli()
diff --git a/tools/schemacode/bidsschematools/render/__init__.py b/tools/schemacode/bidsschematools/render/__init__.py
index a251a1f3cd..7042126476 100644
--- a/tools/schemacode/bidsschematools/render/__init__.py
+++ b/tools/schemacode/bidsschematools/render/__init__.py
@@ -3,6 +3,7 @@
from bidsschematools.render.tables import (
make_columns_table,
make_entity_table,
+ make_json_table,
make_metadata_table,
make_sidecar_table,
make_subobject_table,
@@ -20,6 +21,7 @@
__all__ = [
"make_entity_table",
"make_suffix_table",
+ "make_json_table",
"make_sidecar_table",
"make_metadata_table",
"make_subobject_table",
diff --git a/tools/schemacode/bidsschematools/render/tables.py b/tools/schemacode/bidsschematools/render/tables.py
index 31b6e13fd0..8eb6b386a4 100644
--- a/tools/schemacode/bidsschematools/render/tables.py
+++ b/tools/schemacode/bidsschematools/render/tables.py
@@ -173,7 +173,7 @@ def _make_table_from_rule(
elements: dict[str, str | dict[str, str]] = {}
for table in table_name:
if table_type == "metadata":
- table_schema = schema.rules.sidecars[table]
+ table_schema = schema.rules[table]
new_elements = table_schema.fields
elif table_type == "columns":
table_schema = schema.rules.tabular_data[table]
@@ -407,7 +407,7 @@ def preproc_suffix(row):
return table_str
-def make_sidecar_table(
+def make_json_table(
schema: Namespace,
table_name: ty.Union[str, ty.List[str]],
src_path: ty.Optional[str] = None,
@@ -443,6 +443,45 @@ def make_sidecar_table(
return table_str
+def make_sidecar_table(
+ schema: Namespace,
+ table_name: ty.Union[str, ty.List[str]],
+ src_path: ty.Optional[str] = None,
+ tablefmt: str = "github",
+):
+ """Produce metadata table (markdown) based on requested fields.
+
+ Parameters
+ ----------
+ schema : Namespace
+ The BIDS schema.
+ table_name : str or list of str
+ Qualified name(s) in schema.rules.sidecars
+ src_path : str or None
+ The file where this macro is called, which may be explicitly provided
+ by the "page.file.src_path" variable.
+ tablefmt : string, optional
+ The target table format. The default is "github" (GitHub format).
+
+ Returns
+ -------
+ table_str : str
+ The tabulated table as a Markdown string.
+ """
+ table_str = _make_table_from_rule(
+ schema=schema,
+ table_type="metadata",
+ table_name=[
+ f"sidecars.{table}"
+ for table in ([table_name] if isinstance(table_name, str) else table_name)
+ ],
+ src_path=src_path,
+ tablefmt=tablefmt,
+ )
+
+ return table_str
+
+
def make_metadata_table(schema, field_info, src_path=None, tablefmt="github"):
"""Produce metadata table (markdown) based on requested fields.
diff --git a/tools/schemacode/bidsschematools/rules.py b/tools/schemacode/bidsschematools/rules.py
index 6004b0363b..ea4b054ded 100644
--- a/tools/schemacode/bidsschematools/rules.py
+++ b/tools/schemacode/bidsschematools/rules.py
@@ -69,6 +69,16 @@ def _format_entity(entity, name, pattern, level, directory=False):
if directory and entity not in DIR_ENTITIES:
return ""
+ # For a directory entity appearing in a filename, match the value
+ # found in the directory name
+ # Note that this makes it impossible to do something like
+ # /ses-1_dwi.json instead of /sub-??/ses-1/sub-??_ses-1_dwi.json
+ # We'll assume this is a negligible use case.
+ # It is possible to create such a regular expression, but doing it
+ # in a piecewise fashion is difficult.
+ if not directory and entity in DIR_ENTITIES:
+ return rf"(?({entity}){name}-(?P={entity})_)"
+
label = _capture_regex(entity, pattern, not directory and entity in DIR_ENTITIES)
post = "/" if directory else "_"
diff --git a/tools/schemacode/bidsschematools/tests/test_rules.py b/tools/schemacode/bidsschematools/tests/test_rules.py
index 35b167e199..ca2c5b920e 100644
--- a/tools/schemacode/bidsschematools/tests/test_rules.py
+++ b/tools/schemacode/bidsschematools/tests/test_rules.py
@@ -1,3 +1,5 @@
+import re
+
from bidsschematools import rules
from ..types import Namespace
@@ -13,19 +15,29 @@ def test_entity_rule(schema_obj):
"extensions": [".nii"],
}
)
- assert rules._entity_rule(rule, schema_obj) == {
+ nii_rule = rules._entity_rule(rule, schema_obj)
+ assert nii_rule == {
"regex": (
r"sub-(?P[0-9a-zA-Z]+)/"
r"(?:ses-(?P[0-9a-zA-Z]+)/)?"
r"(?Panat)/"
- r"sub-(?P=subject)_"
- r"(?:ses-(?P=session)_)?"
+ r"(?(subject)sub-(?P=subject)_)"
+ r"(?(session)ses-(?P=session)_)"
r"(?PT1w)"
r"(?P\.nii)\Z"
),
"mandatory": False,
}
+ assert re.match(nii_rule["regex"], "sub-01/anat/sub-01_T1w.nii")
+ assert re.match(nii_rule["regex"], "sub-01/ses-01/anat/sub-01_ses-01_T1w.nii")
+ assert not re.match(nii_rule["regex"], "sub-01/anat/sub-02_T1w.nii")
+ assert not re.match(nii_rule["regex"], "sub-01/sub-01_T1w.nii")
+ assert not re.match(nii_rule["regex"], "sub-01_T1w.nii")
+ assert not re.match(nii_rule["regex"], "sub-01/ses-01/anat/sub-01_T1w.nii")
+ assert not re.match(nii_rule["regex"], "sub-01/anat/sub-01_ses-01_T1w.nii")
+ assert not re.match(nii_rule["regex"], "sub-01/ses-01/anat/sub-01_ses-02_T1w.nii")
+
# Sidecar entities are optional
rule = Namespace.build(
{
@@ -35,18 +47,31 @@ def test_entity_rule(schema_obj):
"extensions": [".json"],
}
)
- assert rules._entity_rule(rule, schema_obj) == {
+ json_rule = rules._entity_rule(rule, schema_obj)
+ assert json_rule == {
"regex": (
r"(?:sub-(?P[0-9a-zA-Z]+)/)?"
r"(?:ses-(?P[0-9a-zA-Z]+)/)?"
r"(?:(?Panat)/)?"
- r"(?:sub-(?P=subject)_)?"
- r"(?:ses-(?P=session)_)?"
+ r"(?(subject)sub-(?P=subject)_)"
+ r"(?(session)ses-(?P=session)_)"
r"(?PT1w)"
r"(?P\.json)\Z"
),
"mandatory": False,
}
+ assert re.match(json_rule["regex"], "sub-01/anat/sub-01_T1w.json")
+ assert re.match(json_rule["regex"], "sub-01/sub-01_T1w.json")
+ assert re.match(json_rule["regex"], "T1w.json")
+ assert re.match(json_rule["regex"], "sub-01/ses-01/anat/sub-01_ses-01_T1w.json")
+ assert re.match(json_rule["regex"], "sub-01/ses-01/sub-01_ses-01_T1w.json")
+ assert not re.match(json_rule["regex"], "sub-01/anat/sub-02_T1w.json")
+ assert not re.match(json_rule["regex"], "sub-01_T1w.json")
+ assert not re.match(json_rule["regex"], "ses-01_T1w.json")
+ assert not re.match(json_rule["regex"], "sub-01/ses-01/anat/sub-01_T1w.json")
+ assert not re.match(json_rule["regex"], "sub-01/anat/sub-01_ses-01_T1w.json")
+ assert not re.match(json_rule["regex"], "sub-01/ses-01/ses-01_T1w.json")
+ assert not re.match(json_rule["regex"], "sub-01/ses-01/anat/sub-01_ses-02_T1w.json")
def test_split_inheritance_rules():
diff --git a/tools/schemacode/bidsschematools/tests/test_validator.py b/tools/schemacode/bidsschematools/tests/test_validator.py
index abd11737f0..90f0695308 100644
--- a/tools/schemacode/bidsschematools/tests/test_validator.py
+++ b/tools/schemacode/bidsschematools/tests/test_validator.py
@@ -29,6 +29,42 @@ def test_inheritance_examples():
assert result["path_tracking"] == incorrect_inheritance
+def test_regression_examples():
+ """Tests that failed on pybids when switching to bst-regex-based validation"""
+ examples = [
+ "/sub-01/ses-ses/sub-01_dwi.bval", # redundant dir /ses-ses/
+ "/sub-01/01_dwi.bvec", # missed subject suffix
+ "/sub-01/sub_dwi.json", # missed subject id
+ "/sub-01/sub-01_23_run-01_dwi.bval", # wrong _23_
+ "/sub-01/sub-01_run-01_dwi.vec", # wrong extension
+ "/sub-01/sub-01_run-01_dwi.jsn", # wrong extension
+ "/sub-01/sub-01_acq_dwi.bval", # missed suffix value
+ "/sub-01/sub-01_acq-23-singleband_dwi.bvec", # redundant -23-
+ "/sub-01/anat/sub-01_acq-singleband_dwi.json", # redundant /anat/
+ "/sub-01/sub-01_recrod-record_acq-singleband_run-01_dwi.bval", # redundant record-record_
+ "/sub_01/sub-01_acq-singleband_run-01_dwi.bvec", # wrong /sub_01/
+ "/sub-01/sub-01_acq-singleband__run-01_dwi.json", # wrong __
+ "/sub-01/ses-test/sub-01_ses_test_dwi.bval", # wrong ses_test
+ "/sub-01/ses-test/sb-01_ses-test_dwi.bvec", # wrong sb-01
+ "/sub-01/ses-test/sub-01_ses-test_dw.json", # wrong modality
+ "/sub-01/ses-test/sub-01_ses-test_run-01_dwi.val", # wrong extension
+ "/sub-01/ses-test/sub-01_run-01_dwi.bvec", # missed session in the filename
+ # This validator adds a .*/ to the regex, so this will be a false negative
+ # If I cared about this validator, I would dig into it, but it doesn't seem worth it.
+ # -cjm 2024.08.14
+ # "/sub-01/ses-test/ses-test_run-01_dwi.json", # missed subject in the filename
+ "/sub-01/ses-test/sub-01_ses-test_acq-singleband.bval", # missed modality
+ "/sub-01/ses-test/sub-01_ses-test_acq-singleband_dwi", # missed extension
+ "/ses-test/sub-01/sub-01_ses-test_acq-singleband_dwi.json", # wrong dirs order
+ "/sub-01/ses-test/sub-02_ses-test_acq-singleband_run-01_dwi.bval", # wrong sub id
+ "/sub-01/sub-01_ses-test_acq-singleband_run-01_dwi.bvec", # ses dir missed
+ "/ses-test/sub-01_ses-test_acq-singleband_run-01_dwi.json", # sub id dir missed
+ ]
+
+ result = validate_bids(examples, dummy_paths=True)
+ assert result["path_tracking"] == examples
+
+
def test_write_report(tmp_path):
from bidsschematools.validator import write_report