geoai 1.0.5 → 1.0.7

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/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.7] - 2026-07-17
11
+
12
+ ### Added
13
+ - OpenAerialMap (OAM) map provider — HOT Imagery STAC + TiTiler (`provider: "oam"`)
14
+ - `examples/07-oam-quickstart` — Rome orthophoto draw-to-detect demo
15
+ - OAM option in `live-examples-nextjs` (flies to Rome; mosaic tiles)
16
+
17
+ ### Fixed
18
+ - Docs Vercel prebuild: ship `remark` deps with docs and run `generate-llms-txt` from `docs/scripts/` so module resolution works when the Vercel root is `docs/`
19
+
20
+ ## [1.0.6] - 2026-07-16
21
+
22
+ ### Added
23
+ - ChangeStar ViT-B building footprint model (`geobase/changestar-building-segmentation-vitb`)
24
+ - Building segmentation factory to route Hub `modelId` to the correct runner
25
+
26
+ ### Changed
27
+ - `building-footprint-segmentation` now defaults to ChangeStar; pass
28
+ `modelId: "geobase/building-footprint-segmentation"` for the lighter model
29
+
10
30
  ## [1.0.5] - 2026-07-15
11
31
 
12
32
  ### Added
@@ -16,9 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
36
  - WMS documentation (`wms.mdx`), map-providers table update, and 17 WMS tests (including pipeline inference)
17
37
  - Project roadmap (`_docs4devs/ROADMAP.md`) for use-case guides and quantization pipeline backlog
18
38
 
19
- ### Changed
20
- - `live-examples-nextjs` pins `geoai` to local `build/` for WMS/TMS provider development
21
-
22
39
  ## [1.0.4] - 2026-07-11
23
40
 
24
41
  ### Added
package/README.md CHANGED
@@ -40,12 +40,12 @@ You can also use GeoAI directly in the browser via CDN:
40
40
 
41
41
  #### Unpkg CDN
42
42
  ```html
43
- <script src="https://unpkg.com/geoai@1.0.5/geoai.js"></script>
43
+ <script src="https://unpkg.com/geoai@1.0.7/geoai.js"></script>
44
44
  ```
45
45
 
46
46
  #### jsDelivr CDN
47
47
  ```html
48
- <script src="https://cdn.jsdelivr.net/npm/geoai@1.0.5/geoai.min.js"></script>
48
+ <script src="https://cdn.jsdelivr.net/npm/geoai@1.0.7/geoai.min.js"></script>
49
49
  ```
50
50
 
51
51
  When using CDN, the library is available globally as `geoai`.
@@ -186,6 +186,17 @@ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
186
186
  });
187
187
  ```
188
188
 
189
+ ### OpenAerialMap (OAM)
190
+ Community aerial imagery via HOT Imagery STAC + TiTiler — no API key.
191
+
192
+ ```javascript
193
+ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
194
+ provider: "oam",
195
+ // itemId: "67826781a07cc20001818cdb", // optional pin
196
+ // mosaic: true, // optional: collection mosaic only
197
+ });
198
+ ```
199
+
189
200
  ### ESRI
190
201
  ESRI World Imagery - no API key required.
191
202
 
@@ -231,6 +242,39 @@ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
231
242
  - [Slides (PDF)](https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/lightning-talk-webgpu-geoai.pdf)
232
243
  - [Slide source (Marp)](talks/lightning-talk-webgpu-geoai.md)
233
244
 
245
+ ## Agents & LLMs
246
+
247
+ ### Install the GeoAI agent skill
248
+
249
+ Use the [Skills CLI](https://skills.sh/) so coding agents (Cursor, Claude Code, Codex, etc.) load GeoAI.js integration guidance automatically:
250
+
251
+ ```bash
252
+ # Install into the current project (recommended)
253
+ npx skills add decision-labs/geoai.js --skill geoai -y
254
+
255
+ # Or install globally for all projects
256
+ npx skills add decision-labs/geoai.js --skill geoai -g -y
257
+ ```
258
+
259
+ List what you have installed:
260
+
261
+ ```bash
262
+ npx skills list
263
+ ```
264
+
265
+ Use the skill once without installing (pipes a prompt into an agent):
266
+
267
+ ```bash
268
+ npx skills use decision-labs/geoai.js@geoai
269
+ ```
270
+
271
+ Source: [`skills/geoai`](skills/geoai) — pipeline API, task picker, providers, and Web Worker patterns for background inference.
272
+
273
+ ### LLM-readable docs
274
+
275
+ - **Index**: [docs.geobase.app/geoai/llms.txt](https://docs.geobase.app/geoai/llms.txt)
276
+ - **Full corpus**: [docs.geobase.app/geoai/llms-full.txt](https://docs.geobase.app/geoai/llms-full.txt) — regenerate with `pnpm docs:llms`
277
+
234
278
  ## Links
235
279
 
236
280
  - **Documentation**: [docs.geobase.app/geoai](https://docs.geobase.app/geoai) - Comprehensive documentation, examples, and API reference
@@ -248,4 +292,4 @@ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) f
248
292
 
249
293
  MIT License - see [LICENSE.md](LICENSE.md) for details.
250
294
 
251
- [//]: <> (Toggle CI on)
295
+ [//]: <> (Toggle CI off)
package/geoai.js CHANGED
@@ -390,7 +390,7 @@ const postProcessYoloOutput = (model_outputs, pixel_values, rawImage, id2label)
390
390
  return model_output;
391
391
  };
392
392
  const parametersChanged = (instance, model_id, providerParams, modelParams) => {
393
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
393
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
394
394
  if (instance.model_id !== model_id) {
395
395
  return true;
396
396
  }
@@ -425,6 +425,11 @@ const parametersChanged = (instance, model_id, providerParams, modelParams) => {
425
425
  return true;
426
426
  }
427
427
  break;
428
+ case "oam":
429
+ if (((_m = instance.providerParams) == null ? void 0 : _m.stacUrl) !== (providerParams == null ? void 0 : providerParams.stacUrl) || ((_n = instance.providerParams) == null ? void 0 : _n.rasterUrl) !== (providerParams == null ? void 0 : providerParams.rasterUrl) || ((_o = instance.providerParams) == null ? void 0 : _o.collection) !== (providerParams == null ? void 0 : providerParams.collection) || ((_p = instance.providerParams) == null ? void 0 : _p.asset) !== (providerParams == null ? void 0 : providerParams.asset) || ((_q = instance.providerParams) == null ? void 0 : _q.itemId) !== (providerParams == null ? void 0 : providerParams.itemId) || ((_r = instance.providerParams) == null ? void 0 : _r.mosaic) !== (providerParams == null ? void 0 : providerParams.mosaic)) {
430
+ return true;
431
+ }
432
+ break;
428
433
  }
429
434
  if (modelParams) {
430
435
  const instanceModelParams = instance.modelParams || {};
@@ -1380,6 +1385,175 @@ class Wms extends MapSource {
1380
1385
  );
1381
1386
  }
1382
1387
  }
1388
+ const DEFAULT_OAM_STAC_URL = "https://api.imagery.hotosm.org/stac";
1389
+ const DEFAULT_OAM_RASTER_URL = "https://api.imagery.hotosm.org/raster";
1390
+ const DEFAULT_OAM_COLLECTION = "openaerialmap";
1391
+ const DEFAULT_OAM_ASSET = "visual";
1392
+ function buildOamMosaicTileUrlTemplate(rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
1393
+ const base = rasterUrl.replace(/\/$/, "");
1394
+ return `${base}/collections/${encodeURIComponent(collection)}/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=${encodeURIComponent(asset)}`;
1395
+ }
1396
+ function buildOamItemTileUrlTemplate(itemId, rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
1397
+ const base = rasterUrl.replace(/\/$/, "");
1398
+ return `${base}/collections/${encodeURIComponent(collection)}/items/${encodeURIComponent(itemId)}/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=${encodeURIComponent(asset)}`;
1399
+ }
1400
+ function pickBestOamItem(features) {
1401
+ if (!features.length) return void 0;
1402
+ return features.slice().sort((a, b) => {
1403
+ var _a, _b, _c, _d, _e, _f;
1404
+ const gsdA = typeof ((_a = a.properties) == null ? void 0 : _a.gsd) === "number" ? a.properties.gsd : Number.POSITIVE_INFINITY;
1405
+ const gsdB = typeof ((_b = b.properties) == null ? void 0 : _b.gsd) === "number" ? b.properties.gsd : Number.POSITIVE_INFINITY;
1406
+ if (gsdA !== gsdB) return gsdA - gsdB;
1407
+ const dateA = Date.parse(
1408
+ String(((_c = a.properties) == null ? void 0 : _c.created) || ((_d = a.properties) == null ? void 0 : _d.datetime) || 0)
1409
+ );
1410
+ const dateB = Date.parse(
1411
+ String(((_e = b.properties) == null ? void 0 : _e.created) || ((_f = b.properties) == null ? void 0 : _f.datetime) || 0)
1412
+ );
1413
+ return dateB - dateA;
1414
+ })[0];
1415
+ }
1416
+ async function searchOamItems(bbox2, options = {}) {
1417
+ const stacUrl = (options.stacUrl || DEFAULT_OAM_STAC_URL).replace(/\/$/, "");
1418
+ const collection = options.collection || DEFAULT_OAM_COLLECTION;
1419
+ const limit = options.limit ?? 10;
1420
+ const fetchImpl = options.fetchImpl || fetch;
1421
+ const response = await fetchImpl(`${stacUrl}/search`, {
1422
+ method: "POST",
1423
+ headers: {
1424
+ "Content-Type": "application/json",
1425
+ Accept: "application/geo+json",
1426
+ ...options.headers || {}
1427
+ },
1428
+ body: JSON.stringify({
1429
+ collections: [collection],
1430
+ bbox: bbox2,
1431
+ limit
1432
+ })
1433
+ });
1434
+ if (!response.ok) {
1435
+ const body = await response.text().catch(() => "");
1436
+ throw new Error(
1437
+ `OAM STAC search failed (${response.status}): ${body || response.statusText}`
1438
+ );
1439
+ }
1440
+ const data = await response.json();
1441
+ return data.features || [];
1442
+ }
1443
+ class Oam extends MapSource {
1444
+ constructor(config = {}) {
1445
+ super();
1446
+ __publicField(this, "stacUrl");
1447
+ __publicField(this, "rasterUrl");
1448
+ __publicField(this, "collection");
1449
+ __publicField(this, "asset");
1450
+ __publicField(this, "itemId");
1451
+ __publicField(this, "mosaic");
1452
+ __publicField(this, "attribution");
1453
+ __publicField(this, "tileSize");
1454
+ __publicField(this, "headers");
1455
+ /** Resolved XYZ template with `{z}/{x}/{y}` placeholders. */
1456
+ __publicField(this, "tileUrlTemplate");
1457
+ __publicField(this, "resolvedForBboxKey");
1458
+ this.stacUrl = (config.stacUrl || DEFAULT_OAM_STAC_URL).replace(/\/$/, "");
1459
+ this.rasterUrl = (config.rasterUrl || DEFAULT_OAM_RASTER_URL).replace(
1460
+ /\/$/,
1461
+ ""
1462
+ );
1463
+ this.collection = config.collection || DEFAULT_OAM_COLLECTION;
1464
+ this.asset = config.asset || DEFAULT_OAM_ASSET;
1465
+ this.itemId = config.itemId;
1466
+ this.mosaic = config.mosaic === true;
1467
+ this.attribution = config.attribution || "OpenAerialMap / HOT Imagery";
1468
+ this.tileSize = config.tileSize || 256;
1469
+ this.headers = config.headers;
1470
+ if (this.itemId) {
1471
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1472
+ this.itemId,
1473
+ this.rasterUrl,
1474
+ this.collection,
1475
+ this.asset
1476
+ );
1477
+ } else if (this.mosaic) {
1478
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1479
+ this.rasterUrl,
1480
+ this.collection,
1481
+ this.asset
1482
+ );
1483
+ } else {
1484
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1485
+ this.rasterUrl,
1486
+ this.collection,
1487
+ this.asset
1488
+ );
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Resolve tile URL template for an AOI via STAC (unless mosaic/itemId pin).
1493
+ */
1494
+ async resolveTileTemplate(polygon2) {
1495
+ if (this.itemId) {
1496
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1497
+ this.itemId,
1498
+ this.rasterUrl,
1499
+ this.collection,
1500
+ this.asset
1501
+ );
1502
+ return this.tileUrlTemplate;
1503
+ }
1504
+ if (this.mosaic) {
1505
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1506
+ this.rasterUrl,
1507
+ this.collection,
1508
+ this.asset
1509
+ );
1510
+ return this.tileUrlTemplate;
1511
+ }
1512
+ const bbox$1 = bbox(polygon2);
1513
+ const bboxKey = bbox$1.map((n) => n.toFixed(6)).join(",");
1514
+ if (this.resolvedForBboxKey === bboxKey && this.tileUrlTemplate) {
1515
+ return this.tileUrlTemplate;
1516
+ }
1517
+ const features = await searchOamItems(bbox$1, {
1518
+ stacUrl: this.stacUrl,
1519
+ collection: this.collection,
1520
+ headers: this.headers
1521
+ });
1522
+ const best = pickBestOamItem(features);
1523
+ if (best == null ? void 0 : best.id) {
1524
+ this.itemId = best.id;
1525
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1526
+ best.id,
1527
+ this.rasterUrl,
1528
+ this.collection,
1529
+ this.asset
1530
+ );
1531
+ } else {
1532
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1533
+ this.rasterUrl,
1534
+ this.collection,
1535
+ this.asset
1536
+ );
1537
+ }
1538
+ this.resolvedForBboxKey = bboxKey;
1539
+ return this.tileUrlTemplate;
1540
+ }
1541
+ async getImage(polygon2, bands, expression, zoomLevel, requiresSquare = false, stitch = true) {
1542
+ await this.resolveTileTemplate(polygon2);
1543
+ return super.getImage(
1544
+ polygon2,
1545
+ bands,
1546
+ expression,
1547
+ zoomLevel,
1548
+ requiresSquare,
1549
+ stitch
1550
+ );
1551
+ }
1552
+ getTileUrlFromTileCoords(tileCoords, instance) {
1553
+ const [x, y, z] = tileCoords;
1554
+ return instance.tileUrlTemplate.replaceAll("{z}", z.toString()).replaceAll("{x}", x.toString()).replaceAll("{y}", y.toString());
1555
+ }
1556
+ }
1383
1557
  class BaseModel {
1384
1558
  constructor(model_id, providerParams, modelParams) {
1385
1559
  __publicField(this, "providerParams");
@@ -1449,6 +1623,19 @@ class BaseModel {
1449
1623
  extraParams: this.providerParams.extraParams
1450
1624
  });
1451
1625
  break;
1626
+ case "oam":
1627
+ this.dataProvider = new Oam({
1628
+ stacUrl: this.providerParams.stacUrl,
1629
+ rasterUrl: this.providerParams.rasterUrl,
1630
+ collection: this.providerParams.collection,
1631
+ asset: this.providerParams.asset,
1632
+ itemId: this.providerParams.itemId,
1633
+ mosaic: this.providerParams.mosaic,
1634
+ attribution: this.providerParams.attribution,
1635
+ tileSize: this.providerParams.tileSize,
1636
+ headers: this.providerParams.headers
1637
+ });
1638
+ break;
1452
1639
  case "sentinel":
1453
1640
  throw new Error("Sentinel provider not implemented yet");
1454
1641
  default:
@@ -3109,6 +3296,377 @@ const _OilStorageTankDetection = class _OilStorageTankDetection extends BaseMode
3109
3296
  };
3110
3297
  __publicField(_OilStorageTankDetection, "instance", null);
3111
3298
  let OilStorageTankDetection = _OilStorageTankDetection;
3299
+ class BaseDenseSegmentationModel extends BaseModel {
3300
+ constructor(model_id, providerParams, modelParams) {
3301
+ super(model_id, providerParams, modelParams);
3302
+ __publicField(this, "model");
3303
+ __publicField(this, "processor");
3304
+ /** Tile edge length in pixels. */
3305
+ __publicField(this, "tileSize", 1024);
3306
+ /** Overlap between adjacent tiles for feather blending. */
3307
+ __publicField(this, "tileOverlap", 64);
3308
+ /** Default probability threshold for binarization. */
3309
+ __publicField(this, "defaultThreshold", 0.5);
3310
+ }
3311
+ async initializeModel() {
3312
+ if (this.model) return;
3313
+ this.processor = await ImageProcessor.from_pretrained(this.model_id);
3314
+ const pretrainedModel = await PreTrainedModel.from_pretrained(
3315
+ this.model_id,
3316
+ this.modelParams
3317
+ );
3318
+ this.model = pretrainedModel.sessions.model;
3319
+ }
3320
+ resolveProbabilityOutput(outputs) {
3321
+ const tensor = outputs[this.outputName];
3322
+ if (!tensor) {
3323
+ throw new Error(
3324
+ `Model output "${this.outputName}" not found; got [${Object.keys(outputs).join(", ")}]`
3325
+ );
3326
+ }
3327
+ const dims = tensor.dims;
3328
+ const raw = tensor.data ?? tensor.cpuData;
3329
+ return {
3330
+ type: tensor.type,
3331
+ dims,
3332
+ size: tensor.size,
3333
+ data: raw
3334
+ };
3335
+ }
3336
+ featherWeight(size, overlap) {
3337
+ const ramp = new Float32Array(size);
3338
+ ramp.fill(1);
3339
+ if (overlap > 0) {
3340
+ for (let i = 0; i < overlap; i++) {
3341
+ const t = i / (overlap - 1 || 1);
3342
+ ramp[i] = t;
3343
+ ramp[size - 1 - i] = t;
3344
+ }
3345
+ }
3346
+ const weights = new Float32Array(size * size);
3347
+ for (let y = 0; y < size; y++) {
3348
+ for (let x = 0; x < size; x++) {
3349
+ weights[y * size + x] = ramp[y] * ramp[x];
3350
+ }
3351
+ }
3352
+ return weights;
3353
+ }
3354
+ async runTile(tileImage) {
3355
+ if (!this.processor || !this.model) {
3356
+ throw new Error("Model or processor not initialized");
3357
+ }
3358
+ const inputs = await this.processor(tileImage);
3359
+ const pixelValues = inputs.pixel_values;
3360
+ const dataCopy = pixelValues.data instanceof Float32Array ? new Float32Array(pixelValues.data) : Float32Array.from(pixelValues.data);
3361
+ const feed = new Tensor("float32", dataCopy, pixelValues.dims);
3362
+ let outputs;
3363
+ try {
3364
+ outputs = await this.model.run({
3365
+ [this.inputName]: feed
3366
+ });
3367
+ } catch (err) {
3368
+ const detail = err instanceof Error ? err.message : typeof err === "number" ? `ONNX Runtime abort (${err}) — often WASM out-of-memory for this ${this.tileSize}px fp32 model. Try WebGPU (Chrome/Edge) or a smaller AOI.` : String(err);
3369
+ throw new Error(`Dense segmentation tile inference failed: ${detail}`);
3370
+ }
3371
+ const { data } = this.resolveProbabilityOutput(outputs);
3372
+ return {
3373
+ data,
3374
+ height: this.tileSize,
3375
+ width: this.tileSize
3376
+ };
3377
+ }
3378
+ /**
3379
+ * Overlapped tiled inference with feather blending, matching the
3380
+ * ChangeStar pipeline (tile_size / overlap / threshold).
3381
+ */
3382
+ async inferDenseMask(geoRawImage, threshold) {
3383
+ const { height, width } = geoRawImage;
3384
+ const tileSize = this.tileSize;
3385
+ const overlap = Math.min(this.tileOverlap, tileSize - 1);
3386
+ const stride = tileSize - overlap;
3387
+ const feather = this.featherWeight(tileSize, overlap);
3388
+ const accum = new Float32Array(height * width);
3389
+ const weightSum = new Float32Array(height * width);
3390
+ for (let rowOff = 0; rowOff < height; rowOff += stride) {
3391
+ for (let colOff = 0; colOff < width; colOff += stride) {
3392
+ const winH = Math.min(tileSize, height - rowOff);
3393
+ const winW = Math.min(tileSize, width - colOff);
3394
+ const croppedRaw = await geoRawImage.crop([
3395
+ colOff,
3396
+ rowOff,
3397
+ colOff + winW - 1,
3398
+ rowOff + winH - 1
3399
+ ]);
3400
+ const padH = tileSize - winH;
3401
+ const padW = tileSize - winW;
3402
+ const paddedRaw = padH > 0 || padW > 0 ? await croppedRaw.pad([0, padW, 0, padH]) : croppedRaw;
3403
+ const tileImage = GeoRawImage.fromRawImage(
3404
+ paddedRaw,
3405
+ geoRawImage.getBounds(),
3406
+ geoRawImage.getCRS()
3407
+ );
3408
+ const { data: prob } = await this.runTile(tileImage);
3409
+ for (let y = 0; y < winH; y++) {
3410
+ for (let x = 0; x < winW; x++) {
3411
+ const srcIdx = y * tileSize + x;
3412
+ const dstIdx = (rowOff + y) * width + (colOff + x);
3413
+ const w = feather[y * tileSize + x];
3414
+ accum[dstIdx] += prob[srcIdx] * w;
3415
+ weightSum[dstIdx] += w;
3416
+ }
3417
+ }
3418
+ }
3419
+ }
3420
+ const binary = new Uint8Array(height * width);
3421
+ for (let i = 0; i < height * width; i++) {
3422
+ const w = weightSum[i] === 0 ? 1 : weightSum[i];
3423
+ binary[i] = accum[i] / w > threshold ? 255 : 0;
3424
+ }
3425
+ const scale = height > 512 || width > 512 ? 2 : 1;
3426
+ const contourH = Math.max(1, Math.floor(height / scale));
3427
+ const contourW = Math.max(1, Math.floor(width / scale));
3428
+ let contourBinary = binary;
3429
+ let contourImage = geoRawImage;
3430
+ if (scale > 1) {
3431
+ contourBinary = new Uint8Array(contourH * contourW);
3432
+ for (let y = 0; y < contourH; y++) {
3433
+ for (let x = 0; x < contourW; x++) {
3434
+ contourBinary[y * contourW + x] = binary[y * scale * width + x * scale];
3435
+ }
3436
+ }
3437
+ contourImage = new GeoRawImage(
3438
+ contourBinary,
3439
+ contourW,
3440
+ contourH,
3441
+ 1,
3442
+ geoRawImage.getBounds(),
3443
+ geoRawImage.getCRS()
3444
+ );
3445
+ }
3446
+ const maskTensor = new Tensor("uint8", contourBinary, [
3447
+ 1,
3448
+ 1,
3449
+ contourH,
3450
+ contourW
3451
+ ]);
3452
+ const masksToFC = maskToGeoJSON({ mask: [maskTensor] }, contourImage);
3453
+ const features = [];
3454
+ masksToFC.forEach((fc) => {
3455
+ fc.features.forEach((feature2) => {
3456
+ feature2.properties = {
3457
+ ...feature2.properties || {},
3458
+ class: "building"
3459
+ };
3460
+ features.push(feature2);
3461
+ });
3462
+ });
3463
+ return {
3464
+ type: "FeatureCollection",
3465
+ features
3466
+ };
3467
+ }
3468
+ async inference(params) {
3469
+ const {
3470
+ inputs: { polygon: polygon2 },
3471
+ postProcessingParams: { confidenceThreshold } = {},
3472
+ mapSourceParams
3473
+ } = params;
3474
+ if (!polygon2) {
3475
+ throw new Error("Polygon input is required for dense segmentation");
3476
+ }
3477
+ if (!polygon2.geometry || polygon2.geometry.type !== "Polygon") {
3478
+ throw new Error("Input must be a valid GeoJSON Polygon feature");
3479
+ }
3480
+ if (!this.initialized) {
3481
+ await this.initialize();
3482
+ }
3483
+ if (!this.model || !this.processor || !this.dataProvider) {
3484
+ throw new Error("Model or data provider not initialized");
3485
+ }
3486
+ const geoRawImage = await this.polygonToImage(
3487
+ polygon2,
3488
+ mapSourceParams == null ? void 0 : mapSourceParams.zoomLevel,
3489
+ mapSourceParams == null ? void 0 : mapSourceParams.bands,
3490
+ mapSourceParams == null ? void 0 : mapSourceParams.expression
3491
+ );
3492
+ const threshold = typeof confidenceThreshold === "number" ? confidenceThreshold : this.defaultThreshold;
3493
+ const taskName = this.model_id.split("/").pop() ?? "dense-segmentation";
3494
+ const inferenceStartTime = performance.now();
3495
+ console.log(`[${taskName}] starting dense segmentation inference...`);
3496
+ const detections = await this.inferDenseMask(geoRawImage, threshold);
3497
+ const inferenceEndTime = performance.now();
3498
+ console.log(
3499
+ `[${taskName}] inference completed. Time taken: ${(inferenceEndTime - inferenceStartTime).toFixed(2)}ms`
3500
+ );
3501
+ return {
3502
+ detections,
3503
+ geoRawImage
3504
+ };
3505
+ }
3506
+ }
3507
+ const _ChangeStarBuildingSegmentation = class _ChangeStarBuildingSegmentation extends BaseDenseSegmentationModel {
3508
+ constructor(model_id, providerParams, modelParams) {
3509
+ super(model_id, providerParams, modelParams);
3510
+ __publicField(this, "inputName", "image");
3511
+ __publicField(this, "outputName", "building_prob");
3512
+ __publicField(this, "tileSize", 1024);
3513
+ __publicField(this, "tileOverlap", 64);
3514
+ __publicField(this, "defaultThreshold", 0.5);
3515
+ }
3516
+ static async getInstance(model_id, providerParams, modelParams) {
3517
+ if (!_ChangeStarBuildingSegmentation.instanceRef || parametersChanged(
3518
+ _ChangeStarBuildingSegmentation.instanceRef,
3519
+ model_id,
3520
+ providerParams,
3521
+ modelParams
3522
+ )) {
3523
+ _ChangeStarBuildingSegmentation.instanceRef = new _ChangeStarBuildingSegmentation(
3524
+ model_id,
3525
+ providerParams,
3526
+ modelParams
3527
+ );
3528
+ await _ChangeStarBuildingSegmentation.instanceRef.initialize();
3529
+ }
3530
+ return { instance: _ChangeStarBuildingSegmentation.instanceRef };
3531
+ }
3532
+ };
3533
+ __publicField(_ChangeStarBuildingSegmentation, "instanceRef", null);
3534
+ __publicField(_ChangeStarBuildingSegmentation, "default_huggingface_id", "geobase/changestar-building-segmentation-vitb");
3535
+ let ChangeStarBuildingSegmentation = _ChangeStarBuildingSegmentation;
3536
+ const _ImageFeatureExtraction = class _ImageFeatureExtraction extends BaseModel {
3537
+ constructor(model_id, providerParams, modelParams) {
3538
+ super(model_id, providerParams, modelParams);
3539
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3540
+ __publicField(this, "extractor");
3541
+ __publicField(this, "patchSize");
3542
+ }
3543
+ static async getInstance(model_id, providerParams, modelParams) {
3544
+ if (!_ImageFeatureExtraction.instance || parametersChanged(
3545
+ _ImageFeatureExtraction.instance,
3546
+ model_id,
3547
+ providerParams,
3548
+ modelParams
3549
+ )) {
3550
+ _ImageFeatureExtraction.instance = new _ImageFeatureExtraction(
3551
+ model_id,
3552
+ providerParams,
3553
+ modelParams
3554
+ );
3555
+ await _ImageFeatureExtraction.instance.initialize();
3556
+ }
3557
+ return { instance: _ImageFeatureExtraction.instance };
3558
+ }
3559
+ async initializeModel() {
3560
+ this.extractor = await pipeline(
3561
+ "image-feature-extraction",
3562
+ this.model_id,
3563
+ this.modelParams
3564
+ );
3565
+ this.extractor.processor.image_processor.do_resize = false;
3566
+ this.patchSize = this.extractor.model.config.patch_size;
3567
+ }
3568
+ /**
3569
+ * Performs image feature extraction on a geographic area specified by a GeoJSON polygon.
3570
+ * Extracts dense feature representations using DINOv3 and computes patch-to-patch similarities.
3571
+ *
3572
+ * @param params - Inference parameters containing polygon and processing options
3573
+ * @returns Promise<ImageFeatureExtractionResults> containing feature vectors, similarity matrix, and metadata
3574
+ * @throws {Error} If data provider, model or processor are not properly initialized
3575
+ */
3576
+ async inference(params) {
3577
+ var _a, _b;
3578
+ const {
3579
+ inputs: { polygon: polygon2 },
3580
+ postProcessingParams: { similarityThreshold = 0.5 } = {},
3581
+ mapSourceParams
3582
+ } = params;
3583
+ if (!polygon2) {
3584
+ throw new Error("Polygon input is required for feature extraction");
3585
+ }
3586
+ if (!polygon2.geometry || polygon2.geometry.type !== "Polygon") {
3587
+ throw new Error("Input must be a valid GeoJSON Polygon feature");
3588
+ }
3589
+ if (!this.initialized) {
3590
+ await this.initialize();
3591
+ }
3592
+ if (!this.extractor || !this.patchSize) {
3593
+ throw new Error("Feature extractor not properly initialized");
3594
+ }
3595
+ const geoRawImage = await this.polygonToImage(
3596
+ polygon2,
3597
+ mapSourceParams == null ? void 0 : mapSourceParams.zoomLevel,
3598
+ mapSourceParams == null ? void 0 : mapSourceParams.bands,
3599
+ mapSourceParams == null ? void 0 : mapSourceParams.expression
3600
+ );
3601
+ const inferenceStartTime = performance.now();
3602
+ console.log("[image-feature-extraction] starting inference...");
3603
+ try {
3604
+ const features = await this.extractor(geoRawImage, {
3605
+ pooling: "none"
3606
+ });
3607
+ const numRegisterTokens = this.extractor.model.config.num_register_tokens ?? 0;
3608
+ const startIndex = 1 + numRegisterTokens;
3609
+ const patchFeatures = features.slice(null, [startIndex, null]);
3610
+ const normalizedFeatures = patchFeatures.normalize(2, -1);
3611
+ const scores = await matmul(
3612
+ normalizedFeatures,
3613
+ normalizedFeatures.permute(0, 2, 1)
3614
+ );
3615
+ const similarityMatrix = (await scores.tolist())[0];
3616
+ const featureVectors = (await patchFeatures.tolist())[0];
3617
+ let filteredFeatures = featureVectors;
3618
+ let filteredSimilarityMatrix = similarityMatrix;
3619
+ if (similarityThreshold > 0) {
3620
+ const highSimilarityPatches = /* @__PURE__ */ new Set();
3621
+ for (let i = 0; i < similarityMatrix.length; i++) {
3622
+ for (let j = 0; j < similarityMatrix[i].length; j++) {
3623
+ if (i !== j && similarityMatrix[i][j] >= similarityThreshold) {
3624
+ highSimilarityPatches.add(i);
3625
+ highSimilarityPatches.add(j);
3626
+ }
3627
+ }
3628
+ }
3629
+ const patchIndices = Array.from(highSimilarityPatches).sort(
3630
+ (a, b) => a - b
3631
+ );
3632
+ filteredFeatures = patchIndices.map((i) => featureVectors[i]);
3633
+ filteredSimilarityMatrix = patchIndices.map(
3634
+ (i) => patchIndices.map((j) => similarityMatrix[i][j])
3635
+ );
3636
+ }
3637
+ const inferenceEndTime = performance.now();
3638
+ console.log(
3639
+ `[image-feature-extraction] inference completed. Time taken: ${(inferenceEndTime - inferenceStartTime).toFixed(2)}ms`
3640
+ );
3641
+ const result = {
3642
+ features: filteredFeatures,
3643
+ similarityMatrix: filteredSimilarityMatrix,
3644
+ patchSize: this.patchSize,
3645
+ geoRawImage,
3646
+ metadata: {
3647
+ numPatches: filteredFeatures.length,
3648
+ featureDimensions: ((_a = filteredFeatures[0]) == null ? void 0 : _a.length) || 0,
3649
+ modelId: this.model_id
3650
+ }
3651
+ };
3652
+ console.log("[image-feature-extraction] Result structure:", {
3653
+ featuresLength: result.features.length,
3654
+ similarityMatrixLength: result.similarityMatrix.length,
3655
+ patchSize: result.patchSize,
3656
+ geoRawImageBounds: (_b = result.geoRawImage) == null ? void 0 : _b.getBounds(),
3657
+ metadata: result.metadata
3658
+ });
3659
+ return result;
3660
+ } catch (error) {
3661
+ console.error("Feature extraction error:", error);
3662
+ throw new Error(
3663
+ `Feature extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`
3664
+ );
3665
+ }
3666
+ }
3667
+ };
3668
+ __publicField(_ImageFeatureExtraction, "instance", null);
3669
+ let ImageFeatureExtraction = _ImageFeatureExtraction;
3112
3670
  const _BuildingFootPrintSegmentation = class _BuildingFootPrintSegmentation extends BaseModel {
3113
3671
  constructor(model_id, providerParams, modelParams) {
3114
3672
  super(model_id, providerParams, modelParams);
@@ -3278,141 +3836,42 @@ const _BuildingFootPrintSegmentation = class _BuildingFootPrintSegmentation exte
3278
3836
  }
3279
3837
  };
3280
3838
  __publicField(_BuildingFootPrintSegmentation, "instance", null);
3839
+ __publicField(_BuildingFootPrintSegmentation, "default_huggingface_id", "geobase/building-footprint-segmentation");
3281
3840
  let BuildingFootPrintSegmentation = _BuildingFootPrintSegmentation;
3282
- const _ImageFeatureExtraction = class _ImageFeatureExtraction extends BaseModel {
3283
- constructor(model_id, providerParams, modelParams) {
3284
- super(model_id, providerParams, modelParams);
3285
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3286
- __publicField(this, "extractor");
3287
- __publicField(this, "patchSize");
3288
- }
3289
- static async getInstance(model_id, providerParams, modelParams) {
3290
- if (!_ImageFeatureExtraction.instance || parametersChanged(
3291
- _ImageFeatureExtraction.instance,
3292
- model_id,
3293
- providerParams,
3294
- modelParams
3295
- )) {
3296
- _ImageFeatureExtraction.instance = new _ImageFeatureExtraction(
3297
- model_id,
3298
- providerParams,
3299
- modelParams
3841
+ const buildingFootprintFactory = (params, modelId, modelParams) => BuildingFootPrintSegmentation.getInstance(modelId, params, modelParams);
3842
+ const changeStarFactory = (params, modelId, modelParams) => ChangeStarBuildingSegmentation.getInstance(
3843
+ modelId,
3844
+ params,
3845
+ modelParams ?? { dtype: "fp32" }
3846
+ );
3847
+ const buildingSegmentationFactories = {
3848
+ [BuildingFootPrintSegmentation.default_huggingface_id]: buildingFootprintFactory,
3849
+ [ChangeStarBuildingSegmentation.default_huggingface_id]: changeStarFactory
3850
+ };
3851
+ const BuildingSegmentationFactory = {
3852
+ get knownModelIds() {
3853
+ return Object.keys(buildingSegmentationFactories);
3854
+ },
3855
+ getInstance(params, modelId, modelParams) {
3856
+ const resolvedId = modelId == null ? void 0 : modelId.trim();
3857
+ if (!resolvedId) {
3858
+ return Promise.reject(
3859
+ new Error(
3860
+ `Building segmentation modelId is required. Known ids: ${this.knownModelIds.join(", ")}`
3861
+ )
3300
3862
  );
3301
- await _ImageFeatureExtraction.instance.initialize();
3302
3863
  }
3303
- return { instance: _ImageFeatureExtraction.instance };
3304
- }
3305
- async initializeModel() {
3306
- this.extractor = await pipeline(
3307
- "image-feature-extraction",
3308
- this.model_id,
3309
- this.modelParams
3310
- );
3311
- this.extractor.processor.image_processor.do_resize = false;
3312
- this.patchSize = this.extractor.model.config.patch_size;
3313
- }
3314
- /**
3315
- * Performs image feature extraction on a geographic area specified by a GeoJSON polygon.
3316
- * Extracts dense feature representations using DINOv3 and computes patch-to-patch similarities.
3317
- *
3318
- * @param params - Inference parameters containing polygon and processing options
3319
- * @returns Promise<ImageFeatureExtractionResults> containing feature vectors, similarity matrix, and metadata
3320
- * @throws {Error} If data provider, model or processor are not properly initialized
3321
- */
3322
- async inference(params) {
3323
- var _a, _b;
3324
- const {
3325
- inputs: { polygon: polygon2 },
3326
- postProcessingParams: { similarityThreshold = 0.5 } = {},
3327
- mapSourceParams
3328
- } = params;
3329
- if (!polygon2) {
3330
- throw new Error("Polygon input is required for feature extraction");
3331
- }
3332
- if (!polygon2.geometry || polygon2.geometry.type !== "Polygon") {
3333
- throw new Error("Input must be a valid GeoJSON Polygon feature");
3334
- }
3335
- if (!this.initialized) {
3336
- await this.initialize();
3337
- }
3338
- if (!this.extractor || !this.patchSize) {
3339
- throw new Error("Feature extractor not properly initialized");
3340
- }
3341
- const geoRawImage = await this.polygonToImage(
3342
- polygon2,
3343
- mapSourceParams == null ? void 0 : mapSourceParams.zoomLevel,
3344
- mapSourceParams == null ? void 0 : mapSourceParams.bands,
3345
- mapSourceParams == null ? void 0 : mapSourceParams.expression
3346
- );
3347
- const inferenceStartTime = performance.now();
3348
- console.log("[image-feature-extraction] starting inference...");
3349
- try {
3350
- const features = await this.extractor(geoRawImage, {
3351
- pooling: "none"
3352
- });
3353
- const numRegisterTokens = this.extractor.model.config.num_register_tokens ?? 0;
3354
- const startIndex = 1 + numRegisterTokens;
3355
- const patchFeatures = features.slice(null, [startIndex, null]);
3356
- const normalizedFeatures = patchFeatures.normalize(2, -1);
3357
- const scores = await matmul(
3358
- normalizedFeatures,
3359
- normalizedFeatures.permute(0, 2, 1)
3360
- );
3361
- const similarityMatrix = (await scores.tolist())[0];
3362
- const featureVectors = (await patchFeatures.tolist())[0];
3363
- let filteredFeatures = featureVectors;
3364
- let filteredSimilarityMatrix = similarityMatrix;
3365
- if (similarityThreshold > 0) {
3366
- const highSimilarityPatches = /* @__PURE__ */ new Set();
3367
- for (let i = 0; i < similarityMatrix.length; i++) {
3368
- for (let j = 0; j < similarityMatrix[i].length; j++) {
3369
- if (i !== j && similarityMatrix[i][j] >= similarityThreshold) {
3370
- highSimilarityPatches.add(i);
3371
- highSimilarityPatches.add(j);
3372
- }
3373
- }
3374
- }
3375
- const patchIndices = Array.from(highSimilarityPatches).sort(
3376
- (a, b) => a - b
3377
- );
3378
- filteredFeatures = patchIndices.map((i) => featureVectors[i]);
3379
- filteredSimilarityMatrix = patchIndices.map(
3380
- (i) => patchIndices.map((j) => similarityMatrix[i][j])
3381
- );
3382
- }
3383
- const inferenceEndTime = performance.now();
3384
- console.log(
3385
- `[image-feature-extraction] inference completed. Time taken: ${(inferenceEndTime - inferenceStartTime).toFixed(2)}ms`
3386
- );
3387
- const result = {
3388
- features: filteredFeatures,
3389
- similarityMatrix: filteredSimilarityMatrix,
3390
- patchSize: this.patchSize,
3391
- geoRawImage,
3392
- metadata: {
3393
- numPatches: filteredFeatures.length,
3394
- featureDimensions: ((_a = filteredFeatures[0]) == null ? void 0 : _a.length) || 0,
3395
- modelId: this.model_id
3396
- }
3397
- };
3398
- console.log("[image-feature-extraction] Result structure:", {
3399
- featuresLength: result.features.length,
3400
- similarityMatrixLength: result.similarityMatrix.length,
3401
- patchSize: result.patchSize,
3402
- geoRawImageBounds: (_b = result.geoRawImage) == null ? void 0 : _b.getBounds(),
3403
- metadata: result.metadata
3404
- });
3405
- return result;
3406
- } catch (error) {
3407
- console.error("Feature extraction error:", error);
3408
- throw new Error(
3409
- `Feature extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`
3864
+ const create = buildingSegmentationFactories[resolvedId];
3865
+ if (!create) {
3866
+ return Promise.reject(
3867
+ new Error(
3868
+ `Unknown building segmentation modelId "${resolvedId}". Known ids: ${this.knownModelIds.join(", ")}`
3869
+ )
3410
3870
  );
3411
3871
  }
3872
+ return create(params, resolvedId, modelParams);
3412
3873
  }
3413
3874
  };
3414
- __publicField(_ImageFeatureExtraction, "instance", null);
3415
- let ImageFeatureExtraction = _ImageFeatureExtraction;
3416
3875
  const modelRegistry = [
3417
3876
  {
3418
3877
  task: "zero-shot-object-detection",
@@ -3597,20 +4056,14 @@ const modelRegistry = [
3597
4056
  {
3598
4057
  task: "building-footprint-segmentation",
3599
4058
  library: "geoai",
3600
- description: "Segments the precise outlines (footprints) of buildings in imagery. Useful for mapping, urban planning, or disaster assessment.",
4059
+ description: "Segments the precise outlines (footprints) of buildings in imagery. Defaults to ChangeStar ViT-B; pass modelId for the lighter footprint model. Useful for mapping, urban planning, or disaster assessment.",
3601
4060
  examples: [
3602
4061
  "Segment building footprints in this city block.",
3603
4062
  "Identify the outlines of all buildings in this image.",
3604
4063
  "Find building perimeters in this urban area."
3605
4064
  ],
3606
4065
  ioConfig: {},
3607
- geobase_ai_pipeline: (params, modelId = "geobase/building-footprint-segmentation", modelParams) => {
3608
- return BuildingFootPrintSegmentation.getInstance(
3609
- modelId,
3610
- params,
3611
- modelParams
3612
- );
3613
- }
4066
+ geobase_ai_pipeline: (params, modelId = ChangeStarBuildingSegmentation.default_huggingface_id, modelParams) => BuildingSegmentationFactory.getInstance(params, modelId, modelParams)
3614
4067
  },
3615
4068
  {
3616
4069
  task: "image-feature-extraction",
package/index.d.ts CHANGED
@@ -198,10 +198,59 @@ declare class Wms extends MapSource {
198
198
  number
199
199
  ], instance: Wms): string;
200
200
  }
201
+ export interface OamConfig {
202
+ /** STAC API root (default HOT Imagery STAC). */
203
+ stacUrl?: string;
204
+ /** TiTiler / raster API root. */
205
+ rasterUrl?: string;
206
+ /** STAC collection id. */
207
+ collection?: string;
208
+ /** Asset key used for RGB tiles (default `visual`). */
209
+ asset?: string;
210
+ /**
211
+ * Pin to a specific STAC item id. Skips search.
212
+ * Item tiles: `/collections/{collection}/items/{itemId}/tiles/...`
213
+ */
214
+ itemId?: string;
215
+ /**
216
+ * When true, use the collection mosaic tile endpoint (no STAC search).
217
+ * When false (default) and `itemId` is unset, search STAC for the AOI and
218
+ * use the best matching item's tiles (fallback to mosaic if none found).
219
+ */
220
+ mosaic?: boolean;
221
+ attribution?: string;
222
+ tileSize?: number;
223
+ headers?: Record<string, string>;
224
+ }
225
+ declare class Oam extends MapSource {
226
+ stacUrl: string;
227
+ rasterUrl: string;
228
+ collection: string;
229
+ asset: string;
230
+ itemId?: string;
231
+ mosaic: boolean;
232
+ attribution: string;
233
+ tileSize: number;
234
+ headers?: Record<string, string>;
235
+ /** Resolved XYZ template with `{z}/{x}/{y}` placeholders. */
236
+ tileUrlTemplate: string;
237
+ private resolvedForBboxKey?;
238
+ constructor(config?: OamConfig);
239
+ /**
240
+ * Resolve tile URL template for an AOI via STAC (unless mosaic/itemId pin).
241
+ */
242
+ resolveTileTemplate(polygon: GeoJSON.Feature): Promise<string>;
243
+ getImage(polygon: any, bands?: number[], expression?: string, zoomLevel?: number, requiresSquare?: boolean, stitch?: boolean): Promise<GeoRawImage | GeoRawImage[][]>;
244
+ protected getTileUrlFromTileCoords(tileCoords: [
245
+ number,
246
+ number,
247
+ number
248
+ ], instance: Oam): string;
249
+ }
201
250
  declare abstract class BaseModel {
202
251
  protected static instance: BaseModel | null;
203
252
  protected providerParams: ProviderParams;
204
- protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | undefined;
253
+ protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | Oam | undefined;
205
254
  protected model_id: string;
206
255
  protected initialized: boolean;
207
256
  protected modelParams?: PretrainedModelOptions;
@@ -447,6 +496,7 @@ declare class BuildingFootPrintSegmentation extends BaseModel {
447
496
  protected static instance: BuildingFootPrintSegmentation | null;
448
497
  protected model: ort.InferenceSession | undefined;
449
498
  protected processor: ImageProcessor | undefined;
499
+ static readonly default_huggingface_id = "geobase/building-footprint-segmentation";
450
500
  private constructor();
451
501
  static getInstance(model_id: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions): Promise<{
452
502
  instance: BuildingFootPrintSegmentation;
@@ -474,6 +524,53 @@ declare class ImageFeatureExtraction extends BaseModel {
474
524
  */
475
525
  inference(params: InferenceParams): Promise<ImageFeatureExtractionResults>;
476
526
  }
527
+ declare abstract class BaseDenseSegmentationModel extends BaseModel {
528
+ protected model: ort.InferenceSession | undefined;
529
+ protected processor: ImageProcessor | undefined;
530
+ /** ONNX input feed name (e.g. "image"). */
531
+ protected abstract readonly inputName: string;
532
+ /** Soft-probability output tensor name (e.g. "building_prob"). */
533
+ protected abstract readonly outputName: string;
534
+ /** Tile edge length in pixels. */
535
+ protected readonly tileSize: number;
536
+ /** Overlap between adjacent tiles for feather blending. */
537
+ protected readonly tileOverlap: number;
538
+ /** Default probability threshold for binarization. */
539
+ protected readonly defaultThreshold: number;
540
+ protected constructor(model_id: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions);
541
+ protected initializeModel(): Promise<void>;
542
+ protected resolveProbabilityOutput(outputs: Record<string, any>): {
543
+ type: string;
544
+ dims: number[];
545
+ size: number;
546
+ data: Float32Array;
547
+ };
548
+ protected featherWeight(size: number, overlap: number): Float32Array;
549
+ protected runTile(tileImage: GeoRawImage): Promise<{
550
+ data: Float32Array;
551
+ height: number;
552
+ width: number;
553
+ }>;
554
+ /**
555
+ * Overlapped tiled inference with feather blending, matching the
556
+ * ChangeStar pipeline (tile_size / overlap / threshold).
557
+ */
558
+ protected inferDenseMask(geoRawImage: GeoRawImage, threshold: number): Promise<GeoJSON.FeatureCollection>;
559
+ inference(params: InferenceParams): Promise<ObjectDetectionResults>;
560
+ }
561
+ declare class ChangeStarBuildingSegmentation extends BaseDenseSegmentationModel {
562
+ protected static instanceRef: ChangeStarBuildingSegmentation | null;
563
+ static readonly default_huggingface_id = "geobase/changestar-building-segmentation-vitb";
564
+ protected readonly inputName = "image";
565
+ protected readonly outputName = "building_prob";
566
+ protected readonly tileSize = 1024;
567
+ protected readonly tileOverlap = 64;
568
+ protected readonly defaultThreshold = 0.5;
569
+ private constructor();
570
+ static getInstance(model_id: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions): Promise<{
571
+ instance: ChangeStarBuildingSegmentation;
572
+ }>;
573
+ }
477
574
  export type MapboxParams = {
478
575
  provider: "mapbox";
479
576
  apiKey: string;
@@ -520,6 +617,24 @@ export type WmsParams = {
520
617
  headers?: Record<string, string>;
521
618
  extraParams?: Record<string, string>;
522
619
  };
620
+ export type OamParams = {
621
+ provider: "oam";
622
+ /** STAC API root (default https://api.imagery.hotosm.org/stac). */
623
+ stacUrl?: string;
624
+ /** Raster/TiTiler API root (default https://api.imagery.hotosm.org/raster). */
625
+ rasterUrl?: string;
626
+ /** STAC collection id (default openaerialmap). */
627
+ collection?: string;
628
+ /** Asset key for RGB tiles (default visual). */
629
+ asset?: string;
630
+ /** Pin to a specific STAC item id (skips search). */
631
+ itemId?: string;
632
+ /** Use collection mosaic tiles instead of per-item STAC search. */
633
+ mosaic?: boolean;
634
+ attribution?: string;
635
+ tileSize?: number;
636
+ headers?: Record<string, string>;
637
+ };
523
638
  export interface InferenceInputs {
524
639
  polygon: GeoJSON.Feature;
525
640
  classLabel?: string;
@@ -538,10 +653,10 @@ export interface InferenceParams {
538
653
  postProcessingParams?: PostProcessingParams;
539
654
  mapSourceParams?: MapSourceParams;
540
655
  }
541
- export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams;
656
+ export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams | OamParams;
542
657
  export type HuggingFaceModelTask = "mask-generation" | "zero-shot-object-detection" | "zero-shot-image-classification" | "object-detection" | "oriented-object-detection" | "image-feature-extraction";
543
658
  export type GeobaseAiModelTask = "damage-assessment" | "vegetation-classification" | "land-cover-classification" | "land-use-classification" | "land-cover-change-detection" | "land-use-change-detection" | "solar-panel-detection" | "ship-detection" | "car-detection" | "wetland-segmentation" | "building-detection" | "oil-storage-tank-detection" | "building-footprint-segmentation";
544
- export type ModelInstance = MaskGeneration | ZeroShotObjectDetection | ObjectDetection | OrientedObjectDetection | LandCoverClassification | SolarPanelDetection | ShipDetection | CarDetection | WetLandSegmentation | BuildingDetection | OilStorageTankDetection | BuildingFootPrintSegmentation | ImageFeatureExtraction;
659
+ export type ModelInstance = MaskGeneration | ZeroShotObjectDetection | ObjectDetection | OrientedObjectDetection | LandCoverClassification | SolarPanelDetection | ShipDetection | CarDetection | WetLandSegmentation | BuildingDetection | ChangeStarBuildingSegmentation | OilStorageTankDetection | BuildingFootPrintSegmentation | ImageFeatureExtraction;
545
660
  export type ModelConfig = {
546
661
  task: HuggingFaceModelTask | GeobaseAiModelTask;
547
662
  library: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geoai",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "A JavaScript library for running Geo AI models in frontend applications",
5
5
  "keywords": [
6
6
  "geoai",
@@ -63,6 +63,7 @@
63
63
  "test": "vitest",
64
64
  "test:coverage": "vitest --coverage",
65
65
  "test:build": "pnpm run build && vitest run test/build.test.ts",
66
+ "docs:llms": "node ./docs/scripts/generate-llms-txt.mjs",
66
67
  "lint:scripts": "eslint . --ext .ts",
67
68
  "lint:styles": "stylelint ./**/*.{css,scss}",
68
69
  "format:scripts": "prettier . --write",