Skip to content

Commit

Permalink
feat(language-detection): add mediapipe language detection
Browse files Browse the repository at this point in the history
  • Loading branch information
AmitMY committed Nov 29, 2023
1 parent 66a8bbf commit 69641a4
Show file tree
Hide file tree
Showing 16 changed files with 189 additions and 72 deletions.
10 changes: 10 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
"input": "./node_modules/@mediapipe/holistic/",
"output": "./assets/models/holistic/"
},
{
"glob": "**/*",
"input": "./node_modules/@mediapipe/tasks-text/wasm/",
"output": "./assets/models/mediapipe-language-detector/"
},
{
"glob": "*.wasm",
"input": "./node_modules/@tensorflow/tfjs-backend-wasm/dist/",
Expand Down Expand Up @@ -170,6 +175,11 @@
"input": "./node_modules/@mediapipe/holistic/",
"output": "./assets/models/holistic/"
},
{
"glob": "**/*",
"input": "./node_modules/@mediapipe/tasks-text/wasm/",
"output": "./assets/models/mediapipe-language-detector/"
},
{
"glob": "*.wasm",
"input": "./node_modules/@tensorflow/tfjs-backend-wasm/dist/",
Expand Down
6 changes: 3 additions & 3 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@
"firebase-functions": "4.5.0",
"http-errors": "2.0.0",
"node-fetch": "2.6.7",
"openai": "4.20.0"
"openai": "4.20.1"
},
"devDependencies": {
"@firebase/firestore-types": "3.0.0",
"@firebase/rules-unit-testing": "3.0.1",
"@types/http-errors": "2.0.4",
"@types/jest": "29.5.10",
"@types/node-fetch": "2.6.9",
"@typescript-eslint/eslint-plugin": "6.12.0",
"@typescript-eslint/parser": "6.12.0",
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"eslint": "8.54.0",
"firebase-functions-test": "3.1.0",
"firebase-tools": "12.9.1",
Expand Down
26 changes: 13 additions & 13 deletions ios/App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ PODS:
- CapacitorCordova (5.5.1)
- CapacitorFilesystem (5.1.4):
- Capacitor
- CapacitorFirebaseAnalytics (5.2.0):
- CapacitorFirebaseAnalytics (5.3.0):
- Capacitor
- CapacitorFirebaseAnalytics/Lite (= 5.2.0)
- CapacitorFirebaseAnalytics/Analytics (5.2.0):
- CapacitorFirebaseAnalytics/Lite (= 5.3.0)
- CapacitorFirebaseAnalytics/Analytics (5.3.0):
- Capacitor
- FirebaseAnalytics (= 10.8.0)
- CapacitorFirebaseAnalytics/Lite (5.2.0):
- CapacitorFirebaseAnalytics/Lite (5.3.0):
- Capacitor
- CapacitorFirebaseApp (5.2.0):
- CapacitorFirebaseApp (5.3.0):
- Capacitor
- FirebaseCore (= 10.8.0)
- CapacitorFirebaseAppCheck (5.2.0):
- CapacitorFirebaseAppCheck (5.3.0):
- Capacitor
- FirebaseAppCheck (= 10.8.0)
- CapacitorFirebaseCrashlytics (5.2.0):
- CapacitorFirebaseCrashlytics (5.3.0):
- Capacitor
- FirebaseCrashlytics (= 10.8.0)
- CapacitorFirebasePerformance (5.2.0):
- CapacitorFirebasePerformance (5.3.0):
- Capacitor
- FirebasePerformance (= 10.8.0)
- CapacitorKeyboard (5.0.6):
Expand Down Expand Up @@ -225,11 +225,11 @@ SPEC CHECKSUMS:
CapacitorBlobWriter: 110eeaf80611f19bf01a8a05ff3672149ed0baad
CapacitorCordova: e128cc7688c070ca0bfa439898a5f609da8dbcfe
CapacitorFilesystem: af704badfbc69f6f8623d9ed313e5490e3723dcb
CapacitorFirebaseAnalytics: 3755413bdeea4cf9169c4063ff621a1ab22ee9ce
CapacitorFirebaseApp: fbdd325f8d67b7be7950b9b76d2aa3d0e1aede9d
CapacitorFirebaseAppCheck: eba071266612be9f851d6caffed4aaa24ada4134
CapacitorFirebaseCrashlytics: 9cdcc0bc73167468586794acc5f1c7ace34ef81a
CapacitorFirebasePerformance: d8f715abf7317b8798a6280990d2cb0fcc431d9c
CapacitorFirebaseAnalytics: 3608521b33801562622c05ce29f8a1d7e34ec68a
CapacitorFirebaseApp: ca0583791fafaae11607c8a643eb1ad4398c59d7
CapacitorFirebaseAppCheck: cdab78d08aa0a85d4748e405739d168514e7c7af
CapacitorFirebaseCrashlytics: b3e1c8491f2ac10affbbdb7fe0edde4424b35b9a
CapacitorFirebasePerformance: ad18ad5633f37047a3f49ec0b9cdea99ffd990d0
CapacitorKeyboard: b978154b024a5f65e044908e37d15b7de58b9d12
CapacitorShare: cd41743331cb71d217c029de54b681cbd91e0fcc
CapacitorSplashScreen: 5fa2ab5e46cf5cc530cf16a51c80c7a986579ccd
Expand Down
21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@
"@angular/service-worker": "17.0.4",
"@angular/ssr": "17.0.3",
"@asymmetrik/ngx-leaflet": "17.0.0",
"@capacitor-firebase/analytics": "5.2.0",
"@capacitor-firebase/app": "5.2.0",
"@capacitor-firebase/app-check": "5.2.0",
"@capacitor-firebase/crashlytics": "5.2.0",
"@capacitor-firebase/performance": "5.2.0",
"@capacitor-firebase/analytics": "5.3.0",
"@capacitor-firebase/app": "5.3.0",
"@capacitor-firebase/app-check": "5.3.0",
"@capacitor-firebase/crashlytics": "5.3.0",
"@capacitor-firebase/performance": "5.3.0",
"@capacitor/android": "5.5.1",
"@capacitor/core": "5.5.1",
"@capacitor/filesystem": "5.1.4",
Expand All @@ -64,6 +64,7 @@
"@ionic/angular": "7.5.6",
"@mediapipe/drawing_utils": "0.3.1675466124",
"@mediapipe/holistic": "0.5.1675471629",
"@mediapipe/tasks-text": "^0.10.8",
"@ngneat/transloco": "6.0.0",
"@ngxs/store": "3.8.1",
"@sign-mt/browsermt": "0.2.3",
Expand All @@ -82,7 +83,7 @@
"cld3-asm": "3.1.1",
"comlink": "4.4.1",
"filesize": "9.0.11",
"firebase": "10.6.0",
"firebase": "10.7.0",
"flag-icons": "7.0.2",
"ionicons": "7.2.1",
"leaflet": "1.9.4",
Expand Down Expand Up @@ -110,7 +111,7 @@
"@capacitor/assets": "3.0.1",
"@capacitor/cli": "5.5.1",
"@ionic/angular-server": "7.5.6",
"@playwright/test": "1.40.0",
"@playwright/test": "1.40.1",
"@sign-mt/configuration": "git://github.com/sign/.github.git",
"@trapezedev/project": "7.0.10",
"@types/dom-mediacapture-transform": "0.1.9",
Expand All @@ -123,8 +124,8 @@
"@types/web-app-manifest": "1.0.7",
"@types/webgl2": "0.0.10",
"@types/wicg-file-system-access": "2023.10.4",
"@typescript-eslint/eslint-plugin": "6.12.0",
"@typescript-eslint/parser": "6.12.0",
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"deepmerge": "4.3.1",
"dotenv": "16.3.1",
"eslint": "8.54.0",
Expand All @@ -149,7 +150,7 @@
"sitemap": "7.1.1",
"tiny-async-pool": "2.1.0",
"ts-node": "10.9.1",
"tsx": "4.5.0",
"tsx": "4.6.0",
"typescript": "5.2.2",
"webpack-bundle-analyzer": "4.10.1",
"zod": "3.22.4"
Expand Down
5 changes: 1 addition & 4 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,17 @@ import {ServiceWorkerModule} from '@angular/service-worker';
import {environment} from '../environments/environment';
import {RouteReuseStrategy} from '@angular/router';
import {IonicModule, IonicRouteStrategy} from '@ionic/angular';
import {isSafari} from './core/constants';
import {AppTranslocoModule} from './core/modules/transloco/transloco.module';
import {AppNgxsModule} from './core/modules/ngxs/ngxs.module';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

const mode = isSafari ? 'ios' : 'md';

@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule.withServerTransition({appId: 'serverApp'}),
BrowserAnimationsModule,
AppNgxsModule,
IonicModule.forRoot({mode}),
IonicModule.forRoot(),
AppRoutingModule,
AppTranslocoModule,

Expand Down
42 changes: 42 additions & 0 deletions src/app/modules/translate/language-detection/cld3.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {Injectable} from '@angular/core';
import {LanguageIdentifier} from 'cld3-asm';
import {GoogleAnalyticsService} from '../../../core/modules/google-analytics/google-analytics.service';
import {TranslationService} from '../translate.service';
import {LanguageDetectionService} from './language-detection.service';

@Injectable({
providedIn: 'root',
})
export class CLD3LanguageDetectionService extends LanguageDetectionService {
private cld: LanguageIdentifier;

constructor(private ga: GoogleAnalyticsService, translationService: TranslationService) {
super(translationService);
}

async init(): Promise<void> {
if (this.cld) {
return;
}
const cld3 = await this.ga.trace(
'language-detector',
'import',
() => import(/* webpackChunkName: "cld3-asm" */ 'cld3-asm')
);
const cldFactory = await this.ga.trace('language-detector', 'load', () => cld3.loadModule());
this.cld = await this.ga.trace('language-detector', 'create', () => cldFactory.create(1, 500));
}

async detectSpokenLanguage(text: string): Promise<string> {
if (!this.cld) {
return this.languageCode(null);
}

const language = await this.ga.trace('language-detector', 'find', () => this.cld.findLanguage(text));
if (language.is_reliable) {
return this.languageCode(language.language);
}

return this.languageCode(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {Injectable} from '@angular/core';
import {TranslationService} from '../translate.service';

const OBSOLETE_LANGUAGE_CODES = {
iw: 'he',
};
const DEFAULT_SPOKEN_LANGUAGE = 'en';

@Injectable({
providedIn: 'root',
})
export abstract class LanguageDetectionService {
constructor(private translationService: TranslationService) {}

abstract init(): Promise<void>;

abstract detectSpokenLanguage(text: string): Promise<string>;

protected languageCode(language: string): string {
const correctedCode = OBSOLETE_LANGUAGE_CODES[language] ?? language;
return this.translationService.spokenLanguages.includes(correctedCode) ? correctedCode : DEFAULT_SPOKEN_LANGUAGE;
}
}
51 changes: 51 additions & 0 deletions src/app/modules/translate/language-detection/mediapipe.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {Injectable} from '@angular/core';
import {GoogleAnalyticsService} from '../../../core/modules/google-analytics/google-analytics.service';
import {TranslationService} from '../translate.service';
import {LanguageDetectionService} from './language-detection.service';
import type {LanguageDetector} from '@mediapipe/tasks-text';

@Injectable({
providedIn: 'root',
})
export class MediaPipeLanguageDetectionService extends LanguageDetectionService {
private detector: LanguageDetector;

constructor(private ga: GoogleAnalyticsService, translationService: TranslationService) {
super(translationService);
}

async init(): Promise<void> {
if (this.detector) {
return;
}

const textTasks = await this.ga.trace(
'language-detector',
'import',
() => import(/* webpackChunkName: "@mediapipe/tasks-text" */ '@mediapipe/tasks-text')
);

this.detector = await this.ga.trace('language-detector', 'create', async () => {
const wasmFiles = await textTasks.FilesetResolver.forTextTasks('assets/models/mediapipe-language-detector/');
return await textTasks.LanguageDetector.createFromModelPath(
wasmFiles,
'assets/models/mediapipe-language-detector/model.tflite'
);
});
}

async detectSpokenLanguage(text: string): Promise<string> {
if (!this.detector) {
return this.languageCode(null);
}

const {languages} = await this.ga.trace('language-detector', 'detect', () => this.detector.detect(text));

if (languages.length === 0) {
// This usually happens when the text is too short.
return this.languageCode(null);
}

return this.languageCode(languages[0].languageCode);
}
}
10 changes: 9 additions & 1 deletion src/app/modules/translate/translate.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ import {NgModule} from '@angular/core';
import {NgxsModule} from '@ngxs/store';
import {TranslateState} from './translate.state';
import {TranslationService} from './translate.service';
import {SignWritingTranslationService} from './signwriting-translation.service';
import {LanguageDetectionService} from './language-detection/language-detection.service';
import {CLD3LanguageDetectionService} from './language-detection/cld3.service';
import {MediaPipeLanguageDetectionService} from './language-detection/mediapipe.service';

@NgModule({
providers: [TranslationService],
providers: [
TranslationService,
SignWritingTranslationService,
{provide: LanguageDetectionService, useClass: MediaPipeLanguageDetectionService},
],
imports: [NgxsModule.forFeature([TranslateState])],
})
export class TranslateModule {}
31 changes: 1 addition & 30 deletions src/app/modules/translate/translate.service.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import {Injectable} from '@angular/core';
import {LanguageIdentifier} from 'cld3-asm';
import {GoogleAnalyticsService} from '../../core/modules/google-analytics/google-analytics.service';
import {from, Observable, switchMap} from 'rxjs';
import {map} from 'rxjs/operators';
import {HttpClient} from '@angular/common/http';
import {AppCheck} from '../../core/helpers/app-check/app-check';

const OBSOLETE_LANGUAGE_CODES = {
iw: 'he',
};
const DEFAULT_SPOKEN_LANGUAGE = 'en';

@Injectable({
providedIn: 'root',
})
export class TranslationService {
private cld: LanguageIdentifier;

signedLanguages = [
'ase',
'gsg',
Expand Down Expand Up @@ -174,27 +165,7 @@ export class TranslationService {
'zu',
];

constructor(private ga: GoogleAnalyticsService, private http: HttpClient) {}

async initCld(): Promise<void> {
if (this.cld) {
return;
}
const cld3 = await this.ga.trace('cld', 'import', () => import(/* webpackChunkName: "cld3-asm" */ 'cld3-asm'));
const cldFactory = await this.ga.trace('cld', 'load', () => cld3.loadModule());
this.cld = await this.ga.trace('cld', 'create', () => cldFactory.create(1, 500));
}

async detectSpokenLanguage(text: string): Promise<string> {
if (!this.cld) {
return DEFAULT_SPOKEN_LANGUAGE;
}

const language = await this.ga.trace('cld', 'find', () => this.cld.findLanguage(text));
const languageCode = language.is_reliable ? language.language : DEFAULT_SPOKEN_LANGUAGE;
const correctedCode = OBSOLETE_LANGUAGE_CODES[languageCode] ?? languageCode;
return this.spokenLanguages.includes(correctedCode) ? correctedCode : DEFAULT_SPOKEN_LANGUAGE;
}
constructor(private http: HttpClient) {}

normalizeSpokenLanguageText(language: string, text: string): Observable<string> {
const params = new URLSearchParams();
Expand Down
Loading

0 comments on commit 69641a4

Please sign in to comment.