diff --git a/.github/workflows/publish_schema.yml b/.github/workflows/publish_schema.yml
new file mode 100644
index 0000000000..c2528e02c4
--- /dev/null
+++ b/.github/workflows/publish_schema.yml
@@ -0,0 +1,89 @@
+name: "Publish schema"
+
+on:
+ push:
+ branches:
+ - "master"
+
+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..db6e77700a 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[all]
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 c59c7ce96a..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.0
+ 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..d5367cfcdd 100644
--- a/src/appendices/qmri.md
+++ b/src/appendices/qmri.md
@@ -133,10 +133,13 @@ A guide for using macros can be found at
Please visit the [file collections appendix](./file-collections.md#magnetic-resonance-imaging) to see the list of currently supported qMRI applications.
-### Quantitative maps are derivatives
+### Outputs are quantitative maps
-Regardless of how they are obtained (pre- or post-generated), qMRI maps are stored in the `derivatives` directory.
-For example a `T1map` can be generated from an `MP2RAGE` file collection using either options.
+qMRI maps are stored differently depending on the process that generated them.
+Pre-generated qMRI maps MAY be stored as part of a raw BIDS dataset,
+whereas they MUST be stored in a derivative BIDS dataset if they were post-generated.
+
+See the example below of a `T1map` generated from an `MP2RAGE` file collection using either option.
If the map is post-generated:
@@ -151,15 +154,25 @@ A guide for using macros can be found at
"qMRI-software-name": {
"sub-01": {
"anat": {
- "sub-01_T1map.nii.gz": "",
+ "sub-01_T1map.nii.gz": " # --> T1 map in a derivative dataset",
"sub-01_T1map.json": "",
- "sub-01_UNIT1.nii.gz": "",
+ "sub-01_UNIT1.nii.gz": " # --> UNI T1 in a derivative dataset",
"sub-01_UNIT1.json": "",
- },
},
},
},
},
+ "sub-01": {
+ "anat": {
+ "sub-01_inv-1_part-mag_MP2RAGE.nii.gz":"",
+ "sub-01_inv-1_part-phase_MP2RAGE.nii.gz":"",
+ "sub-01_inv-1_MP2RAGE.json":"",
+ "sub-01_inv-2_part-mag_MP2RAGE.nii.gz":"",
+ "sub-01_inv-2_part-phase_MP2RAGE.nii.gz":"",
+ "sub-01_inv-2_MP2RAGE.json":"",
+ },
+ },
+ },
}
) }}
@@ -172,25 +185,30 @@ A guide for using macros can be found at
{{ MACROS___make_filetree_example(
{
"ds-example": {
- "derivatives": {
- "Siemens": {
- "sub-01": {
- "anat": {
- "sub-01_T1map.nii.gz": "",
- "sub-01_T1map.json": "",
- "sub-01_UNIT1.nii.gz": "",
- "sub-01_UNIT1.json": "",
- },
- },
- },
+ "sub-01": {
+ "anat": {
+ "sub-01_inv-1_part-mag_MP2RAGE.nii.gz":"",
+ "sub-01_inv-1_part-phase_MP2RAGE.nii.gz":"",
+ "sub-01_inv-1_MP2RAGE.json":"",
+ "sub-01_inv-2_part-mag_MP2RAGE.nii.gz":"",
+ "sub-01_inv-2_part-phase_MP2RAGE.nii.gz":"",
+ "sub-01_inv-2_MP2RAGE.json":"",
+ "sub-01_T1map.nii.gz": " # --> T1 map in a raw dataset",
+ "sub-01_T1map.json": "",
+ "sub-01_UNIT1.nii.gz": " # --> UNI T1 in a raw dataset",
+ "sub-01_UNIT1.json": "",
},
},
+ }
}
) }}
-Note: Even though the process from which pre-generated qMRI maps are obtained (vendor pipelines) is not known,
-vendors generally allow exporting of the corresponding input data.
-It is RECOMMENDED to share them along with the vendor outputs, whenever possible for a qMRI method supported by BIDS.
+!!! note "Sharing of vendor outputs"
+
+ Even though the process from which pre-generated qMRI maps are obtained (vendor pipelines) is not known,
+ vendors generally allow exporting of the corresponding input data.
+ It is RECOMMENDED to share them along with the vendor outputs,
+ whenever possible for a qMRI method supported by BIDS.
### Example datasets
@@ -326,7 +344,7 @@ A guide for using macros can be found at
`dataset_description.json`:
-```text
+```json
{
"Name": "qMRLab Outputs",
"BIDSVersion": "1.5.0",
@@ -526,12 +544,13 @@ as an input to offline calculation of a `T1map` using a dictionary lookup approa
provided by the stock sequence. Instead, the `magnitude` and `phase` images are exported. Please
see the relevant discussion at [qMRLab issue #255](https://github.com/qMRLab/qMRLab/issues/255).
-Therefore, the `UNIT1` image provided by the scanner is RECOMMENDED to be stored under the `anat`
-raw dataset directory along with the `MP2RAGE` file collection and to be used as the primary input
-for quantifying a `T1map`.
+Therefore, the `UNIT1` image provided by the scanner
+SHOULD be stored under the `anat` in a raw BIDS dataset
+along with the `MP2RAGE` file collection
+and to be used as the primary input for quantifying a `T1map`.
-If an additional `UNIT1` image is calculated offline, then the output is to be stored in the
-`derivatives` directory with necessary provenance information.
+If an additional `UNIT1` image is calculated offline,
+then the output MUST be stored in a derivative BIDS dataset with necessary provenance information.
##### `NumberShots` metadata field
@@ -607,10 +626,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 d5e8e6abc9..7b1ae30908 100644
--- a/src/modality-specific-files/magnetic-resonance-imaging-data.md
+++ b/src/modality-specific-files/magnetic-resonance-imaging-data.md
@@ -160,9 +160,13 @@ 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("mri.MRIEchoPlanarImagingAndB0Mapping") }}
+{{ MACROS___make_sidecar_table([
+ "mri.MRIB0FieldIdentifier",
+ "mri.MRIEchoPlanarImagingAndB0FieldSource",
+ ])
+}}
-#### Tissue description
+### Tissue description
{{ MACROS___make_sidecar_table("mri.MRISample") }}
+### Deidentification information
+
+Describes the mechanism or method used to modify or remove metadata
+and/or pixel data to protect the patient or participant's identity.
+
+
+{{ MACROS___make_sidecar_table("mri.DeidentificationMethod") }}
+
+Each object in the `DeidentificationMethodCodeSequence` array includes the following RECOMMENDED keys:
+
+
+{{ MACROS___make_subobject_table("metadata.DeidentificationMethodCodeSequence.items") }}
+
## Anatomy imaging data
Anatomy MRI sequences measure static, structural features of the brain.
@@ -656,18 +685,13 @@ The definitions of these fields can be found in
and a guide for using macros can be found at
https://github.com/bids-standard/bids-specification/blob/master/macros_doc.md
-->
-{{ MACROS___make_suffix_table(["dwi", "sbref"]) }}
-
-Additionally, the following suffixes are used for scanner-generated images:
-
-
-{{ MACROS___make_suffix_table(["ADC", "TRACE"]) }}
+{{ MACROS___make_suffix_table(
+ [
+ "dwi",
+ "sbref",
+ ]
+ )
+}}
+{{ 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:
+
+1. a proprietary DICOM-structured file known as IMA (`*.ima`);
+1. a conventional DICOM MR Spectroscopy Storage format (`*.dcm`);
+1. RDA (`*.rda`), a proprietary file format with a text-formatted header followed by the binary data points;
+1. 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") }}
+
+#### Institution information
+
+
+{{ MACROS___make_sidecar_table("mrs.MRSInstitutionInformation") }}
+
+#### 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/microscopy.md b/src/modality-specific-files/microscopy.md
index d095a7bb4d..569e6a5245 100644
--- a/src/modality-specific-files/microscopy.md
+++ b/src/modality-specific-files/microscopy.md
@@ -55,7 +55,7 @@ Microscopy raw data MUST be stored in one of the following formats:
- [OME-TIFF](https://docs.openmicroscopy.org/ome-model/6.1.2/ome-tiff/specification.html#)
(`.ome.tif` for standard TIFF files or `.ome.btf` for
- [BigTIFF](https://www.awaresystems.be/imaging/tiff/bigtiff.html) files)
+ [BigTIFF](https://web.archive.org/web/20240706160214/https://www.awaresystems.be/imaging/tiff/bigtiff.html) files)
- [OME-ZARR/NGFF](https://ngff.openmicroscopy.org/latest/) (`.ome.zarr` directories)
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/modality-specific-files/positron-emission-tomography.md b/src/modality-specific-files/positron-emission-tomography.md
index f15f3fad40..856789a887 100644
--- a/src/modality-specific-files/positron-emission-tomography.md
+++ b/src/modality-specific-files/positron-emission-tomography.md
@@ -230,6 +230,31 @@ A guide for using macros can be found at
-->
{{ MACROS___make_sidecar_table("pet.PETSample") }}
+#### Deidentification information
+
+Describes the mechanism or method used to modify or remove metadata
+and/or pixel data to protect the patient or participant's identity.
+
+
+{{ MACROS___make_sidecar_table("mri.DeidentificationMethod") }}
+
+Each object in the `DeidentificationMethodCodeSequence` array includes the following RECOMMENDED keys:
+
+
+{{ MACROS___make_subobject_table("metadata.DeidentificationMethodCodeSequence.items") }}
+
#### Task
If the OPTIONAL [`task-`](../appendices/entities.md#task) is used,
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..b179148e73 100644
--- a/src/schema/SCHEMA_VERSION
+++ b/src/schema/SCHEMA_VERSION
@@ -1 +1 @@
-0.10.1-dev
+0.11.3-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 02bdd72915..2ddcb7cc5e 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
x_coordinate:
name: x_coordinate
display_name: Gaze x-coordinate
@@ -266,13 +257,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
@@ -299,12 +286,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
@@ -452,11 +435,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
@@ -464,11 +443,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
@@ -485,12 +460,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
@@ -586,22 +557,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
@@ -634,7 +597,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
@@ -781,7 +743,6 @@ type__optodes:
enum:
- $ref: objects.enums.source.value
- $ref: objects.enums.detector.value
- - n/a
units:
name: units
display_name: Units
@@ -836,11 +797,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
@@ -881,71 +838,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 e177454de3..0843cc6ae8 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 98b3a28e1c..1bfa7d4bcc 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
@@ -244,14 +258,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:
@@ -451,6 +465,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
@@ -610,6 +640,55 @@ DecayCorrectionFactor:
type: array
items:
type: number
+DeidentificationMethod:
+ name: DeidentificationMethod
+ display_name: Deidentification Method
+ description: |
+ A description of the mechanism or method used to remove the Patient's identity.
+ Corresponds to DICOM Tag 0012, 0063 `De-identification Method`.
+ type: array
+ items:
+ type: string
+DeidentificationMethodCodeSequence:
+ name: DeidentificationMethodCodeSequence
+ display_name: Deidentification Method Code Sequence
+ description: |
+ A sequence of code objects describing the mechanism or method use to remove the Patient's identity.
+ Corresponds to DICOM Tag 0012, 0064 `De-identification Method Code Sequence`.
+ type: array
+ items:
+ type: object
+ recommended_fields:
+ - CodeValue
+ - CodeMeaning
+ - CodingSchemeDesignator
+ - CodingSchemeVersion
+ properties:
+ CodeValue:
+ name: CodeValue
+ type: string
+ description: |
+ An identifier that is unambiguous within the Coding Scheme
+ denoted by Coding Scheme Designator and Coding Scheme Version.
+ Corresponds to DICOM Tag 0008, 0100 `Code Value`.
+ CodeMeaning:
+ name: CodeMeaning
+ type: string
+ description: |
+ Text that has meaning to a human and conveys the meaning of the term
+ Corresponds to DICOM Tag 0008, 0104 `Code Meaning`.
+ CodingSchemeDesignator:
+ name: CodingSchemeDesignator
+ type: string
+ description: |
+ The identifier of the coding scheme in which the coded entry is defined.
+ Corresponds to DICOM Tag 0008, 0102 `Coding Scheme Designator`.
+ CodingSchemeVersion:
+ name: CodingSchemeVersion
+ type: string
+ description: |
+ An identifier of the version of the coding scheme if necessary to resolve ambiguity.
+ Corresponds to DICOM Tag 0008, 0103 `Coding Scheme Version`.
DelayAfterTrigger:
name: DelayAfterTrigger
display_name: Delay After Trigger
@@ -893,6 +972,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
@@ -905,12 +991,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
@@ -945,6 +1025,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
@@ -1001,7 +1130,14 @@ EnvironmentCoordinates:
Coordinates origin (or zero), for gaze-on-screen coordinates,
this can be for example: `"top-left"` or `"center"`.
For virtual reality this could be, amongst others, spherical coordinates.
- type: string
+ 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"`.
EpochLength:
name: EpochLength
display_name: Epoch Length
@@ -1154,12 +1290,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
@@ -1996,6 +2126,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:
@@ -2101,6 +2232,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
@@ -2172,7 +2316,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
@@ -2327,6 +2472,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
@@ -2415,6 +2573,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
@@ -2674,13 +2838,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
PupilFitMethod:
name: PupilFitMethod
@@ -2744,6 +2929,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
@@ -2844,6 +3041,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
@@ -2922,6 +3133,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
@@ -3151,6 +3373,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
@@ -3489,6 +3721,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
@@ -3606,6 +3859,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
@@ -3803,6 +4073,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
@@ -3823,12 +4114,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 f029a062bf..36e5ddbd12 100644
--- a/src/schema/objects/suffixes.yaml
+++ b/src/schema/objects/suffixes.yaml
@@ -6,11 +6,6 @@ TwoPE:
display_name: 2-photon excitation microscopy
description: |
2-photon excitation microscopy imaging data
-ADC:
- value: ADC
- display_name: Apparent diffusion coefficient (ADC)
- description:
- Apparent diffusion coefficient (ADC) map
BF:
value: BF
display_name: Bright-field microscopy
@@ -465,11 +460,6 @@ TEM:
display_name: Transmission electron microscopy
description: |
Transmission electron microscopy imaging data
-TRACE:
- value: TRACE
- display_name: Trace diffusion weighted image
- description: |
- Diffusion images proportional to the trace of the diffusion tensor
UNIT1:
value: UNIT1
display_name: Homogeneous (flat) T1-weighted MP2RAGE image
@@ -693,6 +683,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
@@ -825,8 +827,19 @@ 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.
uCT:
value: 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/asl.yaml b/src/schema/rules/checks/asl.yaml
index 1b42593212..2ae11ebeb0 100644
--- a/src/schema/rules/checks/asl.yaml
+++ b/src/schema/rules/checks/asl.yaml
@@ -196,21 +196,21 @@ ASLBackgroundSuppressionNumberPulses:
- length(sidecar.BackgroundSuppressionPulseTime) == sidecar.BackgroundSuppressionNumberPulses
# 181
-ASLTotalAcquiredVolumesASLContextLength:
+ASLTotalAcquiredPairsASLContextLength:
issue:
code: TOTAL_ACQUIRED_VOLUMES_NOT_CONSISTENT
message: |
- The number of values for 'TotalAcquiredVolumes' for this file does not match number of
+ The number of values for 'TotalAcquiredPairs' for this file does not match number of
volumes in the associated 'aslcontext.tsv'.
- 'TotalAcquiredVolumes' is the original number of 3D volumes acquired for each volume defined in the
+ 'TotalAcquiredPairs' is the original number of 3D volumes acquired for each volume defined in the
associated 'aslcontext.tsv'.
level: warning
selectors:
- suffix == "asl"
- type(associations.aslcontext) != "null"
- - '"TotalAcquiredVolumes" in sidecar'
+ - '"TotalAcquiredPairs" in sidecar'
checks:
- - aslcontext.n_rows == sidecar.TotalAcquiredVolumes
+ - aslcontext.n_rows == sidecar.TotalAcquiredPairs
# 184
PostLabelingDelayGreater:
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 1f0f57a123..4c4e143afd 100644
--- a/src/schema/rules/checks/events.yaml
+++ b/src/schema/rules/checks/events.yaml
@@ -10,9 +10,11 @@ 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/fmap.yaml b/src/schema/rules/checks/fmap.yaml
index b6e8e621cc..199b6e44b3 100644
--- a/src/schema/rules/checks/fmap.yaml
+++ b/src/schema/rules/checks/fmap.yaml
@@ -9,6 +9,7 @@ EchoTime12DifferenceUnreasonable:
level: error
selectors:
- suffix == "phasediff"
+ - match(extension, '\.nii(\.gz)?$')
checks:
- sidecar.EchoTime2 - sidecar.EchoTime1 >= 0.0001
- sidecar.EchoTime2 - sidecar.EchoTime1 <= 0.01
@@ -22,6 +23,7 @@ FmapFieldmapWithoutMagnitude:
level: error
selectors:
- suffix == "fieldmap"
+ - match(extension, '\.nii(\.gz)?$')
checks:
- '"magnitude" in associations'
@@ -34,6 +36,7 @@ FmapPhasediffWithoutMagnitude:
level: warning
selectors:
- suffix == "phasediff"
+ - match(extension, '\.nii(\.gz)?$')
checks:
- '"magnitude1" in associations'
diff --git a/src/schema/rules/checks/func.yaml b/src/schema/rules/checks/func.yaml
index cce740a1ec..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,15 +117,13 @@ VolumeTimingDelayTimeMutex:
checks:
- type(sidecar.DelayTime) == "null"
-SliceTimingAcquisitionDurationMutex:
+VolumeTimingMissingAcquisitionDuration:
issue:
- code: SLICE_TIMING_AND_DURATION_MUTUALLY_EXCLUSIVE
+ code: VOLUME_TIMING_MISSING_ACQUISITION_DURATION
message: |
- 'SliceTiming' is defined for this file.
- Neither 'DelayTime' nor 'AcquisitionDuration' may be defined in addition.
+ The field 'VolumeTiming' requires 'AcquisitionDuration' or 'SliceTiming' to be defined.
level: error
selectors:
- - type(sidecar.SliceTiming) != "null"
+ - type(sidecar.VolumeTiming) != "null"
checks:
- - type(sidecar.AcquisitionDuration) == "null"
- - type(sidecar.DelayTime) == "null"
+ - '"SliceTiming" in sidecar || "AcquisitionDuration" in sidecar'
diff --git a/src/schema/rules/checks/hints.yaml b/src/schema/rules/checks/hints.yaml
index 50cc0f05dc..6efaf4ecdc 100644
--- a/src/schema/rules/checks/hints.yaml
+++ b/src/schema/rules/checks/hints.yaml
@@ -34,6 +34,19 @@ TooFewAuthors:
checks:
- length(json.Authors) > 1
+# 115
+EmptyDatasetName:
+ issue:
+ code: EMPTY_DATASET_NAME
+ message: |
+ The Name field of dataset_description.json is present but empty of visible characters.
+ level: warning
+ selectors:
+ - path == '/dataset_description.json'
+ - type(json.Name) != 'null'
+ checks:
+ - match(json.Name, '\S')
+
### Functional files
# 85
diff --git a/src/schema/rules/checks/micr.yaml b/src/schema/rules/checks/micr.yaml
index bf2f4c55c3..431113cc10 100644
--- a/src/schema/rules/checks/micr.yaml
+++ b/src/schema/rules/checks/micr.yaml
@@ -10,17 +10,34 @@ PixelSizeInconsistent:
selectors:
- ome != null
- sidecar.PixelSize != null
- - sidecar.PixelSizeUnit != null
+ - sidecar.PixelSizeUnits != null
checks:
+ # Note that OME-XML uses µm for microns and BIDS uses um
+ # Accept an error up to .001 of the BIDS unit, to account for floating point error
- |
- ome.PhysicalSizeX * 10 ** (-3 * index(["mm", "um", "nm"], ome.PhysicalSizeXUnit))
- == sidecar.PixelSize[0] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnit))
+ ome.PhysicalSizeX * 10 ** (-3 * index(["mm", "µm", "nm"], ome.PhysicalSizeXUnit))
+ - sidecar.PixelSize[0] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ < 0.001 * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
- |
- ome.PhysicalSizeY * 10 ** (-3 * index(["mm", "um", "nm"], ome.PhysicalSizeYUnit))
- == sidecar.PixelSize[1] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnit))
+ sidecar.PixelSize[0] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ - ome.PhysicalSizeX * 10 ** (-3 * index(["mm", "µm", "nm"], ome.PhysicalSizeXUnit))
+ < 0.001 * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
- |
- ome.PhysicalSizeZ * 10 ** (-3 * index(["mm", "um", "nm"], ome.PhysicalSizeZUnit))
- == sidecar.PixelSize[2] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnit))
+ ome.PhysicalSizeY * 10 ** (-3 * index(["mm", "µm", "nm"], ome.PhysicalSizeYUnit))
+ - sidecar.PixelSize[1] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ < 0.001 * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ - |
+ sidecar.PixelSize[1] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ - ome.PhysicalSizeY * 10 ** (-3 * index(["mm", "µm", "nm"], ome.PhysicalSizeYUnit))
+ < 0.001 * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ - |
+ ome.PhysicalSizeZ * 10 ** (-3 * index(["mm", "µm", "nm"], ome.PhysicalSizeZUnit))
+ - sidecar.PixelSize[2] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ < 0.001 * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ - |
+ sidecar.PixelSize[2] * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
+ - ome.PhysicalSizeZ * 10 ** (-3 * index(["mm", "µm", "nm"], ome.PhysicalSizeZUnit))
+ < 0.001 * 10 ** (-3 * index(["mm", "um", "nm"], sidecar.PixelSizeUnits))
# 227
InconsistentTiffExtension:
diff --git a/src/schema/rules/checks/mri.yaml b/src/schema/rules/checks/mri.yaml
index e2efc0216d..91a1520179 100644
--- a/src/schema/rules/checks/mri.yaml
+++ b/src/schema/rules/checks/mri.yaml
@@ -109,9 +109,9 @@ BolusCutOffDelayTimeNotMonotonicallyIncreasing:
level: error
selectors:
- modality == "mri"
- - sidecar.BolusCutoffDelayTime != null
+ - sidecar.BolusCutOffDelayTime != null
checks:
- - allequal(sorted(sidecar.BolusCutoffDelayTime), sidecar.BolusCutoffDelayTime)
+ - allequal(sorted(sidecar.BolusCutOffDelayTime), sidecar.BolusCutOffDelayTime)
# 201
RepetitionTimePreparationNotConsistent:
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 3ec07ce1d2..b4038b9d34 100644
--- a/src/schema/rules/checks/nirs.yaml
+++ b/src/schema/rules/checks/nirs.yaml
@@ -1,15 +1,29 @@
---
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"
- - samplingFrequency == "n/a"
+ - sidecar.SamplingFrequency == "n/a"
checks:
- 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
@@ -21,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
@@ -28,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
@@ -35,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
@@ -42,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'
@@ -49,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"
@@ -70,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"
@@ -79,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"
@@ -88,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"
@@ -97,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/deriv/imaging.yaml b/src/schema/rules/files/deriv/imaging.yaml
index c9a0e12877..e037dcc10c 100644
--- a/src/schema/rules/files/deriv/imaging.yaml
+++ b/src/schema/rules/files/deriv/imaging.yaml
@@ -26,15 +26,6 @@ dwi_volumetric:
density: optional
description: optional
-dwi_isotropic:
- $ref: rules.files.raw.dwi.isotropic
- entities:
- $ref: rules.files.raw.dwi.isotropic.entities
- space: optional
- resolution: optional
- density: optional
- description: optional
-
func_volumetric:
$ref: rules.files.raw.func.func
entities:
diff --git a/src/schema/rules/files/raw/dwi.yaml b/src/schema/rules/files/raw/dwi.yaml
index ff18501af9..22b224b023 100644
--- a/src/schema/rules/files/raw/dwi.yaml
+++ b/src/schema/rules/files/raw/dwi.yaml
@@ -38,23 +38,3 @@ sbref:
run: optional
part: optional
chunk: optional
-
-# Common scanner-generated derivatives need raw names
-isotropic:
- suffixes:
- - ADC
- - TRACE
- extensions:
- - .nii.gz
- - .nii
- - .json
- datatypes:
- - dwi
- entities:
- subject: required
- session: optional
- acquisition: optional
- reconstruction: optional
- direction: optional
- run: optional
- chunk: optional
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 21a1a52e80..4daafe0c8f 100644
--- a/src/schema/rules/files/raw/task.yaml
+++ b/src/schema/rules/files/raw/task.yaml
@@ -93,6 +93,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 dbb4d714e0..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
@@ -57,10 +62,6 @@ MRIFuncTimingParameters:
required for sequences that are described with the `VolumeTiming`
field and that do not have the `SliceTiming` field set to allow for
accurate calculation of "acquisition time"
- issue:
- code: VOLUME_TIMING_MISSING_ACQUISITION_DURATION
- message: |
- The field 'VolumeTiming' requires 'AcquisitionDuration' or 'SliceTiming' to be defined.
DelayAfterTrigger: recommended
# fMRI task information
@@ -68,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 33d851f6af..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: 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 c9b94ec83b..1edcdb157b 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,25 +332,37 @@ MRIFlipAngleLookLockerTrue:
MRISliceAcceleration:
selectors:
- modality == "mri"
+ - match(extension, "^\.nii(\.gz)?$")
fields:
MultibandAccelerationFactor: recommended
MRIAnatomicalLandmarks:
selectors:
- - modality == "mri"
+ - datatype == "anat"
+ - intersects(dataset.datatypes, ["meg"])
+ - match(extension, "^\.nii(\.gz)?$")
fields:
AnatomicalLandmarkCoordinates__mri: recommended
-MRIEchoPlanarImagingAndB0Mapping:
+MRIB0FieldIdentifier:
selectors:
- - modality == "mri"
+ - datatype == 'fmap'
+ - match(extension, '\.nii(\.gz)?$')
fields:
B0FieldIdentifier: recommended
+
+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
@@ -320,3 +373,14 @@ MRIInstitutionInformation:
InstitutionalDepartmentName:
level: recommended
description_addendum: Corresponds to DICOM Tag 0008, 1040 `Institutional Department Name`.
+
+DeidentificationMethod:
+ selectors:
+ - intersects([modality], ["mri", "pet"])
+ fields:
+ DeidentificationMethod:
+ level: optional
+ description_addendum: Corresponds to DICOM Tag 0012, 0063 `De-identification Method`.
+ DeidentificationMethodCodeSequence:
+ level: optional
+ description_addendum: Corresponds to DICOM Tag 0012, 0064 `De-identification Method Code Sequence`.
diff --git a/src/schema/rules/sidecars/mrs.yaml b/src/schema/rules/sidecars/mrs.yaml
new file mode 100644
index 0000000000..67d8d5e13b
--- /dev/null
+++ b/src/schema/rules/sidecars/mrs.yaml
@@ -0,0 +1,181 @@
+#
+# Groups of related metadata fields
+#
+# Assumptions: never need disjunction of selectors
+# Assumptions: top-to-bottom overrides is sufficient logic
+
+---
+# MRS Common metadata fields
+MRSInstitutionInformation:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ InstitutionAddress: recommended
+ InstitutionName: recommended
+ InstitutionalDepartmentName: recommended
+
+MRSScannerHardware:
+ selectors:
+ - modality == "mrs"
+ - match(extension, "^\.nii(\.gz)?$")
+ fields:
+ 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_expressions.py b/tools/schemacode/bidsschematools/tests/test_expressions.py
index 5532ef38ba..33eff61000 100644
--- a/tools/schemacode/bidsschematools/tests/test_expressions.py
+++ b/tools/schemacode/bidsschematools/tests/test_expressions.py
@@ -1,7 +1,21 @@
+from collections.abc import Mapping
+from functools import singledispatch
+from typing import Union
+
import pytest
from pyparsing.exceptions import ParseException
-from ..expressions import ASTNode, expression
+from ..expressions import (
+ Array,
+ ASTNode,
+ BinOp,
+ Element,
+ Function,
+ Property,
+ RightOp,
+ expression,
+)
+from ..types import Namespace
def test_schema_expressions(schema_obj):
@@ -77,3 +91,95 @@ def test_checks(schema_obj):
def test_expected_failures(expr):
with pytest.raises(ParseException):
expression.parse_string(expr)
+
+
+def walk_schema(schema_obj, predicate):
+ for key, value in schema_obj.items():
+ if predicate(key, value):
+ yield key, value
+ if isinstance(value, Mapping):
+ for subkey, value in walk_schema(value, predicate):
+ yield f"{key}.{subkey}", value
+
+
+def test_valid_sidecar_field(schema_obj):
+ """Check sidecar fields actually exist in the metadata listed in the schema.
+
+ Test failures are usually due to typos.
+ """
+ field_names = {field.name for key, field in schema_obj.objects.metadata.items()}
+
+ for key, rule in walk_schema(
+ schema_obj.rules, lambda k, v: isinstance(v, Mapping) and v.get("selectors")
+ ):
+ for selector in rule["selectors"]:
+ ast = expression.parse_string(selector)[0]
+ for name in find_names(ast):
+ if name.startswith(("json.", "sidecar.")):
+ assert (
+ name.split(".", 1)[1] in field_names
+ ), f"Bad field in selector: {name} ({key})"
+ for check in rule.get("checks", []):
+ ast = expression.parse_string(check)[0]
+ for name in find_names(ast):
+ if name.startswith(("json.", "sidecar.")):
+ assert (
+ name.split(".", 1)[1] in field_names
+ ), f"Bad field in check: {name} ({key})"
+
+
+def test_test_valid_sidecar_field():
+ schema_obj = Namespace.build(
+ {
+ "objects": {
+ "metadata": {
+ "a": {"name": "a"},
+ }
+ },
+ "rules": {"myruleA": {"selectors": ["sidecar.a"], "checks": ["json.a == sidecar.a"]}},
+ }
+ )
+ test_valid_sidecar_field(schema_obj)
+
+ schema_obj.objects.metadata.a["name"] = "b"
+ with pytest.raises(AssertionError):
+ test_valid_sidecar_field(schema_obj)
+
+
+@singledispatch
+def find_names(node: Union[ASTNode, str]):
+ # Walk AST nodes
+ if isinstance(node, BinOp):
+ yield from find_names(node.lh)
+ yield from find_names(node.rh)
+ elif isinstance(node, RightOp):
+ yield from find_names(node.rh)
+ elif isinstance(node, Array):
+ for element in node.elements:
+ yield from find_names(element)
+ elif isinstance(node, Element):
+ yield from find_names(node.name)
+ yield from find_names(node.index)
+ elif isinstance(node, (int, float)):
+ return
+ else:
+ raise TypeError(f"Unexpected node type: {node!r}")
+
+
+@find_names.register
+def find_function_names(node: Function):
+ yield node.name
+ for arg in node.args:
+ yield from find_names(arg)
+
+
+@find_names.register
+def find_property_name(node: Property):
+ # Properties are left-associative, so expand the left side
+ yield f"{next(find_names(node.name))}.{node.field}"
+
+
+@find_names.register
+def find_identifiers(node: str):
+ if not node.startswith(('"', "'")):
+ yield node
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