@yibeichan/claude-skills 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/cli.js +272 -0
  4. package/install.py +240 -0
  5. package/package.json +44 -0
  6. package/skills/bidsapp-nidm-standards/SKILL.md +202 -0
  7. package/skills/bidsapp-nidm-standards/references/babs_config.md +20 -0
  8. package/skills/bidsapp-nidm-standards/references/cli_arguments.md +76 -0
  9. package/skills/bidsapp-nidm-standards/references/container_patterns.md +53 -0
  10. package/skills/bidsapp-nidm-standards/references/nidm_integration.md +403 -0
  11. package/skills/bidsapp-nidm-standards/references/repo_structure.md +121 -0
  12. package/skills/bidsapp-nidm-standards/references/testing_patterns.md +82 -0
  13. package/skills/dicom2fmriprep/SKILL.md +377 -0
  14. package/skills/dicom2fmriprep/evals/evals.json +26 -0
  15. package/skills/dicom2fmriprep/references/babs-details.md +407 -0
  16. package/skills/dicom2fmriprep/references/fmriprep-details.md +250 -0
  17. package/skills/dicom2fmriprep/references/heudiconv-details.md +243 -0
  18. package/skills/fmri-ssm/SKILL.md +317 -0
  19. package/skills/fmri-ssm/references/code_templates.md +1570 -0
  20. package/skills/fmri-ssm/references/downstream_analysis.md +680 -0
  21. package/skills/fmri-ssm/references/group_inference.md +608 -0
  22. package/skills/fmri-ssm/references/hrf_modeling.md +447 -0
  23. package/skills/fmri-ssm/references/model_catalog.md +436 -0
  24. package/skills/fmri-ssm/references/paradigm_guide.md +406 -0
  25. package/skills/fmri-ssm/references/preprocessing.md +614 -0
  26. package/skills/fmri-ssm.zip +0 -0
  27. package/skills/neuroimaging-qc/SKILL.md +203 -0
  28. package/skills/neuroimaging-qc/references/eeg_qc.md +400 -0
  29. package/skills/neuroimaging-qc/references/fmri_qc.md +343 -0
  30. package/skills/neuroimaging-qc/references/fnirs_qc.md +430 -0
  31. package/skills/neuroimaging-qc/references/structural_qc.md +454 -0
  32. package/skills/neuroimaging-qc/scripts/parse_fmriprep_confounds.py +153 -0
  33. package/skills/neuroimaging-qc/scripts/parse_mriqc.py +114 -0
  34. package/skills/neuroimaging-qc/scripts/qc_report.py +295 -0
  35. package/skills/scientific-writer/SKILL.md +202 -0
  36. package/skills/scientific-writer/references/citation_styles.md +163 -0
  37. package/skills/scientific-writer/references/field_conventions.md +245 -0
  38. package/skills/scientific-writer/references/figures_tables.md +225 -0
  39. package/skills/scientific-writer/references/reporting_guidelines.md +225 -0
  40. package/skills.json +54 -0
@@ -0,0 +1,403 @@
1
+ # NIDM Integration Reference
2
+
3
+ ## NIDM Workflow
4
+
5
+ The standard NIDM integration follows this sequence:
6
+
7
+ ```
8
+ 1. Check --skip-nidm flag
9
+ ├─ If true: Skip NIDM processing entirely
10
+ └─ If false: Continue to step 2
11
+
12
+ 2. Locate input NIDM file
13
+ └─ Path: <nidm-input-dir>/[sub-{id}/[ses-{session}/]]<filename>.ttl
14
+
15
+ 3. Copy NIDM file to output (if exists)
16
+ ├─ Destination: <output_dir>/<bidsapp_name>/nidm/sub-{id}/[ses-{session}/]<filename>.ttl
17
+ └─ If not found: Log warning and continue
18
+
19
+ 4. Run analysis (unless --skip-<analysis>)
20
+ └─ Output to: <output_dir>/<bidsapp_name>/<analysis>/...
21
+
22
+ 5. Extract metrics from analysis outputs
23
+ └─ Analysis-specific metric extraction
24
+
25
+ 6. Integrate metrics into NIDM file
26
+ ├─ If copied file exists: Overwrite with integrated data
27
+ └─ If no existing file: Create new NIDM file from scratch
28
+ ```
29
+
30
+ ## NIDM File Naming
31
+
32
+ ### With Sessions
33
+ ```
34
+ sub-{id}/ses-{session}/sub-{id}_ses-{session}.ttl
35
+ ```
36
+ Example: `sub-01/ses-baseline/sub-01_ses-baseline.ttl`
37
+
38
+ ### Without Sessions (Session-less)
39
+ ```
40
+ sub-{id}/sub-{id}.ttl
41
+ ```
42
+ Example: `sub-01/sub-01.ttl`
43
+
44
+ **Important**: Session-less data uses `sub-01.ttl`, NOT `sub-01_ses-baseline.ttl`
45
+
46
+ ## NIDM File Organization
47
+
48
+ ### Input NIDM Directory Structure
49
+
50
+ **Option 1: Single file** (for dataset-level NIDM)
51
+ ```
52
+ <nidm-input-dir>/
53
+ └── nidm.ttl
54
+ ```
55
+
56
+ **Option 2: Per-subject files with sessions**
57
+ ```
58
+ <nidm-input-dir>/
59
+ ├── sub-01/
60
+ │ ├── ses-baseline/
61
+ │ │ └── sub-01_ses-baseline.ttl
62
+ │ └── ses-followup/
63
+ │ └── sub-01_ses-followup.ttl
64
+ └── sub-02/
65
+ └── ses-baseline/
66
+ └── sub-02_ses-baseline.ttl
67
+ ```
68
+
69
+ **Option 3: Per-subject files without sessions**
70
+ ```
71
+ <nidm-input-dir>/
72
+ ├── sub-01/
73
+ │ └── sub-01.ttl
74
+ └── sub-02/
75
+ └── sub-02.ttl
76
+ ```
77
+
78
+ ### Output NIDM Directory Structure
79
+
80
+ **With sessions:**
81
+ ```
82
+ <output_dir>/<bidsapp_name>/nidm/
83
+ ├── dataset_description.json
84
+ └── sub-{id}/
85
+ └── ses-{session}/
86
+ └── sub-{id}_ses-{session}.ttl
87
+ ```
88
+
89
+ **Without sessions:**
90
+ ```
91
+ <output_dir>/<bidsapp_name>/nidm/
92
+ ├── dataset_description.json
93
+ └── sub-{id}/
94
+ └── sub-{id}.ttl
95
+ ```
96
+
97
+ ## NIDM File Format
98
+
99
+ NIDM files use Turtle (.ttl) RDF format. Basic structure:
100
+
101
+ ```turtle
102
+ @prefix prov: <http://www.w3.org/ns/prov#> .
103
+ @prefix nidm: <http://purl.org/nidash/nidm#> .
104
+ @prefix freesurfer: <http://purl.org/nidash/freesurfer#> .
105
+
106
+ # Software Agent
107
+ <software/freesurfer> a prov:Agent, prov:SoftwareAgent ;
108
+ prov:type nidm:FreeSurfer ;
109
+ nidm:softwareVersion "8.0.0" .
110
+
111
+ # Processing Activity
112
+ <activity/recon-all> a prov:Activity ;
113
+ prov:wasAssociatedWith <software/freesurfer> ;
114
+ prov:used <input/T1w> ;
115
+ prov:generated <output/segmentation> .
116
+
117
+ # Metrics
118
+ <output/segmentation> a prov:Entity ;
119
+ freesurfer:LeftHippocampalVolume "3847.2"^^xsd:float ;
120
+ freesurfer:RightHippocampalVolume "3912.5"^^xsd:float .
121
+ ```
122
+
123
+ ## NIDM Copy Implementation
124
+
125
+ ### Copy Function Template
126
+
127
+ ```python
128
+ import shutil
129
+ from pathlib import Path
130
+ import logging
131
+
132
+ logger = logging.getLogger(__name__)
133
+
134
+ def copy_nidm_file(nidm_input_dir, output_dir, bidsapp_name, subject_id, session_id=None):
135
+ """
136
+ Copy existing NIDM file to output directory.
137
+
138
+ If no existing NIDM file is found, logs a warning and returns None.
139
+ The NIDM converter should handle creating a new file from scratch.
140
+
141
+ Args:
142
+ nidm_input_dir: Path to input NIDM directory
143
+ output_dir: Base output directory
144
+ bidsapp_name: Name of BIDSapp (e.g., 'freesurfer_nidm')
145
+ subject_id: Subject ID without 'sub-' prefix
146
+ session_id: Session ID without 'ses-' prefix (optional, None for session-less)
147
+
148
+ Returns:
149
+ Path to copied NIDM file in output directory, or None if no input file found
150
+ """
151
+ # Determine input NIDM file path
152
+ input_dir = Path(nidm_input_dir)
153
+
154
+ # Try per-subject structure
155
+ if session_id:
156
+ # With sessions
157
+ filename = f"sub-{subject_id}_ses-{session_id}.ttl"
158
+ input_file = input_dir / f"sub-{subject_id}" / f"ses-{session_id}" / filename
159
+ if not input_file.exists():
160
+ # Try generic nidm.ttl name
161
+ input_file = input_dir / f"sub-{subject_id}" / f"ses-{session_id}" / "nidm.ttl"
162
+ else:
163
+ # Without sessions - use sub-{id}.ttl
164
+ filename = f"sub-{subject_id}.ttl"
165
+ input_file = input_dir / f"sub-{subject_id}" / filename
166
+ if not input_file.exists():
167
+ # Try generic nidm.ttl name
168
+ input_file = input_dir / f"sub-{subject_id}" / "nidm.ttl"
169
+
170
+ # Fall back to dataset-level file
171
+ if not input_file.exists():
172
+ input_file = input_dir / "nidm.ttl"
173
+
174
+ if not input_file.exists():
175
+ logger.warning(
176
+ f"No existing NIDM file found in {nidm_input_dir} for sub-{subject_id}"
177
+ + (f" ses-{session_id}" if session_id else "")
178
+ + ". NIDM conversion will create a new file from scratch."
179
+ )
180
+ return None
181
+
182
+ # Create output directory
183
+ nidm_output_dir = Path(output_dir) / bidsapp_name / "nidm"
184
+ if session_id:
185
+ subject_nidm_dir = nidm_output_dir / f"sub-{subject_id}" / f"ses-{session_id}"
186
+ output_filename = f"sub-{subject_id}_ses-{session_id}.ttl"
187
+ else:
188
+ # Session-less: sub-{id}.ttl
189
+ subject_nidm_dir = nidm_output_dir / f"sub-{subject_id}"
190
+ output_filename = f"sub-{subject_id}.ttl"
191
+
192
+ subject_nidm_dir.mkdir(parents=True, exist_ok=True)
193
+ output_file = subject_nidm_dir / output_filename
194
+
195
+ # Copy file
196
+ shutil.copy2(input_file, output_file)
197
+ logger.info(f"Copied NIDM file from {input_file} to {output_file}")
198
+
199
+ return output_file
200
+ ```
201
+
202
+ ## NIDM Integration Implementation
203
+
204
+ ### Integration Function Template
205
+
206
+ ```python
207
+ import rdflib
208
+ from pathlib import Path
209
+ from datetime import datetime
210
+ import logging
211
+
212
+ logger = logging.getLogger(__name__)
213
+
214
+ def integrate_metrics_to_nidm(nidm_file, metrics, software_name, software_version,
215
+ subject_id, session_id=None, output_dir=None,
216
+ bidsapp_name=None):
217
+ """
218
+ Integrate analysis metrics into NIDM file.
219
+
220
+ If nidm_file is None (no existing file), creates a new NIDM file from scratch.
221
+
222
+ Args:
223
+ nidm_file: Path to NIDM .ttl file (can be None)
224
+ metrics: Dictionary of metrics to add
225
+ software_name: Name of analysis software (e.g., 'FreeSurfer')
226
+ software_version: Version of analysis software
227
+ subject_id: Subject ID without 'sub-' prefix
228
+ session_id: Session ID without 'ses-' prefix (optional)
229
+ output_dir: Base output directory (required if nidm_file is None)
230
+ bidsapp_name: BIDSapp name (required if nidm_file is None)
231
+
232
+ Returns:
233
+ Path to the created/updated NIDM file
234
+ """
235
+ # Determine output file path
236
+ if nidm_file is None:
237
+ if output_dir is None or bidsapp_name is None:
238
+ raise ValueError("output_dir and bidsapp_name required when creating new NIDM file")
239
+
240
+ # Create new NIDM file
241
+ nidm_output_dir = Path(output_dir) / bidsapp_name / "nidm"
242
+ if session_id:
243
+ subject_nidm_dir = nidm_output_dir / f"sub-{subject_id}" / f"ses-{session_id}"
244
+ output_filename = f"sub-{subject_id}_ses-{session_id}.ttl"
245
+ else:
246
+ subject_nidm_dir = nidm_output_dir / f"sub-{subject_id}"
247
+ output_filename = f"sub-{subject_id}.ttl"
248
+
249
+ subject_nidm_dir.mkdir(parents=True, exist_ok=True)
250
+ nidm_file = subject_nidm_dir / output_filename
251
+
252
+ logger.info(f"Creating new NIDM file at {nidm_file}")
253
+ g = rdflib.Graph()
254
+ else:
255
+ # Load existing NIDM graph
256
+ logger.info(f"Loading existing NIDM file from {nidm_file}")
257
+ g = rdflib.Graph()
258
+ g.parse(nidm_file, format='turtle')
259
+
260
+ # Define namespaces
261
+ PROV = rdflib.Namespace('http://www.w3.org/ns/prov#')
262
+ NIDM = rdflib.Namespace('http://purl.org/nidash/nidm#')
263
+ ANALYSIS = rdflib.Namespace(f'http://purl.org/nidash/{software_name.lower()}#')
264
+ XSD = rdflib.Namespace('http://www.w3.org/2001/XMLSchema#')
265
+
266
+ # Bind namespaces
267
+ g.bind('prov', PROV)
268
+ g.bind('nidm', NIDM)
269
+ g.bind(software_name.lower(), ANALYSIS)
270
+ g.bind('xsd', XSD)
271
+
272
+ # Create activity node
273
+ timestamp = datetime.now().isoformat()
274
+ activity_id = rdflib.URIRef(f'activity/{software_name.lower()}-{timestamp}')
275
+ g.add((activity_id, rdflib.RDF.type, PROV.Activity))
276
+ g.add((activity_id, PROV.startedAtTime, rdflib.Literal(datetime.now(), datatype=XSD.dateTime)))
277
+
278
+ # Create software agent
279
+ software_id = rdflib.URIRef(f'software/{software_name.lower()}')
280
+ g.add((software_id, rdflib.RDF.type, PROV.SoftwareAgent))
281
+ g.add((software_id, NIDM.softwareVersion, rdflib.Literal(software_version)))
282
+ g.add((activity_id, PROV.wasAssociatedWith, software_id))
283
+
284
+ # Create output entity with metrics
285
+ output_id = rdflib.URIRef(f'output/{software_name.lower()}-result-{timestamp}')
286
+ g.add((output_id, rdflib.RDF.type, PROV.Entity))
287
+ g.add((activity_id, PROV.generated, output_id))
288
+
289
+ # Add subject/session info
290
+ g.add((output_id, NIDM.subjectID, rdflib.Literal(f"sub-{subject_id}")))
291
+ if session_id:
292
+ g.add((output_id, NIDM.sessionID, rdflib.Literal(f"ses-{session_id}")))
293
+
294
+ # Add metrics
295
+ for metric_name, metric_value in metrics.items():
296
+ predicate = ANALYSIS[metric_name]
297
+ if isinstance(metric_value, float):
298
+ obj = rdflib.Literal(metric_value, datatype=XSD.float)
299
+ elif isinstance(metric_value, int):
300
+ obj = rdflib.Literal(metric_value, datatype=XSD.integer)
301
+ else:
302
+ obj = rdflib.Literal(metric_value)
303
+ g.add((output_id, predicate, obj))
304
+
305
+ # Save graph (overwrites if existing, creates if new)
306
+ g.serialize(destination=str(nidm_file), format='turtle')
307
+ logger.info(f"NIDM file saved to {nidm_file}")
308
+
309
+ return nidm_file
310
+ ```
311
+
312
+ ## Complete NIDM Processing Workflow
313
+
314
+ ```python
315
+ def process_nidm(nidm_input_dir, output_dir, bidsapp_name, subject_id, session_id,
316
+ skip_nidm, analysis_outputs, software_name, software_version):
317
+ """
318
+ Complete NIDM processing workflow.
319
+
320
+ Handles both cases:
321
+ 1. Existing NIDM file: Copy → Extract metrics → Integrate (overwrite)
322
+ 2. No existing file: Warning → Extract metrics → Create new file
323
+ """
324
+
325
+ if skip_nidm:
326
+ logger.info("Skipping NIDM processing (--skip-nidm flag set)")
327
+ return
328
+
329
+ # Step 1: Try to copy existing NIDM file
330
+ nidm_file = copy_nidm_file(nidm_input_dir, output_dir, bidsapp_name,
331
+ subject_id, session_id)
332
+
333
+ # Step 2: Extract metrics from analysis outputs
334
+ logger.info("Extracting metrics from analysis outputs")
335
+ metrics = extract_metrics(analysis_outputs, subject_id, session_id)
336
+
337
+ # Step 3: Integrate metrics into NIDM file
338
+ # This works whether nidm_file exists or is None
339
+ nidm_file = integrate_metrics_to_nidm(
340
+ nidm_file,
341
+ metrics,
342
+ software_name,
343
+ software_version,
344
+ subject_id,
345
+ session_id,
346
+ output_dir,
347
+ bidsapp_name
348
+ )
349
+
350
+ logger.info(f"NIDM processing complete: {nidm_file}")
351
+ ```
352
+
353
+ ## Analysis-Specific Metric Extraction
354
+
355
+ Each analysis tool requires custom metric extraction. Examples:
356
+
357
+ ### FreeSurfer Metrics
358
+
359
+ ```python
360
+ def extract_freesurfer_metrics(freesurfer_dir):
361
+ """Extract metrics from FreeSurfer outputs."""
362
+ metrics = {}
363
+
364
+ # Read aseg.stats for volumetric measures
365
+ aseg_stats = freesurfer_dir / "stats" / "aseg.stats"
366
+ if aseg_stats.exists():
367
+ # Parse aseg.stats file
368
+ # Extract hippocampal volumes, cortical volumes, etc.
369
+ pass
370
+
371
+ # Read aparc.stats for cortical measures
372
+ for hemi in ['lh', 'rh']:
373
+ aparc_stats = freesurfer_dir / "stats" / f"{hemi}.aparc.stats"
374
+ if aparc_stats.exists():
375
+ # Parse cortical thickness, surface area, etc.
376
+ pass
377
+
378
+ return metrics
379
+ ```
380
+
381
+ ### MRIQC Metrics
382
+
383
+ ```python
384
+ def extract_mriqc_metrics(mriqc_output_dir):
385
+ """Extract metrics from MRIQC JSON outputs."""
386
+ import json
387
+
388
+ metrics = {}
389
+
390
+ # Find JSON file with IQMs
391
+ json_files = list(mriqc_output_dir.glob("sub-*_T1w.json"))
392
+ if json_files:
393
+ with open(json_files[0]) as f:
394
+ iqms = json.load(f)
395
+
396
+ # Extract image quality metrics
397
+ metrics['SNR'] = iqms.get('snr_total')
398
+ metrics['CNR'] = iqms.get('cnr')
399
+ metrics['EFC'] = iqms.get('efc')
400
+ # Add more metrics as needed
401
+
402
+ return metrics
403
+ ```
@@ -0,0 +1,121 @@
1
+ # Repository Structure Reference
2
+
3
+ ## Standard Directory Layout
4
+
5
+ ```
6
+ <bidsapp_name>/ # e.g., freesurfer_bidsapp, mriqc-nidm_bidsapp
7
+ ├── src/ # Source code
8
+ │ ├── <analysis_name>/ # Main analysis package
9
+ │ │ ├── __init__.py
10
+ │ │ ├── run.py # CLI entry point
11
+ │ │ ├── <analysis>_runner.py # Core analysis execution
12
+ │ │ ├── utils.py # Utility functions
13
+ │ │ └── validators.py # Input validation
14
+ │ └── nidm/ # NIDM conversion package
15
+ │ ├── __init__.py
16
+ │ ├── nidm_converter.py # Main NIDM conversion logic
17
+ │ ├── nidm_utils.py # NIDM utilities
18
+ │ └── data/ # NIDM reference data
19
+ │ ├── ontology_mappings.json
20
+ │ └── software_metadata.csv
21
+ ├── tests/ # Test suite
22
+ │ ├── __init__.py
23
+ │ ├── test_<analysis>.py # Analysis tests
24
+ │ ├── test_nidm.py # NIDM conversion tests
25
+ │ ├── test_integration.py # End-to-end tests
26
+ │ └── fixtures/ # Test data
27
+ │ ├── bids_data/
28
+ │ └── nidm_data/
29
+ ├── examples/ # Usage examples
30
+ │ ├── example_usage.sh # Basic usage script
31
+ │ └── babs_integration.sh # BABS workflow example
32
+ ├── Dockerfile # Docker container definition
33
+ ├── Singularity # Singularity/Apptainer definition
34
+ ├── setup.py # Package setup and metadata
35
+ ├── setup.cfg # Setup configuration
36
+ ├── requirements.txt # Python dependencies
37
+ ├── VERSION # Version number (e.g., 0.1.0)
38
+ ├── LICENSE # MIT license
39
+ ├── README.md # Usage documentation
40
+ ├── .gitignore # Git ignore patterns
41
+ └── .github/ # GitHub workflows (optional)
42
+ └── workflows/
43
+ └── test.yml # CI/CD tests
44
+ ```
45
+
46
+ ## File Responsibilities
47
+
48
+ ### src/<analysis_name>/run.py
49
+ - CLI argument parsing using argparse
50
+ - Input validation
51
+ - Orchestrate analysis and NIDM workflows
52
+ - Error handling and logging
53
+
54
+ ### src/<analysis_name>/<analysis>_runner.py
55
+ - Execute the actual analysis tool
56
+ - Handle tool-specific configurations
57
+ - Manage intermediate outputs
58
+ - Extract metrics for NIDM
59
+
60
+ ### src/nidm/nidm_converter.py
61
+ - Copy input NIDM file to output directory
62
+ - Extract relevant metrics from analysis outputs
63
+ - Integrate metrics into NIDM file (overwrite)
64
+ - Validate NIDM output
65
+
66
+ ### Dockerfile
67
+ - Define container image
68
+ - Install analysis tool dependencies
69
+ - Install Python package
70
+ - Set entry point to run.py
71
+
72
+ ### Singularity
73
+ - Define Apptainer/Singularity container
74
+ - Use existing base images where possible
75
+ - Install analysis tool and Python package
76
+ - Configure for HPC execution
77
+
78
+ ### setup.py
79
+ - Package metadata (name, version, author)
80
+ - Define entry points for CLI commands
81
+ - Specify dependencies
82
+ - Include package data files
83
+
84
+ ## Key Naming Conventions
85
+
86
+ - **Repository name**: `<tool>_bidsapp` or `<tool>-nidm_bidsapp`
87
+ - Examples: `freesurfer_bidsapp`, `mriqc-nidm_bidsapp`, `ants_bidsapp`
88
+
89
+ - **Package name**: `<tool>_nidm` (in setup.py)
90
+ - Examples: `freesurfer_nidm`, `mriqc_nidm`, `ants_nidm`
91
+
92
+ - **Output directory**: `<tool>_nidm` or `<tool>-nidm`
93
+ - Must match package name for consistency
94
+
95
+ - **Container name**: `<tool>-nidm-bidsapp-<version>`
96
+ - Example: `mriqc-nidm-bidsapp-0-1-0`
97
+
98
+ ## Modularity Requirements
99
+
100
+ The NIDM conversion component must be:
101
+ - **Self-contained**: All NIDM logic in `src/nidm/` directory
102
+ - **Reusable**: Can be imported and used independently
103
+ - **Analysis-specific**: Tailored to the specific analysis tool's outputs
104
+ - **Consistent API**: Standard function signatures across all BIDSapps
105
+
106
+ Example NIDM module API:
107
+
108
+ ```python
109
+ # In src/nidm/nidm_converter.py
110
+ def copy_nidm_input(input_dir, output_dir, subject_id, session_id):
111
+ """Copy existing NIDM file to output directory."""
112
+ pass
113
+
114
+ def extract_metrics(analysis_output_dir, subject_id, session_id):
115
+ """Extract relevant metrics from analysis outputs."""
116
+ pass
117
+
118
+ def integrate_to_nidm(nidm_file, metrics):
119
+ """Integrate extracted metrics into NIDM file."""
120
+ pass
121
+ ```
@@ -0,0 +1,82 @@
1
+ # Testing Patterns
2
+
3
+ ## Test Structure
4
+
5
+ ```
6
+ tests/
7
+ ├── __init__.py
8
+ ├── test_<analysis>.py # Analysis-specific tests
9
+ ├── test_nidm.py # NIDM conversion tests
10
+ ├── test_integration.py # End-to-end tests
11
+ └── fixtures/ # Test data
12
+ ├── bids_data/ # Minimal BIDS dataset
13
+ └── nidm_data/ # Sample NIDM files
14
+ ```
15
+
16
+ ## Test Coverage Requirements
17
+
18
+ 1. **CLI Argument Parsing**
19
+ - Valid arguments accepted
20
+ - Invalid arguments rejected
21
+ - Default values correct
22
+
23
+ 2. **BIDS Validation**
24
+ - Valid BIDS datasets accepted
25
+ - Invalid datasets rejected
26
+ - Required files detected
27
+
28
+ 3. **Analysis Execution**
29
+ - Tool runs successfully with valid inputs
30
+ - Error handling for invalid inputs
31
+ - Output files created
32
+
33
+ 4. **NIDM Conversion**
34
+ - Input NIDM file copied correctly
35
+ - Metrics extracted from analysis outputs
36
+ - NIDM file updated with new metrics
37
+ - TTL format valid
38
+
39
+ 5. **Output Structure**
40
+ - Correct directory structure created
41
+ - BIDS-compliant derivatives
42
+ - dataset_description.json files present
43
+
44
+ ## Example Tests
45
+
46
+ ```python
47
+ import pytest
48
+ from pathlib import Path
49
+
50
+ def test_cli_args():
51
+ """Test CLI argument parsing."""
52
+ from <package>.run import parse_args
53
+
54
+ args = parse_args([
55
+ '/data/bids',
56
+ '/data/output',
57
+ 'participant',
58
+ '--participant-label', '01',
59
+ '--nidm-input-dir', '/data/nidm'
60
+ ])
61
+
62
+ assert args.bids_dir == '/data/bids'
63
+ assert args.participant_label == ['01']
64
+
65
+ def test_nidm_copy(tmp_path):
66
+ """Test NIDM file copying."""
67
+ from <package>.nidm.nidm_converter import copy_nidm_file
68
+
69
+ # Setup test data
70
+ nidm_input = tmp_path / "nidm_input"
71
+ nidm_input.mkdir()
72
+ (nidm_input / "nidm.ttl").write_text("test nidm content")
73
+
74
+ # Copy file
75
+ output = tmp_path / "output"
76
+ nidm_file = copy_nidm_file(nidm_input, output, "test_app", "01")
77
+
78
+ assert nidm_file.exists()
79
+ assert nidm_file.read_text() == "test nidm content"
80
+ ```
81
+
82
+ Use pytest fixtures for test data and temporary directories.