smartpool-examples 0.1.6__tar.gz → 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {smartpool_examples-0.1.6/smartpool_examples.egg-info → smartpool_examples-0.1.7}/PKG-INFO +2 -2
  2. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/pyproject.toml +2 -2
  3. smartpool_examples-0.1.7/smartpool_examples/__init__.py +1 -0
  4. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/count_prime/__main__.py +24 -4
  5. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/__main__.py +1 -1
  6. smartpool_examples-0.1.7/smartpool_examples/onnx_infer/__main__.py +158 -0
  7. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/onnx_infer/config.py +2 -6
  8. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/onnx_infer/data_utils.py +60 -22
  9. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/onnx_infer/inference.py +133 -130
  10. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7/smartpool_examples.egg-info}/PKG-INFO +2 -2
  11. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples.egg-info/requires.txt +1 -1
  12. smartpool_examples-0.1.6/smartpool_examples/onnx_infer/__init__.py +0 -0
  13. smartpool_examples-0.1.6/smartpool_examples/onnx_infer/__main__.py +0 -88
  14. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/LICENSE +0 -0
  15. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/README.md +0 -0
  16. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/setup.cfg +0 -0
  17. {smartpool_examples-0.1.6/smartpool_examples → smartpool_examples-0.1.7/smartpool_examples/count_prime}/__init__.py +0 -0
  18. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/count_prime/count_prime.py +0 -0
  19. {smartpool_examples-0.1.6/smartpool_examples/count_prime → smartpool_examples-0.1.7/smartpool_examples/cross_validation}/__init__.py +0 -0
  20. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/config.py +0 -0
  21. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/data_utils.py +0 -0
  22. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/model_utils.py +0 -0
  23. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/LeNet5.py +0 -0
  24. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/MLP.py +0 -0
  25. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/ModernCNN.py +0 -0
  26. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/ResNeXt.py +0 -0
  27. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/ResNeXtV2.py +0 -0
  28. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/ResNet.py +0 -0
  29. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/ResNetV2.py +0 -0
  30. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/models/__init__.py +0 -0
  31. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples/cross_validation/visualization.py +0 -0
  32. {smartpool_examples-0.1.6/smartpool_examples/cross_validation → smartpool_examples-0.1.7/smartpool_examples/onnx_infer}/__init__.py +0 -0
  33. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples.egg-info/SOURCES.txt +0 -0
  34. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples.egg-info/dependency_links.txt +0 -0
  35. {smartpool_examples-0.1.6 → smartpool_examples-0.1.7}/smartpool_examples.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smartpool-examples
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Examples for smartpool.
5
5
  Author-email: "王炳辉 (Bing-Hui WANG)" <binghui.wang@foxmail.com>
6
6
  License: MIT
@@ -11,7 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: pysmartpool>=0.1.6
14
+ Requires-Dist: pysmartpool>=0.1.7
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: scikit-learn
17
17
  Requires-Dist: numpy
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "smartpool-examples"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "Examples for smartpool."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -17,7 +17,7 @@ classifiers = [
17
17
  "Operating System :: OS Independent",
18
18
  ]
19
19
  dependencies = [
20
- "pysmartpool>=0.1.6",
20
+ "pysmartpool>=0.1.7",
21
21
  "matplotlib",
22
22
  "scikit-learn",
23
23
  "numpy",
@@ -0,0 +1 @@
1
+ __version__ = "0.1.7"
@@ -8,12 +8,28 @@ if __name__ == "__main__":
8
8
  target_folder = os.path.abspath(self_folder + "/../../../smartpool").replace("\\", "/")
9
9
  sys.path.append(target_folder)
10
10
 
11
+ import typer
11
12
  from count_prime import count_prime
12
13
 
13
- from smartpool import ProcessPool
14
+ from smartpool import InterpreterPool, ProcessPool, ThreadPool
14
15
 
15
- if __name__ == "__main__":
16
- print("Use ProcessPool to count prime numbers lower than 10000.")
16
+ app = typer.Typer()
17
+
18
+ POOL_MAP = {
19
+ "ThreadPool": ThreadPool,
20
+ "ProcessPool": ProcessPool,
21
+ "InterpreterPool": InterpreterPool,
22
+ }
23
+
24
+
25
+ @app.command()
26
+ def main(pool: str = typer.Option("ProcessPool", "--pool", help="Pool type to use")):
27
+ pool_cls = POOL_MAP.get(pool)
28
+ if pool_cls is None:
29
+ print(f"Unknown pool: {pool}. Choose from {list(POOL_MAP.keys())}")
30
+ raise typer.Exit(code=1)
31
+
32
+ print(f"Use {pool} to count prime numbers lower than 10000.")
17
33
  print(f"See source code at folder {self_folder}")
18
34
 
19
35
  tasks = []
@@ -23,7 +39,7 @@ if __name__ == "__main__":
23
39
  tasks.append((start, stop))
24
40
  start = stop
25
41
 
26
- with ProcessPool() as pool:
42
+ with pool_cls() as pool:
27
43
  futures = []
28
44
  for task in tasks:
29
45
  future = pool.submit(count_prime, args=task)
@@ -31,3 +47,7 @@ if __name__ == "__main__":
31
47
 
32
48
  total_primes_count = sum(future.result() for future in futures)
33
49
  print(total_primes_count)
50
+
51
+
52
+ if __name__ == "__main__":
53
+ app()
@@ -47,8 +47,8 @@ def main(
47
47
  help="max number of workers to use, 0 to use all available cores"
48
48
  )
49
49
  ):
50
- import os
51
50
  import importlib
51
+ import os
52
52
  os.environ["RAY_ACCEL_ENV_VAR_OVERRIDE_ON_ZERO"] = "0"
53
53
 
54
54
  print(f"Use {pool} to do 5-fold cross validatation for 7 deep learning models for handwritten digit recognition task.")
@@ -0,0 +1,158 @@
1
+ if __name__ == "__main__":
2
+ import os
3
+ import sys
4
+ self_folder = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
5
+ target_folder = os.path.abspath(self_folder + "/../../../smartpool").replace("\\", "/")
6
+ sys.path.append(target_folder)
7
+
8
+ import typer
9
+
10
+ app = typer.Typer(help="Use smartpool to do YOLOv8n ONNX inference on COCO val2017 with real-time progress.")
11
+
12
+ @app.command()
13
+ def main(
14
+ thread_pool_max_workers: int = typer.Option(
15
+ 4,
16
+ "--thread_pool_max_workers",
17
+ help="max number of thread pool (for preprocess and postprocess) workers to use, 0 to use all available cores"
18
+ ),
19
+ session_pool_max_workers: int = typer.Option(
20
+ 0,
21
+ "--session_pool_max_workers",
22
+ help="max number of infer session pool workers to use, 0 to use all available cores"
23
+ ),
24
+ ):
25
+ import os
26
+
27
+ print("Use `python -m smartpool_examples.onnx_infer --help` to see all options.")
28
+ print(f"See source code at folder {os.path.dirname(os.path.abspath(__file__))}")
29
+ print()
30
+
31
+ try:
32
+ import onnxruntime
33
+ except ImportError:
34
+ print("ONNX Runtime is not installed. Follow https://onnxruntime.ai/docs/install/ instructions to install ONNX Runtime.")
35
+ exit(1)
36
+
37
+ import queue
38
+ import time
39
+ from functools import partial
40
+
41
+ from rich.console import Group
42
+ from rich.live import Live
43
+ from rich.progress import (
44
+ BarColumn,
45
+ Progress,
46
+ TextColumn,
47
+ TimeRemainingColumn,
48
+ )
49
+ from rich.text import Text
50
+
51
+ from smartpool import DataSize, GPUInfo, InferSessionPool, Resource, ThreadPool
52
+
53
+ self_folder = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
54
+ sys.path.append(self_folder)
55
+
56
+ from concurrent.futures import Future
57
+
58
+ from config import DATASET_DIR, MODEL_PATH, OUTPUT_DIR
59
+ from data_utils import download_dataset, download_model
60
+ from inference import postprocess, preprocess
61
+
62
+ download_model()
63
+ download_dataset()
64
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
65
+
66
+ image_paths = sorted(DATASET_DIR.glob("*.jpg"))
67
+ model_path_str = str(MODEL_PATH.resolve())
68
+ output_dir_str = str(OUTPUT_DIR.resolve())
69
+
70
+ thread_pool = ThreadPool(max_workers=thread_pool_max_workers)
71
+ infer_session_pool = InferSessionPool(model_path_str, max_workers=session_pool_max_workers)
72
+
73
+ cpu_res = Resource(
74
+ cpu_cores_in_python=1,
75
+ cpu_cores_out_of_python=1,
76
+ cpu_mem=81*DataSize.MB
77
+ )
78
+ gpu_res = Resource(
79
+ cpu_cores_in_python=1,
80
+ cpu_cores_out_of_python=1,
81
+ cpu_mem=200*DataSize.MB,
82
+ gpu_cores=100,
83
+ gpu_mem=200*DataSize.MB
84
+ )
85
+
86
+ result_queue = queue.Queue()
87
+
88
+ with Progress(
89
+ TextColumn("[progress.description]{task.description}"),
90
+ BarColumn(),
91
+ TextColumn("{task.completed}/{task.total}"),
92
+ TimeRemainingColumn(),
93
+ ) as progress, Live(refresh_per_second=4) as live:
94
+ n_tasks = len(image_paths)
95
+ preprocess_submit_progress_task = progress.add_task("preprocess submit", total=n_tasks)
96
+ preprocess_done_progress_task = progress.add_task("preprocess done", total=n_tasks)
97
+ infer_submit_progress_task = progress.add_task("infer submit", total=n_tasks)
98
+ infer_done_progress_task = progress.add_task("infer done", total=n_tasks)
99
+ postprocess_submit_progress_task = progress.add_task("postprocess submit", total=n_tasks)
100
+ postprocess_done_progress_task = progress.add_task("postprocess done", total=n_tasks)
101
+
102
+ def update_task_count():
103
+ lines = []
104
+ for provider in GPUInfo.supported_onnx_providers():
105
+ lines.append(Text(f"task count infer with {provider}: {infer_session_pool.task_count_with_provider(provider)}"))
106
+ live.update(Group(*lines))
107
+
108
+ def postprocess_done_callback(postprocess_future: Future):
109
+ update_task_count()
110
+ postprocess_future.result()
111
+ progress.update(postprocess_done_progress_task, advance=1)
112
+ result_queue.put(1)
113
+
114
+ def infer_done_callback(image_path, img, scale, pad, infer_future: Future):
115
+ update_task_count()
116
+ outputs = infer_future.result()[0]
117
+ progress.update(infer_done_progress_task, advance=1)
118
+
119
+ output_path = output_dir_str + "/" + os.path.basename(image_path)
120
+ postprocess_future: Future = thread_pool.submit(
121
+ postprocess,
122
+ args=(img, outputs, output_path, scale, pad),
123
+ cpu_mode_res=cpu_res
124
+ )
125
+ progress.update(postprocess_submit_progress_task, advance=1)
126
+ postprocess_future.add_done_callback(postprocess_done_callback)
127
+
128
+ def preprocess_done_callback(image_path, preprocess_future: Future):
129
+ update_task_count()
130
+ progress.update(preprocess_done_progress_task, advance=1)
131
+ img, blob, scale, pad = preprocess_future.result()
132
+ infer_future: Future = infer_session_pool.submit(args=(blob,), cpu_mode_res=cpu_res, gpu_mode_res=gpu_res)
133
+ progress.update(infer_submit_progress_task, advance=1)
134
+ infer_future.add_done_callback(partial(infer_done_callback, image_path, img, scale, pad))
135
+
136
+ for image_path in image_paths:
137
+ update_task_count()
138
+ image_path = str(image_path)
139
+ preprocess_future: Future = thread_pool.submit(
140
+ preprocess,
141
+ args=(image_path,),
142
+ cpu_mode_res=cpu_res
143
+ )
144
+ progress.update(preprocess_submit_progress_task, advance=1)
145
+ preprocess_future.add_done_callback(partial(preprocess_done_callback, image_path))
146
+
147
+ start_time = time.perf_counter()
148
+
149
+ for _ in range(n_tasks):
150
+ result_queue.get()
151
+
152
+ elapsed = time.perf_counter() - start_time
153
+ print(f"\ninference completed in {elapsed:.2f} seconds")
154
+ print(f"Output: {OUTPUT_DIR.resolve()}")
155
+
156
+
157
+ if __name__ == "__main__":
158
+ app()
@@ -8,12 +8,8 @@ OUTPUT_DIR = PACKAGE_DIR / "data" / "output"
8
8
  MODEL_URL = "https://hf-mirror.com/Kalray/yolov8/resolve/main/yolov8n.onnx"
9
9
  MODEL_PATH = MODEL_DIR / "yolov8n.onnx"
10
10
 
11
- COCO_IMAGE_URL = "http://images.cocodataset.org/val2017/{}.jpg"
12
- COCO_IMAGE_IDS = [
13
- "000000000139", "000000000285", "000000000632", "000000000724", "000000000776",
14
- "000000000785", "000000000802", "000000000872", "000000001000", "000000001268",
15
- "000000001296", "000000001353", "000000001490", "000000001503", "000000001532",
16
- "000000001584", "000000001675", "000000001818", "000000096001", "000000202001",
11
+ COCO_ZIP_URLS = [
12
+ "http://images.cocodataset.org/zips/val2017.zip",
17
13
  ]
18
14
 
19
15
  COCO_CLASSES = [
@@ -1,15 +1,11 @@
1
+ import contextlib
1
2
  import time
2
3
  import urllib.error
3
4
  import urllib.request
5
+ import zipfile
4
6
  from pathlib import Path
5
7
 
6
- from config import (
7
- COCO_IMAGE_IDS,
8
- COCO_IMAGE_URL,
9
- DATASET_DIR,
10
- MODEL_PATH,
11
- MODEL_URL,
12
- )
8
+ from config import COCO_ZIP_URLS, DATASET_DIR, MODEL_PATH, MODEL_URL
13
9
  from rich.progress import (
14
10
  BarColumn,
15
11
  DownloadColumn,
@@ -27,7 +23,7 @@ def download_file(url: str, dest: Path, desc: str = "", max_retries: int = 3):
27
23
  for attempt in range(1, max_retries + 1):
28
24
  try:
29
25
  req = urllib.request.Request(url, method="GET")
30
- resp = urllib.request.urlopen(req, timeout=60)
26
+ resp = urllib.request.urlopen(req, timeout=120)
31
27
  total = int(resp.headers.get("Content-Length", 0))
32
28
  chunk_size = 8192
33
29
  with Progress(
@@ -48,6 +44,13 @@ def download_file(url: str, dest: Path, desc: str = "", max_retries: int = 3):
48
44
  pbar.update(task, advance=len(chunk))
49
45
  tmp.rename(dest)
50
46
  return
47
+ except urllib.error.HTTPError as e:
48
+ if e.code == 404:
49
+ raise
50
+ last_err = e
51
+ if attempt < max_retries:
52
+ delay = 2 ** attempt
53
+ time.sleep(delay)
51
54
  except (urllib.error.URLError, TimeoutError, OSError) as e:
52
55
  last_err = e
53
56
  if attempt < max_retries:
@@ -56,23 +59,58 @@ def download_file(url: str, dest: Path, desc: str = "", max_retries: int = 3):
56
59
  raise last_err
57
60
 
58
61
 
62
+ def _count_images():
63
+ return len(list(DATASET_DIR.rglob("*.jpg")))
64
+
65
+
66
+ def _flatten_images():
67
+ for p in list(DATASET_DIR.rglob("*.jpg")):
68
+ if p.parent is not DATASET_DIR:
69
+ p.replace(DATASET_DIR / p.name)
70
+ for p in list(DATASET_DIR.iterdir()):
71
+ if p.is_dir() and p.name != "val2017":
72
+ for f in list(p.rglob("*")):
73
+ if f.is_file():
74
+ f.unlink()
75
+
76
+ with contextlib.suppress(OSError):
77
+ p.rmdir()
78
+
79
+
80
+ def download_dataset():
81
+ DATASET_DIR.mkdir(parents=True, exist_ok=True)
82
+
83
+ _flatten_images()
84
+ if _count_images() > 4000:
85
+ print(f"[SKIP DOWNLOAD] Dataset exists ({_count_images()} images)")
86
+ return
87
+
88
+ print("[DOWNLOAD] val2017.zip (~1GB) ...")
89
+ zip_path = DATASET_DIR / "val2017.zip"
90
+
91
+ for url in COCO_ZIP_URLS:
92
+ try:
93
+ download_file(url, zip_path, "COCO val2017")
94
+ break
95
+ except Exception:
96
+ print(f" mirror failed: {url}")
97
+ continue
98
+ else:
99
+ raise RuntimeError("all mirrors failed")
100
+
101
+ print(f"[EXTRACT] val2017.zip -> {DATASET_DIR} ...")
102
+ with zipfile.ZipFile(zip_path) as zf:
103
+ zf.extractall(DATASET_DIR)
104
+
105
+ zip_path.unlink()
106
+ _flatten_images()
107
+ n = _count_images()
108
+ print(f"[DONE] {n} images ready")
109
+
110
+
59
111
  def download_model():
60
112
  if MODEL_PATH.exists():
61
113
  print(f"[SKIP DOWNLOAD] Model {MODEL_PATH.name} exists")
62
114
  return
63
115
  print("[DOWNLOAD] YOLOv8n ONNX...")
64
116
  download_file(MODEL_URL, MODEL_PATH, "YOLOv8n")
65
-
66
-
67
- def download_dataset():
68
- DATASET_DIR.mkdir(parents=True, exist_ok=True)
69
- existing = {p.stem for p in DATASET_DIR.glob("*.jpg")}
70
- to_dl = [iid for iid in COCO_IMAGE_IDS if iid not in existing]
71
- if not to_dl:
72
- print(f"[SKIP DOWNLOAD] Dataset exists ({len(COCO_IMAGE_IDS)} images)")
73
- return
74
- print(f"[DOWNLOAD] {len(to_dl)} COCO images...")
75
- for iid in to_dl:
76
- url = COCO_IMAGE_URL.format(iid)
77
- dest = DATASET_DIR / f"{iid}.jpg"
78
- download_file(url, dest, f"COCO {iid}")
@@ -1,130 +1,133 @@
1
- import os
2
- from typing import Dict, List, Tuple
3
-
4
- import cv2
5
- import numpy as np
6
- from config import COCO_CLASSES
7
-
8
- from smartpool import InferSessionPool, Resource
9
-
10
-
11
- def letterbox(img: np.ndarray, new_shape=(640, 640), color=(114, 114, 114)):
12
- shape = img.shape[:2]
13
- r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
14
- new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r)))
15
- dw = new_shape[1] - new_unpad[0]
16
- dh = new_shape[0] - new_unpad[1]
17
- dw, dh = dw / 2, dh / 2
18
- top = int(round(dh - 0.1))
19
- bottom = int(round(dh + 0.1))
20
- left = int(round(dw - 0.1))
21
- right = int(round(dw + 0.1))
22
- img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
23
- img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
24
- return img, r, (left, top)
25
-
26
-
27
- def preprocess(img: np.ndarray):
28
- img, scale, pad = letterbox(img)
29
- img = img.astype(np.float32) / 255.0
30
- img = img.transpose(2, 0, 1)[np.newaxis, ...]
31
- return img, scale, pad
32
-
33
-
34
- def nms(boxes: np.ndarray, scores: np.ndarray, iou_threshold: float = 0.5):
35
- if len(boxes) == 0:
36
- return []
37
- x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
38
- areas = (x2 - x1) * (y2 - y1)
39
- order = scores.argsort()[::-1]
40
- keep = []
41
- while order.size > 0:
42
- i = order[0]
43
- keep.append(i)
44
- xx1 = np.maximum(x1[i], x1[order[1:]])
45
- yy1 = np.maximum(y1[i], y1[order[1:]])
46
- xx2 = np.minimum(x2[i], x2[order[1:]])
47
- yy2 = np.minimum(y2[i], y2[order[1:]])
48
- w = np.maximum(0.0, xx2 - xx1)
49
- h = np.maximum(0.0, yy2 - yy1)
50
- inter = w * h
51
- iou = inter / (areas[i] + areas[order[1:]] - inter)
52
- inds = np.where(iou <= iou_threshold)[0]
53
- order = order[inds + 1]
54
- return keep
55
-
56
-
57
- def postprocess(output: np.ndarray, orig_shape: Tuple[int, int], scale: float,
58
- pad: Tuple[int, int], conf_thresh: float = 0.5, iou_thresh: float = 0.5):
59
- predictions = np.squeeze(output).T
60
- box_data = predictions[:, :4]
61
- class_scores = predictions[:, 4:]
62
- scores = class_scores.max(axis=1)
63
- class_ids = class_scores.argmax(axis=1)
64
- mask = scores > conf_thresh
65
- if not mask.any():
66
- return []
67
- boxes = box_data[mask]
68
- scores = scores[mask]
69
- class_ids = class_ids[mask]
70
-
71
- boxes_xyxy = np.zeros_like(boxes)
72
- boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2
73
- boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2
74
- boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2
75
- boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2
76
-
77
- keep = nms(boxes_xyxy, scores, iou_thresh)
78
- if not keep:
79
- return []
80
-
81
- boxes_xyxy = boxes_xyxy[keep]
82
- scores = scores[keep]
83
- class_ids = class_ids[keep]
84
-
85
- left, top = pad
86
- boxes_xyxy = (boxes_xyxy - np.array([left, top, left, top])) / scale
87
- boxes_xyxy = np.clip(boxes_xyxy, 0, [orig_shape[1], orig_shape[0], orig_shape[1], orig_shape[0]])
88
-
89
- return [
90
- {"bbox": boxes_xyxy[i].tolist(), "score": float(scores[i]), "class_id": int(class_ids[i])}
91
- for i in range(len(boxes_xyxy))
92
- ]
93
-
94
-
95
- def draw_detections(img: np.ndarray, detections: List[Dict]) -> np.ndarray:
96
- colors = [
97
- (56, 56, 255), (151, 157, 255), (31, 112, 255), (29, 178, 255),
98
- (49, 210, 207), (10, 249, 72), (23, 204, 146), (134, 219, 61),
99
- (52, 147, 26), (187, 135, 1), (132, 145, 137), (168, 229, 11),
100
- ]
101
- for det in detections:
102
- x1, y1, x2, y2 = map(int, det["bbox"])
103
- cls_id = det["class_id"]
104
- score = det["score"]
105
- color = colors[cls_id % len(colors)]
106
- label = f"{COCO_CLASSES[cls_id]} {score:.2f}"
107
- cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
108
- (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
109
- cv2.rectangle(img, (x1, y1 - th - 4), (x1 + tw, y1), color, -1)
110
- cv2.putText(img, label, (x1, y1 - 2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
111
- return img
112
-
113
-
114
- def infer_task(image_path: str, model_path_str: str, output_dir_str: str, infer_session_pool: InferSessionPool) -> str:
115
- img = cv2.imread(image_path)
116
- if img is None:
117
- raise ValueError(f"Failed to read image: {image_path}")
118
-
119
- h, w = img.shape[:2]
120
- blob, scale, pad = preprocess(img)
121
- gpu_res = Resource(cpu_cores_in_python=1, cpu_mem=512*1024**2,
122
- gpu_cores=1000, gpu_mem=2*1024**3)
123
- cpu_res = Resource(cpu_cores_in_python=2, cpu_mem=2*1024**3)
124
- future = infer_session_pool.submit(model_path_str, args=(blob,), cpu_mode_res=cpu_res, gpu_mode_res=gpu_res)
125
- outputs = future.result()[0]
126
- detections = postprocess(outputs, (h, w), scale, pad)
127
- result_img = draw_detections(img, detections)
128
-
129
- out_path = os.path.join(output_dir_str, os.path.basename(image_path))
130
- cv2.imwrite(out_path, result_img)
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from concurrent.futures import Future
5
+ from typing import TYPE_CHECKING, Dict, List, Tuple
6
+
7
+ import cv2
8
+ import numpy as np
9
+ from config import COCO_CLASSES
10
+
11
+ if TYPE_CHECKING:
12
+ from smartpool import InferSessionPool, Resource
13
+
14
+
15
+ def letterbox(img: np.ndarray, new_shape=(640, 640), color=(114, 114, 114)):
16
+ shape = img.shape[:2]
17
+ r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
18
+ new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r)))
19
+ dw = new_shape[1] - new_unpad[0]
20
+ dh = new_shape[0] - new_unpad[1]
21
+ dw, dh = dw / 2, dh / 2
22
+ top = int(round(dh - 0.1))
23
+ bottom = int(round(dh + 0.1))
24
+ left = int(round(dw - 0.1))
25
+ right = int(round(dw + 0.1))
26
+ img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
27
+ img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
28
+ return img, r, (left, top)
29
+
30
+
31
+ def preprocess(image_path: str):
32
+ img = cv2.imread(image_path)
33
+ if img is None:
34
+ raise ValueError(f"failed to read image: {image_path}")
35
+
36
+ blob, scale, pad = letterbox(img)
37
+ blob = blob.astype(np.float32) / 255.0
38
+ blob = np.ascontiguousarray(blob.transpose(2, 0, 1)[np.newaxis, ...])
39
+ return img, blob, scale, pad
40
+
41
+
42
+ def nms(boxes: np.ndarray, scores: np.ndarray, iou_threshold: float = 0.5):
43
+ if len(boxes) == 0:
44
+ return []
45
+ x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
46
+ areas = (x2 - x1) * (y2 - y1)
47
+ order = scores.argsort()[::-1]
48
+ keep = []
49
+ while order.size > 0:
50
+ i = order[0]
51
+ keep.append(i)
52
+ xx1 = np.maximum(x1[i], x1[order[1:]])
53
+ yy1 = np.maximum(y1[i], y1[order[1:]])
54
+ xx2 = np.minimum(x2[i], x2[order[1:]])
55
+ yy2 = np.minimum(y2[i], y2[order[1:]])
56
+ w = np.maximum(0.0, xx2 - xx1)
57
+ h = np.maximum(0.0, yy2 - yy1)
58
+ inter = w * h
59
+ iou = inter / (areas[i] + areas[order[1:]] - inter)
60
+ inds = np.where(iou <= iou_threshold)[0]
61
+ order = order[inds + 1]
62
+ return keep
63
+
64
+
65
+ def draw_detections(src_img: np.ndarray, output_path: str, detections: List[Dict]):
66
+ colors = [
67
+ (56, 56, 255), (151, 157, 255), (31, 112, 255), (29, 178, 255),
68
+ (49, 210, 207), (10, 249, 72), (23, 204, 146), (134, 219, 61),
69
+ (52, 147, 26), (187, 135, 1), (132, 145, 137), (168, 229, 11),
70
+ ]
71
+ for det in detections:
72
+ x1, y1, x2, y2 = map(int, det["bbox"])
73
+ cls_id = det["class_id"]
74
+ score = det["score"]
75
+ color = colors[cls_id % len(colors)]
76
+ label = f"{COCO_CLASSES[cls_id]} {score:.2f}"
77
+ cv2.rectangle(src_img, (x1, y1), (x2, y2), color, 2)
78
+ (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
79
+ cv2.rectangle(src_img, (x1, y1 - th - 4), (x1 + tw, y1), color, -1)
80
+ cv2.putText(src_img, label, (x1, y1 - 2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
81
+
82
+ cv2.imwrite(output_path, src_img)
83
+
84
+
85
+ def postprocess(
86
+ src_img:np.ndarray, output: np.ndarray, output_path:str,
87
+ scale: float, pad: Tuple[int, int], conf_thresh: float = 0.5,
88
+ iou_thresh: float = 0.5
89
+ ):
90
+ orig_shape = (src_img.shape[0], src_img.shape[1])
91
+ predictions = np.squeeze(output).T
92
+ box_data = predictions[:, :4]
93
+ class_scores = predictions[:, 4:]
94
+ scores = class_scores.max(axis=1)
95
+ class_ids = class_scores.argmax(axis=1)
96
+ mask = scores > conf_thresh
97
+ if not mask.any():
98
+ return []
99
+ boxes = box_data[mask]
100
+ scores = scores[mask]
101
+ class_ids = class_ids[mask]
102
+
103
+ boxes_xyxy = np.zeros_like(boxes)
104
+ boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2
105
+ boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2
106
+ boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2
107
+ boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2
108
+
109
+ keep = nms(boxes_xyxy, scores, iou_thresh)
110
+ if not keep:
111
+ return []
112
+
113
+ boxes_xyxy = boxes_xyxy[keep]
114
+ scores = scores[keep]
115
+ class_ids = class_ids[keep]
116
+
117
+ left, top = pad
118
+ boxes_xyxy = (boxes_xyxy - np.array([left, top, left, top])) / scale
119
+ boxes_xyxy = np.clip(boxes_xyxy, 0, [orig_shape[1], orig_shape[0], orig_shape[1], orig_shape[0]])
120
+
121
+ detections: List[Dict] = [
122
+ {"bbox": boxes_xyxy[i].tolist(), "score": float(scores[i]), "class_id": int(class_ids[i])}
123
+ for i in range(len(boxes_xyxy))
124
+ ]
125
+
126
+ draw_detections(src_img, output_path, detections)
127
+
128
+ def infer_task(image_path:str, output_dir_path:str, infer_session_pool: InferSessionPool, cpu_res: Resource, gpu_res: Resource)->None:
129
+ img, blob, scale, pad = preprocess(image_path)
130
+ infer_future: Future = infer_session_pool.submit(args=(blob,), cpu_mode_res=cpu_res, gpu_mode_res=gpu_res)
131
+ outputs = infer_future.result()[0]
132
+ output_path = output_dir_path + "/" + os.path.basename(image_path)
133
+ postprocess(img, outputs, output_path, scale, pad)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smartpool-examples
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Examples for smartpool.
5
5
  Author-email: "王炳辉 (Bing-Hui WANG)" <binghui.wang@foxmail.com>
6
6
  License: MIT
@@ -11,7 +11,7 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: pysmartpool>=0.1.6
14
+ Requires-Dist: pysmartpool>=0.1.7
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: scikit-learn
17
17
  Requires-Dist: numpy
@@ -1,4 +1,4 @@
1
- pysmartpool>=0.1.6
1
+ pysmartpool>=0.1.7
2
2
  matplotlib
3
3
  scikit-learn
4
4
  numpy
@@ -1,88 +0,0 @@
1
- if __name__ == "__main__":
2
- import os
3
- import sys
4
- self_folder = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
5
- target_folder = os.path.abspath(self_folder + "/../../../smartpool").replace("\\", "/")
6
- sys.path.append(target_folder)
7
-
8
- import os
9
- import time
10
- from concurrent.futures import as_completed
11
-
12
- import typer
13
- from rich.progress import (
14
- BarColumn,
15
- Progress,
16
- TextColumn,
17
- TimeRemainingColumn,
18
- )
19
-
20
- from smartpool import InferSessionPool, Resource, ThreadPool
21
-
22
- app = typer.Typer(help="Use smartpool to do YOLOv8n ONNX inference on COCO val2017 with real-time progress.")
23
-
24
-
25
- @app.command()
26
- def main(
27
- max_workers: int = typer.Option(
28
- 0,
29
- "--max_workers",
30
- help="max number of workers to use, 0 to use all available cores"
31
- ),
32
- ):
33
- self_folder = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
34
- sys.path.append(self_folder)
35
-
36
- from config import DATASET_DIR, MODEL_PATH, OUTPUT_DIR
37
- from data_utils import download_dataset, download_model
38
- from inference import infer_task
39
-
40
- print("Use `python -m smartpool_examples.onnx_infer --help` to see all options.")
41
- print(f"See source code at folder {os.path.dirname(os.path.abspath(__file__))}")
42
- print()
43
-
44
- download_model()
45
- download_dataset()
46
- OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
47
-
48
- image_paths = sorted(DATASET_DIR.glob("*.jpg"))
49
- cpu_res = Resource(cpu_cores_in_python=1, cpu_mem=2*1024**3)
50
-
51
- model_path_str = str(MODEL_PATH.resolve())
52
- output_dir_str = str(OUTPUT_DIR.resolve())
53
- n_workers = max_workers
54
-
55
- print(f"\nSubmit {len(image_paths)} tasks to ThreadPool({n_workers})...")
56
- thread_pool = ThreadPool(max_workers=n_workers)
57
- infer_session_pool = InferSessionPool(max_workers=n_workers)
58
- infer_session_pool.print_info = True
59
-
60
- futures = []
61
- for path in image_paths:
62
- future = thread_pool.submit(
63
- infer_task,
64
- args=(str(path), model_path_str, output_dir_str, infer_session_pool),
65
- cpu_mode_res=cpu_res
66
- )
67
- futures.append(future)
68
-
69
- start_time = time.perf_counter()
70
- with Progress(
71
- TextColumn("[progress.description]{task.description}"),
72
- BarColumn(),
73
- TextColumn("{task.completed}/{task.total}"),
74
- TimeRemainingColumn(),
75
- ) as progress:
76
- task = progress.add_task("infer", total=len(futures))
77
- for f in as_completed(futures):
78
- f.result()
79
- progress.update(task, advance=1)
80
- progress.update(task, description=f"infer [{progress.tasks[0].completed}/{progress.tasks[0].total}]")
81
-
82
- elapsed = time.perf_counter() - start_time
83
- print(f"\ninference completed in {elapsed:.2f} seconds")
84
- print(f"Output: {OUTPUT_DIR.resolve()}")
85
-
86
-
87
- if __name__ == "__main__":
88
- app()