Coverage for src / tracekit / search / context.py: 100%
34 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-11 23:04 +0000
1"""Context extraction around points of interest.
4This module provides efficient extraction of signal context around
5events, maintaining original time references for debugging workflows.
6"""
8from typing import Any
10import numpy as np
11from numpy.typing import NDArray
14def extract_context(
15 trace: NDArray[np.float64],
16 index: int | list[int] | NDArray[np.int_],
17 *,
18 before: int = 100,
19 after: int = 100,
20 sample_rate: float | None = None,
21 include_metadata: bool = True,
22) -> dict[str, Any] | list[dict[str, Any]]:
23 """Extract signal context around a point of interest.
25 : Context extraction with time reference preservation.
26 Supports batch extraction for multiple indices and optional protocol data.
28 Args:
29 trace: Input signal trace
30 index: Sample index or list of indices to extract context around.
31 Can be int, list of ints, or numpy array.
32 before: Number of samples to include before index (default: 100)
33 after: Number of samples to include after index (default: 100)
34 sample_rate: Optional sample rate in Hz for time calculations
35 include_metadata: Include metadata dict with context info (default: True)
37 Returns:
38 If index is scalar: Single context dictionary
39 If index is list/array: List of context dictionaries
41 Each context dictionary contains:
42 - data: Extracted sub-trace array
43 - start_index: Starting index in original trace
44 - end_index: Ending index in original trace
45 - center_index: Center index (original query index)
46 - time_reference: Time offset if sample_rate provided
47 - length: Number of samples in context
49 Raises:
50 ValueError: If index is out of bounds
51 ValueError: If before or after are negative
53 Examples:
54 >>> # Extract context around a glitch
55 >>> trace = np.random.randn(1000)
56 >>> glitch_index = 500
57 >>> context = extract_context(
58 ... trace,
59 ... glitch_index,
60 ... before=50,
61 ... after=50,
62 ... sample_rate=1e6
63 ... )
64 >>> print(f"Context length: {len(context['data'])}")
65 >>> print(f"Time reference: {context['time_reference']*1e6:.2f} µs")
67 >>> # Batch extraction for multiple events
68 >>> event_indices = [100, 200, 300]
69 >>> contexts = extract_context(
70 ... trace,
71 ... event_indices,
72 ... before=25,
73 ... after=25
74 ... )
75 >>> print(f"Extracted {len(contexts)} contexts")
77 Notes:
78 - Handles edge cases at trace boundaries automatically
79 - Context may be shorter than before+after at boundaries
80 - Time reference is relative to start of extracted context
81 - Original trace is not modified
83 References:
84 SRCH-003: Context Extraction
85 """
86 if before < 0 or after < 0:
87 raise ValueError("before and after must be non-negative")
89 if trace.size == 0:
90 raise ValueError("Trace cannot be empty")
92 # Handle single index vs multiple indices
93 if isinstance(index, int | np.integer):
94 indices = [int(index)]
95 return_single = True
96 else:
97 indices = [int(i) for i in index]
98 return_single = False
100 # Validate indices
101 for idx in indices:
102 if idx < 0 or idx >= len(trace):
103 raise ValueError(f"Index {idx} out of bounds for trace of length {len(trace)}")
105 # Extract contexts
106 contexts = []
108 for idx in indices:
109 # Calculate window bounds with boundary handling
110 start_idx = max(0, idx - before)
111 end_idx = min(len(trace), idx + after + 1)
113 # Extract data
114 data = trace[start_idx:end_idx].copy()
116 # Build context dictionary
117 context: dict[str, Any] = {
118 "data": data,
119 "start_index": start_idx,
120 "end_index": end_idx,
121 "center_index": idx,
122 "length": len(data),
123 }
125 # Add time reference if sample rate provided
126 if sample_rate is not None:
127 time_offset = start_idx / sample_rate
128 context["time_reference"] = time_offset
129 context["sample_rate"] = sample_rate
131 # Time array for the context
132 dt = 1.0 / sample_rate
133 context["time_array"] = np.arange(len(data)) * dt + time_offset
135 if include_metadata:
136 context["metadata"] = {
137 "samples_before": idx - start_idx,
138 "samples_after": end_idx - idx - 1,
139 "at_start_boundary": start_idx == 0,
140 "at_end_boundary": end_idx == len(trace),
141 }
143 contexts.append(context)
145 # Return single context or list
146 if return_single:
147 return contexts[0]
148 else:
149 return contexts