Skip to content

refactor(math): extract core base class and de-duplicate math module (#47)#62

Merged
kalwalt merged 1 commit into
devfrom
refactor/47-dedup-math
Jul 8, 2026
Merged

refactor(math): extract core base class and de-duplicate math module (#47)#62
kalwalt merged 1 commit into
devfrom
refactor/47-dedup-math

Conversation

@kalwalt

@kalwalt kalwalt commented Jul 7, 2026

Copy link
Copy Markdown
Member

First de-duplication step of #47 — the proof-of-pattern PR that every subsequent module extraction will follow. Guarded by the 57-test parity suite (#39/#49).

What changed

New src/core/core.ts — the base class, extracted

The base jsfeatNext class (constants, per-instance cache/data-type helpers, static module slots) moves out of the monolith verbatim. Why it must be its own file: modules extend jsfeatNext (that's the public contract — instance methods like get_data_type() and static inheritance like jsfeatNext.math.EPSILON depend on it), and a module can't extend a class defined in the same file that imports the module back — that's a circular ESM import that throws at load time. The one-way flow is now:

core ← modules (math, …) ← jsfeatNext.ts (aggregator) ← index.ts

This is the audit's §4 target architecture starting to materialize (see also #40).

src/math/math.ts — stub replaced by the real implementation

The type-only stub (every method throw new Error("Method not implemented.")) is gone; the inline implementation from the monolith moved in verbatim (get_gaussian_kernel, deprecated perspective_4point_transform, qsort, median). Extraction was done mechanically (sed line-range copy), not retyped.

One deliberate signature fix: median() now accepts Int32Array | Float32Array as well as number[]motion_estimator.lmeds always called it with a Float32Array cache buffer at runtime; the old stub hid that behind any. Types only, zero behavior change.

src/jsfeatNext.ts — shrinks by ~560 lines

Imports the base from core, attaches math from its module. On its way to becoming a thin aggregator.

Known-temporary (tightened as #47 proceeds)

The affine2d/homography2d static slots on the base are typed any until those classes are extracted from the monolith in a later PR.

Verification (behavior-preserving, proven)

  • tsc --noEmit → clean
  • npm test57/57 parity tests pass
  • UMD build verified: require shape ({ jsfeatNext }), new jsfeatNext.math() instanceof jsfeatNexttrue, static-constant inheritance (jsfeatNext.math.EPSILON === jsfeatNext.EPSILON) → true, gaussian-kernel numerics identical
  • dist//types/ deliberately untouched (rebuilt at release time, per fix(motion_estimator): restore affine2d error() and check_subset() (#51) #52 precedent)

Next modules in the queue (same pattern)

imgprocfast_cornerspyramid_tlinalgorbyape06motion_estimator (+ affine2d/homography2d/motion_model) → optical_flow_lk

Advances #47.

🤖 Generated with Claude Code

…47)

First de-duplication step of the stub/monolith cleanup (#47), guarded by the
57-test parity suite.

- NEW src/core/core.ts: the base jsfeatNext class (constants, per-instance
  cache/data-type helpers, static module slots), extracted verbatim from the
  monolith. Modules can now extend it without a circular import against the
  aggregator — the keystone every subsequent #47 extraction reuses.
- src/math/math.ts: replace the type-only stub (every method threw 'Method
  not implemented') with the REAL implementation moved verbatim from the
  monolith (get_gaussian_kernel, deprecated perspective_4point_transform,
  qsort, median). Only deliberate change: median() now accepts typed arrays
  in its signature — motion_estimator.lmeds always called it with a
  Float32Array cache buffer; the old stub hid that behind 'any'.
- src/jsfeatNext.ts: shrinks by ~560 lines; imports the base from core and
  attaches math from its module (jsfeatNext.math = math).

Temporary (tightened as #47 proceeds): the affine2d/homography2d static
slots on the base are typed 'any' until those classes move out of the
monolith.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57; UMD
require shape, instanceof chain (new jsfeatNext.math() instanceof
jsfeatNext), static-constant inheritance (jsfeatNext.math.EPSILON) and
gaussian-kernel numerics all checked against the pre-refactor build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kalwalt kalwalt added this to the Parity & Modernization milestone Jul 7, 2026
@kalwalt kalwalt self-assigned this Jul 7, 2026
@kalwalt kalwalt added enhancement New feature or request Typescript all about Typescript code design labels Jul 7, 2026
@kalwalt kalwalt merged commit a8a393e into dev Jul 8, 2026
4 checks passed
kalwalt added a commit that referenced this pull request Jul 8, 2026
Second de-duplication step of #47, following the pattern proven in #62.

- src/imgproc/imgproc.ts: replace the type-only stub with the REAL
  implementation moved verbatim from the monolith (grayscale, resample,
  box_blur_gray, gaussian_blur, hough_transform, pyrdown, scharr/sobel
  derivatives, compute_integral_image, equalize_histogram, canny,
  warp_perspective, warp_affine, skindetector). Only deliberate change:
  gaussian_blur instantiates the math module directly (import from
  ../math/math) instead of via the jsfeatNext.math static slot, removing
  an attach-order dependency on the aggregator.
- src/jsfeatNext.ts: shrinks by ~1050 lines; attaches imgproc from its
  module (jsfeatNext.imgproc = imgproc).
- Side effect: the latent trap in src/orb/rectify_patch.ts (it imports
  imgproc, which until now was the throwing stub) is healed — it resolves
  to the real implementation.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (14 of
those pin imgproc bit-for-bit vs original jsfeat); UMD build checked
(instanceof chain, static-constant inheritance, grayscale + gaussian_blur
smoke on the bundle).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 8, 2026
Third de-duplication step of #47, following the #62/#63 pattern.

- src/fast_corners/fast_corners.ts: replace the type-only stub with the
  REAL implementation moved verbatim from the monolith (set_threshold,
  detect, _cmp_offsets; imports _cmp_score_16 from ./fast_private, which
  was already a real module).
- src/jsfeatNext.ts: shrinks by ~225 lines; attaches fast_corners from its
  module.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (detector
parity suite pins fast_corners.detect against original jsfeat); UMD bundle
smoke-checked (instanceof chain, static inheritance, corner detection on a
synthetic image).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 8, 2026
Third de-duplication step of #47, following the #62/#63 pattern.

- src/fast_corners/fast_corners.ts: replace the type-only stub with the
  REAL implementation moved verbatim from the monolith (set_threshold,
  detect, _cmp_offsets; imports _cmp_score_16 from ./fast_private, which
  was already a real module).
- src/jsfeatNext.ts: shrinks by ~225 lines; attaches fast_corners from its
  module.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (detector
parity suite pins fast_corners.detect against original jsfeat); UMD bundle
smoke-checked (instanceof chain, static inheritance, corner detection on a
synthetic image).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 8, 2026
… kernels (#47)

Eighth de-duplication step of #47 — the biggest structural win.

- NEW src/motion_model/motion_model.ts: motion_model + affine2d +
  homography2d kernel classes, moved verbatim from the monolith (they were
  module-local classes there). Kernels instantiate linalg via direct module
  import instead of the jsfeatNext.linalg static slot.
- src/motion_estimator/motion_estimator.ts: replace the type-only stub with
  the REAL implementation (get_subset, find_inliers, ransac, lmeds); lmeds
  instantiates math via direct module import.
- src/core/core.ts: the temporary any-typed affine2d/homography2d static
  slots (a known TODO since #62) now have precise typeof types via type-only
  imports.
- src/jsfeatNext.ts: down to ~380 lines — pure aggregator except for
  optical_flow_lk, the last inline module.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (3 parity
tests pin ransac/lmeds with identical models AND inlier masks vs original
jsfeat on seeded data); UMD bundle smoke-checked (instanceof for estimator
and both kernels, affine RANSAC recovers the synthetic model).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 8, 2026
One-shot artifact rebuild for the integration of the stub/monolith
de-duplication (#62-#70) into dev, per the agreed convention (refactor PRs
ship source only; artifacts are rebuilt at integration/release points).

- dist/jsfeatNext.js (UMD) + dist/jsfeatNext.mjs (ESM) rebuilt from the
  refactored source
- types/ regenerated: now mirrors the new module layout, including
  types/src/core/core.d.ts and types/src/motion_model/motion_model.d.ts

Verified: npm test 57/57; all 18 public modules attach on the fresh UMD
bundle; VERSION 0.7.6 intact; no .d.ts files leak into dist/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 9, 2026
Second de-duplication step of #47, following the pattern proven in #62.

- src/imgproc/imgproc.ts: replace the type-only stub with the REAL
  implementation moved verbatim from the monolith (grayscale, resample,
  box_blur_gray, gaussian_blur, hough_transform, pyrdown, scharr/sobel
  derivatives, compute_integral_image, equalize_histogram, canny,
  warp_perspective, warp_affine, skindetector). Only deliberate change:
  gaussian_blur instantiates the math module directly (import from
  ../math/math) instead of via the jsfeatNext.math static slot, removing
  an attach-order dependency on the aggregator.
- src/jsfeatNext.ts: shrinks by ~1050 lines; attaches imgproc from its
  module (jsfeatNext.imgproc = imgproc).
- Side effect: the latent trap in src/orb/rectify_patch.ts (it imports
  imgproc, which until now was the throwing stub) is healed — it resolves
  to the real implementation.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (14 of
those pin imgproc bit-for-bit vs original jsfeat); UMD build checked
(instanceof chain, static-constant inheritance, grayscale + gaussian_blur
smoke on the bundle).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 9, 2026
Third de-duplication step of #47, following the #62/#63 pattern.

- src/fast_corners/fast_corners.ts: replace the type-only stub with the
  REAL implementation moved verbatim from the monolith (set_threshold,
  detect, _cmp_offsets; imports _cmp_score_16 from ./fast_private, which
  was already a real module).
- src/jsfeatNext.ts: shrinks by ~225 lines; attaches fast_corners from its
  module.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (detector
parity suite pins fast_corners.detect against original jsfeat); UMD bundle
smoke-checked (instanceof chain, static inheritance, corner detection on a
synthetic image).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 9, 2026
… kernels (#47)

Eighth de-duplication step of #47 — the biggest structural win.

- NEW src/motion_model/motion_model.ts: motion_model + affine2d +
  homography2d kernel classes, moved verbatim from the monolith (they were
  module-local classes there). Kernels instantiate linalg via direct module
  import instead of the jsfeatNext.linalg static slot.
- src/motion_estimator/motion_estimator.ts: replace the type-only stub with
  the REAL implementation (get_subset, find_inliers, ransac, lmeds); lmeds
  instantiates math via direct module import.
- src/core/core.ts: the temporary any-typed affine2d/homography2d static
  slots (a known TODO since #62) now have precise typeof types via type-only
  imports.
- src/jsfeatNext.ts: down to ~380 lines — pure aggregator except for
  optical_flow_lk, the last inline module.

Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (3 parity
tests pin ransac/lmeds with identical models AND inlier masks vs original
jsfeat on seeded data); UMD bundle smoke-checked (instanceof for estimator
and both kernels, affine RANSAC recovers the synthetic model).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kalwalt added a commit that referenced this pull request Jul 9, 2026
One-shot artifact rebuild for the integration of the stub/monolith
de-duplication (#62-#70) into dev, per the agreed convention (refactor PRs
ship source only; artifacts are rebuilt at integration/release points).

- dist/jsfeatNext.js (UMD) + dist/jsfeatNext.mjs (ESM) rebuilt from the
  refactored source
- types/ regenerated: now mirrors the new module layout, including
  types/src/core/core.d.ts and types/src/motion_model/motion_model.d.ts

Verified: npm test 57/57; all 18 public modules attach on the fresh UMD
bundle; VERSION 0.7.6 intact; no .d.ts files leak into dist/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kalwalt kalwalt deleted the refactor/47-dedup-math branch July 10, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code design enhancement New feature or request Typescript all about Typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant