vision-electronic-indexing-pi 0.1.3 → 0.1.4
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.
- package/.pi/extensions/vision-inventory-mcp/README.md +2 -2
- package/.pi/extensions/vision-inventory-mcp/index.ts +2 -2
- package/.pi/skills/vision-inventory-workflow/SKILL.md +1 -1
- package/README.md +5 -5
- package/package.json +1 -1
- package/scripts/inventory_folder_to_csv.py +9 -8
- package/vision_inventory_mcp.py +6 -5
|
@@ -60,7 +60,7 @@ The agent workflow:
|
|
|
60
60
|
- `/vision-inventory-agent-bom` runs the full agent-assisted datasheet-enrichment workflow.
|
|
61
61
|
- `/vision-inventory-restart` restarts the local Python vision bridge.
|
|
62
62
|
|
|
63
|
-
Options are forwarded to `scripts/inventory_folder_to_csv.py`, such as `--recursive`, `--limit`, `--max-side`, and `--jpeg-quality`.
|
|
63
|
+
Options are forwarded to `scripts/inventory_folder_to_csv.py`, such as `--recursive`, `--limit`, `--max-side`, and `--jpeg-quality`. The default `--max-side 0` sends images at full resolution; set a positive value to resize.
|
|
64
64
|
|
|
65
65
|
## Agent tools
|
|
66
66
|
|
|
@@ -81,7 +81,7 @@ This package intentionally does **not** bundle:
|
|
|
81
81
|
The main output is `inventory.csv`, with columns:
|
|
82
82
|
|
|
83
83
|
```text
|
|
84
|
-
|
|
84
|
+
likely_part
|
|
85
85
|
candidate_parts
|
|
86
86
|
amount
|
|
87
87
|
sighting_count
|
|
@@ -362,7 +362,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
362
362
|
],
|
|
363
363
|
parameters: Type.Object({
|
|
364
364
|
image_path: Type.String({ description: "Path to the image file, relative to the project root or absolute." }),
|
|
365
|
-
max_side: Type.Optional(Type.Integer({ description: "Maximum resized image side before submission.", default:
|
|
365
|
+
max_side: Type.Optional(Type.Integer({ description: "Maximum resized image side before submission. Use 0 for full resolution.", default: 0 })),
|
|
366
366
|
jpeg_quality: Type.Optional(Type.Integer({ description: "JPEG conversion quality from 1 to 100.", default: 96 })),
|
|
367
367
|
custom_prompt: Type.Optional(Type.String({ description: "Optional custom analysis prompt." })),
|
|
368
368
|
}),
|
|
@@ -383,7 +383,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
383
383
|
parameters: Type.Object({
|
|
384
384
|
folder_path: Type.String({ description: "Folder path, relative to the project root or absolute." }),
|
|
385
385
|
recursive: Type.Optional(Type.Boolean({ description: "Whether to scan subfolders.", default: false })),
|
|
386
|
-
max_side: Type.Optional(Type.Integer({ description: "Maximum resized image side before submission.", default:
|
|
386
|
+
max_side: Type.Optional(Type.Integer({ description: "Maximum resized image side before submission. Use 0 for full resolution.", default: 0 })),
|
|
387
387
|
jpeg_quality: Type.Optional(Type.Integer({ description: "JPEG conversion quality from 1 to 100.", default: 96 })),
|
|
388
388
|
limit: Type.Optional(Type.Integer({ description: "Optional maximum number of images to process." })),
|
|
389
389
|
}),
|
|
@@ -23,7 +23,7 @@ Use `/vision-inventory-setup` to configure credentials and check/install Python
|
|
|
23
23
|
/vision-inventory-agent-bom <image_folder> <output_dir> [options]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Options are forwarded to `scripts/inventory_folder_to_csv.py`, for example `--recursive`, `--limit 3`, `--max-side
|
|
26
|
+
Options are forwarded to `scripts/inventory_folder_to_csv.py`, for example `--recursive`, `--limit 3`, `--max-side 0`, and `--jpeg-quality 96`. `--max-side 0` means full resolution and is the default.
|
|
27
27
|
|
|
28
28
|
## Agent Rules
|
|
29
29
|
|
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ Useful options:
|
|
|
99
99
|
```text
|
|
100
100
|
/vision-inventory-agent-bom ./photos ./output --recursive
|
|
101
101
|
/vision-inventory-agent-bom ./photos ./output --limit 3
|
|
102
|
-
/vision-inventory-agent-bom ./photos ./output --max-side
|
|
102
|
+
/vision-inventory-agent-bom ./photos ./output --max-side 0 --jpeg-quality 96
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
The agent workflow will:
|
|
@@ -135,13 +135,13 @@ verified=false
|
|
|
135
135
|
|
|
136
136
|
## CSV output columns
|
|
137
137
|
|
|
138
|
-
`inventory.csv` is deduplicated by
|
|
138
|
+
`inventory.csv` is deduplicated by `likely_part`, the main/final part number column. Multiple images, or multiple candidates from one image, can merge into one BOM row when they resolve to the same `likely_part`.
|
|
139
139
|
|
|
140
140
|
Columns:
|
|
141
141
|
|
|
142
142
|
| Column | Description |
|
|
143
143
|
|---|---|
|
|
144
|
-
| `
|
|
144
|
+
| `likely_part` | Main dedupe key/final likely part number, usually from datasheet enrichment. |
|
|
145
145
|
| `candidate_parts` | Candidate part numbers extracted from visual markings. |
|
|
146
146
|
| `amount` | Estimated quantity for the merged BOM row. |
|
|
147
147
|
| `sighting_count` | Number of evidence rows merged into this BOM row. |
|
|
@@ -242,7 +242,7 @@ Before sending an image to Cloudflare Workers AI, the Python server:
|
|
|
242
242
|
|
|
243
243
|
1. Opens the image with Pillow.
|
|
244
244
|
2. Applies EXIF orientation correction.
|
|
245
|
-
3.
|
|
245
|
+
3. Sends full resolution by default; resizes only when `max_side` is set to a positive value and the image is larger than that limit.
|
|
246
246
|
4. Converts transparency to a white background.
|
|
247
247
|
5. Converts the image to RGB.
|
|
248
248
|
6. Encodes it as JPEG.
|
|
@@ -251,7 +251,7 @@ Before sending an image to Cloudflare Workers AI, the Python server:
|
|
|
251
251
|
Defaults:
|
|
252
252
|
|
|
253
253
|
```text
|
|
254
|
-
max_side:
|
|
254
|
+
max_side: 0 (full resolution)
|
|
255
255
|
jpeg_quality: 96
|
|
256
256
|
model: @cf/meta/llama-4-scout-17b-16e-instruct
|
|
257
257
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vision-electronic-indexing-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Pi package for agent-assisted electronics/PCB image inventory with Cloudflare Workers AI vision and datasheet enrichment.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -261,7 +261,7 @@ def image_part_rows(results: List[Dict[str, Any]], cache: Dict[str, Any]) -> Lis
|
|
|
261
261
|
rows.append({
|
|
262
262
|
"image": image_name,
|
|
263
263
|
"candidate_part": "",
|
|
264
|
-
"
|
|
264
|
+
"likely_part": "",
|
|
265
265
|
"amount": 0,
|
|
266
266
|
"description": "",
|
|
267
267
|
"datasheet_url": "",
|
|
@@ -286,6 +286,7 @@ def image_part_rows(results: List[Dict[str, Any]], cache: Dict[str, Any]) -> Lis
|
|
|
286
286
|
|
|
287
287
|
for candidate, candidate_evidence in sorted(evidence_by_candidate.items()):
|
|
288
288
|
enrichment = lookup_enrichment(candidate, cache)
|
|
289
|
+
likely_part = str(enrichment.get("normalized_part") or candidate).strip().upper()
|
|
289
290
|
amount = estimate_amount_for_candidate(result, candidate, evidence_count=len(candidate_evidence))
|
|
290
291
|
observed_markings = sorted({row["observed_marking"] for row in candidate_evidence})
|
|
291
292
|
observations = "; ".join(
|
|
@@ -298,7 +299,7 @@ def image_part_rows(results: List[Dict[str, Any]], cache: Dict[str, Any]) -> Lis
|
|
|
298
299
|
rows.append({
|
|
299
300
|
"image": image_name,
|
|
300
301
|
"candidate_part": candidate,
|
|
301
|
-
"
|
|
302
|
+
"likely_part": likely_part,
|
|
302
303
|
"amount": amount,
|
|
303
304
|
"description": enrichment.get("description", ""),
|
|
304
305
|
"datasheet_url": enrichment.get("datasheet_url", ""),
|
|
@@ -328,7 +329,7 @@ def write_final_csv(results: List[Dict[str, Any]], cache: Dict[str, Any], output
|
|
|
328
329
|
evidence_fieldnames = [
|
|
329
330
|
"image",
|
|
330
331
|
"candidate_part",
|
|
331
|
-
"
|
|
332
|
+
"likely_part",
|
|
332
333
|
"amount",
|
|
333
334
|
"description",
|
|
334
335
|
"datasheet_url",
|
|
@@ -345,7 +346,7 @@ def write_final_csv(results: List[Dict[str, Any]], cache: Dict[str, Any], output
|
|
|
345
346
|
grouped: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
346
347
|
no_part_rows: List[Dict[str, Any]] = []
|
|
347
348
|
for row in evidence_rows:
|
|
348
|
-
part = str(row.get("
|
|
349
|
+
part = str(row.get("likely_part") or row.get("candidate_part") or "").strip().upper()
|
|
349
350
|
if not part:
|
|
350
351
|
no_part_rows.append(row)
|
|
351
352
|
else:
|
|
@@ -362,7 +363,7 @@ def write_final_csv(results: List[Dict[str, Any]], cache: Dict[str, Any], output
|
|
|
362
363
|
amount = sum(int(row.get("amount", 0) or 0) for row in rows_for_part)
|
|
363
364
|
|
|
364
365
|
bom_rows.append({
|
|
365
|
-
"
|
|
366
|
+
"likely_part": part,
|
|
366
367
|
"candidate_parts": " | ".join(sorted({str(row["candidate_part"]) for row in rows_for_part if row.get("candidate_part")})),
|
|
367
368
|
"amount": amount,
|
|
368
369
|
"sighting_count": len(rows_for_part),
|
|
@@ -380,7 +381,7 @@ def write_final_csv(results: List[Dict[str, Any]], cache: Dict[str, Any], output
|
|
|
380
381
|
|
|
381
382
|
for row in no_part_rows:
|
|
382
383
|
bom_rows.append({
|
|
383
|
-
"
|
|
384
|
+
"likely_part": "",
|
|
384
385
|
"candidate_parts": "",
|
|
385
386
|
"amount": 0,
|
|
386
387
|
"sighting_count": 1,
|
|
@@ -397,7 +398,7 @@ def write_final_csv(results: List[Dict[str, Any]], cache: Dict[str, Any], output
|
|
|
397
398
|
})
|
|
398
399
|
|
|
399
400
|
bom_fieldnames = [
|
|
400
|
-
"
|
|
401
|
+
"likely_part",
|
|
401
402
|
"candidate_parts",
|
|
402
403
|
"amount",
|
|
403
404
|
"sighting_count",
|
|
@@ -424,7 +425,7 @@ def parse_args() -> argparse.Namespace:
|
|
|
424
425
|
parser.add_argument("--recursive", action="store_true", help="Scan image_folder recursively")
|
|
425
426
|
parser.add_argument("--limit", type=int, default=None, help="Maximum number of images to process")
|
|
426
427
|
parser.add_argument("--skip-vision", action="store_true", help="Reuse existing output_dir/raw/*.json instead of calling vision AI")
|
|
427
|
-
parser.add_argument("--max-side", type=int, default=vision.DEFAULT_MAX_SIDE, help="Maximum resized image side")
|
|
428
|
+
parser.add_argument("--max-side", type=int, default=vision.DEFAULT_MAX_SIDE, help="Maximum resized image side; use 0 for full resolution (default)")
|
|
428
429
|
parser.add_argument("--jpeg-quality", type=int, default=vision.DEFAULT_JPEG_QUALITY, help="JPEG quality for model input")
|
|
429
430
|
return parser.parse_args()
|
|
430
431
|
|
package/vision_inventory_mcp.py
CHANGED
|
@@ -56,7 +56,7 @@ except ImportError: # pragma: no cover - compatibility fallback
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
DEFAULT_MODEL = os.getenv("WORKERS_AI_MODEL", "@cf/meta/llama-4-scout-17b-16e-instruct")
|
|
59
|
-
DEFAULT_MAX_SIDE =
|
|
59
|
+
DEFAULT_MAX_SIDE = 0
|
|
60
60
|
DEFAULT_JPEG_QUALITY = 96
|
|
61
61
|
DEFAULT_MAX_TOKENS = 1600
|
|
62
62
|
DEFAULT_TEMPERATURE = 0.05
|
|
@@ -184,8 +184,8 @@ def prepare_image_data_url(
|
|
|
184
184
|
max_side: int = DEFAULT_MAX_SIDE,
|
|
185
185
|
jpeg_quality: int = DEFAULT_JPEG_QUALITY,
|
|
186
186
|
) -> Tuple[Optional[str], Optional[Dict[str, Any]]]:
|
|
187
|
-
if max_side < 256:
|
|
188
|
-
return None, error_response("max_side must be at least 256.")
|
|
187
|
+
if max_side and max_side < 256:
|
|
188
|
+
return None, error_response("max_side must be 0 for full resolution or at least 256.")
|
|
189
189
|
|
|
190
190
|
if jpeg_quality < 1 or jpeg_quality > 100:
|
|
191
191
|
return None, error_response("jpeg_quality must be between 1 and 100.")
|
|
@@ -194,8 +194,9 @@ def prepare_image_data_url(
|
|
|
194
194
|
image = Image.open(image_path)
|
|
195
195
|
image = ImageOps.exif_transpose(image)
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
if max_side:
|
|
198
|
+
resample = getattr(Image, "Resampling", Image).LANCZOS
|
|
199
|
+
image.thumbnail((max_side, max_side), resample)
|
|
199
200
|
|
|
200
201
|
# Convert transparency to white background before JPEG encoding.
|
|
201
202
|
if image.mode in ("RGBA", "LA"):
|