diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7b54e1 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# web_academy_prototype + +## 專案目標 +此 repository 的 PoC 主軸是**線上教學平台核心流程**。 +核心流程定義請參考 `docs/PRD-Integrated.md`。 + +`local_service_win` 是整體 PoC 其中一個模組,負責本機硬體控制與推論流程驗證。 + +## PoC 範圍與路線圖 +- 主目標:線上教學平台核心流程 PoC。 +- Local Service PoC: + - Windows:已在本 repo(`local_service_win/`)。 + - Linux:規劃中(KneronPLUS 已支援)。 + - macOS:規劃中(待 KneronPLUS 支援)。 +- 網頁流程 PoC:規劃中(後續加入相關專案或模組)。 +- `local_agent_win/`:會納入此專案範圍。 + +## 目前已存在模組 +- `local_service_win/` + - Python + FastAPI localhost 服務。 + - 透過 KneronPLUS(`kp`)與 Kneron USB 裝置互動。 + - 涵蓋掃描、連線、模型載入、推論流程。 + - 預設位址:`http://127.0.0.1:4398`。 + +目前 Windows local service 資料流: +`Client (Browser/App) -> LocalAPI (127.0.0.1:4398) -> KneronPLUS kp -> KL520/KL720` + +## 版本相容性(目前觀察) +- 你目前環境使用 Python `3.13` 看起來可運作。 +- KneronPLUS 既有生態資訊常見以 Python `3.9` 為主。 +- 後續建議補上正式相容矩陣(Python / KP 版本)。 + +## 專案結構(目前) +```text +web_academy_prototype/ +|- docs/ +| `- PRD-Integrated.md +|- local_service_win/ +| |- .gitignore +| |- KneronPLUS-3.1.2-py3-none-any.whl +| |- requirements.txt +| |- STRATEGY.md +| |- LocalAPI/ +| | |- __init__.py +| | `- main.py +| `- TestRes/ +| `- API 測試素材(模型與圖片;圖片已內嵌為 Base64,可直接放入推論請求) +| |- TEST_PAIRS.md +| |- Images/ +| | |- Pic64View.html +| | |- bike_cars_street_224x224.html +| | `- one_bike_many_cars_800x800.html +| `- Models/ +| |- models_520.nef +| |- kl520_20004_fcos-drk53s_w512h512.nef +| |- kl520_20005_yolov5-noupsample_w640h640.nef +| |- kl720_20004_fcos-drk53s_w512h512.nef +| `- kl720_20005_yolov5-noupsample_w640h640.nef +`- README.md +``` + +## Pic64View 工具說明 +- 檔案:`local_service_win/TestRes/Images/Pic64View.html` +- 用途:本機快速預覽 Base64 圖片字串,方便測試 `/inference/run` 的 `image_base64` 內容是否正確。 +- 輸入格式: + - 可直接貼 `data:image/...;base64,...`。 + - 也可只貼純 Base64,工具會自動補上 `data:image/png;base64,` 前綴再渲染。 +- 操作: + - `Render`:顯示預覽圖。 + - `Clear`:清空輸入與預覽結果。 + +## 快速開始(local_service_win) +1. 安裝相依套件: +```powershell +cd local_service_win +python -m pip install -r requirements.txt +``` + +2. 安裝 KneronPLUS wheel: +```powershell +python -m pip install .\KneronPLUS-3.1.2-py3-none-any.whl +``` + +3. 啟動本機服務: +```powershell +python .\LocalAPI\main.py +``` + +## 參考文件 +- 核心流程與產品規劃:`docs/PRD-Integrated.md` +- Windows local service 策略:`local_service_win/STRATEGY.md` diff --git a/local_service_win/.gitignore b/local_service_win/.gitignore new file mode 100644 index 0000000..b23a173 --- /dev/null +++ b/local_service_win/.gitignore @@ -0,0 +1,140 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff +instance/ +.webassets-cache + +# Scrapy stuff +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +#Pipfile.lock + +# poetry +#poetry.lock + +# pdm +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# IDE +.vscode/ +.idea/ diff --git a/local_service_win/KneronPLUS-3.1.2-py3-none-any.whl b/local_service_win/KneronPLUS-3.1.2-py3-none-any.whl new file mode 100644 index 0000000..c8f5b22 Binary files /dev/null and b/local_service_win/KneronPLUS-3.1.2-py3-none-any.whl differ diff --git a/local_service_win/LocalAPI/__init__.py b/local_service_win/LocalAPI/__init__.py new file mode 100644 index 0000000..da5f2a6 --- /dev/null +++ b/local_service_win/LocalAPI/__init__.py @@ -0,0 +1 @@ +# LocalAPI package diff --git a/local_service_win/LocalAPI/main.py b/local_service_win/LocalAPI/main.py new file mode 100644 index 0000000..8b60e8d --- /dev/null +++ b/local_service_win/LocalAPI/main.py @@ -0,0 +1,383 @@ +from __future__ import annotations + +import base64 +import threading +from dataclasses import dataclass +from typing import Any, Dict, List, Optional + +from fastapi import FastAPI, HTTPException, Request +from fastapi.responses import JSONResponse +from pydantic import BaseModel, Field + +import kp + + +SERVICE_VERSION = "0.1.0" + + +@dataclass +class DeviceState: + device_group: Optional[kp.DeviceGroup] = None + port_id: Optional[int] = None + model_desc: Optional[kp.ModelNefDescriptor] = None + + +STATE = DeviceState() +STATE_LOCK = threading.Lock() + + +app = FastAPI(title="Kneron LocalAPI", version=SERVICE_VERSION) + + +def _ok(data: Any) -> Dict[str, Any]: + return {"ok": True, "data": data, "error": None} + + +def _err(code: str, message: str) -> Dict[str, Any]: + return {"ok": False, "data": None, "error": {"code": code, "message": message}} + + +def _require_device() -> kp.DeviceGroup: + if STATE.device_group is None: + raise HTTPException(status_code=400, detail=_err("NO_DEVICE", "No connected device")) + return STATE.device_group + + +def _image_format_from_str(value: str) -> kp.ImageFormat: + value = value.upper() + mapping = { + "RGB565": kp.ImageFormat.KP_IMAGE_FORMAT_RGB565, + "RGBA8888": kp.ImageFormat.KP_IMAGE_FORMAT_RGBA8888, + "RAW8": kp.ImageFormat.KP_IMAGE_FORMAT_RAW8, + "YUYV": kp.ImageFormat.KP_IMAGE_FORMAT_YUYV, + "YUV420": kp.ImageFormat.KP_IMAGE_FORMAT_YUV420, + } + if value not in mapping: + raise HTTPException( + status_code=400, + detail=_err("INVALID_IMAGE_FORMAT", f"Unsupported image_format: {value}"), + ) + return mapping[value] + + +def _channels_ordering_from_str(value: str) -> kp.ChannelOrdering: + value = value.upper() + mapping = { + "HCW": kp.ChannelOrdering.KP_CHANNEL_ORDERING_HCW, + "CHW": kp.ChannelOrdering.KP_CHANNEL_ORDERING_CHW, + "HWC": kp.ChannelOrdering.KP_CHANNEL_ORDERING_HWC, + "DEFAULT": kp.ChannelOrdering.KP_CHANNEL_ORDERING_DEFAULT, + } + if value not in mapping: + raise HTTPException( + status_code=400, + detail=_err("INVALID_CHANNEL_ORDERING", f"Unsupported channels_ordering: {value}"), + ) + return mapping[value] + + +def _product_name_from_id(product_id: int) -> str: + try: + return kp.ProductId(product_id).name.replace("KP_DEVICE_", "") + except ValueError: + return "UNKNOWN" + + +@app.exception_handler(HTTPException) +def http_exception_handler(_: Request, exc: HTTPException) -> JSONResponse: + if isinstance(exc.detail, dict) and "ok" in exc.detail: + return JSONResponse(status_code=exc.status_code, content=exc.detail) + return JSONResponse( + status_code=exc.status_code, + content=_err("HTTP_ERROR", str(exc.detail)), + ) + + +@app.exception_handler(Exception) +def unhandled_exception_handler(_: Request, exc: Exception) -> JSONResponse: + return JSONResponse( + status_code=500, + content=_err("INTERNAL_ERROR", str(exc)), + ) + + +class ConnectRequest(BaseModel): + port_id: Optional[int] = Field(default=None) + scan_index: Optional[int] = Field(default=None) + timeout_ms: Optional[int] = Field(default=5000) + + +class FirmwareLoadRequest(BaseModel): + scpu_path: str + ncpu_path: str + + +class ModelLoadRequest(BaseModel): + nef_path: str + + +class InferenceRunRequest(BaseModel): + model_id: int + image_format: str + width: int + height: int + image_base64: str + channels_ordering: str = "DEFAULT" + output_dtype: str = "float32" + + +@app.get("/health") +def health() -> Dict[str, Any]: + return _ok({"status": "up"}) + + +@app.get("/version") +def version() -> Dict[str, Any]: + return _ok( + { + "service_version": SERVICE_VERSION, + "kneronplus_version": kp.core.get_version(), + } + ) + + +@app.get("/devices") +def devices() -> Dict[str, Any]: + device_list = kp.core.scan_devices() + devices_out = [] + for idx, device in enumerate(device_list.device_descriptor_list): + devices_out.append( + { + "scan_index": idx, + "usb_port_id": device.usb_port_id, + "vendor_id": device.vendor_id, + "product_id": f"0x{device.product_id:X}", + "product_name": _product_name_from_id(device.product_id), + "link_speed": device.link_speed.name, + "usb_port_path": device.usb_port_path, + "kn_number": device.kn_number, + "is_connectable": device.is_connectable, + "firmware": device.firmware, + } + ) + return _ok({"devices": devices_out}) + + +@app.post("/devices/connect") +def connect(req: ConnectRequest) -> Dict[str, Any]: + with STATE_LOCK: + if STATE.device_group is not None: + try: + kp.core.disconnect_devices(STATE.device_group) + except kp.ApiKPException: + pass + STATE.device_group = None + STATE.port_id = None + STATE.model_desc = None + + port_id = req.port_id + if port_id is None: + device_list = kp.core.scan_devices() + if device_list.device_descriptor_number == 0: + raise HTTPException(status_code=404, detail=_err("NO_DEVICE", "No device found")) + if req.scan_index is None: + scan_index = 0 + else: + scan_index = req.scan_index + if scan_index < 0 or scan_index >= device_list.device_descriptor_number: + raise HTTPException( + status_code=400, + detail=_err("INVALID_SCAN_INDEX", f"Invalid scan_index: {scan_index}"), + ) + port_id = device_list.device_descriptor_list[scan_index].usb_port_id + + try: + device_group = kp.core.connect_devices([int(port_id)]) + if req.timeout_ms is not None: + kp.core.set_timeout(device_group, int(req.timeout_ms)) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + + STATE.device_group = device_group + STATE.port_id = int(port_id) + return _ok({"connected": True, "port_id": STATE.port_id}) + + +@app.post("/devices/disconnect") +def disconnect() -> Dict[str, Any]: + with STATE_LOCK: + if STATE.device_group is not None: + try: + kp.core.disconnect_devices(STATE.device_group) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + STATE.device_group = None + STATE.port_id = None + STATE.model_desc = None + return _ok({"connected": False}) + + +@app.post("/firmware/load") +def firmware_load(req: FirmwareLoadRequest) -> Dict[str, Any]: + device_group = _require_device() + try: + kp.core.load_firmware_from_file(device_group, req.scpu_path, req.ncpu_path) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + return _ok({"loaded": True}) + + +@app.post("/models/load") +def models_load(req: ModelLoadRequest) -> Dict[str, Any]: + device_group = _require_device() + try: + model_desc = kp.core.load_model_from_file(device_group, req.nef_path) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + + with STATE_LOCK: + STATE.model_desc = model_desc + + models = [] + for model in model_desc.models: + models.append( + { + "id": model.id, + "input_nodes": len(model.input_nodes), + "output_nodes": len(model.output_nodes), + "max_raw_out_size": model.max_raw_out_size, + } + ) + + return _ok({"models": models}) + + +def _reset_device_and_clear_state(device_group: kp.DeviceGroup) -> None: + kp.core.reset_device(device_group, kp.ResetMode.KP_RESET_REBOOT) + kp.core.disconnect_devices(device_group) + + +@app.post("/models/clear") +def models_clear() -> Dict[str, Any]: + device_group = _require_device() + try: + _reset_device_and_clear_state(device_group) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + + with STATE_LOCK: + STATE.device_group = None + STATE.port_id = None + STATE.model_desc = None + + return _ok({"cleared": True}) + + +@app.post("/models/reset") +def models_reset() -> Dict[str, Any]: + return models_clear() + + +@app.post("/inference/run") +def inference_run(req: InferenceRunRequest) -> Dict[str, Any]: + device_group = _require_device() + image_format = _image_format_from_str(req.image_format) + channels_ordering = _channels_ordering_from_str(req.channels_ordering) + if req.output_dtype.lower() != "float32": + raise HTTPException( + status_code=400, + detail=_err("INVALID_OUTPUT_DTYPE", "Only float32 output is supported in PoC"), + ) + + try: + if STATE.port_id is not None: + kp.core.get_model_info(device_group, STATE.port_id) + except kp.ApiKPException as exc: + if exc.api_return_code == kp.ApiReturnCode.KP_ERROR_MODEL_NOT_LOADED_35: + raise HTTPException( + status_code=500, + detail=_err( + "KP_ERROR_MODEL_NOT_LOADED_35", + str(kp.ApiReturnCode.KP_ERROR_MODEL_NOT_LOADED_35), + ), + ) + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + + try: + image_bytes = base64.b64decode(req.image_base64) + except (ValueError, TypeError): + raise HTTPException( + status_code=400, + detail=_err("INVALID_BASE64", "image_base64 is not valid base64 data"), + ) + + input_image = kp.GenericInputNodeImage( + image=image_bytes, + width=req.width, + height=req.height, + image_format=image_format, + ) + + input_desc = kp.GenericImageInferenceDescriptor( + model_id=req.model_id, + input_node_image_list=[input_image], + ) + + try: + kp.inference.generic_image_inference_send(device_group, input_desc) + result = kp.inference.generic_image_inference_receive(device_group) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + + outputs = [] + for node_idx in range(result.header.num_output_node): + try: + node_output = kp.inference.generic_inference_retrieve_float_node( + node_idx, result, channels_ordering + ) + except kp.ApiKPException as exc: + raise HTTPException( + status_code=500, + detail=_err(str(exc.api_return_code), str(exc)), + ) + + data_bytes = node_output.ndarray.astype("float32").tobytes() + outputs.append( + { + "node_idx": node_idx, + "name": node_output.name, + "dtype": "float32", + "shape": node_output.shape, + "data_base64": base64.b64encode(data_bytes).decode("ascii"), + "channels_ordering": channels_ordering.name, + } + ) + + return _ok({"outputs": outputs}) + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="127.0.0.1", port=4398) diff --git a/local_service_win/STRATEGY.md b/local_service_win/STRATEGY.md new file mode 100644 index 0000000..d719a39 --- /dev/null +++ b/local_service_win/STRATEGY.md @@ -0,0 +1,298 @@ +# Kneron Dongle PoC (Windows) - Strategy + +## Scope (PoC) +- OS: Windows only. +- Devices: KL520, KL720. +- Control path: Browser -> localhost HTTP service -> KneronPLUS (kp wrapper + DLL). +- Non-goals: macOS/Linux support, production hardening, installer automation for all platforms. + +## Required Installation (Windows) +Before running the local service, install Python dependencies and the KneronPLUS wheel. + +### 1. Install dependencies from requirements +```powershell +cd local_service_win +python -m pip install -r requirements.txt +``` + +### 2. Install KneronPLUS wheel +```powershell +cd local_service_win +python -m pip install .\KneronPLUS-3.1.2-py3-none-any.whl +``` + +### 3. (Optional) Force reinstall KneronPLUS wheel +Use this when switching versions or seeing package mismatch issues. +```powershell +cd local_service_win +python -m pip install --force-reinstall .\KneronPLUS-3.1.2-py3-none-any.whl +``` + +## Cross-Project Workflow +This repo is the main PoC implementation. If additional references are required, we can switch to +other repos during the same conversation and return here as needed. This is workable. + +## High-Level Architecture +- Browser UI + - Talks to localhost HTTP service for control APIs. + - Uses WebSocket for streaming inference. + - No direct USB access from browser. +- Local Service (Windows) + - Owns Kneron device lifecycle and IO. + - Uses Python `kp` high-level API (backed by `libkplus.dll`). + - Exposes HTTP endpoints for scan/connect/model/firmware/inference. +- KneronPLUS Runtime + - `kp` Python wrapper + DLLs + required USB driver. + - Version pinned inside installer to avoid mismatches. + +## API Spec (PoC) +### Conventions +- Base URL: `http://127.0.0.1:4398` +- WebSocket URL: `ws://127.0.0.1:4398/ws` +- Response envelope: + ```json + { + "ok": true, + "data": {}, + "error": null + } + ``` + ```json + { + "ok": false, + "data": null, + "error": { "code": "KP_ERROR_CONNECT_FAILED", "message": "..." } + } + ``` + +### `GET /health` +Response +```json +{ "ok": true, "data": { "status": "up" }, "error": null } +``` + +### `GET /version` +Response +```json +{ + "ok": true, + "data": { + "service_version": "0.1.0", + "kneronplus_version": "3.0.0" + }, + "error": null +} +``` + +### `GET /devices` +Response +```json +{ + "ok": true, + "data": { + "devices": [ + { + "scan_index": 0, + "usb_port_id": 32, + "product_id": 0x520, + "link_speed": "High-Speed", + "usb_port_path": "1-3", + "kn_number": 12345, + "is_connectable": true, + "firmware": "KDP2" + } + ] + }, + "error": null +} +``` + +### `POST /devices/connect` +Request +```json +{ "port_id": 32 } +``` +Response +```json +{ + "ok": true, + "data": { + "connected": true, + "port_id": 32 + }, + "error": null +} +``` + +### `POST /devices/disconnect` +Response +```json +{ "ok": true, "data": { "connected": false }, "error": null } +``` + +### `POST /firmware/load` +Request +```json +{ + "scpu_path": "C:\\path\\fw_scpu.bin", + "ncpu_path": "C:\\path\\fw_ncpu.bin" +} +``` +Response +```json +{ "ok": true, "data": { "loaded": true }, "error": null } +``` + +### `POST /models/load` +Request +```json +{ "nef_path": "C:\\path\\model.nef" } +``` +Response +```json +{ + "ok": true, + "data": { + "model_id": 1, + "input_tensor_count": 1, + "output_tensor_count": 1 + }, + "error": null +} +``` + +### `POST /models/clear` +Notes +- PoC uses device reset to clear RAM model. +Response +```json +{ "ok": true, "data": { "cleared": true }, "error": null } +``` + +### `POST /models/reset` +Notes +- Alias of `/models/clear`, uses device reset to clear RAM model. +Response +```json +{ "ok": true, "data": { "reset": true }, "error": null } +``` + +### `POST /inference/run` +Request (image inference, single image) +```json +{ + "model_id": 1, + "image_format": "RGB888", + "width": 224, + "height": 224, + "image_base64": "..." +} +``` +Response +```json +{ + "ok": true, + "data": { + "outputs": [ + { "node_idx": 0, "dtype": "float", "shape": [1, 1000], "data_base64": "..." } + ] + }, + "error": null +} +``` + +### `WS /ws` (streaming inference) +Notes +- For camera/video stream, use WebSocket for low-latency send/receive. +- HTTP endpoints remain for control operations during PoC. +Message (client -> server) +```json +{ + "type": "inference_frame", + "model_id": 1, + "image_format": "RGB888", + "width": 224, + "height": 224, + "image_base64": "..." +} +``` +Message (server -> client) +```json +{ + "type": "inference_result", + "outputs": [ + { "node_idx": 0, "dtype": "float", "shape": [1, 1000], "data_base64": "..." } + ] +} +``` + +### `POST /firmware/update` +- Reserved for flash update (later; may need C wrapper). + +## Packaging (PoC) +- Single Windows installer: + - Includes driver, `kp` wrapper, DLLs, and service. + - Ensures fixed versions (no external Kneron tools required). + - Reference from `C:\Users\user\Documents\KNEOX\README.md`: + - Install KneronPLUS wheel from `external/kneron_plus_{version}/package/{platform}/` + - `pip install KneronPLUS-{version}-py3-none-any.whl` (use `--force-reinstall` if needed) + - PyInstaller must bundle `kp\lib` with the app. + - Example: + ```shell + pyinstaller --onefile --windowed main.py --additional-hooks-dir=hooks --add-data "uxui;uxui" --add-data "src;src" --add-data "C:\path\to\venv\Lib\site-packages\kp\lib;kp\lib" + ``` + +## Risks / Constraints +- Flash model update / flash firmware update may not be exposed in Python. + - Use C library or request Kneron to expose in wrapper if required. +- Browser security model prevents direct USB access; local service is required. + +## API Test Progress (Windows PoC) +### Completed +- `GET /health` +- `GET /version` +- `GET /devices` +- `POST /devices/connect` +- `POST /devices/disconnect` + +### Pending +- `POST /firmware/load` +- `POST /models/load` +- `POST /models/clear` +- `POST /models/reset` +- `POST /inference/run` +- `WS /ws` +- `POST /firmware/update` + +### Paired Test Requirement +- `POST /models/load` and `POST /inference/run` must be tested as a pair in the same flow. +- Test pairs are defined in `local_service_win/TestRes/TEST_PAIRS.md`. + +### Model/Inference Test Pairs +#### KL520 +1. YOLOv5 (model zoo) + - Model: `kl520_20005_yolov5-noupsample_w640h640.nef` + - Image: `one_bike_many_cars_800x800` (Base64) +2. FCOS (model zoo) + - Model: `kl520_20004_fcos-drk53s_w512h512.nef` + - Image: `one_bike_many_cars_800x800` (Base64) +3. Tiny YOLO v3 (generic demo) + - Model: `models_520.nef` + - Image: `bike_cars_street_224x224` (Base64) +4. Tiny YOLO v3 (multithread demo) + - Model: `models_520.nef` + - Image: `bike_cars_street_224x224` (Base64) + +#### KL720 +1. YOLOv5 (model zoo) + - Model: `kl720_20005_yolov5-noupsample_w640h640.nef` + - Image: `one_bike_many_cars_800x800` (Base64) +2. FCOS (model zoo) + - Model: `kl720_20004_fcos-drk53s_w512h512.nef` + - Image: `one_bike_many_cars_800x800` (Base64) + +## Next Steps (After Strategy) +- Confirm endpoint payloads (JSON schema). +- Decide service framework (FastAPI/Flask). +- Define error model and device state machine. +- Plan installer workflow (driver + service). diff --git a/local_service_win/TestRes/Images/Pic64View.html b/local_service_win/TestRes/Images/Pic64View.html new file mode 100644 index 0000000..a972019 --- /dev/null +++ b/local_service_win/TestRes/Images/Pic64View.html @@ -0,0 +1,83 @@ + + + + + + Pic64View + + + +
+

Pic64View

+

+ Paste a Base64 image string (with or without data URL prefix) and click "Render". +

+ +
+ + +
+ Preview will appear here +
+ + + + diff --git a/local_service_win/TestRes/Images/bike_cars_street_224x224.html b/local_service_win/TestRes/Images/bike_cars_street_224x224.html new file mode 100644 index 0000000..a9e9c3e --- /dev/null +++ b/local_service_win/TestRes/Images/bike_cars_street_224x224.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/local_service_win/TestRes/Images/one_bike_many_cars_800x800.html b/local_service_win/TestRes/Images/one_bike_many_cars_800x800.html new file mode 100644 index 0000000..ff12d84 --- /dev/null +++ b/local_service_win/TestRes/Images/one_bike_many_cars_800x800.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/local_service_win/TestRes/Models/kl520_20004_fcos-drk53s_w512h512.nef b/local_service_win/TestRes/Models/kl520_20004_fcos-drk53s_w512h512.nef new file mode 100644 index 0000000..8d9138a Binary files /dev/null and b/local_service_win/TestRes/Models/kl520_20004_fcos-drk53s_w512h512.nef differ diff --git a/local_service_win/TestRes/Models/kl520_20005_yolov5-noupsample_w640h640.nef b/local_service_win/TestRes/Models/kl520_20005_yolov5-noupsample_w640h640.nef new file mode 100644 index 0000000..3ebaf01 Binary files /dev/null and b/local_service_win/TestRes/Models/kl520_20005_yolov5-noupsample_w640h640.nef differ diff --git a/local_service_win/TestRes/Models/kl720_20004_fcos-drk53s_w512h512.nef b/local_service_win/TestRes/Models/kl720_20004_fcos-drk53s_w512h512.nef new file mode 100644 index 0000000..e540bff Binary files /dev/null and b/local_service_win/TestRes/Models/kl720_20004_fcos-drk53s_w512h512.nef differ diff --git a/local_service_win/TestRes/Models/kl720_20005_yolov5-noupsample_w640h640.nef b/local_service_win/TestRes/Models/kl720_20005_yolov5-noupsample_w640h640.nef new file mode 100644 index 0000000..64a0bec Binary files /dev/null and b/local_service_win/TestRes/Models/kl720_20005_yolov5-noupsample_w640h640.nef differ diff --git a/local_service_win/TestRes/Models/models_520.nef b/local_service_win/TestRes/Models/models_520.nef new file mode 100644 index 0000000..adefdaa Binary files /dev/null and b/local_service_win/TestRes/Models/models_520.nef differ diff --git a/local_service_win/TestRes/TEST_PAIRS.md b/local_service_win/TestRes/TEST_PAIRS.md new file mode 100644 index 0000000..aa03e11 --- /dev/null +++ b/local_service_win/TestRes/TEST_PAIRS.md @@ -0,0 +1,29 @@ +# Model/Image Test Pairs (from kneron_plus examples) + +## KL520 +- YOLOv5 (model zoo) + - Model: `res/models/KL520/yolov5-noupsample_w640h640_kn-model-zoo/kl520_20005_yolov5-noupsample_w640h640.nef` + - Image: `res/images/one_bike_many_cars_800x800.bmp` + - Source: `examples_model_zoo/kl520_kn-model-zoo_generic_image_inference_post_yolov5/kl520_kn-model-zoo_generic_image_inference_post_yolov5.c` +- FCOS (model zoo) + - Model: `res/models/KL520/fcos-drk53s_w512h512_kn-model-zoo/kl520_20004_fcos-drk53s_w512h512.nef` + - Image: `res/images/one_bike_many_cars_800x800.bmp` + - Source: `examples_model_zoo/kl520_kn-model-zoo_generic_image_inference_post_fcos/kl520_kn-model-zoo_generic_image_inference_post_fcos.c` +- Tiny YOLO v3 (generic demo) + - Model: `res/models/KL520/tiny_yolo_v3/models_520.nef` + - Image: `res/images/bike_cars_street_224x224.bmp` + - Source: `examples/kl520_demo_generic_image_inference_post_yolo/kl520_demo_generic_image_inference_post_yolo.c` +- Tiny YOLO v3 (multithread demo) + - Model: `res/models/KL520/tiny_yolo_v3/models_520.nef` + - Image: `res/images/bike_cars_street_224x224.bmp` + - Source: `examples/kl520_demo_generic_image_inference_multithread/kl520_demo_generic_image_inference_multithread.c` + +## KL720 +- YOLOv5 (model zoo) + - Model: `res/models/KL720/yolov5-noupsample_w640h640_kn-model-zoo/kl720_20005_yolov5-noupsample_w640h640.nef` + - Image: `res/images/one_bike_many_cars_800x800.bmp` + - Source: `examples_model_zoo/kl720_kn-model-zoo_generic_image_inference_post_yolov5/kl720_kn-model-zoo_generic_image_inference_post_yolov5.c` +- FCOS (model zoo) + - Model: `res/models/KL720/fcos-drk53s_w512h512_kn-model-zoo/kl720_20004_fcos-drk53s_w512h512.nef` + - Image: `res/images/one_bike_many_cars_800x800.bmp` + - Source: `examples_model_zoo/kl720_kn-model-zoo_generic_image_inference_post_fcos/kl720_kn-model-zoo_generic_image_inference_post_fcos.c` diff --git a/local_service_win/requirements.txt b/local_service_win/requirements.txt new file mode 100644 index 0000000..59dd9d8 --- /dev/null +++ b/local_service_win/requirements.txt @@ -0,0 +1,15 @@ +# Core SDK (installed via local wheel; see STRATEGY.md) +# KneronPLUS==3.0.0 + +# HTTP service +fastapi +uvicorn + +# Reference packages from C:\Users\user\Documents\KNEOX\README.md +PyQt5 +opencv-python +pyinstaller +pyarmor + +# Common dependency for kp data handling +numpy