Skip to content
Open
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
12 changes: 12 additions & 0 deletions Atif/Gsoc-HealingStones/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
__pycache__/
*.py[cod]
*$py.class

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
50 changes: 50 additions & 0 deletions Atif/Gsoc-HealingStones/README_ONBOARDING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Mayan Stele CLI Usage Guide

This guide covers the new structured output capabilities for automation and CI/CD integration.

## Machine-Readable Output (--json / --report)

Use the `--json` flag (alias `--report`) to get a machine-readable JSON object on `stdout`. When enabled, all other logging is suppressed.

### Example: Validation
```bash
python main_pipeline.py check-data data/input --json
```

**JSON Schema:**
```json
{
"command": "check-data",
"status": "PASS",
"input_metadata": {
"input_path": "data/input",
"ply_count": 5
},
"errors": [],
"warnings": [],
"timestamp": "2026-01-27T10:00:00Z"
}
```

### Example: Dry Run (Simulation)
```bash
python batch_processor.py dry-run data/input data/output --report
```

---

## Command Reference

### main_pipeline.py
- `reconstruct <input> <output>`: Run full reconstruction.
- `check-data <input>`: Check input availability.
- `validate <input>`: Verify data integrity.
- `dry-run <input> <output>`: Simulate pipeline initialization.

### batch_processor.py
- `validate <input>`: Comprehensive PLY mesh validation.
- `preprocess <input> <output>`: Clean, center, and normalize meshes.
- `dry-run <input> [output]`: Validate paths without processing.

> [!NOTE]
> **Exit Codes**: Every command returns `0` on `PASS` and `1` on `FAIL`, regardless of whether `--json` is used.
218 changes: 148 additions & 70 deletions Atif/Gsoc-HealingStones/batch_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,25 @@
from sklearn.cluster import DBSCAN
import json
import argparse
import sys
from typing import List, Dict, Tuple, Optional
import cv2
import cli_utils
from contextlib import contextmanager

@contextmanager
def suppress_stdout(enable=True):
"""Context manager to suppress stdout prints."""
if not enable:
yield
return
with open(os.devnull, 'w') as fnull:
old_stdout = sys.stdout
sys.stdout = fnull
try:
yield
finally:
sys.stdout = old_stdout

class PLYValidator:
"""
Expand Down Expand Up @@ -502,94 +519,155 @@ def main():
parser = argparse.ArgumentParser(description="PLY Preprocessing and Validation Tools")
subparsers = parser.add_subparsers(dest='command', help='Available commands')

# helper
def add_common_args(sub_parser):
sub_parser.add_argument('--json', action='store_true', help='Output results as JSON')
sub_parser.add_argument('--report', action='store_true', help='Alias for --json')

# Validation command
validate_parser = subparsers.add_parser('validate', help='Validate PLY files')
add_common_args(validate_parser)
validate_parser.add_argument('input', help='PLY file or directory to validate')
validate_parser.add_argument('--output', '-o', help='Output validation report file')
validate_parser.add_argument('--json', action='store_true', help='Output results as JSON')

# check-data command (alias for validate)
check_parser = subparsers.add_parser('check-data', help='Alias for validate')
add_common_args(check_parser)
check_parser.add_argument('input', help='PLY file or directory to check')
check_parser.add_argument('--output', '-o', help='Output validation report file')

# Preprocessing command
preprocess_parser = subparsers.add_parser('preprocess', help='Preprocess PLY files')
add_common_args(preprocess_parser)
preprocess_parser.add_argument('input', help='Input PLY file or directory')
preprocess_parser.add_argument('output', help='Output PLY file or directory')
preprocess_parser.add_argument('--no-clean', action='store_true', help='Skip mesh cleaning')
preprocess_parser.add_argument('--no-normalize', action='store_true', help='Skip scale normalization')
preprocess_parser.add_argument('--no-center', action='store_true', help='Skip centering')
preprocess_parser.add_argument('--no-enhance-colors', action='store_true', help='Skip color enhancement')

# dry-run command
dry_parser = subparsers.add_parser('dry-run', help='Dry run of preprocessing/validation')
add_common_args(dry_parser)
dry_parser.add_argument('input', help='Input path')
dry_parser.add_argument('output', nargs='?', help='Output path (optional)')

args = parser.parse_args()

if args.command == 'validate':
validator = PLYValidator()

input_path = Path(args.input)

if input_path.is_file():
# Validate single file
result = validator.validate_ply_file(args.input)

if args.json:
print(json.dumps(result, indent=2))
else:
print(f"File: {result['filepath']}")
print(f"Valid: {result['valid']}")
if result['warnings']:
print("Warnings:")
for warning in result['warnings']:
print(f" - {warning}")
if result['errors']:
print("Errors:")
for error in result['errors']:
print(f" - {error}")

elif input_path.is_dir():
# Validate directory
results = validator.validate_directory(args.input)

if args.json:
if args.output:
with open(args.output, 'w') as f:
json.dump(results, f, indent=2)
if not args.command:
parser.print_help()
sys.exit(1)

json_mode = cli_utils.is_json_mode(args)

with suppress_stdout(enable=json_mode):
try:
input_path = Path(args.input) if hasattr(args, 'input') else None
output_path = Path(args.output) if (hasattr(args, 'output') and args.output) else None

if args.command in ['validate', 'check-data']:
validator = PLYValidator()

if input_path.is_file():
# Validate single file
result = validator.validate_ply_file(args.input)
status = "PASS" if result['valid'] else "FAIL"
if json_mode:
metadata = {
"input_path": result['filepath'],
"statistics": result['statistics']
}
cli_utils.format_json_output(args.command, status, metadata, result['errors'], result['warnings'])
else:
print(f"File: {result['filepath']}")
print(f"Valid: {result['valid']}")
if result['warnings']:
print("Warnings:")
for warning in result['warnings']:
print(f" - {warning}")
if result['errors']:
print("Errors:")
for error in result['errors']:
print(f" - {error}")
sys.exit(0 if status == "PASS" else 1)

elif input_path.is_dir():
# Validate directory
results = validator.validate_directory(args.input)
status = "PASS" if results['files_with_errors'] == 0 else "FAIL"
if json_mode:
metadata = {
"input_path": results['directory'],
"total_files": results['total_files'],
"valid_files": results['valid_files']
}
cli_utils.format_json_output(args.command, status, metadata)
else:
validator.generate_validation_report(results, args.output)
sys.exit(0 if status == "PASS" else 1)
else:
print(json.dumps(results, indent=2))
msg = f"Error: {args.input} is not a valid file or directory"
if json_mode:
cli_utils.format_json_output(args.command, "FAIL", errors=[msg])
else:
print(msg)
sys.exit(1)

elif args.command == 'preprocess':
preprocessor = PLYPreprocessor()
options = {
'clean': not args.no_clean,
'normalize_scale': not args.no_normalize,
'center': not args.no_center,
'enhance_colors': not args.no_enhance_colors
}

success = False
if input_path.is_file():
success = preprocessor.preprocess_file(str(input_path), str(output_path), **options)
elif input_path.is_dir():
results = preprocessor.preprocess_directory(str(input_path), str(output_path), **options)
success = results['failed'] == 0

status = "PASS" if success else "FAIL"
if json_mode:
metadata = {
"input_path": str(input_path),
"output_path": str(output_path)
}
cli_utils.format_json_output(args.command, status, metadata)

sys.exit(0 if success else 1)

elif args.command == 'dry-run':
# Dry run: check if input exists and is valid PLY
status = "PASS" if input_path.exists() else "FAIL"
errors = [] if status == "PASS" else [f"Path {args.input} does not exist"]
metadata = {
"input_path": str(input_path),
"output_path": str(output_path) if output_path else None
}

if input_path.is_dir():
ply_files = list(input_path.glob("*.ply"))
metadata["ply_count"] = len(ply_files)
if not ply_files:
status = "FAIL"
errors.append("No PLY files found")

if json_mode:
cli_utils.format_json_output(args.command, status, metadata, errors)
else:
print(f"Dry run: {status}")
for e in errors: print(f"Error: {e}")
sys.exit(0 if status == "PASS" else 1)

except Exception as e:
if json_mode:
cli_utils.format_json_output(args.command, "FAIL", errors=[str(e)])
else:
validator.generate_validation_report(results, args.output)

else:
print(f"Error: {args.input} is not a valid file or directory")

elif args.command == 'preprocess':
preprocessor = PLYPreprocessor()

input_path = Path(args.input)
output_path = Path(args.output)

# Set preprocessing options
options = {
'clean': not args.no_clean,
'normalize_scale': not args.no_normalize,
'center': not args.no_center,
'enhance_colors': not args.no_enhance_colors
}

if input_path.is_file():
# Preprocess single file
success = preprocessor.preprocess_file(str(input_path), str(output_path), **options)
if not success:
exit(1)

elif input_path.is_dir():
# Preprocess directory
results = preprocessor.preprocess_directory(str(input_path), str(output_path), **options)
if results['failed'] > 0:
exit(1)

else:
print(f"Error: {args.input} is not a valid file or directory")
exit(1)

else:
parser.print_help()
print(f"Error: {e}")
sys.exit(1)

if __name__ == "__main__":
main()
32 changes: 32 additions & 0 deletions Atif/Gsoc-HealingStones/cli_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import json
from datetime import datetime
import sys

def format_json_output(command, status, metadata=None, errors=None, warnings=None):
"""
Format and print a deterministic JSON object for CLI output.

Args:
command (str): The CLI command executed (e.g., check-data, validate, dry-run).
status (str): Outcome status (e.g., PASS, FAIL).
metadata (dict, optional): Input metadata like paths, counts, etc.
errors (list, optional): List of error messages.
warnings (list, optional): List of warning messages.
"""
output = {
"command": command,
"status": status,
"input_metadata": metadata or {},
"errors": errors or [],
"warnings": warnings or [],
"timestamp": datetime.utcnow().isoformat() + "Z"
}

# Use sys.__stdout__ directly to ensure output even if sys.stdout is redirected
json.dump(output, sys.__stdout__, indent=2, sort_keys=True)
sys.__stdout__.write('\n')
sys.__stdout__.flush()

def is_json_mode(args):
"""Check if JSON mode is enabled based on argparse arguments."""
return getattr(args, 'json', False) or getattr(args, 'report', False)
Loading