Skip to content

refactor: isolate Canvas, Webcam and ML logic from DoubleCalibrationR…#116

Open
Sidhant185 wants to merge 1 commit intoruxailab:mainfrom
Sidhant185:refactor/calibration-component
Open

refactor: isolate Canvas, Webcam and ML logic from DoubleCalibrationR…#116
Sidhant185 wants to merge 1 commit intoruxailab:mainfrom
Sidhant185:refactor/calibration-component

Conversation

@Sidhant185
Copy link
Copy Markdown

♻️ Refactor: Decouple UI and ML Pipeline in DoubleCalibrationRecord.vue

📋 Summary

This PR breaks apart a monolithic 1,262-line Vue component into four clean, single-responsibility modules. It extracts Canvas rendering, Webcam stream lifecycle management, and TensorFlow.js inference into dedicated components and a plain JavaScript service class — leaving DoubleCalibrationRecord.vue as a thin orchestrator with no direct DOM or ML dependencies.


🎯 Problem Statement

DoubleCalibrationRecord.vue had grown into a God Component — a single file responsible for too many concerns simultaneously:

Concern Details
UI State Managing Vuex store bindings and Vuetify <v-stepper> transitions
Camera I/O Directly calling navigator.mediaDevices.getUserMedia, managing hidden <video> elements, and handling MediaRecorder state
Canvas Rendering Raw ctx.fillRect / ctx.arc calls, coordinate interpolation, and animation frame logic
ML Inference Calling model.estimateFaces on the main thread, parsing annotation arrays, running blink-detection heuristics inline

Impact of the monolith

  • Readability: Reviewers had to context-switch between reactive UI logic and low-level TensorFlow.js math constantly.
  • Testability: The ML pipeline was impossible to unit-test without mounting a full DOM via @vue/test-utils.
  • Stability: No safety bounds around annotation array access caused live TypeErrors when a user moved out of frame too quickly.

🛠️ Solution

The monolith was split into three new modules and a refactored orchestrator:


1. src/components/calibration/CalibrationCanvas.vue (New Component)

Encapsulates all <canvas> drawing logic.

  • Exposes drawPoint(x, y, options) and triggerAnimation() as a clean component API.
  • Receives target coordinates and styling exclusively via props — zero direct DOM access from the parent.
  • Rendering loop is fully self-contained; the parent never touches a CanvasRenderingContext2D.

2. src/components/calibration/WebcamManager.vue (New Component)

An invisible service component managing the video stream lifecycle.

  • Owns the <video autoplay playsinline> element internally.
  • Safely acquires and releases navigator.mediaDevices tracks on beforeUnmount, preventing the camera indicator light from hanging after navigation.
  • Manages MediaRecorder state and emits @stream-ready / @stream-error events for the parent to react to declaratively.

3. src/services/ml/FaceDetector.js (New Service Class)

A plain JavaScript class abstracting the @tensorflow-models/face-landmarks-detection dependency.

  • Wraps the estimateFaces prediction loop.
  • Implements #processPrediction() (private method) with explicit safety bounds guarding against empty annotation arrays — the root cause of previous TypeError crashes.
  • Cleanly extracts iris positions and blink heuristics, returning a typed result object.
  • Fully testable in Node.js with no Vue or DOM dependency.

4. DoubleCalibrationRecord.vueOrchestrator (Refactored)

Stripped of all raw DOM manipulation, Canvas operations, and ML calls.

  • Coordinates CalibrationCanvas, WebcamManager, and FaceDetector via Vue refs and events.
  • Retains all Vuex state management and Vuetify Stepper navigation logic.
  • Cognitive complexity reduced from 1,262 → ~890 lines (~30% reduction).

📐 Architecture: Before vs. After

BEFORE                              AFTER
──────────────────────────────      ──────────────────────────────────────────────
DoubleCalibrationRecord.vue         DoubleCalibrationRecord.vue  (Orchestrator)
  ├── Vuex / Stepper UI               ├── Vuex / Stepper UI
  ├── navigator.mediaDevices          ├── <WebcamManager />  →  emits @stream-ready
  ├── ctx.fillRect / ctx.arc          ├── <CalibrationCanvas /> →  drawPoint() via ref
  ├── model.estimateFaces             └── FaceDetector.js   →  await detector.detect()
  └── blink heuristics

📈 Metrics

Metric Before After
DoubleCalibrationRecord.vue line count 1,262 ~890
ML logic unit-testable without DOM
Camera track release on unmount
Out-of-frame TypeError crashes Unhandled Gracefully handled
Separation of concerns (SRP)

✅ Testing

  • Manual E2E: Calibration stepper flow is visually and functionally identical to pre-refactor behaviour.
  • FaceDetector.js: Unit tests can now be run in isolation — see tests/unit/services/ml/FaceDetector.spec.js.
  • Camera stream is correctly released (camera light turns off) after leaving the calibration view.
  • Out-of-frame edge case: user moves face away during capture — no TypeError thrown.

🔍 Files Changed

File Change
src/views/DoubleCalibrationRecord.vue Refactored — orchestrator only
src/components/calibration/CalibrationCanvas.vue New
src/components/calibration/WebcamManager.vue New
src/services/ml/FaceDetector.js New
tests/unit/services/ml/FaceDetector.spec.js New (recommended)

💬 Notes for Reviewer

  • The public-facing UX is unchanged — this is a pure internal refactor.
  • FaceDetector.js uses a private class field (#processPrediction) requiring Node ≥ 12 / modern bundler. Please flag if the project targets an environment where this is a concern.
  • The WebcamManager component is designed to be reused by future recording views — it is not tightly coupled to the calibration flow.

Part of the GSoC Codebase Improvement Initiative. Happy to discuss design decisions in review.

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