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.
@@ -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
- normalized_part
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: 4000 })),
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: 4000 })),
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 4000`, and `--jpeg-quality 96`.
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 4000 --jpeg-quality 96
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 normalized part number. Multiple images, or multiple candidates from one image, can merge into one BOM row.
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
- | `normalized_part` | Final normalized part number, usually from datasheet enrichment. |
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. Resizes only if the image is larger than `max_side`.
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: 4000
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",
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
- "normalized_part": "",
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
- "normalized_part": enrichment.get("normalized_part", candidate),
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
- "normalized_part",
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("normalized_part") or row.get("candidate_part") or "").strip().upper()
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
- "normalized_part": part,
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
- "normalized_part": "",
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
- "normalized_part",
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
 
@@ -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 = 4000
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
- resample = getattr(Image, "Resampling", Image).LANCZOS
198
- image.thumbnail((max_side, max_side), resample)
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"):