- Strip trailing whitespace from all Python files - Fix import sorting (I001) across all modules - Convert Optional[X] to X | None syntax (UP045) - Remove unused imports (F401) - Convert lambda assignments to def functions (E731) - Add TYPE_CHECKING import for forward references - Update pyproject.toml ruff config: - Move select/ignore to [tool.ruff.lint] section - Add per-file ignores for DCT colorspace naming (N803/N806) - Add per-file ignores for __init__.py import structure (E402) - Exclude defunct test_routes.py - Remove frontends/web/test_routes.py (defunct debug snippet) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
232 lines
6.6 KiB
Python
232 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Standalone DCT crash diagnostic script.
|
|
Run this outside of Flask to isolate the issue.
|
|
|
|
Usage:
|
|
python test_dct_crash.py /path/to/your/large_image.jpg
|
|
"""
|
|
|
|
import sys
|
|
import gc
|
|
import traceback
|
|
import io
|
|
|
|
print("=" * 60)
|
|
print("DCT CRASH DIAGNOSTIC TOOL")
|
|
print("=" * 60)
|
|
|
|
# Step 1: Check Python and library versions
|
|
print("\n[1] ENVIRONMENT INFO")
|
|
print(f"Python: {sys.version}")
|
|
|
|
try:
|
|
import numpy as np
|
|
print(f"NumPy: {np.__version__}")
|
|
except ImportError as e:
|
|
print(f"NumPy: NOT INSTALLED - {e}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
import scipy
|
|
print(f"SciPy: {scipy.__version__}")
|
|
except ImportError as e:
|
|
print(f"SciPy: NOT INSTALLED - {e}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
from PIL import Image
|
|
import PIL
|
|
print(f"Pillow: {PIL.__version__}")
|
|
except ImportError as e:
|
|
print(f"Pillow: NOT INSTALLED - {e}")
|
|
sys.exit(1)
|
|
|
|
# Step 2: Check which DCT module we're using
|
|
print("\n[2] DCT MODULE CHECK")
|
|
try:
|
|
from scipy.fft import dct, idct
|
|
print("Using: scipy.fft (preferred)")
|
|
DCT_MODULE = "scipy.fft"
|
|
except ImportError:
|
|
try:
|
|
from scipy.fftpack import dct, idct
|
|
print("Using: scipy.fftpack (legacy)")
|
|
DCT_MODULE = "scipy.fftpack"
|
|
except ImportError:
|
|
print("ERROR: No DCT module available!")
|
|
sys.exit(1)
|
|
|
|
# Step 3: Test basic DCT on small array
|
|
print("\n[3] BASIC DCT TEST (8x8 block)")
|
|
try:
|
|
test_block = np.random.rand(8, 8).astype(np.float64)
|
|
|
|
# 1D DCT on rows
|
|
result = dct(test_block[0, :], norm='ortho')
|
|
print(f" 1D DCT: OK (output shape: {result.shape})")
|
|
|
|
# 1D IDCT
|
|
recovered = idct(result, norm='ortho')
|
|
error = np.max(np.abs(test_block[0, :] - recovered))
|
|
print(f" 1D IDCT: OK (roundtrip error: {error:.2e})")
|
|
|
|
# 2D via separable
|
|
temp = np.zeros_like(test_block)
|
|
for i in range(8):
|
|
temp[:, i] = dct(test_block[:, i], norm='ortho')
|
|
result2d = np.zeros_like(temp)
|
|
for i in range(8):
|
|
result2d[i, :] = dct(temp[i, :], norm='ortho')
|
|
print(f" 2D DCT: OK")
|
|
|
|
gc.collect()
|
|
print(" GC after basic test: OK")
|
|
|
|
except Exception as e:
|
|
print(f" FAILED: {e}")
|
|
traceback.print_exc()
|
|
|
|
# Step 4: Test with larger arrays (stress test)
|
|
print("\n[4] STRESS TEST (many 8x8 blocks)")
|
|
try:
|
|
NUM_BLOCKS = 10000
|
|
print(f" Processing {NUM_BLOCKS} blocks...")
|
|
|
|
for i in range(NUM_BLOCKS):
|
|
block = np.random.rand(8, 8).astype(np.float64)
|
|
|
|
# Forward DCT
|
|
temp = np.zeros_like(block)
|
|
for j in range(8):
|
|
temp[:, j] = dct(block[:, j], norm='ortho')
|
|
result = np.zeros_like(temp)
|
|
for j in range(8):
|
|
result[j, :] = dct(temp[j, :], norm='ortho')
|
|
|
|
# Inverse DCT
|
|
temp2 = np.zeros_like(result)
|
|
for j in range(8):
|
|
temp2[j, :] = idct(result[j, :], norm='ortho')
|
|
recovered = np.zeros_like(temp2)
|
|
for j in range(8):
|
|
recovered[:, j] = idct(temp2[:, j], norm='ortho')
|
|
|
|
if i % 1000 == 0:
|
|
gc.collect()
|
|
print(f" {i}/{NUM_BLOCKS} blocks processed...")
|
|
|
|
gc.collect()
|
|
print(f" Stress test PASSED")
|
|
|
|
except Exception as e:
|
|
print(f" FAILED at block {i}: {e}")
|
|
traceback.print_exc()
|
|
|
|
# Step 5: Test with actual image if provided
|
|
if len(sys.argv) > 1:
|
|
image_path = sys.argv[1]
|
|
print(f"\n[5] IMAGE TEST: {image_path}")
|
|
|
|
try:
|
|
with open(image_path, 'rb') as f:
|
|
image_data = f.read()
|
|
print(f" File size: {len(image_data) / 1024 / 1024:.2f} MB")
|
|
|
|
img = Image.open(io.BytesIO(image_data))
|
|
width, height = img.size
|
|
print(f" Dimensions: {width}x{height}")
|
|
print(f" Format: {img.format}")
|
|
print(f" Mode: {img.mode}")
|
|
|
|
# Convert to grayscale float array
|
|
gray = img.convert('L')
|
|
arr = np.array(gray, dtype=np.float64)
|
|
img.close()
|
|
gray.close()
|
|
print(f" Array shape: {arr.shape}")
|
|
print(f" Array dtype: {arr.dtype}")
|
|
|
|
# Pad to block boundary
|
|
BLOCK_SIZE = 8
|
|
h, w = arr.shape
|
|
new_h = ((h + BLOCK_SIZE - 1) // BLOCK_SIZE) * BLOCK_SIZE
|
|
new_w = ((w + BLOCK_SIZE - 1) // BLOCK_SIZE) * BLOCK_SIZE
|
|
|
|
if new_h != h or new_w != w:
|
|
padded = np.zeros((new_h, new_w), dtype=np.float64)
|
|
padded[:h, :w] = arr
|
|
arr = padded
|
|
print(f" Padded to: {arr.shape}")
|
|
|
|
blocks_y = arr.shape[0] // BLOCK_SIZE
|
|
blocks_x = arr.shape[1] // BLOCK_SIZE
|
|
total_blocks = blocks_y * blocks_x
|
|
print(f" Total 8x8 blocks: {total_blocks}")
|
|
|
|
# Process ALL blocks
|
|
print(f" Processing all blocks with DCT...")
|
|
|
|
processed = 0
|
|
for by in range(blocks_y):
|
|
for bx in range(blocks_x):
|
|
y = by * BLOCK_SIZE
|
|
x = bx * BLOCK_SIZE
|
|
|
|
block = arr[y:y+BLOCK_SIZE, x:x+BLOCK_SIZE].copy()
|
|
|
|
# Forward DCT
|
|
temp = np.zeros((8, 8), dtype=np.float64)
|
|
for i in range(8):
|
|
temp[:, i] = dct(block[:, i], norm='ortho')
|
|
dct_block = np.zeros((8, 8), dtype=np.float64)
|
|
for i in range(8):
|
|
dct_block[i, :] = dct(temp[i, :], norm='ortho')
|
|
|
|
# Inverse DCT
|
|
temp2 = np.zeros((8, 8), dtype=np.float64)
|
|
for i in range(8):
|
|
temp2[i, :] = idct(dct_block[i, :], norm='ortho')
|
|
recovered = np.zeros((8, 8), dtype=np.float64)
|
|
for i in range(8):
|
|
recovered[:, i] = idct(temp2[:, i], norm='ortho')
|
|
|
|
processed += 1
|
|
|
|
# GC after each row of blocks
|
|
if by % 50 == 0:
|
|
gc.collect()
|
|
print(f" Row {by}/{blocks_y} ({processed}/{total_blocks} blocks)")
|
|
|
|
gc.collect()
|
|
print(f" Image DCT test PASSED ({processed} blocks)")
|
|
|
|
except Exception as e:
|
|
print(f" FAILED: {e}")
|
|
traceback.print_exc()
|
|
|
|
else:
|
|
print("\n[5] IMAGE TEST: Skipped (no image path provided)")
|
|
print(" Usage: python test_dct_crash.py /path/to/image.jpg")
|
|
|
|
# Step 6: Final cleanup test
|
|
print("\n[6] FINAL CLEANUP TEST")
|
|
try:
|
|
gc.collect()
|
|
gc.collect()
|
|
gc.collect()
|
|
print(" Multiple GC cycles: OK")
|
|
except Exception as e:
|
|
print(f" FAILED: {e}")
|
|
|
|
print("\n" + "=" * 60)
|
|
print("If this script completes without 'free(): invalid size',")
|
|
print("the issue is likely in PIL/jpegio interaction, not scipy DCT.")
|
|
print("=" * 60)
|
|
|
|
# Keep process alive briefly to catch delayed crashes
|
|
import time
|
|
print("\nWaiting 2 seconds for delayed crashes...")
|
|
time.sleep(2)
|
|
print("Done - no crash detected!")
|