Skip to content

Commit e7da665

Browse files
authored
Merge pull request #1186 from VisLab/add_examples
Added a validate sidecar script
2 parents 59da1c2 + 0e43a12 commit e7da665

File tree

5 files changed

+459
-12
lines changed

5 files changed

+459
-12
lines changed

hed/cli/cli.py

Lines changed: 142 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def validate_bids_cmd(
251251

252252

253253
@validate.command(
254-
name="hed-string",
254+
name="string",
255255
epilog="""
256256
This command validates a HED annotation string against a specified HED schema
257257
version. It can optionally process definitions and check for warnings in addition
@@ -260,25 +260,25 @@ def validate_bids_cmd(
260260
\b
261261
Examples:
262262
# Basic validation of a HED string
263-
hedpy validate hed-string "Event, (Sensory-event, (Visual-presentation, (Computer-screen, Face)))" -sv 8.3.0
263+
hedpy validate string "Event, (Sensory-event, (Visual-presentation, (Computer-screen, Face)))" -sv 8.3.0
264264
265265
# Validate with definitions
266-
hedpy validate hed-string "Event, Def/MyDef" -sv 8.4.0 -d "(Definition/MyDef, (Action, Move))"
266+
hedpy validate string "Event, Def/MyDef" -sv 8.4.0 -d "(Definition/MyDef, (Action, Move))"
267267
268268
# Validate with multiple schemas (base + library)
269-
hedpy validate hed-string "Event, Action" -sv 8.3.0 -sv score_1.1.0
269+
hedpy validate string "Event, Action" -sv 8.3.0 -sv score_1.1.0
270270
271271
# Check for warnings as well as errors
272-
hedpy validate hed-string "Event, Action/Button-press" -sv 8.4.0 --check-for-warnings
272+
hedpy validate string "Event, Action/Button-press" -sv 8.4.0 --check-for-warnings
273273
274274
# Save validation results to a file
275-
hedpy validate hed-string "Event" -sv 8.4.0 -o validation_results.txt
275+
hedpy validate string "Event" -sv 8.4.0 -o validation_results.txt
276276
277277
# Output results in JSON format
278-
hedpy validate hed-string "Event, Action" -sv 8.4.0 -f json
278+
hedpy validate string "Event, Action" -sv 8.4.0 -f json
279279
280280
# Verbose output with informational messages
281-
hedpy validate hed-string "Event, (Action, Move)" -sv 8.4.0 --verbose
281+
hedpy validate string "Event, (Action, Move)" -sv 8.4.0 --verbose
282282
""",
283283
)
284284
@click.argument("hed_string")
@@ -404,6 +404,140 @@ def validate_hed_string_cmd(
404404
ctx.exit(result if result is not None else 0)
405405

406406

407+
@validate.command(
408+
name="sidecar",
409+
epilog="""
410+
This command validates a BIDS JSON sidecar file against a specified HED schema
411+
version.
412+
413+
\b
414+
Examples:
415+
# Basic HED validation of a BIDS sidecar
416+
hedpy validate sidecar path/to/sidecar.json -sv 8.3.0
417+
418+
# Validate with multiple schemas (base + library)
419+
hedpy validate sidecar path/to/sidecar.json -sv 8.3.0 -sv score_1.1.0
420+
421+
# Check for warnings as well as errors
422+
hedpy validate sidecar path/to/sidecar.json -sv 8.4.0 --check-for-warnings
423+
424+
# Save validation results to a file
425+
hedpy validate sidecar path/to/sidecar.json -sv 8.4.0 -o validation_results.txt
426+
""",
427+
)
428+
@click.argument("sidecar_file", type=click.Path(exists=True))
429+
# Validation options
430+
@optgroup.group("Validation options")
431+
@optgroup.option(
432+
"-sv",
433+
"--schema-version",
434+
required=True,
435+
multiple=True,
436+
metavar="VERSION",
437+
help="HED schema version(s) to validate against (e.g., '8.4.0'). Can be specified multiple times for multiple schemas (e.g., -sv lang_1.1.0 -sv score_2.1.0)",
438+
)
439+
@optgroup.option(
440+
"-w",
441+
"--check-for-warnings",
442+
is_flag=True,
443+
help="Check for warnings as well as errors",
444+
)
445+
# Output options
446+
@optgroup.group("Output options")
447+
@optgroup.option(
448+
"-f",
449+
"--format",
450+
type=click.Choice(["text", "json"]),
451+
default="text",
452+
show_default="text",
453+
help="Output format for validation results (text: human-readable; json: structured format for programmatic use)",
454+
)
455+
@optgroup.option(
456+
"-o",
457+
"--output-file",
458+
type=click.Path(),
459+
default="",
460+
metavar=METAVAR_FILE,
461+
help="Path for output file to hold validation results; if not specified, output to stdout",
462+
)
463+
# Logging options
464+
@optgroup.group("Logging options")
465+
@optgroup.option(
466+
"-l",
467+
"--log-level",
468+
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
469+
default="WARNING",
470+
show_default="WARNING",
471+
help="Log level for diagnostic messages",
472+
)
473+
@optgroup.option(
474+
"-v",
475+
"--verbose",
476+
is_flag=True,
477+
help="Output informational messages (equivalent to --log-level INFO)",
478+
)
479+
@optgroup.option(
480+
"-lf",
481+
"--log-file",
482+
type=click.Path(),
483+
metavar=METAVAR_FILE,
484+
help="File path for saving log output; logs still go to stderr unless --log-quiet is also used",
485+
)
486+
@optgroup.option(
487+
"-lq",
488+
"--log-quiet",
489+
is_flag=True,
490+
help="Suppress log output to stderr; only applicable when --log-file is used (logs go only to file)",
491+
)
492+
@optgroup.option(
493+
"--no-log",
494+
is_flag=True,
495+
help="Disable all logging output",
496+
)
497+
@click.pass_context
498+
def validate_sidecar_cmd(
499+
ctx,
500+
sidecar_file,
501+
schema_version,
502+
check_for_warnings,
503+
format,
504+
output_file,
505+
log_level,
506+
log_file,
507+
log_quiet,
508+
no_log,
509+
verbose,
510+
):
511+
"""Validate HED in a BIDS sidecar file.
512+
513+
SIDECAR_FILE: The path to the BIDS sidecar file to validate.
514+
"""
515+
from hed.scripts.validate_hed_sidecar import main as validate_sidecar_main
516+
517+
args = [sidecar_file]
518+
for version in schema_version:
519+
args.extend(["-sv", version])
520+
if check_for_warnings:
521+
args.append("-w")
522+
if format:
523+
args.extend(["-f", format])
524+
if output_file:
525+
args.extend(["-o", output_file])
526+
if log_level:
527+
args.extend(["-l", log_level])
528+
if log_file:
529+
args.extend(["-lf", log_file])
530+
if log_quiet:
531+
args.append("-lq")
532+
if no_log:
533+
args.append("--no-log")
534+
if verbose:
535+
args.append("-v")
536+
537+
result = validate_sidecar_main(args)
538+
ctx.exit(result if result is not None else 0)
539+
540+
407541
@schema.command(name="validate")
408542
@click.argument("schema_path", type=click.Path(exists=True), nargs=-1, required=True)
409543
@click.option("--add-all-extensions", is_flag=True, help="Always verify all versions of the same schema are equal")
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env python
2+
"""
3+
Validates a BIDS sidecar against a specified schema version.
4+
5+
This script validates HED in a BIDS JSON sidecar file
6+
against a specified HED schema version.
7+
"""
8+
9+
import argparse
10+
import sys
11+
import os
12+
from hed.models import Sidecar
13+
from hed.errors import ErrorHandler
14+
from hed.schema import load_schema_version
15+
from hed.scripts.script_utils import setup_logging, format_validation_results
16+
17+
18+
def get_parser():
19+
"""Create the argument parser for validate_hed_sidecar.
20+
21+
Returns:
22+
argparse.ArgumentParser: Configured argument parser.
23+
"""
24+
parser = argparse.ArgumentParser(
25+
description="Validate a BIDS sidecar file against a HED schema", formatter_class=argparse.RawDescriptionHelpFormatter
26+
)
27+
28+
# Required arguments
29+
parser.add_argument("sidecar_file", help="BIDS sidecar file to validate")
30+
parser.add_argument(
31+
"-sv",
32+
"--schema-version",
33+
required=True,
34+
nargs="+",
35+
dest="schema_version",
36+
help="HED schema version(s) to validate against (e.g., '8.4.0' or '8.3.0 score_1.1.0' for multiple schemas)",
37+
)
38+
39+
# Optional arguments
40+
parser.add_argument(
41+
"-w",
42+
"--check-for-warnings",
43+
action="store_true",
44+
dest="check_for_warnings",
45+
help="Check for warnings in addition to errors",
46+
)
47+
48+
# Output options
49+
output_group = parser.add_argument_group("Output options")
50+
output_group.add_argument(
51+
"-f",
52+
"--format",
53+
choices=["text", "json"],
54+
default="text",
55+
help="Output format for validation results (default: %(default)s)",
56+
)
57+
output_group.add_argument(
58+
"-o",
59+
"--output-file",
60+
default="",
61+
dest="output_file",
62+
help="Output file for validation results; if not specified, output to stdout",
63+
)
64+
65+
# Logging options
66+
logging_group = parser.add_argument_group("Logging options")
67+
logging_group.add_argument(
68+
"-l",
69+
"--log-level",
70+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
71+
default="WARNING",
72+
dest="log_level",
73+
help="Logging level (default: %(default)s)",
74+
)
75+
logging_group.add_argument("-lf", "--log-file", default="", dest="log_file", help="File path for saving log output")
76+
logging_group.add_argument(
77+
"-lq", "--log-quiet", action="store_true", dest="log_quiet", help="Suppress log output to stderr when using --log-file"
78+
)
79+
logging_group.add_argument("--no-log", action="store_true", dest="no_log", help="Disable all logging output")
80+
logging_group.add_argument("-v", "--verbose", action="store_true", help="Output informational messages")
81+
82+
return parser
83+
84+
85+
def main(arg_list=None):
86+
"""Main function for validating a BIDS sidecar.
87+
88+
Parameters:
89+
arg_list (list or None): Command line arguments.
90+
"""
91+
parser = get_parser()
92+
args = parser.parse_args(arg_list)
93+
94+
# Set up logging
95+
setup_logging(args.log_level, args.log_file, args.log_quiet, args.verbose, args.no_log)
96+
97+
import logging
98+
99+
logger = logging.getLogger("validate_hed_sidecar")
100+
effective_level_name = logging.getLevelName(logger.getEffectiveLevel())
101+
logger.info(
102+
"Starting BIDS sidecar HED validation with effective log level: %s (requested: %s, verbose=%s)",
103+
effective_level_name,
104+
args.log_level,
105+
"on" if args.verbose else "off",
106+
)
107+
108+
try:
109+
# Load schema (handle single version or list of versions)
110+
schema_versions = args.schema_version[0] if len(args.schema_version) == 1 else args.schema_version
111+
logging.info(f"Loading HED schema version(s) {schema_versions}")
112+
schema = load_schema_version(schema_versions)
113+
114+
# Parse Sidecar
115+
logging.info("Loading BIDS sidecar file")
116+
sidecar = Sidecar(args.sidecar_file, name=os.path.basename(args.sidecar_file))
117+
118+
# Validate BIDS sidecar
119+
logging.info("Validating BIDS sidecar")
120+
error_handler = ErrorHandler(check_for_warnings=args.check_for_warnings)
121+
issues = sidecar.validate(schema, name=sidecar.name, error_handler=error_handler)
122+
123+
# Handle output
124+
if issues:
125+
# Format validation errors
126+
output = format_validation_results(
127+
issues, output_format=args.format, title_message="BIDS sidecar validation errors:"
128+
)
129+
130+
# Write output
131+
if args.output_file:
132+
with open(args.output_file, "w") as f:
133+
f.write(output)
134+
logging.info(f"Validation errors written to {args.output_file}")
135+
else:
136+
print(output)
137+
138+
return 1 # Exit with error code if validation failed
139+
else:
140+
# Success message
141+
success_msg = "BIDS sidecar has valid HED!"
142+
if args.output_file:
143+
with open(args.output_file, "w") as f:
144+
f.write(success_msg + "\n")
145+
logging.info(f"Validation results written to {args.output_file}")
146+
else:
147+
print(success_msg)
148+
149+
return 0
150+
151+
except Exception as e:
152+
logging.error(f"Validation failed: {str(e)}")
153+
return 1
154+
155+
156+
if __name__ == "__main__":
157+
sys.exit(main())

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ hedpy = "hed.cli.cli:main"
100100
# Legacy commands (deprecated - use 'hedpy' instead)
101101
validate_bids = "hed.scripts.validate_bids:main"
102102
validate_hed_string = "hed.scripts.validate_hed_string:main"
103+
validate_hed_sidecar = "hed.scripts.validate_hed_sidecar:main"
103104
hed_extract_bids_sidecar = "hed.scripts.hed_extract_bids_sidecar:main"
104105
hed_validate_schemas = "hed.scripts.validate_schemas:main"
105106
hed_update_schemas = "hed.scripts.hed_convert_schema:main"

0 commit comments

Comments
 (0)