Add local service (win)

This commit is contained in:
warrenchen 2026-02-13 13:48:43 +09:00
parent 1902cd0f84
commit 6f3800687e
16 changed files with 1042 additions and 0 deletions

91
README.md Normal file
View File

@ -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`

140
local_service_win/.gitignore vendored Normal file
View File

@ -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/

Binary file not shown.

View File

@ -0,0 +1 @@
# LocalAPI package

View File

@ -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)

View File

@ -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).

View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pic64View</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 16px;
background: #f7f7f7;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
textarea {
width: 100%;
min-height: 160px;
padding: 12px;
box-sizing: border-box;
font-family: Consolas, monospace;
}
.controls {
margin: 12px 0;
display: flex;
gap: 8px;
align-items: center;
}
img {
max-width: 100%;
border: 1px solid #ddd;
background: #fff;
}
.hint {
color: #555;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<h1>Pic64View</h1>
<p class="hint">
Paste a Base64 image string (with or without data URL prefix) and click "Render".
</p>
<textarea id="base64Input" placeholder="Paste Base64 here..."></textarea>
<div class="controls">
<button id="renderBtn">Render</button>
<button id="clearBtn">Clear</button>
</div>
<img id="preview" alt="Preview will appear here" />
</div>
<script>
const input = document.getElementById("base64Input");
const preview = document.getElementById("preview");
const renderBtn = document.getElementById("renderBtn");
const clearBtn = document.getElementById("clearBtn");
function normalizeBase64(value) {
const trimmed = value.trim();
if (trimmed.startsWith("data:image")) {
return trimmed;
}
return "data:image/png;base64," + trimmed;
}
renderBtn.addEventListener("click", () => {
const value = input.value;
if (!value.trim()) {
return;
}
preview.src = normalizeBase64(value);
});
clearBtn.addEventListener("click", () => {
input.value = "";
preview.removeAttribute("src");
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -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`

View File

@ -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