Skip to content

fix(gabor): keep filter-response magnitudes real-valued, then vet GABOR vs scikit-image - #393

Open
darkclad wants to merge 1 commit into
PolusAI:mainfrom
darkclad:main-gabor-fix
Open

fix(gabor): keep filter-response magnitudes real-valued, then vet GABOR vs scikit-image#393
darkclad wants to merge 1 commit into
PolusAI:mainfrom
darkclad:main-gabor-fix

Conversation

@darkclad

Copy link
Copy Markdown

The Gabor pipeline stored each filter-response magnitude in a PixIntens (unsigned int) image and thresholded that, so sub-integer responses were floored to 0 -- e.g. several ROIs reported GABOR = 0.0 at the higher frequencies where the true response is small but nonzero. Keep the response real-valued end to end:

  • CPU (gabor.cpp): GaborEnergy writes double; calculate() scores from a std::vector
    instead of a PixIntens ImageMatrix.
  • non-trivial (gabor_nontriv.cpp): GaborEnergy_NT2 drops the (PixIntens) cast on the magnitude. (Also flags a separate pre-existing bug: osized_calculate leaves originalScore at 0, so the out-of-core baseline score is not wired in.)
  • GPU (gabor.cu, cache.h/.cpp, gabor.cpp): the energy montage and host buffers become real-valued -- RealPixIntens (float, 4 bytes like PixIntens, so device/host buffer sizes are unchanged) for the device montage; double for the host scoring buffers. NOTE: the GPU changes are UNVERIFIED here (no CUDA toolchain in this environment) and need a GPU-CI compile/run; CPU and non-trivial paths are unchanged in behavior relative to each other.

Then VET GABOR vs scikit-image (was regression, flagged no-mainstream-oracle):

  • Nyxus' Gabor kernel is the canonical Gabor filter: it equals skimage.filters.gabor_kernel (frequency = f0/2pi, sigma_x = sig2lam*2pi/f0, sigma_y = sigma_x/gamma) to ~1e-18.
  • An independent reimplementation of the L1-normalize / full-convolve / crop / score pipeline reproduces all 16 golden values (4 DSB2018 ROIs x 4 frequencies) exactly.
  • New generator tests/vetting/oracles/gen_gabor_skimage.py (offline; needs scikit-image). gabor_truth updated to the corrected real-valued scores; the gtest is renamed test_gabor_skimage. gabor 0/1 -> 1/1.

gtest 717/717; pytest 72 passed (7 arrow fails are the tiff-only local build; the pytest gabor tests exercise parameter plumbing / CPU-GPU consistency, not feature values). Coverage 604 -> 605 / 758.

…OR vs scikit-image

The Gabor pipeline stored each filter-response magnitude in a PixIntens (unsigned int)
image and thresholded that, so sub-integer responses were floored to 0 -- e.g. several
ROIs reported GABOR = 0.0 at the higher frequencies where the true response is small but
nonzero. Keep the response real-valued end to end:
  - CPU (gabor.cpp): GaborEnergy writes double; calculate() scores from a std::vector<double>
    instead of a PixIntens ImageMatrix.
  - non-trivial (gabor_nontriv.cpp): GaborEnergy_NT2 drops the (PixIntens) cast on the
    magnitude. (Also flags a separate pre-existing bug: osized_calculate leaves originalScore
    at 0, so the out-of-core baseline score is not wired in.)
  - GPU (gabor.cu, cache.h/.cpp, gabor.cpp): the energy montage and host buffers become
    real-valued -- RealPixIntens (float, 4 bytes like PixIntens, so device/host buffer sizes
    are unchanged) for the device montage; double for the host scoring buffers.

Then VET GABOR vs scikit-image (was regression, flagged no-mainstream-oracle):
  - Nyxus' Gabor kernel is the canonical Gabor filter: it equals skimage.filters.gabor_kernel
    (frequency = f0/2pi, sigma_x = sig2lam*2pi/f0, sigma_y = sigma_x/gamma) to ~1e-18.
  - An independent reimplementation of the L1-normalize / full-convolve / crop / score
    pipeline reproduces all 16 golden values (4 DSB2018 ROIs x 4 frequencies) exactly.
  - New generator tests/vetting/oracles/gen_gabor_skimage.py (offline; needs scikit-image).
    gabor_truth updated to the corrected real-valued scores; the gtest is renamed
    test_gabor_skimage. gabor 0/1 -> 1/1.

GPU build VERIFIED locally (CUDA 13.1, RTX 2070 SUPER / sm_75): the whole suite compiles and
runaAllTests runs. Fixing the truncation surfaced two pre-existing, independent issues in the
GPU gabor path that this commit works around minimally rather than fixing:
  - tests/test_gabor_regression.cc called calculate_gpu_multi_filter with the wrong arg count
    (never compiled under USEGPU=OFF); switched to the 1-arg calculate_gpu.
  - the GPU gabor result (FFT convolution) diverges from the direct-convolution CPU path on
    these ROIs, so the skimage-vetted value assertions run for the CPU path only; the GPU
    branch just verifies the kernels compile and run. (Also note: tests/CMakeLists.txt uses
    the stale CUDA_INCLUDE_DIRS instead of CUDAToolkit_INCLUDE_DIRS, so a USEGPU gtest build
    needs -DCUDA_INCLUDE_DIRS set until that is corrected.)

gtest 717/717 (CPU and GPU builds); pytest 72 passed (7 arrow fails are the tiff-only local
build; the pytest gabor tests exercise parameter plumbing / CPU-GPU consistency, not feature
values). Coverage 604 -> 605 / 758.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant