Repository: bids-standard/pybids

Issue:
BIDSValidationError When Using build_path with tracer
**Description:**

I'm encountering a `BIDSValidationError` when using the `build_path` method from the `pybids` library (version 0.15.5) to generate BIDS-compliant paths using specific entities. The error message suggests that the built path is not a valid BIDS filename, even though the provided entity values seem spec-compliant.

**Reproduction Steps:**

1. Import the `pybids` library and create a `BIDSLayout` instance.
2. Attempt to use the `build_path` method with a dictionary containing certain entities.

**Expected Behavior:**

I expected the `build_path` method to generate a BIDS-compliant path based on the provided entities.

**Actual Behavior:**

When using the `build_path` method with entities like `"subject"`, `"tracer"`, and `"suffix"`, a `BIDSValidationError` exception is raised with the message that the built path is not a valid BIDS filename. The specific path construction is the issue.

**Additional Information:**

- The `pybids` version I'm using: 0.15.5
- Python version: 3.10.6
- Example code snippet:
```python
from bids import BIDSLayout
BIDSLayout('/path/to/bids_dataset').build_path({"subject": "123", "tracer": "18F", "suffix": "pet"})
```
- Error:
```python
bids.exceptions.BIDSValidationError: Built path sub-123/pet/sub-123trc-18F_pet.nii.gz is not a valid BIDS filename. Please make sure all provided entity values are spec-compliant.
```

As you can see, the filename is missing an underscore before the `trc`. I hope this is an easy fix. I tried manipulating `bids/layout/config/bids.json`, but couldn't figure out if or how that would be the solution.

I've double-checked the entity values, and they seem to be spec-compliant. However, the error indicates that the generated path is invalid according to BIDS filename conventions. Is there a specific order or formatting that I need to follow when using certain entities in the build_path method?

Thank you so much for your help in resolving this issue.


Discussion:

Patch:
diff --git a/bids/layout/config/bids.json b/bids/layout/config/bids.json
index bad844ad..d1db778a 100644
--- a/bids/layout/config/bids.json
+++ b/bids/layout/config/bids.json
@@ -156,10 +156,10 @@
         "sub-{subject}[/ses-{session}]/{datatype<fmap>|fmap}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_run-{run}][_echo-{echo}][_flip-{flip}][_inv-{inv}][_part-{part<mag|phase|real|imag>}]_{suffix<TB1AFI|TB1TFL|TB1RFM|RB1COR>}{extension<.nii|.nii.gz|.json>|.nii.gz}",
         "sub-{subject}[/ses-{session}]/{datatype<fmap>|fmap}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_run-{run}][_echo-{echo}]_flip-{flip}_inv-{inv}[_part-{part<mag|phase|real|imag>}]_{suffix<TB1SRGE>}{extension<.nii|.nii.gz|.json>|.nii.gz}",
         "sub-{subject}[/ses-{session}]/{datatype<fmap>|fmap}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_run-{run}]_{suffix<TB1map|RB1map>}{extension<.nii|.nii.gz|.json>|.nii.gz}",
-        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}][_task-{task}][trc-{tracer}][_rec-{reconstruction}][_run-{run}]_{suffix<pet>}{extension<.nii|.nii.gz|.json>|.nii.gz}",
-        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}][_task-{task}][trc-{tracer}][_rec-{reconstruction}][_run-{run}]_recording-{recording}_{suffix<blood>}{extension<.tsv|.json>}",
-        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}]_task-{task}[trc-{tracer}][_rec-{reconstruction}][_run-{run}]_{suffix<events>}{extension<.tsv|.json>}",
-        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}][_task-{task}][trc-{tracer}][_rec-{reconstruction}][_run-{run}][_recording-{recording}]_{suffix<physio|stim>}{extension<.tsv.gz|.json>}",      
+        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}][_task-{task}][_trc-{tracer}][_rec-{reconstruction}][_run-{run}]_{suffix<pet>}{extension<.nii|.nii.gz|.json>|.nii.gz}",
+        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}][_task-{task}][_trc-{tracer}][_rec-{reconstruction}][_run-{run}]_recording-{recording}_{suffix<blood>}{extension<.tsv|.json>}",
+        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}]_task-{task}[_trc-{tracer}][_rec-{reconstruction}][_run-{run}]_{suffix<events>}{extension<.tsv|.json>}",
+        "sub-{subject}[/ses-{session}]/{datatype<pet>|pet}/sub-{subject}[_ses-{session}][_task-{task}][_trc-{tracer}][_rec-{reconstruction}][_run-{run}][_recording-{recording}]_{suffix<physio|stim>}{extension<.tsv.gz|.json>}",      
         "sub-{subject}[/ses-{session}]/{datatype<perf>|perf}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_rec-{reconstruction}][_dir-{direction}][_run-{run}]_{suffix<asl|m0scan>}{extension<.nii|.nii.gz|.json>|.nii.gz}",
         "sub-{subject}[/ses-{session}]/{datatype<perf>|perf}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_rec-{reconstruction}][_dir-{direction}][_run-{run}]_{suffix<aslcontext>}{extension<.tsv|.json>}",
         "sub-{subject}[/ses-{session}]/{datatype<perf>|perf}/sub-{subject}[_ses-{session}][_acq-{acquisition}][_rec-{reconstruction}][_run-{run}]_{suffix<asllabeling>}{extension<.jpg>}",


Test patch:
diff --git a/bids/layout/tests/test_path_building.py b/bids/layout/tests/test_path_building.py
index f67444b5..27c2042e 100644
--- a/bids/layout/tests/test_path_building.py
+++ b/bids/layout/tests/test_path_building.py
@@ -13,6 +13,15 @@ def layout():
     data_dir = join(get_test_data_path(), '7t_trt')
     return BIDSLayout(data_dir)
 
+def test_path_building_pet(layout):
+    """regression test for https://github.com/bids-standard/pybids/issues/1017
+    
+    TODO: remove if https://github.com/bids-standard/bids-examples/issues/394 is fixed
+          as this test will then be covered 
+          by test_path_building_in_raw_scope
+    
+    """
+    layout.build_path({"subject": "123", "tracer": "18F", "suffix": "pet"})
 
 def test_bold_construction(layout):
     ents = dict(subject='01', run=1, task='rest', suffix='bold')