geoai 1.0.5 → 1.0.6
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 +10 -3
- package/README.md +3 -3
- package/geoai.js +402 -136
- package/index.d.ts +49 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.6] - 2026-07-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- ChangeStar ViT-B building footprint model (`geobase/changestar-building-segmentation-vitb`)
|
|
14
|
+
- Building segmentation factory to route Hub `modelId` to the correct runner
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `building-footprint-segmentation` now defaults to ChangeStar; pass
|
|
18
|
+
`modelId: "geobase/building-footprint-segmentation"` for the lighter model
|
|
19
|
+
|
|
10
20
|
## [1.0.5] - 2026-07-15
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -16,9 +26,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
26
|
- WMS documentation (`wms.mdx`), map-providers table update, and 17 WMS tests (including pipeline inference)
|
|
17
27
|
- Project roadmap (`_docs4devs/ROADMAP.md`) for use-case guides and quantization pipeline backlog
|
|
18
28
|
|
|
19
|
-
### Changed
|
|
20
|
-
- `live-examples-nextjs` pins `geoai` to local `build/` for WMS/TMS provider development
|
|
21
|
-
|
|
22
29
|
## [1.0.4] - 2026-07-11
|
|
23
30
|
|
|
24
31
|
### 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.
|
|
43
|
+
<script src="https://unpkg.com/geoai@1.0.6/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.
|
|
48
|
+
<script src="https://cdn.jsdelivr.net/npm/geoai@1.0.6/geoai.min.js"></script>
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
When using CDN, the library is available globally as `geoai`.
|
|
@@ -248,4 +248,4 @@ We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) f
|
|
|
248
248
|
|
|
249
249
|
MIT License - see [LICENSE.md](LICENSE.md) for details.
|
|
250
250
|
|
|
251
|
-
[//]: <> (Toggle CI
|
|
251
|
+
[//]: <> (Toggle CI off)
|
package/geoai.js
CHANGED
|
@@ -3109,6 +3109,377 @@ const _OilStorageTankDetection = class _OilStorageTankDetection extends BaseMode
|
|
|
3109
3109
|
};
|
|
3110
3110
|
__publicField(_OilStorageTankDetection, "instance", null);
|
|
3111
3111
|
let OilStorageTankDetection = _OilStorageTankDetection;
|
|
3112
|
+
class BaseDenseSegmentationModel extends BaseModel {
|
|
3113
|
+
constructor(model_id, providerParams, modelParams) {
|
|
3114
|
+
super(model_id, providerParams, modelParams);
|
|
3115
|
+
__publicField(this, "model");
|
|
3116
|
+
__publicField(this, "processor");
|
|
3117
|
+
/** Tile edge length in pixels. */
|
|
3118
|
+
__publicField(this, "tileSize", 1024);
|
|
3119
|
+
/** Overlap between adjacent tiles for feather blending. */
|
|
3120
|
+
__publicField(this, "tileOverlap", 64);
|
|
3121
|
+
/** Default probability threshold for binarization. */
|
|
3122
|
+
__publicField(this, "defaultThreshold", 0.5);
|
|
3123
|
+
}
|
|
3124
|
+
async initializeModel() {
|
|
3125
|
+
if (this.model) return;
|
|
3126
|
+
this.processor = await ImageProcessor.from_pretrained(this.model_id);
|
|
3127
|
+
const pretrainedModel = await PreTrainedModel.from_pretrained(
|
|
3128
|
+
this.model_id,
|
|
3129
|
+
this.modelParams
|
|
3130
|
+
);
|
|
3131
|
+
this.model = pretrainedModel.sessions.model;
|
|
3132
|
+
}
|
|
3133
|
+
resolveProbabilityOutput(outputs) {
|
|
3134
|
+
const tensor = outputs[this.outputName];
|
|
3135
|
+
if (!tensor) {
|
|
3136
|
+
throw new Error(
|
|
3137
|
+
`Model output "${this.outputName}" not found; got [${Object.keys(outputs).join(", ")}]`
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
const dims = tensor.dims;
|
|
3141
|
+
const raw = tensor.data ?? tensor.cpuData;
|
|
3142
|
+
return {
|
|
3143
|
+
type: tensor.type,
|
|
3144
|
+
dims,
|
|
3145
|
+
size: tensor.size,
|
|
3146
|
+
data: raw
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
featherWeight(size, overlap) {
|
|
3150
|
+
const ramp = new Float32Array(size);
|
|
3151
|
+
ramp.fill(1);
|
|
3152
|
+
if (overlap > 0) {
|
|
3153
|
+
for (let i = 0; i < overlap; i++) {
|
|
3154
|
+
const t = i / (overlap - 1 || 1);
|
|
3155
|
+
ramp[i] = t;
|
|
3156
|
+
ramp[size - 1 - i] = t;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
const weights = new Float32Array(size * size);
|
|
3160
|
+
for (let y = 0; y < size; y++) {
|
|
3161
|
+
for (let x = 0; x < size; x++) {
|
|
3162
|
+
weights[y * size + x] = ramp[y] * ramp[x];
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
return weights;
|
|
3166
|
+
}
|
|
3167
|
+
async runTile(tileImage) {
|
|
3168
|
+
if (!this.processor || !this.model) {
|
|
3169
|
+
throw new Error("Model or processor not initialized");
|
|
3170
|
+
}
|
|
3171
|
+
const inputs = await this.processor(tileImage);
|
|
3172
|
+
const pixelValues = inputs.pixel_values;
|
|
3173
|
+
const dataCopy = pixelValues.data instanceof Float32Array ? new Float32Array(pixelValues.data) : Float32Array.from(pixelValues.data);
|
|
3174
|
+
const feed = new Tensor("float32", dataCopy, pixelValues.dims);
|
|
3175
|
+
let outputs;
|
|
3176
|
+
try {
|
|
3177
|
+
outputs = await this.model.run({
|
|
3178
|
+
[this.inputName]: feed
|
|
3179
|
+
});
|
|
3180
|
+
} catch (err) {
|
|
3181
|
+
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);
|
|
3182
|
+
throw new Error(`Dense segmentation tile inference failed: ${detail}`);
|
|
3183
|
+
}
|
|
3184
|
+
const { data } = this.resolveProbabilityOutput(outputs);
|
|
3185
|
+
return {
|
|
3186
|
+
data,
|
|
3187
|
+
height: this.tileSize,
|
|
3188
|
+
width: this.tileSize
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
/**
|
|
3192
|
+
* Overlapped tiled inference with feather blending, matching the
|
|
3193
|
+
* ChangeStar pipeline (tile_size / overlap / threshold).
|
|
3194
|
+
*/
|
|
3195
|
+
async inferDenseMask(geoRawImage, threshold) {
|
|
3196
|
+
const { height, width } = geoRawImage;
|
|
3197
|
+
const tileSize = this.tileSize;
|
|
3198
|
+
const overlap = Math.min(this.tileOverlap, tileSize - 1);
|
|
3199
|
+
const stride = tileSize - overlap;
|
|
3200
|
+
const feather = this.featherWeight(tileSize, overlap);
|
|
3201
|
+
const accum = new Float32Array(height * width);
|
|
3202
|
+
const weightSum = new Float32Array(height * width);
|
|
3203
|
+
for (let rowOff = 0; rowOff < height; rowOff += stride) {
|
|
3204
|
+
for (let colOff = 0; colOff < width; colOff += stride) {
|
|
3205
|
+
const winH = Math.min(tileSize, height - rowOff);
|
|
3206
|
+
const winW = Math.min(tileSize, width - colOff);
|
|
3207
|
+
const croppedRaw = await geoRawImage.crop([
|
|
3208
|
+
colOff,
|
|
3209
|
+
rowOff,
|
|
3210
|
+
colOff + winW - 1,
|
|
3211
|
+
rowOff + winH - 1
|
|
3212
|
+
]);
|
|
3213
|
+
const padH = tileSize - winH;
|
|
3214
|
+
const padW = tileSize - winW;
|
|
3215
|
+
const paddedRaw = padH > 0 || padW > 0 ? await croppedRaw.pad([0, padW, 0, padH]) : croppedRaw;
|
|
3216
|
+
const tileImage = GeoRawImage.fromRawImage(
|
|
3217
|
+
paddedRaw,
|
|
3218
|
+
geoRawImage.getBounds(),
|
|
3219
|
+
geoRawImage.getCRS()
|
|
3220
|
+
);
|
|
3221
|
+
const { data: prob } = await this.runTile(tileImage);
|
|
3222
|
+
for (let y = 0; y < winH; y++) {
|
|
3223
|
+
for (let x = 0; x < winW; x++) {
|
|
3224
|
+
const srcIdx = y * tileSize + x;
|
|
3225
|
+
const dstIdx = (rowOff + y) * width + (colOff + x);
|
|
3226
|
+
const w = feather[y * tileSize + x];
|
|
3227
|
+
accum[dstIdx] += prob[srcIdx] * w;
|
|
3228
|
+
weightSum[dstIdx] += w;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
const binary = new Uint8Array(height * width);
|
|
3234
|
+
for (let i = 0; i < height * width; i++) {
|
|
3235
|
+
const w = weightSum[i] === 0 ? 1 : weightSum[i];
|
|
3236
|
+
binary[i] = accum[i] / w > threshold ? 255 : 0;
|
|
3237
|
+
}
|
|
3238
|
+
const scale = height > 512 || width > 512 ? 2 : 1;
|
|
3239
|
+
const contourH = Math.max(1, Math.floor(height / scale));
|
|
3240
|
+
const contourW = Math.max(1, Math.floor(width / scale));
|
|
3241
|
+
let contourBinary = binary;
|
|
3242
|
+
let contourImage = geoRawImage;
|
|
3243
|
+
if (scale > 1) {
|
|
3244
|
+
contourBinary = new Uint8Array(contourH * contourW);
|
|
3245
|
+
for (let y = 0; y < contourH; y++) {
|
|
3246
|
+
for (let x = 0; x < contourW; x++) {
|
|
3247
|
+
contourBinary[y * contourW + x] = binary[y * scale * width + x * scale];
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
contourImage = new GeoRawImage(
|
|
3251
|
+
contourBinary,
|
|
3252
|
+
contourW,
|
|
3253
|
+
contourH,
|
|
3254
|
+
1,
|
|
3255
|
+
geoRawImage.getBounds(),
|
|
3256
|
+
geoRawImage.getCRS()
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
const maskTensor = new Tensor("uint8", contourBinary, [
|
|
3260
|
+
1,
|
|
3261
|
+
1,
|
|
3262
|
+
contourH,
|
|
3263
|
+
contourW
|
|
3264
|
+
]);
|
|
3265
|
+
const masksToFC = maskToGeoJSON({ mask: [maskTensor] }, contourImage);
|
|
3266
|
+
const features = [];
|
|
3267
|
+
masksToFC.forEach((fc) => {
|
|
3268
|
+
fc.features.forEach((feature2) => {
|
|
3269
|
+
feature2.properties = {
|
|
3270
|
+
...feature2.properties || {},
|
|
3271
|
+
class: "building"
|
|
3272
|
+
};
|
|
3273
|
+
features.push(feature2);
|
|
3274
|
+
});
|
|
3275
|
+
});
|
|
3276
|
+
return {
|
|
3277
|
+
type: "FeatureCollection",
|
|
3278
|
+
features
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
async inference(params) {
|
|
3282
|
+
const {
|
|
3283
|
+
inputs: { polygon: polygon2 },
|
|
3284
|
+
postProcessingParams: { confidenceThreshold } = {},
|
|
3285
|
+
mapSourceParams
|
|
3286
|
+
} = params;
|
|
3287
|
+
if (!polygon2) {
|
|
3288
|
+
throw new Error("Polygon input is required for dense segmentation");
|
|
3289
|
+
}
|
|
3290
|
+
if (!polygon2.geometry || polygon2.geometry.type !== "Polygon") {
|
|
3291
|
+
throw new Error("Input must be a valid GeoJSON Polygon feature");
|
|
3292
|
+
}
|
|
3293
|
+
if (!this.initialized) {
|
|
3294
|
+
await this.initialize();
|
|
3295
|
+
}
|
|
3296
|
+
if (!this.model || !this.processor || !this.dataProvider) {
|
|
3297
|
+
throw new Error("Model or data provider not initialized");
|
|
3298
|
+
}
|
|
3299
|
+
const geoRawImage = await this.polygonToImage(
|
|
3300
|
+
polygon2,
|
|
3301
|
+
mapSourceParams == null ? void 0 : mapSourceParams.zoomLevel,
|
|
3302
|
+
mapSourceParams == null ? void 0 : mapSourceParams.bands,
|
|
3303
|
+
mapSourceParams == null ? void 0 : mapSourceParams.expression
|
|
3304
|
+
);
|
|
3305
|
+
const threshold = typeof confidenceThreshold === "number" ? confidenceThreshold : this.defaultThreshold;
|
|
3306
|
+
const taskName = this.model_id.split("/").pop() ?? "dense-segmentation";
|
|
3307
|
+
const inferenceStartTime = performance.now();
|
|
3308
|
+
console.log(`[${taskName}] starting dense segmentation inference...`);
|
|
3309
|
+
const detections = await this.inferDenseMask(geoRawImage, threshold);
|
|
3310
|
+
const inferenceEndTime = performance.now();
|
|
3311
|
+
console.log(
|
|
3312
|
+
`[${taskName}] inference completed. Time taken: ${(inferenceEndTime - inferenceStartTime).toFixed(2)}ms`
|
|
3313
|
+
);
|
|
3314
|
+
return {
|
|
3315
|
+
detections,
|
|
3316
|
+
geoRawImage
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
const _ChangeStarBuildingSegmentation = class _ChangeStarBuildingSegmentation extends BaseDenseSegmentationModel {
|
|
3321
|
+
constructor(model_id, providerParams, modelParams) {
|
|
3322
|
+
super(model_id, providerParams, modelParams);
|
|
3323
|
+
__publicField(this, "inputName", "image");
|
|
3324
|
+
__publicField(this, "outputName", "building_prob");
|
|
3325
|
+
__publicField(this, "tileSize", 1024);
|
|
3326
|
+
__publicField(this, "tileOverlap", 64);
|
|
3327
|
+
__publicField(this, "defaultThreshold", 0.5);
|
|
3328
|
+
}
|
|
3329
|
+
static async getInstance(model_id, providerParams, modelParams) {
|
|
3330
|
+
if (!_ChangeStarBuildingSegmentation.instanceRef || parametersChanged(
|
|
3331
|
+
_ChangeStarBuildingSegmentation.instanceRef,
|
|
3332
|
+
model_id,
|
|
3333
|
+
providerParams,
|
|
3334
|
+
modelParams
|
|
3335
|
+
)) {
|
|
3336
|
+
_ChangeStarBuildingSegmentation.instanceRef = new _ChangeStarBuildingSegmentation(
|
|
3337
|
+
model_id,
|
|
3338
|
+
providerParams,
|
|
3339
|
+
modelParams
|
|
3340
|
+
);
|
|
3341
|
+
await _ChangeStarBuildingSegmentation.instanceRef.initialize();
|
|
3342
|
+
}
|
|
3343
|
+
return { instance: _ChangeStarBuildingSegmentation.instanceRef };
|
|
3344
|
+
}
|
|
3345
|
+
};
|
|
3346
|
+
__publicField(_ChangeStarBuildingSegmentation, "instanceRef", null);
|
|
3347
|
+
__publicField(_ChangeStarBuildingSegmentation, "default_huggingface_id", "geobase/changestar-building-segmentation-vitb");
|
|
3348
|
+
let ChangeStarBuildingSegmentation = _ChangeStarBuildingSegmentation;
|
|
3349
|
+
const _ImageFeatureExtraction = class _ImageFeatureExtraction extends BaseModel {
|
|
3350
|
+
constructor(model_id, providerParams, modelParams) {
|
|
3351
|
+
super(model_id, providerParams, modelParams);
|
|
3352
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3353
|
+
__publicField(this, "extractor");
|
|
3354
|
+
__publicField(this, "patchSize");
|
|
3355
|
+
}
|
|
3356
|
+
static async getInstance(model_id, providerParams, modelParams) {
|
|
3357
|
+
if (!_ImageFeatureExtraction.instance || parametersChanged(
|
|
3358
|
+
_ImageFeatureExtraction.instance,
|
|
3359
|
+
model_id,
|
|
3360
|
+
providerParams,
|
|
3361
|
+
modelParams
|
|
3362
|
+
)) {
|
|
3363
|
+
_ImageFeatureExtraction.instance = new _ImageFeatureExtraction(
|
|
3364
|
+
model_id,
|
|
3365
|
+
providerParams,
|
|
3366
|
+
modelParams
|
|
3367
|
+
);
|
|
3368
|
+
await _ImageFeatureExtraction.instance.initialize();
|
|
3369
|
+
}
|
|
3370
|
+
return { instance: _ImageFeatureExtraction.instance };
|
|
3371
|
+
}
|
|
3372
|
+
async initializeModel() {
|
|
3373
|
+
this.extractor = await pipeline(
|
|
3374
|
+
"image-feature-extraction",
|
|
3375
|
+
this.model_id,
|
|
3376
|
+
this.modelParams
|
|
3377
|
+
);
|
|
3378
|
+
this.extractor.processor.image_processor.do_resize = false;
|
|
3379
|
+
this.patchSize = this.extractor.model.config.patch_size;
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Performs image feature extraction on a geographic area specified by a GeoJSON polygon.
|
|
3383
|
+
* Extracts dense feature representations using DINOv3 and computes patch-to-patch similarities.
|
|
3384
|
+
*
|
|
3385
|
+
* @param params - Inference parameters containing polygon and processing options
|
|
3386
|
+
* @returns Promise<ImageFeatureExtractionResults> containing feature vectors, similarity matrix, and metadata
|
|
3387
|
+
* @throws {Error} If data provider, model or processor are not properly initialized
|
|
3388
|
+
*/
|
|
3389
|
+
async inference(params) {
|
|
3390
|
+
var _a, _b;
|
|
3391
|
+
const {
|
|
3392
|
+
inputs: { polygon: polygon2 },
|
|
3393
|
+
postProcessingParams: { similarityThreshold = 0.5 } = {},
|
|
3394
|
+
mapSourceParams
|
|
3395
|
+
} = params;
|
|
3396
|
+
if (!polygon2) {
|
|
3397
|
+
throw new Error("Polygon input is required for feature extraction");
|
|
3398
|
+
}
|
|
3399
|
+
if (!polygon2.geometry || polygon2.geometry.type !== "Polygon") {
|
|
3400
|
+
throw new Error("Input must be a valid GeoJSON Polygon feature");
|
|
3401
|
+
}
|
|
3402
|
+
if (!this.initialized) {
|
|
3403
|
+
await this.initialize();
|
|
3404
|
+
}
|
|
3405
|
+
if (!this.extractor || !this.patchSize) {
|
|
3406
|
+
throw new Error("Feature extractor not properly initialized");
|
|
3407
|
+
}
|
|
3408
|
+
const geoRawImage = await this.polygonToImage(
|
|
3409
|
+
polygon2,
|
|
3410
|
+
mapSourceParams == null ? void 0 : mapSourceParams.zoomLevel,
|
|
3411
|
+
mapSourceParams == null ? void 0 : mapSourceParams.bands,
|
|
3412
|
+
mapSourceParams == null ? void 0 : mapSourceParams.expression
|
|
3413
|
+
);
|
|
3414
|
+
const inferenceStartTime = performance.now();
|
|
3415
|
+
console.log("[image-feature-extraction] starting inference...");
|
|
3416
|
+
try {
|
|
3417
|
+
const features = await this.extractor(geoRawImage, {
|
|
3418
|
+
pooling: "none"
|
|
3419
|
+
});
|
|
3420
|
+
const numRegisterTokens = this.extractor.model.config.num_register_tokens ?? 0;
|
|
3421
|
+
const startIndex = 1 + numRegisterTokens;
|
|
3422
|
+
const patchFeatures = features.slice(null, [startIndex, null]);
|
|
3423
|
+
const normalizedFeatures = patchFeatures.normalize(2, -1);
|
|
3424
|
+
const scores = await matmul(
|
|
3425
|
+
normalizedFeatures,
|
|
3426
|
+
normalizedFeatures.permute(0, 2, 1)
|
|
3427
|
+
);
|
|
3428
|
+
const similarityMatrix = (await scores.tolist())[0];
|
|
3429
|
+
const featureVectors = (await patchFeatures.tolist())[0];
|
|
3430
|
+
let filteredFeatures = featureVectors;
|
|
3431
|
+
let filteredSimilarityMatrix = similarityMatrix;
|
|
3432
|
+
if (similarityThreshold > 0) {
|
|
3433
|
+
const highSimilarityPatches = /* @__PURE__ */ new Set();
|
|
3434
|
+
for (let i = 0; i < similarityMatrix.length; i++) {
|
|
3435
|
+
for (let j = 0; j < similarityMatrix[i].length; j++) {
|
|
3436
|
+
if (i !== j && similarityMatrix[i][j] >= similarityThreshold) {
|
|
3437
|
+
highSimilarityPatches.add(i);
|
|
3438
|
+
highSimilarityPatches.add(j);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
const patchIndices = Array.from(highSimilarityPatches).sort(
|
|
3443
|
+
(a, b) => a - b
|
|
3444
|
+
);
|
|
3445
|
+
filteredFeatures = patchIndices.map((i) => featureVectors[i]);
|
|
3446
|
+
filteredSimilarityMatrix = patchIndices.map(
|
|
3447
|
+
(i) => patchIndices.map((j) => similarityMatrix[i][j])
|
|
3448
|
+
);
|
|
3449
|
+
}
|
|
3450
|
+
const inferenceEndTime = performance.now();
|
|
3451
|
+
console.log(
|
|
3452
|
+
`[image-feature-extraction] inference completed. Time taken: ${(inferenceEndTime - inferenceStartTime).toFixed(2)}ms`
|
|
3453
|
+
);
|
|
3454
|
+
const result = {
|
|
3455
|
+
features: filteredFeatures,
|
|
3456
|
+
similarityMatrix: filteredSimilarityMatrix,
|
|
3457
|
+
patchSize: this.patchSize,
|
|
3458
|
+
geoRawImage,
|
|
3459
|
+
metadata: {
|
|
3460
|
+
numPatches: filteredFeatures.length,
|
|
3461
|
+
featureDimensions: ((_a = filteredFeatures[0]) == null ? void 0 : _a.length) || 0,
|
|
3462
|
+
modelId: this.model_id
|
|
3463
|
+
}
|
|
3464
|
+
};
|
|
3465
|
+
console.log("[image-feature-extraction] Result structure:", {
|
|
3466
|
+
featuresLength: result.features.length,
|
|
3467
|
+
similarityMatrixLength: result.similarityMatrix.length,
|
|
3468
|
+
patchSize: result.patchSize,
|
|
3469
|
+
geoRawImageBounds: (_b = result.geoRawImage) == null ? void 0 : _b.getBounds(),
|
|
3470
|
+
metadata: result.metadata
|
|
3471
|
+
});
|
|
3472
|
+
return result;
|
|
3473
|
+
} catch (error) {
|
|
3474
|
+
console.error("Feature extraction error:", error);
|
|
3475
|
+
throw new Error(
|
|
3476
|
+
`Feature extraction failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
__publicField(_ImageFeatureExtraction, "instance", null);
|
|
3482
|
+
let ImageFeatureExtraction = _ImageFeatureExtraction;
|
|
3112
3483
|
const _BuildingFootPrintSegmentation = class _BuildingFootPrintSegmentation extends BaseModel {
|
|
3113
3484
|
constructor(model_id, providerParams, modelParams) {
|
|
3114
3485
|
super(model_id, providerParams, modelParams);
|
|
@@ -3278,141 +3649,42 @@ const _BuildingFootPrintSegmentation = class _BuildingFootPrintSegmentation exte
|
|
|
3278
3649
|
}
|
|
3279
3650
|
};
|
|
3280
3651
|
__publicField(_BuildingFootPrintSegmentation, "instance", null);
|
|
3652
|
+
__publicField(_BuildingFootPrintSegmentation, "default_huggingface_id", "geobase/building-footprint-segmentation");
|
|
3281
3653
|
let BuildingFootPrintSegmentation = _BuildingFootPrintSegmentation;
|
|
3282
|
-
const
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3654
|
+
const buildingFootprintFactory = (params, modelId, modelParams) => BuildingFootPrintSegmentation.getInstance(modelId, params, modelParams);
|
|
3655
|
+
const changeStarFactory = (params, modelId, modelParams) => ChangeStarBuildingSegmentation.getInstance(
|
|
3656
|
+
modelId,
|
|
3657
|
+
params,
|
|
3658
|
+
modelParams ?? { dtype: "fp32" }
|
|
3659
|
+
);
|
|
3660
|
+
const buildingSegmentationFactories = {
|
|
3661
|
+
[BuildingFootPrintSegmentation.default_huggingface_id]: buildingFootprintFactory,
|
|
3662
|
+
[ChangeStarBuildingSegmentation.default_huggingface_id]: changeStarFactory
|
|
3663
|
+
};
|
|
3664
|
+
const BuildingSegmentationFactory = {
|
|
3665
|
+
get knownModelIds() {
|
|
3666
|
+
return Object.keys(buildingSegmentationFactories);
|
|
3667
|
+
},
|
|
3668
|
+
getInstance(params, modelId, modelParams) {
|
|
3669
|
+
const resolvedId = modelId == null ? void 0 : modelId.trim();
|
|
3670
|
+
if (!resolvedId) {
|
|
3671
|
+
return Promise.reject(
|
|
3672
|
+
new Error(
|
|
3673
|
+
`Building segmentation modelId is required. Known ids: ${this.knownModelIds.join(", ")}`
|
|
3674
|
+
)
|
|
3300
3675
|
);
|
|
3301
|
-
await _ImageFeatureExtraction.instance.initialize();
|
|
3302
3676
|
}
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
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"}`
|
|
3677
|
+
const create = buildingSegmentationFactories[resolvedId];
|
|
3678
|
+
if (!create) {
|
|
3679
|
+
return Promise.reject(
|
|
3680
|
+
new Error(
|
|
3681
|
+
`Unknown building segmentation modelId "${resolvedId}". Known ids: ${this.knownModelIds.join(", ")}`
|
|
3682
|
+
)
|
|
3410
3683
|
);
|
|
3411
3684
|
}
|
|
3685
|
+
return create(params, resolvedId, modelParams);
|
|
3412
3686
|
}
|
|
3413
3687
|
};
|
|
3414
|
-
__publicField(_ImageFeatureExtraction, "instance", null);
|
|
3415
|
-
let ImageFeatureExtraction = _ImageFeatureExtraction;
|
|
3416
3688
|
const modelRegistry = [
|
|
3417
3689
|
{
|
|
3418
3690
|
task: "zero-shot-object-detection",
|
|
@@ -3597,20 +3869,14 @@ const modelRegistry = [
|
|
|
3597
3869
|
{
|
|
3598
3870
|
task: "building-footprint-segmentation",
|
|
3599
3871
|
library: "geoai",
|
|
3600
|
-
description: "Segments the precise outlines (footprints) of buildings in imagery. Useful for mapping, urban planning, or disaster assessment.",
|
|
3872
|
+
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
3873
|
examples: [
|
|
3602
3874
|
"Segment building footprints in this city block.",
|
|
3603
3875
|
"Identify the outlines of all buildings in this image.",
|
|
3604
3876
|
"Find building perimeters in this urban area."
|
|
3605
3877
|
],
|
|
3606
3878
|
ioConfig: {},
|
|
3607
|
-
geobase_ai_pipeline: (params, modelId =
|
|
3608
|
-
return BuildingFootPrintSegmentation.getInstance(
|
|
3609
|
-
modelId,
|
|
3610
|
-
params,
|
|
3611
|
-
modelParams
|
|
3612
|
-
);
|
|
3613
|
-
}
|
|
3879
|
+
geobase_ai_pipeline: (params, modelId = ChangeStarBuildingSegmentation.default_huggingface_id, modelParams) => BuildingSegmentationFactory.getInstance(params, modelId, modelParams)
|
|
3614
3880
|
},
|
|
3615
3881
|
{
|
|
3616
3882
|
task: "image-feature-extraction",
|
package/index.d.ts
CHANGED
|
@@ -447,6 +447,7 @@ declare class BuildingFootPrintSegmentation extends BaseModel {
|
|
|
447
447
|
protected static instance: BuildingFootPrintSegmentation | null;
|
|
448
448
|
protected model: ort.InferenceSession | undefined;
|
|
449
449
|
protected processor: ImageProcessor | undefined;
|
|
450
|
+
static readonly default_huggingface_id = "geobase/building-footprint-segmentation";
|
|
450
451
|
private constructor();
|
|
451
452
|
static getInstance(model_id: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions): Promise<{
|
|
452
453
|
instance: BuildingFootPrintSegmentation;
|
|
@@ -474,6 +475,53 @@ declare class ImageFeatureExtraction extends BaseModel {
|
|
|
474
475
|
*/
|
|
475
476
|
inference(params: InferenceParams): Promise<ImageFeatureExtractionResults>;
|
|
476
477
|
}
|
|
478
|
+
declare abstract class BaseDenseSegmentationModel extends BaseModel {
|
|
479
|
+
protected model: ort.InferenceSession | undefined;
|
|
480
|
+
protected processor: ImageProcessor | undefined;
|
|
481
|
+
/** ONNX input feed name (e.g. "image"). */
|
|
482
|
+
protected abstract readonly inputName: string;
|
|
483
|
+
/** Soft-probability output tensor name (e.g. "building_prob"). */
|
|
484
|
+
protected abstract readonly outputName: string;
|
|
485
|
+
/** Tile edge length in pixels. */
|
|
486
|
+
protected readonly tileSize: number;
|
|
487
|
+
/** Overlap between adjacent tiles for feather blending. */
|
|
488
|
+
protected readonly tileOverlap: number;
|
|
489
|
+
/** Default probability threshold for binarization. */
|
|
490
|
+
protected readonly defaultThreshold: number;
|
|
491
|
+
protected constructor(model_id: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions);
|
|
492
|
+
protected initializeModel(): Promise<void>;
|
|
493
|
+
protected resolveProbabilityOutput(outputs: Record<string, any>): {
|
|
494
|
+
type: string;
|
|
495
|
+
dims: number[];
|
|
496
|
+
size: number;
|
|
497
|
+
data: Float32Array;
|
|
498
|
+
};
|
|
499
|
+
protected featherWeight(size: number, overlap: number): Float32Array;
|
|
500
|
+
protected runTile(tileImage: GeoRawImage): Promise<{
|
|
501
|
+
data: Float32Array;
|
|
502
|
+
height: number;
|
|
503
|
+
width: number;
|
|
504
|
+
}>;
|
|
505
|
+
/**
|
|
506
|
+
* Overlapped tiled inference with feather blending, matching the
|
|
507
|
+
* ChangeStar pipeline (tile_size / overlap / threshold).
|
|
508
|
+
*/
|
|
509
|
+
protected inferDenseMask(geoRawImage: GeoRawImage, threshold: number): Promise<GeoJSON.FeatureCollection>;
|
|
510
|
+
inference(params: InferenceParams): Promise<ObjectDetectionResults>;
|
|
511
|
+
}
|
|
512
|
+
declare class ChangeStarBuildingSegmentation extends BaseDenseSegmentationModel {
|
|
513
|
+
protected static instanceRef: ChangeStarBuildingSegmentation | null;
|
|
514
|
+
static readonly default_huggingface_id = "geobase/changestar-building-segmentation-vitb";
|
|
515
|
+
protected readonly inputName = "image";
|
|
516
|
+
protected readonly outputName = "building_prob";
|
|
517
|
+
protected readonly tileSize = 1024;
|
|
518
|
+
protected readonly tileOverlap = 64;
|
|
519
|
+
protected readonly defaultThreshold = 0.5;
|
|
520
|
+
private constructor();
|
|
521
|
+
static getInstance(model_id: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions): Promise<{
|
|
522
|
+
instance: ChangeStarBuildingSegmentation;
|
|
523
|
+
}>;
|
|
524
|
+
}
|
|
477
525
|
export type MapboxParams = {
|
|
478
526
|
provider: "mapbox";
|
|
479
527
|
apiKey: string;
|
|
@@ -541,7 +589,7 @@ export interface InferenceParams {
|
|
|
541
589
|
export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams;
|
|
542
590
|
export type HuggingFaceModelTask = "mask-generation" | "zero-shot-object-detection" | "zero-shot-image-classification" | "object-detection" | "oriented-object-detection" | "image-feature-extraction";
|
|
543
591
|
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;
|
|
592
|
+
export type ModelInstance = MaskGeneration | ZeroShotObjectDetection | ObjectDetection | OrientedObjectDetection | LandCoverClassification | SolarPanelDetection | ShipDetection | CarDetection | WetLandSegmentation | BuildingDetection | ChangeStarBuildingSegmentation | OilStorageTankDetection | BuildingFootPrintSegmentation | ImageFeatureExtraction;
|
|
545
593
|
export type ModelConfig = {
|
|
546
594
|
task: HuggingFaceModelTask | GeobaseAiModelTask;
|
|
547
595
|
library: string;
|