File size: 11,722 Bytes
be88765
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/usr/bin/env python3
"""
Validation Utilities for Training Sample Generation

This module contains functions for dry run validation, argument checking,
and other validation utilities.
"""

import os
import logging
import argparse
from typing import Dict, List, Tuple, Any

# Import necessary modules from other utils files
from .io_utils import get_dataset_name
from .preview_utils import preview_data_splits
from .submap_utils import get_default_num_samples

logger = logging.getLogger(__name__)

def _validate_and_setup_args(args: argparse.Namespace) -> bool:
    """
    Validate and setup command line arguments.
    
    Args:
        args: Parsed command line arguments
        
    Returns:
        True if validation passed, False otherwise
    """
    # Validate input path
    if not os.path.exists(args.input):
        logger.error(f"Input path does not exist: {args.input}")
        return False
    
    # Validate and set HDF5 arguments
    if args.save_hdf5 or args.hdf5_only:
        if args.hdf5_output is None:
            # Auto-generate HDF5 output path based on output directory name
            # Use output directory name as dataset name instead of input directory name
            output_dataset_name = get_dataset_name(args.output, args.dataset_name)
            args.hdf5_output = os.path.join(args.output, f"{output_dataset_name}.hdf5")
            logger.info(f"Auto-generated HDF5 output path: {args.hdf5_output}")
        
        if not args.hdf5_output.endswith(('.hdf5', '.h5')):
            logger.error("HDF5 output file must have .hdf5 or .h5 extension")
            return False
    
    # Validate hdf5_only mode
    if args.hdf5_only:
        if not (args.save_hdf5 or args.hdf5_output):
            logger.error("--hdf5_only requires --save_hdf5 or --hdf5_output to be specified")
            return False
        logger.info("HDF5-only mode: will only convert existing PLY/NPY files to HDF5")
    
    return True

def perform_dry_run(args, data_loader, sequences_to_process):
    """Perform dry run to check configuration and data paths."""
    logger.info("=" * 50)
    logger.info("DRY RUN - Checking configuration and data paths")
    logger.info("=" * 50)
    
    # Check data root exists
    if not os.path.exists(args.data_root):
        logger.error(f"Data root path does not exist: {args.data_root}")
        return
    logger.info(f"βœ“ Data root path exists: {args.data_root}")
    
    # Check output directory can be created
    try:
        os.makedirs(args.output_dir, exist_ok=True)
        logger.info(f"βœ“ Output directory can be created: {args.output_dir}")
    except Exception as e:
        logger.error(f"βœ— Cannot create output directory: {e}")
        return
    
    # Check each sequence and calculate total samples
    total_samples = 0
    sequence_samples = {}
    
    for sequence in sequences_to_process:
        logger.info(f"Checking sequence {sequence}...")
        try:
            sequence_info = data_loader.get_sequence_info(sequence)
            frame_count = sequence_info['frame_count']
            
            # Use effective frame count if keyframe filtering is available
            if hasattr(data_loader, 'get_effective_frame_count'):
                # Set sequence temporarily to get effective count
                try:
                    data_loader.set_sequence(sequence)
                    effective_count = data_loader.get_effective_frame_count()
                    if effective_count != frame_count:
                        logger.info(f"  βœ“ Sequence {sequence}: {frame_count} frames -> {effective_count} keyframes")
                        frame_count = effective_count
                    else:
                        logger.info(f"  βœ“ Sequence {sequence}: {frame_count} frames")
                except Exception as e:
                    logger.warning(f"  ! Could not get effective frame count for {sequence}: {e}")
                    logger.info(f"  βœ“ Sequence {sequence}: {frame_count} frames (using original count)")
            else:
                logger.info(f"  βœ“ Sequence {sequence}: {frame_count} frames")
            
            # Show frame limiting info
            if args.max_frames_per_sequence and frame_count > args.max_frames_per_sequence:
                logger.info(f"  βœ“ Would limit to {args.max_frames_per_sequence} frames (random sampling)")
            elif args.max_frames_per_sequence:
                logger.info(f"  βœ“ Would use all {frame_count} frames (within limit)")
            else:
                logger.info(f"  βœ“ Would use all {frame_count} frames (no limit)")
            
            # Determine number of samples
            num_samples = args.num_samples or get_default_num_samples(sequence, frame_count, data_loader, args.sample_count_multiplier)
            
            # Apply max_samples_per_sequence limit
            if num_samples > args.max_samples_per_sequence:
                logger.info(f"  βœ“ Would generate {num_samples} samples (limited from calculated value)")
                logger.info(f"  βœ“ Applying max_samples_per_sequence limit: {args.max_samples_per_sequence}")
                num_samples = args.max_samples_per_sequence
            else:
                logger.info(f"  βœ“ Would generate {num_samples} samples")
            
            # Store for total calculation
            sequence_samples[sequence] = num_samples
            total_samples += num_samples
            
        except Exception as e:
            logger.error(f"  βœ— Error checking sequence {sequence}: {e}")
    
    # Check HDF5 path if specified
    if args.create_hdf5 and args.hdf5_output_path:
        hdf5_dir = os.path.dirname(args.hdf5_output_path)
        if hdf5_dir and not os.path.exists(hdf5_dir):
            try:
                os.makedirs(hdf5_dir, exist_ok=True)
                logger.info(f"βœ“ HDF5 output directory can be created: {hdf5_dir}")
            except Exception as e:
                logger.error(f"βœ— Cannot create HDF5 output directory: {e}")
    
    logger.info("=" * 50)
    logger.info("DRY RUN COMPLETE - All checks passed!")
    logger.info("=" * 50)
    logger.info("SAMPLE COUNT SUMMARY:")
    logger.info(f"  Total sequences to process: {len(sequences_to_process)}")
    logger.info(f"  Total samples to generate: {total_samples}")
    logger.info(f"  Max samples per sequence limit: {args.max_samples_per_sequence}")
    logger.info("  Per-sequence breakdown:")
    for sequence, num_samples in sequence_samples.items():
        logger.info(f"    {sequence}: {num_samples} samples")
    
    # Show data splitting preview
    if args.val_sequences:
        logger.info("=" * 50)
        logger.info("MANUAL VALIDATION SEQUENCES SPECIFIED:")
        logger.info(f"  Validation sequences: {args.val_sequences}")
        logger.info(f"  This will override automatic sequence-based splitting")
        if args.mixed_val_split:
            logger.info(f"  Mixed validation enabled: Additional random samples from training sequences will be added to validation")
        logger.info("=" * 50)
    else:
        if args.mixed_val_split:
            logger.info("=" * 50)
            logger.info("MIXED VALIDATION SPLIT ENABLED:")
            logger.info(f"  Validation will include sequence-based samples PLUS random samples from training sequences")
            logger.info(f"  Target validation ratio: {1 - args.train_ratio:.3f}")
            logger.info("=" * 50)
        preview_data_splits(sequence_samples, args.train_ratio, args.split_by_sequence, args.seed, data_loader, args.val_sequences)
    
    logger.info("=" * 50)

def process_sequences(args, data_loader, sequences_to_process, sequences_dir):
    """Process all sequences and return total samples and statistics."""
    from .processing_utils import process_sequence_with_loader
    
    total_samples = 0
    sequence_stats = {}
    
    for sequence in sequences_to_process:
        logger.info(f"Processing sequence {sequence}")
        
        # Get sequence info and determine samples
        sequence_info = data_loader.get_sequence_info(sequence)
        frame_count = sequence_info['frame_count']
        
        # Use estimated effective frame count to avoid expensive pre-loading for sample calculation
        if hasattr(data_loader, 'estimate_effective_frame_count'):
            try:
                estimated_frame_count = data_loader.estimate_effective_frame_count(sequence)
                if estimated_frame_count != frame_count:
                    logger.info(f"Using estimated effective frame count for {sequence}: {frame_count} -> ~{estimated_frame_count} frames (for sample calculation)")
                    frame_count = estimated_frame_count
            except Exception as e:
                logger.warning(f"Could not estimate effective frame count for {sequence}: {e}")
        
        num_samples = args.num_samples or get_default_num_samples(sequence, frame_count, data_loader, args.sample_count_multiplier)
        
        # Apply max_samples_per_sequence limit
        if num_samples > args.max_samples_per_sequence:
            logger.info(f"Limiting samples for sequence {sequence} from {num_samples} to {args.max_samples_per_sequence}")
            num_samples = args.max_samples_per_sequence
        
        # Log processing info
        if args.max_frames_per_sequence and frame_count > args.max_frames_per_sequence:
            logger.info(f"Sequence {sequence}: {frame_count} frames, limiting to {args.max_frames_per_sequence} frames, generating {num_samples} samples")
        else:
            logger.info(f"Sequence {sequence}: {frame_count} frames, generating {num_samples} samples")
        
        # Process sequence
        sequence_output_dir = os.path.join(sequences_dir, sequence)
        
        # Log actual effective frame count if available (after set_sequence is called in processing)
        logger.info(f"Starting processing for sequence {sequence}...")
        
        num_generated, stats = process_sequence_with_loader(
            data_loader=data_loader,
            sequence=sequence,
            output_dir=sequence_output_dir,
            num_samples_to_generate=num_samples,
            min_frames_per_submap=args.min_frames_per_submap,
            max_frames_per_submap=args.max_frames_per_submap,
            min_spatial_threshold=args.min_spatial_threshold,
            max_spatial_threshold=args.max_spatial_threshold,
            min_submaps_per_sample=args.min_submaps_per_sample,
            max_submaps_per_sample=args.max_submaps_per_sample,
            voxel_size=args.voxel_size,
            downsample_method=args.downsample_method,
            num_points_downsample=args.num_points_downsample,
            start_frame=args.start_frame,
            end_frame=args.end_frame,
            max_frames_per_sequence=args.max_frames_per_sequence,
            min_overlap_ratio=args.min_overlap_ratio,
            max_overlap_ratio=args.max_overlap_ratio,
            overlap_method=args.overlap_method,
            min_frame_interval=args.min_frame_interval,
            max_frame_interval=args.max_frame_interval,
            overlap_voxel_size=args.overlap_voxel_size,
            max_attempts=args.max_attempts,
            enable_deskewing=args.enable_deskewing,
            random_drop_to_single_frame=args.random_drop_to_single_frame
        )
        
        total_samples += num_generated
        sequence_stats[sequence] = {
            'training_samples_generated': num_generated,
            'statistics': stats
        }
        
        logger.info(f"Sequence {sequence}: {num_generated} training samples")
    
    return total_samples, sequence_stats