dr-stone commited on
Commit
ecf5ff4
·
verified ·
1 Parent(s): 7b864ca

Upload phone screen classifier

Browse files
README.md CHANGED
@@ -110,6 +110,7 @@ The exported model accepts dynamic batch, height, and width.
110
  The helpers intentionally return only `screen` and `safety`.
111
 
112
  Keep `onnx/model.onnx.data` beside `onnx/model.onnx`; ONNX Runtime loads the external tensor data when it opens the graph.
 
113
 
114
  ### Download
115
 
@@ -135,15 +136,19 @@ Import the helper from the exported model folder:
135
  from inference.python import Classifier, classify
136
 
137
  print(classify("example.png"))
 
138
 
139
  classifier = Classifier(model_dir)
140
  print(classifier.classify_batch(["one.png", "two.png"]))
 
 
 
141
  ```
142
 
143
  Or run it directly:
144
 
145
  ```bash
146
- python inference/python.py example.png another.png
147
  ```
148
 
149
  ### TypeScript
@@ -160,15 +165,19 @@ Import the helper from the exported model folder:
160
  import { Classifier, classify } from './inference/typescript.ts';
161
 
162
  console.log(await classify('example.png'));
 
163
 
164
  const classifier = await Classifier.create(modelDir);
165
  console.log(await classifier.classifyBatch(['one.png', 'two.png']));
 
 
 
166
  ```
167
 
168
  Or run it directly:
169
 
170
  ```bash
171
- bun inference/typescript.ts example.png another.png
172
  ```
173
 
174
  ## Training Data
 
110
  The helpers intentionally return only `screen` and `safety`.
111
 
112
  Keep `onnx/model.onnx.data` beside `onnx/model.onnx`; ONNX Runtime loads the external tensor data when it opens the graph.
113
+ By default the helpers load the FP32 model at `onnx/model.onnx`. Pass `fp16` to load `onnx/model.fp16.onnx`.
114
 
115
  ### Download
116
 
 
136
  from inference.python import Classifier, classify
137
 
138
  print(classify("example.png"))
139
+ print(classify("example.png", model_format="fp16"))
140
 
141
  classifier = Classifier(model_dir)
142
  print(classifier.classify_batch(["one.png", "two.png"]))
143
+
144
+ fp16_classifier = Classifier(model_dir, model_format="fp16")
145
+ print(fp16_classifier.classify_batch(["one.png", "two.png"]))
146
  ```
147
 
148
  Or run it directly:
149
 
150
  ```bash
151
+ python inference/python.py example.png another.png --model-format fp16
152
  ```
153
 
154
  ### TypeScript
 
165
  import { Classifier, classify } from './inference/typescript.ts';
166
 
167
  console.log(await classify('example.png'));
168
+ console.log(await classify('example.png', modelDir, 'fp16'));
169
 
170
  const classifier = await Classifier.create(modelDir);
171
  console.log(await classifier.classifyBatch(['one.png', 'two.png']));
172
+
173
+ const fp16Classifier = await Classifier.create(modelDir, 'fp16');
174
+ console.log(await fp16Classifier.classifyBatch(['one.png', 'two.png']));
175
  ```
176
 
177
  Or run it directly:
178
 
179
  ```bash
180
+ bun inference/typescript.ts example.png another.png --model-format fp16
181
  ```
182
 
183
  ## Training Data
inference/__pycache__/python.cpython-312.pyc ADDED
Binary file (10.9 kB). View file
 
inference/python.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
 
3
  import json
4
  from pathlib import Path
5
- from typing import Sequence, TypedDict, cast
6
 
7
  import numpy as np
8
  import onnxruntime as ort
@@ -11,6 +11,11 @@ from PIL import Image, ImageOps
11
 
12
  MODEL_DIR = Path(__file__).resolve().parent.parent
13
  PADDING_MULTIPLE = 32
 
 
 
 
 
14
 
15
 
16
  class Preprocess(TypedDict):
@@ -38,9 +43,10 @@ class Classifier:
38
  self,
39
  model_dir: str | Path = MODEL_DIR,
40
  providers: Sequence[str] = ("CPUExecutionProvider",),
 
41
  ) -> None:
42
  self.directory = Path(model_dir)
43
- self.session = ort.InferenceSession(str(self.directory / "onnx" / "model.onnx"), providers=list(providers))
44
  self.preprocess = load_preprocess(self.directory / "preprocess.json")
45
  self.labels = load_labels(self.directory / "inference" / "labels.json")
46
 
@@ -55,12 +61,27 @@ class Classifier:
55
  return decode_predictions(screen_logits, safety_logits, self.labels)
56
 
57
 
58
- def classify(image_path: str | Path, model_dir: str | Path = MODEL_DIR) -> Prediction:
59
- return Classifier(model_dir).classify(image_path)
 
 
 
 
60
 
61
 
62
- def classify_batch(image_paths: Sequence[str | Path], model_dir: str | Path = MODEL_DIR) -> list[Prediction]:
63
- return Classifier(model_dir).classify_batch(image_paths)
 
 
 
 
 
 
 
 
 
 
 
64
 
65
 
66
  def preprocess_image(image_path: Path, preprocess: Preprocess) -> np.ndarray:
@@ -138,7 +159,8 @@ if __name__ == "__main__":
138
  parser = argparse.ArgumentParser(description="Classify images with the exported ONNX model.")
139
  parser.add_argument("images", nargs="+")
140
  parser.add_argument("--model-dir", default=str(MODEL_DIR))
 
141
  args = parser.parse_args()
142
- predictions = classify_batch(args.images, args.model_dir)
143
  value: Prediction | list[Prediction] = predictions[0] if len(predictions) == 1 else predictions
144
  print(json.dumps(value, indent=2))
 
2
 
3
  import json
4
  from pathlib import Path
5
+ from typing import Literal, Sequence, TypedDict, cast
6
 
7
  import numpy as np
8
  import onnxruntime as ort
 
11
 
12
  MODEL_DIR = Path(__file__).resolve().parent.parent
13
  PADDING_MULTIPLE = 32
14
+ ModelFormat = Literal["fp32", "fp16"]
15
+ MODEL_FILENAMES: dict[ModelFormat, str] = {
16
+ "fp32": "model.onnx",
17
+ "fp16": "model.fp16.onnx",
18
+ }
19
 
20
 
21
  class Preprocess(TypedDict):
 
43
  self,
44
  model_dir: str | Path = MODEL_DIR,
45
  providers: Sequence[str] = ("CPUExecutionProvider",),
46
+ model_format: ModelFormat = "fp32",
47
  ) -> None:
48
  self.directory = Path(model_dir)
49
+ self.session = ort.InferenceSession(str(model_path(self.directory, model_format)), providers=list(providers))
50
  self.preprocess = load_preprocess(self.directory / "preprocess.json")
51
  self.labels = load_labels(self.directory / "inference" / "labels.json")
52
 
 
61
  return decode_predictions(screen_logits, safety_logits, self.labels)
62
 
63
 
64
+ def classify(
65
+ image_path: str | Path,
66
+ model_dir: str | Path = MODEL_DIR,
67
+ model_format: ModelFormat = "fp32",
68
+ ) -> Prediction:
69
+ return Classifier(model_dir, model_format=model_format).classify(image_path)
70
 
71
 
72
+ def classify_batch(
73
+ image_paths: Sequence[str | Path],
74
+ model_dir: str | Path = MODEL_DIR,
75
+ model_format: ModelFormat = "fp32",
76
+ ) -> list[Prediction]:
77
+ return Classifier(model_dir, model_format=model_format).classify_batch(image_paths)
78
+
79
+
80
+ def model_path(directory: Path, model_format: ModelFormat) -> Path:
81
+ path = directory / "onnx" / MODEL_FILENAMES[model_format]
82
+ if not path.is_file():
83
+ raise FileNotFoundError(f"Missing {model_format} ONNX model: {path}")
84
+ return path
85
 
86
 
87
  def preprocess_image(image_path: Path, preprocess: Preprocess) -> np.ndarray:
 
159
  parser = argparse.ArgumentParser(description="Classify images with the exported ONNX model.")
160
  parser.add_argument("images", nargs="+")
161
  parser.add_argument("--model-dir", default=str(MODEL_DIR))
162
+ parser.add_argument("--model-format", choices=tuple(MODEL_FILENAMES), default="fp32")
163
  args = parser.parse_args()
164
+ predictions = classify_batch(args.images, args.model_dir, cast(ModelFormat, args.model_format))
165
  value: Prediction | list[Prediction] = predictions[0] if len(predictions) == 1 else predictions
166
  print(json.dumps(value, indent=2))
inference/typescript.ts CHANGED
@@ -6,6 +6,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
6
 
7
  const MODEL_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..');
8
  const PADDING_MULTIPLE = 32;
 
 
 
 
 
9
 
10
  export class Classifier {
11
  private constructor(
@@ -27,8 +32,8 @@ export class Classifier {
27
  * @param modelDir Directory containing the exported model artifacts.
28
  * @returns Ready-to-use classifier instance.
29
  */
30
- static async create(modelDir = MODEL_DIR): Promise<Classifier> {
31
- const session = await ort.InferenceSession.create(resolve(modelDir, 'onnx/model.onnx'), {
32
  executionProviders: ['cpu'],
33
  });
34
  const preprocess = JSON.parse(await readFile(resolve(modelDir, 'preprocess.json'), 'utf8')) as {
@@ -80,8 +85,12 @@ export class Classifier {
80
  * @param modelDir Directory containing the exported model artifacts.
81
  * @returns Predicted public screen and safety labels.
82
  */
83
- export async function classify(imagePath: string, modelDir = MODEL_DIR): Promise<{ screen: string; safety: string }> {
84
- const classifier = await Classifier.create(modelDir);
 
 
 
 
85
  if (imagePath.length === 0) {
86
  throw new Error('Image path must not be empty.');
87
  }
@@ -98,8 +107,9 @@ export async function classify(imagePath: string, modelDir = MODEL_DIR): Promise
98
  export async function classifyBatch(
99
  imagePaths: readonly string[],
100
  modelDir = MODEL_DIR,
 
101
  ): Promise<Array<{ screen: string; safety: string }>> {
102
- const classifier = await Classifier.create(modelDir);
103
  if (imagePaths.length === 0) {
104
  return [];
105
  }
@@ -275,9 +285,21 @@ if (script !== undefined && import.meta.url === pathToFileURL(resolve(script)).h
275
  if (modelFlag >= 0) {
276
  args.splice(modelFlag, 2);
277
  }
 
 
 
 
 
278
  if (args.length === 0 || modelDir === undefined) {
279
- throw new Error('Usage: bun inference/typescript.ts <image...> [--model-dir export_dir]');
280
  }
281
- const predictions = await classifyBatch(args, modelDir);
282
  process.stdout.write(`${JSON.stringify(predictions.length === 1 ? predictions[0] : predictions, null, 2)}\n`);
283
  }
 
 
 
 
 
 
 
 
6
 
7
  const MODEL_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..');
8
  const PADDING_MULTIPLE = 32;
9
+ export type ModelFormat = 'fp32' | 'fp16';
10
+ const MODEL_FILES: Record<ModelFormat, string> = {
11
+ fp32: 'model.onnx',
12
+ fp16: 'model.fp16.onnx',
13
+ };
14
 
15
  export class Classifier {
16
  private constructor(
 
32
  * @param modelDir Directory containing the exported model artifacts.
33
  * @returns Ready-to-use classifier instance.
34
  */
35
+ static async create(modelDir = MODEL_DIR, modelFormat: ModelFormat = 'fp32'): Promise<Classifier> {
36
+ const session = await ort.InferenceSession.create(resolve(modelDir, 'onnx', MODEL_FILES[modelFormat]), {
37
  executionProviders: ['cpu'],
38
  });
39
  const preprocess = JSON.parse(await readFile(resolve(modelDir, 'preprocess.json'), 'utf8')) as {
 
85
  * @param modelDir Directory containing the exported model artifacts.
86
  * @returns Predicted public screen and safety labels.
87
  */
88
+ export async function classify(
89
+ imagePath: string,
90
+ modelDir = MODEL_DIR,
91
+ modelFormat: ModelFormat = 'fp32',
92
+ ): Promise<{ screen: string; safety: string }> {
93
+ const classifier = await Classifier.create(modelDir, modelFormat);
94
  if (imagePath.length === 0) {
95
  throw new Error('Image path must not be empty.');
96
  }
 
107
  export async function classifyBatch(
108
  imagePaths: readonly string[],
109
  modelDir = MODEL_DIR,
110
+ modelFormat: ModelFormat = 'fp32',
111
  ): Promise<Array<{ screen: string; safety: string }>> {
112
+ const classifier = await Classifier.create(modelDir, modelFormat);
113
  if (imagePaths.length === 0) {
114
  return [];
115
  }
 
285
  if (modelFlag >= 0) {
286
  args.splice(modelFlag, 2);
287
  }
288
+ const formatFlag = args.indexOf('--model-format');
289
+ const modelFormat = formatFlag >= 0 ? parseModelFormat(args[formatFlag + 1]) : 'fp32';
290
+ if (formatFlag >= 0) {
291
+ args.splice(formatFlag, 2);
292
+ }
293
  if (args.length === 0 || modelDir === undefined) {
294
+ throw new Error('Usage: bun inference/typescript.ts <image...> [--model-dir export_dir] [--model-format fp32|fp16]');
295
  }
296
+ const predictions = await classifyBatch(args, modelDir, modelFormat);
297
  process.stdout.write(`${JSON.stringify(predictions.length === 1 ? predictions[0] : predictions, null, 2)}\n`);
298
  }
299
+
300
+ function parseModelFormat(value: string | undefined): ModelFormat {
301
+ if (value === 'fp32' || value === 'fp16') {
302
+ return value;
303
+ }
304
+ throw new Error('Model format must be fp32 or fp16.');
305
+ }