brownhci / WebGazer

WebGazer.js: Scalable Webcam EyeTracking Using User Interactions
https://webgazer.cs.brown.edu
Other
3.54k stars 536 forks source link

Reference to global webgazer object in utils.mjs #304

Open fredcallaway opened 1 year ago

fredcallaway commented 1 year ago

Copy of #296 (seems to have been closed without a fix).

I'm using ES6 modules (import webgazer).

Uncaught (in promise) ReferenceError: webgazer is not defined
    at util.getEyeFeats (util.mjs:42:5)
    at util_regression.addData (util_regression.mjs:221:42)
    at util_regression.setData (util_regression.mjs:184:10)
    at loadGlobalData (index.mjs:436:15)
jeffhuang commented 1 year ago

I'm traveling and not able to test this right now, but what about importing the default module

import webgazer from 'webgazer'

fredcallaway commented 1 year ago

Yup, adding that line to utils.mjs fixes it!

jeffhuang commented 1 year ago

Great, if you could submit a PR, I'll take a look when I'm back home

fredcallaway commented 1 year ago

Given the simplicity of the fix, I think it makes more sense for an existing contributor (with a fork) to make this change.

jeffhuang commented 1 year ago

@Skylion007 the import webgazer from 'webgazer' line breaks the webpack build npm run build because well, it doesn't exist when it's building. Any idea of the usual workaround, so that it can also be imported?

Bmastaz commented 1 year ago

any update ?

jeffhuang commented 1 year ago

@Bmastaz the fix doesn't work (see my message above), so there isn't a good solution right now. If anyone proposes a solution, we can consider it, but right now this is still an open issue.

dev-vinicius-andrade commented 7 months ago

I know that it's not ideal, currently I'm trying to implement it in a angular app with Typescript.

I apologize in advance if I can't be clear enougth explaining.

So, I've created a composable which is basically a wrapper to override the getEyeFeats method inside util.

import { WebGazer, WebGazerParams } from 'webgazer';

export async function useWebgazer(
  params: Partial<WebGazerParams> = {}
): Promise<WebGazer> {
  const webgazer = (await import('webgazer')).default;
  const resizeWidth = 10;
  const resizeHeight = 6;
  webgazer.util.getEyeFeats = (eyes: any) => {
    const process = (eye: any) => {
      const resized = webgazer.util.resizeEye(eye, resizeWidth, resizeHeight);
      if (!resized) return;
      const gray = webgazer.util.grayscale(
        resized.data,
        resized.width,
        resized.height
      );
      let hist: any = [];
      webgazer.util.equalizeHistogram(gray, 5, hist);
      return hist;
    };

    if (webgazer.params.trackEye == 'left') {
      return process(eyes.left);
    } else if (webgazer.params.trackEye == 'right') {
      return process(eyes.right);
    } else {
      return [].concat(process(eyes.left), process(eyes.right));
    }
  };
  webgazer.params = {
    ...webgazer.params,
    ...params,
  };
  return webgazer;
}

To make the intelisense work better I've also created a webgazer.d.ts and added to tsconfig.app.json imported types

declare module 'webgazer' {
  export interface GazeData {
    x: number;
    y: number;
  }
  export interface WebGazerParams {
    applyKalmanFilter: boolean;
    camConstraints: WebGazerParamsCamConstraints;
    dataTimestep: number;
    faceFeedbackBoxId: string;
    faceFeedbackBoxRatio: number;
    faceOverlayId: string;
    gazeDotId: string;
    getEventTypes: () => string[];
    mirrorVideo: boolean;
    moveTickSize: number;
    saveDataAcrossSessions: boolean;
    showFaceFeedbackBox: boolean;
    showFaceOverlay: boolean;
    showGazeDot: boolean;
    showVideo: boolean;
    showVideoPreview: boolean;
    storingPoints: number;
    trackEye: 'left' | 'right' | 'both';
    videoContainerId: string;
    videoElementCanvasId: string;
    videoElementId: string;
    videoViewerHeight: number;
    videoViewerWidth: number;
  }
  export interface WebGazerParamsCamConstraints {
    video: WebGazerParamsCamConstraintsVideo;
  }
  export interface WebGazerParamsCamConstraintsVideo {
    width: WebGazerParamsCamConstraintsVideoSize;
    height: WebGazerParamsCamConstraintsVideoSize;
  }
  export interface WebGazerParamsCamConstraintsVideoSize {
    min: number;
    ideal: number;
    max: number;
  }
  export interface WebGazerUtil extends any {
    getEyeFeats: (eye: any) => any;
    resizeEye: (
      eye: any,
      resizeWidth: number,
      resizeHeight: number
    ) => ImageData;
    grayscale: (data: Uint8ClampedArray, width: number, height: number) => any;
    equalizeHistogram: (src: any, step: any, dst: any) => any;
  }
  export interface WebGazer {
    webgazer: WebGazer;
    setGazeListener(
      callback: (data: GazeData | null, elapsedTime: number) => void
    ): WebGazer;
    begin(): Promise<WebGazer>;
    pause(): WebGazer;
    resume(): WebGazer;
    resetCalibration(): WebGazer;
    showPredictionPoints(show: boolean): WebGazer;
    showVideo(show: boolean): WebGazer;
    showFaceOverlay(show: boolean): WebGazer;
    isReady(): boolean;
    params: WebGazerParams;
    util: WebGazerUtil;
  }

  const webgazer: WebGazer;
  export default webgazer;
  export = webgazer;
}

And here is how I start it in my component page

  async startWebGazer() {
   // don't forget to import your composable
    const webgazer = await useWebgazer();
    await webgazer
      .setGazeListener((data, elapsedTime) => {
        if (!data) return;
        this.eyePosition = {
          x: data.x,
          y: data.y,
        };
      })
      .begin();
  }

This way at least everything that I need for now is working, and if I need to change anything else I can just create an override to the desired method and use the current imported webgazer instance.