Skip to content

Commit 8b90025

Browse files
popojanclaude
andcommitted
Fix monochrome video detection and add test videos
- Fix empty color detection for monochrome schemes using time-flow direction: filled cells sum increases 0→59 (forward), empty cells decrease (backward). Only used as tiebreaker when top 2 colors have similar frequency. - Add IMG_6782.MOV: signature test (P=8191, N=1983) - Add IMG_6789.MOV: monochrome red + signature (P=1234567, N=196) - Both videos stripped of GPS/location metadata Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9dc2854 commit 8b90025

6 files changed

Lines changed: 94 additions & 14 deletions

File tree

.gitattributes

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.MOV filter=lfs diff=lfs merge=lfs -text
2-
32
# Override GitHub language detection - show C as primary
43
inverse/* linguist-vendored
4+
test_videos/IMG_6789.MOV filter=lfs diff=lfs merge=lfs -text
5+
test_videos/IMG_6782.MOV filter=lfs diff=lfs merge=lfs -text

inverse/run_all_tests.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@ test_video "IMG_6743.MOV" "29458875" "1970-01-0100:00:00" "1" "0"
5151
# Standard encoding: P=1, N=0
5252
test_video "IMG_6746.MOV" "1065579753" "0000-01-0100:00:00" "1" "0"
5353

54-
# Signature encoding test
55-
# Raw k = minute * P + N, decoded minutes = (k - N) / P = 29465913
54+
# Signature encoding tests
55+
# Raw k = minute * P + N, decoded minutes = (k - N) / P
5656
test_video "IMG_6782.MOV" "241355295366" "1970-01-0100:00:00" "8191" "1983"
5757

58+
# Monochrome red with signature (tests time-flow based empty color detection)
59+
test_video "IMG_6789.MOV" "36381401836815" "1970-01-0100:00:00" "1234567" "196"
60+
5861
echo
5962
echo "=== Results ==="
6063
echo "Passed: $PASS"

inverse/video_analyzer.py

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,64 @@ def detect_empty_color(cell_colors):
842842
return np.array([v + 10 for v in most_frequent], dtype=np.uint8)
843843

844844

845+
def detect_empty_color_by_time_flow(warped_frames, all_cell_colors):
846+
"""
847+
Detect empty color using time-flow direction as tiebreaker.
848+
849+
In non-monochrome setups, empty color is most frequent and easily detected.
850+
In monochrome setups, filled and empty have similar total area, so we use
851+
time-flow direction to distinguish:
852+
- Filled cells: sum increases 0→59 (forward time)
853+
- Empty cells: sum decreases 60→1 (backward time)
854+
"""
855+
quantized = (np.array(all_cell_colors) // 20) * 20
856+
color_counts = {}
857+
for c in quantized:
858+
key = tuple(c)
859+
color_counts[key] = color_counts.get(key, 0) + 1
860+
861+
sorted_colors = sorted(color_counts.items(), key=lambda x: -x[1])
862+
if len(sorted_colors) < 2:
863+
return np.array([v + 10 for v in sorted_colors[0][0]], dtype=np.uint8)
864+
865+
top1_key, top1_count = sorted_colors[0]
866+
top2_key, top2_count = sorted_colors[1]
867+
868+
# If top color is clearly dominant (>1.5x second), use it (non-monochrome case)
869+
if top1_count > top2_count * 1.5:
870+
return np.array([v + 10 for v in top1_key], dtype=np.uint8)
871+
872+
# Similar frequency (monochrome case) - use time-flow to break tie
873+
color_candidates = [np.array([v + 10 for v in c[0]], dtype=np.uint8)
874+
for c in sorted_colors[:2]]
875+
876+
best_empty = color_candidates[0]
877+
best_flow_score = -float('inf')
878+
879+
for candidate_empty in color_candidates:
880+
test_tol = find_adaptive_threshold(warped_frames[:min(60, len(warped_frames))], candidate_empty)
881+
sums = []
882+
for warped in warped_frames[:min(90, len(warped_frames))]:
883+
visible = get_visible_cells_warped(warped, candidate_empty, test_tol)
884+
sums.append(sum(visible) % 60)
885+
886+
# Forward time = increasing sums (positive flow_score)
887+
flow_score = 0
888+
for i in range(1, len(sums)):
889+
diff = sums[i] - sums[i-1]
890+
if diff < -30:
891+
diff += 60
892+
elif diff > 30:
893+
diff -= 60
894+
flow_score += diff
895+
896+
if flow_score > best_flow_score:
897+
best_flow_score = flow_score
898+
best_empty = candidate_empty
899+
900+
return best_empty
901+
902+
845903
def get_empty_color_candidates(cell_colors, max_candidates=3):
846904
"""Get multiple empty color candidates for validation testing."""
847905
all_colors_array = np.array(cell_colors)
@@ -1037,15 +1095,8 @@ def run_simple_detection(frames, tolerance, verbose=False):
10371095
color = sample_cell_color_warped(warped, cell_id)
10381096
all_cell_colors.append(color)
10391097

1040-
# Use quantized color detection across all frames (like old version)
1041-
all_colors_array = np.array(all_cell_colors)
1042-
quantized = (all_colors_array // 20) * 20
1043-
color_counts = {}
1044-
for c in quantized:
1045-
key = tuple(c)
1046-
color_counts[key] = color_counts.get(key, 0) + 1
1047-
most_frequent = max(color_counts, key=color_counts.get)
1048-
empty_color = np.array([v + 10 for v in most_frequent], dtype=np.uint8)
1098+
# Detect empty color using time-flow direction as tiebreaker for monochrome
1099+
empty_color = detect_empty_color_by_time_flow(warped_frames, all_cell_colors)
10491100

10501101
# Find adaptive threshold from score distribution
10511102
adaptive_tol = find_adaptive_threshold(warped_frames, empty_color)
@@ -1112,8 +1163,8 @@ def run_hough_detection(frames, quad_pts, tolerance, verbose=False):
11121163
for cell_id in CELL_LAYOUT.keys():
11131164
all_cell_colors.append(sample_cell_color_warped(warped, cell_id))
11141165

1115-
# Detect empty color
1116-
empty_color = detect_empty_color(all_cell_colors)
1166+
# Detect empty color using time-flow direction as tiebreaker for monochrome
1167+
empty_color = detect_empty_color_by_time_flow(warped_frames, all_cell_colors)
11171168

11181169
# Find adaptive threshold from score distribution
11191170
adaptive_tol = find_adaptive_threshold(warped_frames, empty_color)

test_videos/IMG_6782.MOV

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e90fb5c2e86106ece5c552f9f0bb26c5ab9e326e433f6562e12cba003589e6c4
3+
size 20748185

test_videos/IMG_6789.MOV

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:1e5435f863207833b66a884f2c68edda7dddc5ec5b4c07ff587ce5f5dc732c95
3+
size 16449042

test_videos/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ Videos recorded before this change will show the local time origin when analyzed
7777
- **Expected Result**: 60/60 seconds matched
7878
- **Notes**: Tests overflow handling for dates before year 1; origin uses ISO 8601 extended format
7979

80+
### IMG_6782.MOV (Multi-minute Signature Test)
81+
- **Characteristics**: Multi-minute recording with signature encoding (P, N parameters)
82+
- **Recorded**: Properly mounted (not handheld) for stable detection
83+
- **Clock Origin**: 1970-01-01 00:00:00 UTC (Unix epoch)
84+
- **Expected P**: 8191
85+
- **Expected N**: 1983
86+
- **Expected Result**: 60/60 seconds matched per minute, signature detected
87+
- **Notes**: Tests multi-minute signature detection; requires 2+ minutes to detect P/N values
88+
89+
### IMG_6789.MOV (Monochrome Red with Signature)
90+
- **Characteristics**: Monochrome red color scheme with signature encoding
91+
- **Recorded**: 2026-01-11 14:16:42 CET
92+
- **Clock Origin**: 1970-01-01 00:00:00 UTC (Unix epoch)
93+
- **Expected k**: 36,381,401,836,815
94+
- **Expected P**: 1234567
95+
- **Expected N**: 196
96+
- **Expected Result**: 60/60 seconds matched
97+
- **Notes**: Tests time-flow based empty color detection for monochrome schemes where filled and empty cells have similar total area over time. The analyzer detects the correct "empty" color by checking which color assignment produces forward-flowing time (increasing cell sums).
98+
8099
## Running Tests
81100

82101
```bash

0 commit comments

Comments
 (0)