Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 142 additions & 8 deletions hed/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def validate_bids_cmd(


@validate.command(
name="hed-string",
name="string",
epilog="""
This command validates a HED annotation string against a specified HED schema
version. It can optionally process definitions and check for warnings in addition
Expand All @@ -260,25 +260,25 @@ def validate_bids_cmd(
\b
Examples:
# Basic validation of a HED string
hedpy validate hed-string "Event, (Sensory-event, (Visual-presentation, (Computer-screen, Face)))" -sv 8.3.0
hedpy validate string "Event, (Sensory-event, (Visual-presentation, (Computer-screen, Face)))" -sv 8.3.0

# Validate with definitions
hedpy validate hed-string "Event, Def/MyDef" -sv 8.4.0 -d "(Definition/MyDef, (Action, Move))"
hedpy validate string "Event, Def/MyDef" -sv 8.4.0 -d "(Definition/MyDef, (Action, Move))"

# Validate with multiple schemas (base + library)
hedpy validate hed-string "Event, Action" -sv 8.3.0 -sv score_1.1.0
hedpy validate string "Event, Action" -sv 8.3.0 -sv score_1.1.0

# Check for warnings as well as errors
hedpy validate hed-string "Event, Action/Button-press" -sv 8.4.0 --check-for-warnings
hedpy validate string "Event, Action/Button-press" -sv 8.4.0 --check-for-warnings

# Save validation results to a file
hedpy validate hed-string "Event" -sv 8.4.0 -o validation_results.txt
hedpy validate string "Event" -sv 8.4.0 -o validation_results.txt

# Output results in JSON format
hedpy validate hed-string "Event, Action" -sv 8.4.0 -f json
hedpy validate string "Event, Action" -sv 8.4.0 -f json

# Verbose output with informational messages
hedpy validate hed-string "Event, (Action, Move)" -sv 8.4.0 --verbose
hedpy validate string "Event, (Action, Move)" -sv 8.4.0 --verbose
""",
)
@click.argument("hed_string")
Expand Down Expand Up @@ -404,6 +404,140 @@ def validate_hed_string_cmd(
ctx.exit(result if result is not None else 0)


@validate.command(
name="sidecar",
epilog="""
This command validates a BIDS JSON sidecar file against a specified HED schema
version.

\b
Examples:
# Basic HED validation of a BIDS sidecar
hedpy validate sidecar path/to/sidecar.json -sv 8.3.0

# Validate with multiple schemas (base + library)
hedpy validate sidecar path/to/sidecar.json -sv 8.3.0 -sv score_1.1.0

# Check for warnings as well as errors
hedpy validate sidecar path/to/sidecar.json -sv 8.4.0 --check-for-warnings

# Save validation results to a file
hedpy validate sidecar path/to/sidecar.json -sv 8.4.0 -o validation_results.txt
""",
)
@click.argument("sidecar_file", type=click.Path(exists=True))
# Validation options
@optgroup.group("Validation options")
@optgroup.option(
"-sv",
"--schema-version",
required=True,
multiple=True,
metavar="VERSION",
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)",
)
@optgroup.option(
"-w",
"--check-for-warnings",
is_flag=True,
help="Check for warnings as well as errors",
)
# Output options
@optgroup.group("Output options")
@optgroup.option(
"-f",
"--format",
type=click.Choice(["text", "json"]),
default="text",
show_default="text",
help="Output format for validation results (text: human-readable; json: structured format for programmatic use)",
)
@optgroup.option(
"-o",
"--output-file",
type=click.Path(),
default="",
metavar=METAVAR_FILE,
help="Path for output file to hold validation results; if not specified, output to stdout",
)
# Logging options
@optgroup.group("Logging options")
@optgroup.option(
"-l",
"--log-level",
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
default="WARNING",
show_default="WARNING",
help="Log level for diagnostic messages",
)
@optgroup.option(
"-v",
"--verbose",
is_flag=True,
help="Output informational messages (equivalent to --log-level INFO)",
)
@optgroup.option(
"-lf",
"--log-file",
type=click.Path(),
metavar=METAVAR_FILE,
help="File path for saving log output; logs still go to stderr unless --log-quiet is also used",
)
@optgroup.option(
"-lq",
"--log-quiet",
is_flag=True,
help="Suppress log output to stderr; only applicable when --log-file is used (logs go only to file)",
)
@optgroup.option(
"--no-log",
is_flag=True,
help="Disable all logging output",
)
@click.pass_context
def validate_sidecar_cmd(
ctx,
sidecar_file,
schema_version,
check_for_warnings,
format,
output_file,
log_level,
log_file,
log_quiet,
no_log,
verbose,
):
"""Validate HED in a BIDS sidecar file.

SIDECAR_FILE: The path to the BIDS sidecar file to validate.
"""
from hed.scripts.validate_hed_sidecar import main as validate_sidecar_main

args = [sidecar_file]
for version in schema_version:
args.extend(["-sv", version])
if check_for_warnings:
args.append("-w")
if format:
args.extend(["-f", format])
if output_file:
args.extend(["-o", output_file])
if log_level:
args.extend(["-l", log_level])
if log_file:
args.extend(["-lf", log_file])
if log_quiet:
args.append("-lq")
if no_log:
args.append("--no-log")
if verbose:
args.append("-v")

result = validate_sidecar_main(args)
ctx.exit(result if result is not None else 0)


@schema.command(name="validate")
@click.argument("schema_path", type=click.Path(exists=True), nargs=-1, required=True)
@click.option("--add-all-extensions", is_flag=True, help="Always verify all versions of the same schema are equal")
Expand Down
157 changes: 157 additions & 0 deletions hed/scripts/validate_hed_sidecar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python
"""
Validates a BIDS sidecar against a specified schema version.

This script validates HED in a BIDS JSON sidecar file
against a specified HED schema version.
"""

import argparse
import sys
import os
from hed.models import Sidecar
from hed.errors import ErrorHandler
from hed.schema import load_schema_version
from hed.scripts.script_utils import setup_logging, format_validation_results


def get_parser():
"""Create the argument parser for validate_hed_sidecar.

Returns:
argparse.ArgumentParser: Configured argument parser.
"""
parser = argparse.ArgumentParser(
description="Validate a BIDS sidecar file against a HED schema", formatter_class=argparse.RawDescriptionHelpFormatter
)

# Required arguments
parser.add_argument("sidecar_file", help="BIDS sidecar file to validate")
parser.add_argument(
"-sv",
"--schema-version",
required=True,
nargs="+",
dest="schema_version",
help="HED schema version(s) to validate against (e.g., '8.4.0' or '8.3.0 score_1.1.0' for multiple schemas)",
)

# Optional arguments
parser.add_argument(
"-w",
"--check-for-warnings",
action="store_true",
dest="check_for_warnings",
help="Check for warnings in addition to errors",
)

# Output options
output_group = parser.add_argument_group("Output options")
output_group.add_argument(
"-f",
"--format",
choices=["text", "json"],
default="text",
help="Output format for validation results (default: %(default)s)",
)
output_group.add_argument(
"-o",
"--output-file",
default="",
dest="output_file",
help="Output file for validation results; if not specified, output to stdout",
)

# Logging options
logging_group = parser.add_argument_group("Logging options")
logging_group.add_argument(
"-l",
"--log-level",
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="WARNING",
dest="log_level",
help="Logging level (default: %(default)s)",
)
logging_group.add_argument("-lf", "--log-file", default="", dest="log_file", help="File path for saving log output")
logging_group.add_argument(
"-lq", "--log-quiet", action="store_true", dest="log_quiet", help="Suppress log output to stderr when using --log-file"
)
logging_group.add_argument("--no-log", action="store_true", dest="no_log", help="Disable all logging output")
logging_group.add_argument("-v", "--verbose", action="store_true", help="Output informational messages")

return parser


def main(arg_list=None):
"""Main function for validating a BIDS sidecar.

Parameters:
arg_list (list or None): Command line arguments.
"""
parser = get_parser()
args = parser.parse_args(arg_list)

# Set up logging
setup_logging(args.log_level, args.log_file, args.log_quiet, args.verbose, args.no_log)

import logging

logger = logging.getLogger("validate_hed_sidecar")
effective_level_name = logging.getLevelName(logger.getEffectiveLevel())
logger.info(
"Starting BIDS sidecar HED validation with effective log level: %s (requested: %s, verbose=%s)",
effective_level_name,
args.log_level,
"on" if args.verbose else "off",
)

try:
# Load schema (handle single version or list of versions)
schema_versions = args.schema_version[0] if len(args.schema_version) == 1 else args.schema_version
logging.info(f"Loading HED schema version(s) {schema_versions}")
schema = load_schema_version(schema_versions)

# Parse Sidecar
logging.info("Loading BIDS sidecar file")
sidecar = Sidecar(args.sidecar_file, name=os.path.basename(args.sidecar_file))

# Validate BIDS sidecar
logging.info("Validating BIDS sidecar")
error_handler = ErrorHandler(check_for_warnings=args.check_for_warnings)
issues = sidecar.validate(schema, name=sidecar.name, error_handler=error_handler)

# Handle output
if issues:
# Format validation errors
output = format_validation_results(
issues, output_format=args.format, title_message="BIDS sidecar validation errors:"
)

# Write output
if args.output_file:
with open(args.output_file, "w") as f:
f.write(output)
logging.info(f"Validation errors written to {args.output_file}")
else:
print(output)

return 1 # Exit with error code if validation failed
else:
# Success message
success_msg = "BIDS sidecar has valid HED!"
if args.output_file:
with open(args.output_file, "w") as f:
f.write(success_msg + "\n")
logging.info(f"Validation results written to {args.output_file}")
else:
print(success_msg)

return 0

except Exception as e:
logging.error(f"Validation failed: {str(e)}")
return 1


if __name__ == "__main__":
sys.exit(main())
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ hedpy = "hed.cli.cli:main"
# Legacy commands (deprecated - use 'hedpy' instead)
validate_bids = "hed.scripts.validate_bids:main"
validate_hed_string = "hed.scripts.validate_hed_string:main"
validate_hed_sidecar = "hed.scripts.validate_hed_sidecar:main"
hed_extract_bids_sidecar = "hed.scripts.hed_extract_bids_sidecar:main"
hed_validate_schemas = "hed.scripts.validate_schemas:main"
hed_update_schemas = "hed.scripts.hed_convert_schema:main"
Expand Down
Loading