Image Classification
timm
ONNX
Safetensors
mobile-screenshots
phone-screenshots
screenshot-analysis
content-safety
Instructions to use yapwithai/phone-screen-classifier with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- timm
How to use yapwithai/phone-screen-classifier with timm:
import timm model = timm.create_model("hf_hub:yapwithai/phone-screen-classifier", pretrained=True) - Notebooks
- Google Colab
- Kaggle
Upload phone screen classifier
Browse files- README.md +11 -2
- inference/__pycache__/python.cpython-312.pyc +0 -0
- inference/python.py +29 -7
- inference/typescript.ts +29 -7
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
|
| 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(
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
|
| 62 |
-
def classify_batch(
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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(
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|