@huggingface/transformers 3.0.0-alpha.9 → 3.0.0
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/README.md +33 -22
- package/dist/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/dist/transformers.cjs +2515 -2525
- package/dist/transformers.cjs.map +1 -1
- package/dist/transformers.js +3529 -3455
- package/dist/transformers.js.map +1 -1
- package/dist/transformers.min.cjs +25 -25
- package/dist/transformers.min.cjs.map +1 -1
- package/dist/transformers.min.js +39 -40
- package/dist/transformers.min.js.map +1 -1
- package/dist/transformers.min.mjs +56 -57
- package/dist/transformers.min.mjs.map +1 -1
- package/dist/transformers.mjs +2551 -2538
- package/dist/transformers.mjs.map +1 -1
- package/package.json +14 -13
- package/src/backends/onnx.js +24 -19
- package/src/configs.js +19 -4
- package/src/env.js +5 -9
- package/src/generation/logits_process.js +40 -37
- package/src/models.js +326 -514
- package/src/ops/registry.js +14 -3
- package/src/pipelines.js +5 -4
- package/src/processors.js +390 -351
- package/src/tokenizers.js +140 -175
- package/src/utils/constants.js +1 -1
- package/src/utils/core.js +12 -0
- package/src/utils/data-structures.js +13 -11
- package/src/utils/hub.js +1 -1
- package/src/utils/maths.js +14 -5
- package/src/utils/tensor.js +60 -13
- package/types/backends/onnx.d.ts +5 -2
- package/types/backends/onnx.d.ts.map +1 -1
- package/types/configs.d.ts +29 -3
- package/types/configs.d.ts.map +1 -1
- package/types/env.d.ts +4 -2
- package/types/env.d.ts.map +1 -1
- package/types/generation/logits_process.d.ts.map +1 -1
- package/types/models.d.ts +116 -289
- package/types/models.d.ts.map +1 -1
- package/types/ops/registry.d.ts +6 -6
- package/types/ops/registry.d.ts.map +1 -1
- package/types/pipelines.d.ts +1 -2
- package/types/pipelines.d.ts.map +1 -1
- package/types/processors.d.ts +55 -51
- package/types/processors.d.ts.map +1 -1
- package/types/tokenizers.d.ts +23 -32
- package/types/tokenizers.d.ts.map +1 -1
- package/types/utils/constants.d.ts +1 -1
- package/types/utils/constants.d.ts.map +1 -1
- package/types/utils/core.d.ts +7 -0
- package/types/utils/core.d.ts.map +1 -1
- package/types/utils/data-structures.d.ts +6 -6
- package/types/utils/data-structures.d.ts.map +1 -1
- package/types/utils/hub.d.ts +1 -1
- package/types/utils/hub.d.ts.map +1 -1
- package/types/utils/maths.d.ts +2 -2
- package/types/utils/maths.d.ts.map +1 -1
- package/types/utils/tensor.d.ts +27 -1
- package/types/utils/tensor.d.ts.map +1 -1
package/src/processors.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
} from './utils/maths.js';
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
import { Tensor, cat, interpolate, stack, interpolate_4d } from './utils/tensor.js';
|
|
43
|
+
import { Tensor, cat, interpolate, stack, interpolate_4d, full } from './utils/tensor.js';
|
|
44
44
|
|
|
45
45
|
import { RawImage } from './utils/image.js';
|
|
46
46
|
import {
|
|
@@ -73,7 +73,7 @@ function center_to_corners_format([centerX, centerY, width, height]) {
|
|
|
73
73
|
* @param {Tensor} outputs.logits The logits
|
|
74
74
|
* @param {Tensor} outputs.pred_boxes The predicted boxes.
|
|
75
75
|
* @param {number} [threshold=0.5] The threshold to use for the scores.
|
|
76
|
-
* @param {number
|
|
76
|
+
* @param {[number, number][]} [target_sizes=null] The sizes of the original images.
|
|
77
77
|
* @param {boolean} [is_zero_shot=false] Whether zero-shot object detection was performed.
|
|
78
78
|
* @return {Object[]} An array of objects containing the post-processed outputs.
|
|
79
79
|
* @private
|
|
@@ -150,6 +150,364 @@ function post_process_object_detection(outputs, threshold = 0.5, target_sizes =
|
|
|
150
150
|
return toReturn;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Post-processes the outputs of the model (for semantic segmentation).
|
|
156
|
+
* @param {*} outputs Raw outputs of the model.
|
|
157
|
+
* @param {[number, number][]} [target_sizes=null] List of tuples corresponding to the requested final size
|
|
158
|
+
* (height, width) of each prediction. If unset, predictions will not be resized.
|
|
159
|
+
* @returns {{segmentation: Tensor; labels: number[]}[]} The semantic segmentation maps.
|
|
160
|
+
*/
|
|
161
|
+
function post_process_semantic_segmentation(outputs, target_sizes = null) {
|
|
162
|
+
|
|
163
|
+
const logits = outputs.logits;
|
|
164
|
+
const batch_size = logits.dims[0];
|
|
165
|
+
|
|
166
|
+
if (target_sizes !== null && target_sizes.length !== batch_size) {
|
|
167
|
+
throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const toReturn = [];
|
|
171
|
+
for (let i = 0; i < batch_size; ++i) {
|
|
172
|
+
const target_size = target_sizes !== null ? target_sizes[i] : null;
|
|
173
|
+
|
|
174
|
+
let data = logits[i];
|
|
175
|
+
|
|
176
|
+
// 1. If target_size is not null, we need to resize the masks to the target size
|
|
177
|
+
if (target_size !== null) {
|
|
178
|
+
// resize the masks to the target size
|
|
179
|
+
data = interpolate(data, target_size, 'bilinear', false);
|
|
180
|
+
}
|
|
181
|
+
const [height, width] = target_size ?? data.dims.slice(-2);
|
|
182
|
+
|
|
183
|
+
const segmentation = new Tensor(
|
|
184
|
+
'int32',
|
|
185
|
+
new Int32Array(height * width),
|
|
186
|
+
[height, width]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Buffer to store current largest value
|
|
190
|
+
const buffer = data[0].data;
|
|
191
|
+
const segmentation_data = segmentation.data;
|
|
192
|
+
for (let j = 1; j < data.dims[0]; ++j) {
|
|
193
|
+
const row = data[j].data;
|
|
194
|
+
for (let k = 0; k < row.length; ++k) {
|
|
195
|
+
if (row[k] > buffer[k]) {
|
|
196
|
+
buffer[k] = row[k];
|
|
197
|
+
segmentation_data[k] = j;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Store which objects have labels
|
|
203
|
+
// This is much more efficient that creating a set of the final values
|
|
204
|
+
const hasLabel = new Array(data.dims[0]);
|
|
205
|
+
for (let j = 0; j < segmentation_data.length; ++j) {
|
|
206
|
+
const index = segmentation_data[j];
|
|
207
|
+
hasLabel[index] = index;
|
|
208
|
+
}
|
|
209
|
+
/** @type {number[]} The unique list of labels that were detected */
|
|
210
|
+
const labels = hasLabel.filter(x => x !== undefined);
|
|
211
|
+
|
|
212
|
+
toReturn.push({ segmentation, labels });
|
|
213
|
+
}
|
|
214
|
+
return toReturn;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Binarize the given masks using `object_mask_threshold`, it returns the associated values of `masks`, `scores` and `labels`.
|
|
220
|
+
* @param {Tensor} class_logits The class logits.
|
|
221
|
+
* @param {Tensor} mask_logits The mask logits.
|
|
222
|
+
* @param {number} object_mask_threshold A number between 0 and 1 used to binarize the masks.
|
|
223
|
+
* @param {number} num_labels The number of labels.
|
|
224
|
+
* @returns {[Tensor[], number[], number[]]} The binarized masks, the scores, and the labels.
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
function remove_low_and_no_objects(class_logits, mask_logits, object_mask_threshold, num_labels) {
|
|
228
|
+
|
|
229
|
+
const mask_probs_item = [];
|
|
230
|
+
const pred_scores_item = [];
|
|
231
|
+
const pred_labels_item = [];
|
|
232
|
+
|
|
233
|
+
for (let j = 0; j < class_logits.dims[0]; ++j) {
|
|
234
|
+
const cls = class_logits[j];
|
|
235
|
+
const mask = mask_logits[j];
|
|
236
|
+
|
|
237
|
+
const pred_label = max(cls.data)[1];
|
|
238
|
+
if (pred_label === num_labels) {
|
|
239
|
+
// Is the background, so we ignore it
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const scores = softmax(cls.data);
|
|
244
|
+
const pred_score = scores[pred_label];
|
|
245
|
+
if (pred_score > object_mask_threshold) {
|
|
246
|
+
mask_probs_item.push(mask);
|
|
247
|
+
pred_scores_item.push(pred_score);
|
|
248
|
+
pred_labels_item.push(pred_label);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return [mask_probs_item, pred_scores_item, pred_labels_item];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Checks whether the segment is valid or not.
|
|
257
|
+
* @param {Int32Array} mask_labels Labels for each pixel in the mask.
|
|
258
|
+
* @param {Tensor[]} mask_probs Probabilities for each pixel in the masks.
|
|
259
|
+
* @param {number} k The class id of the segment.
|
|
260
|
+
* @param {number} mask_threshold The mask threshold.
|
|
261
|
+
* @param {number} overlap_mask_area_threshold The overlap mask area threshold.
|
|
262
|
+
* @returns {[boolean, number[]]} Whether the segment is valid or not, and the indices of the valid labels.
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
function check_segment_validity(
|
|
266
|
+
mask_labels,
|
|
267
|
+
mask_probs,
|
|
268
|
+
k,
|
|
269
|
+
mask_threshold = 0.5,
|
|
270
|
+
overlap_mask_area_threshold = 0.8
|
|
271
|
+
) {
|
|
272
|
+
// mask_k is a 1D array of indices, indicating where the mask is equal to k
|
|
273
|
+
const mask_k = [];
|
|
274
|
+
let mask_k_area = 0;
|
|
275
|
+
let original_area = 0;
|
|
276
|
+
|
|
277
|
+
const mask_probs_k_data = mask_probs[k].data;
|
|
278
|
+
|
|
279
|
+
// Compute the area of all the stuff in query k
|
|
280
|
+
for (let i = 0; i < mask_labels.length; ++i) {
|
|
281
|
+
if (mask_labels[i] === k) {
|
|
282
|
+
mask_k.push(i);
|
|
283
|
+
++mask_k_area;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (mask_probs_k_data[i] >= mask_threshold) {
|
|
287
|
+
++original_area;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
let mask_exists = mask_k_area > 0 && original_area > 0;
|
|
291
|
+
|
|
292
|
+
// Eliminate disconnected tiny segments
|
|
293
|
+
if (mask_exists) {
|
|
294
|
+
// Perform additional check
|
|
295
|
+
let area_ratio = mask_k_area / original_area;
|
|
296
|
+
mask_exists = area_ratio > overlap_mask_area_threshold;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return [mask_exists, mask_k]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Computes the segments.
|
|
304
|
+
* @param {Tensor[]} mask_probs The mask probabilities.
|
|
305
|
+
* @param {number[]} pred_scores The predicted scores.
|
|
306
|
+
* @param {number[]} pred_labels The predicted labels.
|
|
307
|
+
* @param {number} mask_threshold The mask threshold.
|
|
308
|
+
* @param {number} overlap_mask_area_threshold The overlap mask area threshold.
|
|
309
|
+
* @param {Set<number>} label_ids_to_fuse The label ids to fuse.
|
|
310
|
+
* @param {number[]} target_size The target size of the image.
|
|
311
|
+
* @returns {[Tensor, Array<{id: number, label_id: number, score: number}>]} The computed segments.
|
|
312
|
+
* @private
|
|
313
|
+
*/
|
|
314
|
+
function compute_segments(
|
|
315
|
+
mask_probs,
|
|
316
|
+
pred_scores,
|
|
317
|
+
pred_labels,
|
|
318
|
+
mask_threshold,
|
|
319
|
+
overlap_mask_area_threshold,
|
|
320
|
+
label_ids_to_fuse = null,
|
|
321
|
+
target_size = null,
|
|
322
|
+
) {
|
|
323
|
+
const [height, width] = target_size ?? mask_probs[0].dims;
|
|
324
|
+
|
|
325
|
+
const segmentation = new Tensor(
|
|
326
|
+
'int32',
|
|
327
|
+
new Int32Array(height * width),
|
|
328
|
+
[height, width]
|
|
329
|
+
);
|
|
330
|
+
const segments = [];
|
|
331
|
+
|
|
332
|
+
// 1. If target_size is not null, we need to resize the masks to the target size
|
|
333
|
+
if (target_size !== null) {
|
|
334
|
+
// resize the masks to the target size
|
|
335
|
+
for (let i = 0; i < mask_probs.length; ++i) {
|
|
336
|
+
mask_probs[i] = interpolate(mask_probs[i], target_size, 'bilinear', false);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 2. Weigh each mask by its prediction score
|
|
341
|
+
// NOTE: `mask_probs` is updated in-place
|
|
342
|
+
//
|
|
343
|
+
// Temporary storage for the best label/scores for each pixel ([height, width]):
|
|
344
|
+
const mask_labels = new Int32Array(mask_probs[0].data.length);
|
|
345
|
+
const bestScores = new Float32Array(mask_probs[0].data.length);
|
|
346
|
+
|
|
347
|
+
for (let i = 0; i < mask_probs.length; ++i) {
|
|
348
|
+
let score = pred_scores[i];
|
|
349
|
+
|
|
350
|
+
const mask_probs_i_data = mask_probs[i].data;
|
|
351
|
+
|
|
352
|
+
for (let j = 0; j < mask_probs_i_data.length; ++j) {
|
|
353
|
+
mask_probs_i_data[j] *= score
|
|
354
|
+
if (mask_probs_i_data[j] > bestScores[j]) {
|
|
355
|
+
mask_labels[j] = i;
|
|
356
|
+
bestScores[j] = mask_probs_i_data[j];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let current_segment_id = 0;
|
|
362
|
+
|
|
363
|
+
// let stuff_memory_list = {}
|
|
364
|
+
const segmentation_data = segmentation.data;
|
|
365
|
+
for (let k = 0; k < pred_labels.length; ++k) {
|
|
366
|
+
const pred_class = pred_labels[k];
|
|
367
|
+
|
|
368
|
+
// TODO add `should_fuse`
|
|
369
|
+
// let should_fuse = pred_class in label_ids_to_fuse
|
|
370
|
+
|
|
371
|
+
// Check if mask exists and large enough to be a segment
|
|
372
|
+
const [mask_exists, mask_k] = check_segment_validity(
|
|
373
|
+
mask_labels,
|
|
374
|
+
mask_probs,
|
|
375
|
+
k,
|
|
376
|
+
mask_threshold,
|
|
377
|
+
overlap_mask_area_threshold
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
if (!mask_exists) {
|
|
381
|
+
// Nothing to see here
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// TODO
|
|
386
|
+
// if (pred_class in stuff_memory_list) {
|
|
387
|
+
// current_segment_id = stuff_memory_list[pred_class]
|
|
388
|
+
// } else {
|
|
389
|
+
// current_segment_id += 1;
|
|
390
|
+
// }
|
|
391
|
+
++current_segment_id;
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
// Add current object segment to final segmentation map
|
|
395
|
+
for (const index of mask_k) {
|
|
396
|
+
segmentation_data[index] = current_segment_id;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
segments.push({
|
|
400
|
+
id: current_segment_id,
|
|
401
|
+
label_id: pred_class,
|
|
402
|
+
// was_fused: should_fuse, TODO
|
|
403
|
+
score: pred_scores[k],
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
// TODO
|
|
407
|
+
// if(should_fuse){
|
|
408
|
+
// stuff_memory_list[pred_class] = current_segment_id
|
|
409
|
+
// }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return [segmentation, segments];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Post-process the model output to generate the final panoptic segmentation.
|
|
418
|
+
* @param {*} outputs The model output to post process
|
|
419
|
+
* @param {number} [threshold=0.5] The probability score threshold to keep predicted instance masks.
|
|
420
|
+
* @param {number} [mask_threshold=0.5] Threshold to use when turning the predicted masks into binary values.
|
|
421
|
+
* @param {number} [overlap_mask_area_threshold=0.8] The overlap mask area threshold to merge or discard small disconnected parts within each binary instance mask.
|
|
422
|
+
* @param {Set<number>} [label_ids_to_fuse=null] The labels in this state will have all their instances be fused together.
|
|
423
|
+
* @param {[number, number][]} [target_sizes=null] The target sizes to resize the masks to.
|
|
424
|
+
* @returns {Array<{ segmentation: Tensor, segments_info: Array<{id: number, label_id: number, score: number}>}>}
|
|
425
|
+
*/
|
|
426
|
+
function post_process_panoptic_segmentation(
|
|
427
|
+
outputs,
|
|
428
|
+
threshold = 0.5,
|
|
429
|
+
mask_threshold = 0.5,
|
|
430
|
+
overlap_mask_area_threshold = 0.8,
|
|
431
|
+
label_ids_to_fuse = null,
|
|
432
|
+
target_sizes = null,
|
|
433
|
+
) {
|
|
434
|
+
if (label_ids_to_fuse === null) {
|
|
435
|
+
console.warn("`label_ids_to_fuse` unset. No instance will be fused.")
|
|
436
|
+
label_ids_to_fuse = new Set();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const class_queries_logits = outputs.class_queries_logits ?? outputs.logits; // [batch_size, num_queries, num_classes+1]
|
|
440
|
+
const masks_queries_logits = outputs.masks_queries_logits ?? outputs.pred_masks; // [batch_size, num_queries, height, width]
|
|
441
|
+
|
|
442
|
+
const mask_probs = masks_queries_logits.sigmoid() // [batch_size, num_queries, height, width]
|
|
443
|
+
|
|
444
|
+
let [batch_size, num_queries, num_labels] = class_queries_logits.dims;
|
|
445
|
+
num_labels -= 1; // Remove last class (background)
|
|
446
|
+
|
|
447
|
+
if (target_sizes !== null && target_sizes.length !== batch_size) {
|
|
448
|
+
throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits")
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let toReturn = [];
|
|
452
|
+
for (let i = 0; i < batch_size; ++i) {
|
|
453
|
+
let target_size = target_sizes !== null ? target_sizes[i] : null;
|
|
454
|
+
|
|
455
|
+
let class_logits = class_queries_logits[i];
|
|
456
|
+
let mask_logits = mask_probs[i];
|
|
457
|
+
|
|
458
|
+
let [mask_probs_item, pred_scores_item, pred_labels_item] = remove_low_and_no_objects(class_logits, mask_logits, threshold, num_labels);
|
|
459
|
+
|
|
460
|
+
if (pred_labels_item.length === 0) {
|
|
461
|
+
// No mask found
|
|
462
|
+
let [height, width] = target_size ?? mask_logits.dims.slice(-2);
|
|
463
|
+
|
|
464
|
+
let segmentation = new Tensor(
|
|
465
|
+
'int32',
|
|
466
|
+
new Int32Array(height * width).fill(-1),
|
|
467
|
+
[height, width]
|
|
468
|
+
)
|
|
469
|
+
toReturn.push({
|
|
470
|
+
segmentation: segmentation,
|
|
471
|
+
segments_info: []
|
|
472
|
+
});
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
// Get segmentation map and segment information of batch item
|
|
478
|
+
let [segmentation, segments] = compute_segments(
|
|
479
|
+
mask_probs_item,
|
|
480
|
+
pred_scores_item,
|
|
481
|
+
pred_labels_item,
|
|
482
|
+
mask_threshold,
|
|
483
|
+
overlap_mask_area_threshold,
|
|
484
|
+
label_ids_to_fuse,
|
|
485
|
+
target_size,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
toReturn.push({
|
|
489
|
+
segmentation: segmentation,
|
|
490
|
+
segments_info: segments
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return toReturn;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Post-processes the outputs of the model (for instance segmentation).
|
|
500
|
+
* @param {*} outputs Raw outputs of the model.
|
|
501
|
+
* @param {number} [threshold=0.5] The probability score threshold to keep predicted instance masks.
|
|
502
|
+
* @param {[number, number][]} [target_sizes=null] List of tuples corresponding to the requested final size
|
|
503
|
+
* (height, width) of each prediction. If unset, predictions will not be resized.
|
|
504
|
+
* @returns {Array<{ segmentation: Tensor, segments_info: Array<{id: number, label_id: number, score: number}>}>}
|
|
505
|
+
*/
|
|
506
|
+
function post_process_instance_segmentation(outputs, threshold = 0.5, target_sizes = null) {
|
|
507
|
+
throw new Error('Not implemented yet');
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
510
|
+
|
|
153
511
|
/**
|
|
154
512
|
* Named tuple to indicate the order we are using is (height x width), even though
|
|
155
513
|
* the Graphics’ industry standard is (width x height).
|
|
@@ -726,72 +1084,19 @@ export class ImageFeatureExtractor extends FeatureExtractor {
|
|
|
726
1084
|
|
|
727
1085
|
}
|
|
728
1086
|
|
|
1087
|
+
export class SapiensFeatureExtractor extends ImageFeatureExtractor {
|
|
1088
|
+
/** @type {typeof post_process_semantic_segmentation} */
|
|
1089
|
+
post_process_semantic_segmentation(...args) {
|
|
1090
|
+
return post_process_semantic_segmentation(...args);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
729
1093
|
export class SegformerFeatureExtractor extends ImageFeatureExtractor {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
* @param {*} outputs Raw outputs of the model.
|
|
734
|
-
* @param {number[][]} [target_sizes=null] List of tuples corresponding to the requested final size
|
|
735
|
-
* (height, width) of each prediction. If unset, predictions will not be resized.
|
|
736
|
-
* @returns {{segmentation: Tensor; labels: number[]}[]} The semantic segmentation maps.
|
|
737
|
-
*/
|
|
738
|
-
post_process_semantic_segmentation(outputs, target_sizes = null) {
|
|
739
|
-
|
|
740
|
-
const logits = outputs.logits;
|
|
741
|
-
const batch_size = logits.dims[0];
|
|
742
|
-
|
|
743
|
-
if (target_sizes !== null && target_sizes.length !== batch_size) {
|
|
744
|
-
throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits")
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const toReturn = [];
|
|
748
|
-
for (let i = 0; i < batch_size; ++i) {
|
|
749
|
-
const target_size = target_sizes !== null ? target_sizes[i] : null;
|
|
750
|
-
|
|
751
|
-
let data = logits[i];
|
|
752
|
-
|
|
753
|
-
// 1. If target_size is not null, we need to resize the masks to the target size
|
|
754
|
-
if (target_size !== null) {
|
|
755
|
-
// resize the masks to the target size
|
|
756
|
-
data = interpolate(data, target_size, 'bilinear', false);
|
|
757
|
-
}
|
|
758
|
-
const [height, width] = target_size ?? data.dims.slice(-2);
|
|
759
|
-
|
|
760
|
-
const segmentation = new Tensor(
|
|
761
|
-
'int32',
|
|
762
|
-
new Int32Array(height * width),
|
|
763
|
-
[height, width]
|
|
764
|
-
);
|
|
765
|
-
|
|
766
|
-
// Buffer to store current largest value
|
|
767
|
-
const buffer = data[0].data;
|
|
768
|
-
const segmentation_data = segmentation.data;
|
|
769
|
-
for (let j = 1; j < data.dims[0]; ++j) {
|
|
770
|
-
const row = data[j].data;
|
|
771
|
-
for (let k = 0; k < row.length; ++k) {
|
|
772
|
-
if (row[k] > buffer[k]) {
|
|
773
|
-
buffer[k] = row[k];
|
|
774
|
-
segmentation_data[k] = j;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Store which objects have labels
|
|
780
|
-
// This is much more efficient that creating a set of the final values
|
|
781
|
-
const hasLabel = new Array(data.dims[0]);
|
|
782
|
-
const out = segmentation.data;
|
|
783
|
-
for (let j = 0; j < out.length; ++j) {
|
|
784
|
-
const index = out[j];
|
|
785
|
-
hasLabel[index] = index;
|
|
786
|
-
}
|
|
787
|
-
/** @type {number[]} The unique list of labels that were detected */
|
|
788
|
-
const labels = hasLabel.filter(x => x !== undefined);
|
|
789
|
-
|
|
790
|
-
toReturn.push({ segmentation, labels });
|
|
791
|
-
}
|
|
792
|
-
return toReturn;
|
|
1094
|
+
/** @type {typeof post_process_semantic_segmentation} */
|
|
1095
|
+
post_process_semantic_segmentation(...args) {
|
|
1096
|
+
return post_process_semantic_segmentation(...args);
|
|
793
1097
|
}
|
|
794
1098
|
}
|
|
1099
|
+
export class PvtImageProcessor extends ImageFeatureExtractor { }
|
|
795
1100
|
export class DPTFeatureExtractor extends ImageFeatureExtractor { }
|
|
796
1101
|
export class DPTImageProcessor extends DPTFeatureExtractor { } // NOTE: extends DPTFeatureExtractor
|
|
797
1102
|
export class BitImageProcessor extends ImageFeatureExtractor { }
|
|
@@ -862,7 +1167,7 @@ export class MobileNetV4FeatureExtractor extends ImageFeatureExtractor { }
|
|
|
862
1167
|
export class MobileViTFeatureExtractor extends ImageFeatureExtractor { }
|
|
863
1168
|
export class MobileViTImageProcessor extends MobileViTFeatureExtractor { } // NOTE extends MobileViTFeatureExtractor
|
|
864
1169
|
export class OwlViTFeatureExtractor extends ImageFeatureExtractor {
|
|
865
|
-
/** @type {post_process_object_detection} */
|
|
1170
|
+
/** @type {typeof post_process_object_detection} */
|
|
866
1171
|
post_process_object_detection(...args) {
|
|
867
1172
|
return post_process_object_detection(...args);
|
|
868
1173
|
}
|
|
@@ -870,7 +1175,7 @@ export class OwlViTFeatureExtractor extends ImageFeatureExtractor {
|
|
|
870
1175
|
export class Owlv2ImageProcessor extends OwlViTFeatureExtractor { } // NOTE extends OwlViTFeatureExtractor
|
|
871
1176
|
|
|
872
1177
|
export class RTDetrImageProcessor extends ImageFeatureExtractor {
|
|
873
|
-
/** @type {post_process_object_detection} */
|
|
1178
|
+
/** @type {typeof post_process_object_detection} */
|
|
874
1179
|
post_process_object_detection(...args) {
|
|
875
1180
|
return post_process_object_detection(...args);
|
|
876
1181
|
}
|
|
@@ -931,302 +1236,32 @@ export class DetrFeatureExtractor extends ImageFeatureExtractor {
|
|
|
931
1236
|
// TODO support different mask sizes (not just 64x64)
|
|
932
1237
|
// Currently, just fill pixel mask with 1s
|
|
933
1238
|
const maskSize = [result.pixel_values.dims[0], 64, 64];
|
|
934
|
-
const pixel_mask =
|
|
935
|
-
'int64',
|
|
936
|
-
new BigInt64Array(maskSize.reduce((a, b) => a * b)).fill(1n),
|
|
937
|
-
maskSize
|
|
938
|
-
);
|
|
1239
|
+
const pixel_mask = full(maskSize, 1n);
|
|
939
1240
|
|
|
940
1241
|
return { ...result, pixel_mask };
|
|
941
1242
|
}
|
|
942
1243
|
|
|
943
|
-
/**
|
|
944
|
-
* Post-processes the outputs of the model (for object detection).
|
|
945
|
-
* @param {Object} outputs The outputs of the model that must be post-processed
|
|
946
|
-
* @param {Tensor} outputs.logits The logits
|
|
947
|
-
* @param {Tensor} outputs.pred_boxes The predicted boxes.
|
|
948
|
-
* @return {Object[]} An array of objects containing the post-processed outputs.
|
|
949
|
-
*/
|
|
950
|
-
|
|
951
|
-
/** @type {post_process_object_detection} */
|
|
1244
|
+
/** @type {typeof post_process_object_detection} */
|
|
952
1245
|
post_process_object_detection(...args) {
|
|
953
1246
|
return post_process_object_detection(...args);
|
|
954
1247
|
}
|
|
955
1248
|
|
|
956
|
-
/**
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
* @param {Tensor} mask_logits The mask logits.
|
|
960
|
-
* @param {number} object_mask_threshold A number between 0 and 1 used to binarize the masks.
|
|
961
|
-
* @param {number} num_labels The number of labels.
|
|
962
|
-
* @returns {[Tensor[], number[], number[]]} The binarized masks, the scores, and the labels.
|
|
963
|
-
*/
|
|
964
|
-
remove_low_and_no_objects(class_logits, mask_logits, object_mask_threshold, num_labels) {
|
|
965
|
-
|
|
966
|
-
let mask_probs_item = [];
|
|
967
|
-
let pred_scores_item = [];
|
|
968
|
-
let pred_labels_item = [];
|
|
969
|
-
|
|
970
|
-
for (let j = 0; j < class_logits.dims[0]; ++j) {
|
|
971
|
-
let cls = class_logits[j];
|
|
972
|
-
let mask = mask_logits[j];
|
|
973
|
-
|
|
974
|
-
let pred_label = max(cls.data)[1];
|
|
975
|
-
if (pred_label === num_labels) {
|
|
976
|
-
// Is the background, so we ignore it
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
let scores = softmax(cls.data);
|
|
981
|
-
let pred_score = scores[pred_label];
|
|
982
|
-
if (pred_score > object_mask_threshold) {
|
|
983
|
-
mask_probs_item.push(mask);
|
|
984
|
-
pred_scores_item.push(pred_score);
|
|
985
|
-
pred_labels_item.push(pred_label);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
return [mask_probs_item, pred_scores_item, pred_labels_item];
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Checks whether the segment is valid or not.
|
|
995
|
-
* @param {Int32Array} mask_labels Labels for each pixel in the mask.
|
|
996
|
-
* @param {Tensor[]} mask_probs Probabilities for each pixel in the masks.
|
|
997
|
-
* @param {number} k The class id of the segment.
|
|
998
|
-
* @param {number} mask_threshold The mask threshold.
|
|
999
|
-
* @param {number} overlap_mask_area_threshold The overlap mask area threshold.
|
|
1000
|
-
* @returns {[boolean, number[]]} Whether the segment is valid or not, and the indices of the valid labels.
|
|
1001
|
-
*/
|
|
1002
|
-
check_segment_validity(
|
|
1003
|
-
mask_labels,
|
|
1004
|
-
mask_probs,
|
|
1005
|
-
k,
|
|
1006
|
-
mask_threshold = 0.5,
|
|
1007
|
-
overlap_mask_area_threshold = 0.8
|
|
1008
|
-
) {
|
|
1009
|
-
// mask_k is a 1D array of indices, indicating where the mask is equal to k
|
|
1010
|
-
let mask_k = [];
|
|
1011
|
-
let mask_k_area = 0;
|
|
1012
|
-
let original_area = 0;
|
|
1013
|
-
|
|
1014
|
-
const mask_probs_k_data = mask_probs[k].data;
|
|
1015
|
-
|
|
1016
|
-
// Compute the area of all the stuff in query k
|
|
1017
|
-
for (let i = 0; i < mask_labels.length; ++i) {
|
|
1018
|
-
if (mask_labels[i] === k) {
|
|
1019
|
-
mask_k.push(i);
|
|
1020
|
-
++mask_k_area;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
if (mask_probs_k_data[i] >= mask_threshold) {
|
|
1024
|
-
++original_area;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
let mask_exists = mask_k_area > 0 && original_area > 0;
|
|
1028
|
-
|
|
1029
|
-
// Eliminate disconnected tiny segments
|
|
1030
|
-
if (mask_exists) {
|
|
1031
|
-
// Perform additional check
|
|
1032
|
-
let area_ratio = mask_k_area / original_area;
|
|
1033
|
-
mask_exists = area_ratio > overlap_mask_area_threshold;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
return [mask_exists, mask_k]
|
|
1249
|
+
/** @type {typeof post_process_panoptic_segmentation} */
|
|
1250
|
+
post_process_panoptic_segmentation(...args) {
|
|
1251
|
+
return post_process_panoptic_segmentation(...args);
|
|
1037
1252
|
}
|
|
1038
1253
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
* @param {number[]} pred_scores The predicted scores.
|
|
1043
|
-
* @param {number[]} pred_labels The predicted labels.
|
|
1044
|
-
* @param {number} mask_threshold The mask threshold.
|
|
1045
|
-
* @param {number} overlap_mask_area_threshold The overlap mask area threshold.
|
|
1046
|
-
* @param {Set<number>} label_ids_to_fuse The label ids to fuse.
|
|
1047
|
-
* @param {number[]} target_size The target size of the image.
|
|
1048
|
-
* @returns {[Tensor, Array<{id: number, label_id: number, score: number}>]} The computed segments.
|
|
1049
|
-
*/
|
|
1050
|
-
compute_segments(
|
|
1051
|
-
mask_probs,
|
|
1052
|
-
pred_scores,
|
|
1053
|
-
pred_labels,
|
|
1054
|
-
mask_threshold,
|
|
1055
|
-
overlap_mask_area_threshold,
|
|
1056
|
-
label_ids_to_fuse = null,
|
|
1057
|
-
target_size = null,
|
|
1058
|
-
) {
|
|
1059
|
-
let [height, width] = target_size ?? mask_probs[0].dims;
|
|
1060
|
-
|
|
1061
|
-
let segmentation = new Tensor(
|
|
1062
|
-
'int32',
|
|
1063
|
-
new Int32Array(height * width),
|
|
1064
|
-
[height, width]
|
|
1065
|
-
);
|
|
1066
|
-
let segments = [];
|
|
1067
|
-
|
|
1068
|
-
// 1. If target_size is not null, we need to resize the masks to the target size
|
|
1069
|
-
if (target_size !== null) {
|
|
1070
|
-
// resize the masks to the target size
|
|
1071
|
-
for (let i = 0; i < mask_probs.length; ++i) {
|
|
1072
|
-
mask_probs[i] = interpolate(mask_probs[i], target_size, 'bilinear', false);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// 2. Weigh each mask by its prediction score
|
|
1077
|
-
// NOTE: `mask_probs` is updated in-place
|
|
1078
|
-
//
|
|
1079
|
-
// Temporary storage for the best label/scores for each pixel ([height, width]):
|
|
1080
|
-
let mask_labels = new Int32Array(mask_probs[0].data.length);
|
|
1081
|
-
let bestScores = new Float32Array(mask_probs[0].data.length);
|
|
1082
|
-
|
|
1083
|
-
for (let i = 0; i < mask_probs.length; ++i) {
|
|
1084
|
-
let score = pred_scores[i];
|
|
1085
|
-
|
|
1086
|
-
const mask_probs_i_data = mask_probs[i].data;
|
|
1087
|
-
|
|
1088
|
-
for (let j = 0; j < mask_probs_i_data.length; ++j) {
|
|
1089
|
-
mask_probs_i_data[j] *= score
|
|
1090
|
-
if (mask_probs_i_data[j] > bestScores[j]) {
|
|
1091
|
-
mask_labels[j] = i;
|
|
1092
|
-
bestScores[j] = mask_probs_i_data[j];
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
let current_segment_id = 0;
|
|
1098
|
-
|
|
1099
|
-
// let stuff_memory_list = {}
|
|
1100
|
-
const segmentation_data = segmentation.data;
|
|
1101
|
-
for (let k = 0; k < pred_labels.length; ++k) {
|
|
1102
|
-
let pred_class = pred_labels[k];
|
|
1103
|
-
|
|
1104
|
-
// TODO add `should_fuse`
|
|
1105
|
-
// let should_fuse = pred_class in label_ids_to_fuse
|
|
1106
|
-
|
|
1107
|
-
// Check if mask exists and large enough to be a segment
|
|
1108
|
-
let [mask_exists, mask_k] = this.check_segment_validity(
|
|
1109
|
-
mask_labels,
|
|
1110
|
-
mask_probs,
|
|
1111
|
-
k,
|
|
1112
|
-
mask_threshold,
|
|
1113
|
-
overlap_mask_area_threshold
|
|
1114
|
-
)
|
|
1115
|
-
|
|
1116
|
-
if (!mask_exists) {
|
|
1117
|
-
// Nothing to see here
|
|
1118
|
-
continue;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// TODO
|
|
1122
|
-
// if (pred_class in stuff_memory_list) {
|
|
1123
|
-
// current_segment_id = stuff_memory_list[pred_class]
|
|
1124
|
-
// } else {
|
|
1125
|
-
// current_segment_id += 1;
|
|
1126
|
-
// }
|
|
1127
|
-
++current_segment_id;
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
// Add current object segment to final segmentation map
|
|
1131
|
-
for (let index of mask_k) {
|
|
1132
|
-
segmentation_data[index] = current_segment_id;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
segments.push({
|
|
1136
|
-
id: current_segment_id,
|
|
1137
|
-
label_id: pred_class,
|
|
1138
|
-
// was_fused: should_fuse, TODO
|
|
1139
|
-
score: pred_scores[k],
|
|
1140
|
-
})
|
|
1141
|
-
|
|
1142
|
-
// TODO
|
|
1143
|
-
// if(should_fuse){
|
|
1144
|
-
// stuff_memory_list[pred_class] = current_segment_id
|
|
1145
|
-
// }
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
return [segmentation, segments];
|
|
1254
|
+
post_process_instance_segmentation() {
|
|
1255
|
+
// TODO
|
|
1256
|
+
throw Error("Not implemented yet");
|
|
1149
1257
|
}
|
|
1258
|
+
}
|
|
1150
1259
|
|
|
1151
|
-
|
|
1152
|
-
* Post-process the model output to generate the final panoptic segmentation.
|
|
1153
|
-
* @param {*} outputs The model output to post process
|
|
1154
|
-
* @param {number} [threshold=0.5] The probability score threshold to keep predicted instance masks.
|
|
1155
|
-
* @param {number} [mask_threshold=0.5] Threshold to use when turning the predicted masks into binary values.
|
|
1156
|
-
* @param {number} [overlap_mask_area_threshold=0.8] The overlap mask area threshold to merge or discard small disconnected parts within each binary instance mask.
|
|
1157
|
-
* @param {Set<number>} [label_ids_to_fuse=null] The labels in this state will have all their instances be fused together.
|
|
1158
|
-
* @param {number[][]} [target_sizes=null] The target sizes to resize the masks to.
|
|
1159
|
-
* @returns {Array<{ segmentation: Tensor, segments_info: Array<{id: number, label_id: number, score: number}>}>}
|
|
1160
|
-
*/
|
|
1161
|
-
post_process_panoptic_segmentation(
|
|
1162
|
-
outputs,
|
|
1163
|
-
threshold = 0.5,
|
|
1164
|
-
mask_threshold = 0.5,
|
|
1165
|
-
overlap_mask_area_threshold = 0.8,
|
|
1166
|
-
label_ids_to_fuse = null,
|
|
1167
|
-
target_sizes = null,
|
|
1168
|
-
) {
|
|
1169
|
-
if (label_ids_to_fuse === null) {
|
|
1170
|
-
console.warn("`label_ids_to_fuse` unset. No instance will be fused.")
|
|
1171
|
-
label_ids_to_fuse = new Set();
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
const class_queries_logits = outputs.logits; // [batch_size, num_queries, num_classes+1]
|
|
1175
|
-
const masks_queries_logits = outputs.pred_masks; // [batch_size, num_queries, height, width]
|
|
1176
|
-
|
|
1177
|
-
const mask_probs = masks_queries_logits.sigmoid() // [batch_size, num_queries, height, width]
|
|
1178
|
-
|
|
1179
|
-
let [batch_size, num_queries, num_labels] = class_queries_logits.dims;
|
|
1180
|
-
num_labels -= 1; // Remove last class (background)
|
|
1181
|
-
|
|
1182
|
-
if (target_sizes !== null && target_sizes.length !== batch_size) {
|
|
1183
|
-
throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits")
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
let toReturn = [];
|
|
1187
|
-
for (let i = 0; i < batch_size; ++i) {
|
|
1188
|
-
let target_size = target_sizes !== null ? target_sizes[i] : null;
|
|
1189
|
-
|
|
1190
|
-
let class_logits = class_queries_logits[i];
|
|
1191
|
-
let mask_logits = mask_probs[i];
|
|
1192
|
-
|
|
1193
|
-
let [mask_probs_item, pred_scores_item, pred_labels_item] = this.remove_low_and_no_objects(class_logits, mask_logits, threshold, num_labels);
|
|
1194
|
-
|
|
1195
|
-
if (pred_labels_item.length === 0) {
|
|
1196
|
-
// No mask found
|
|
1197
|
-
let [height, width] = target_size ?? mask_logits.dims.slice(-2);
|
|
1198
|
-
|
|
1199
|
-
let segmentation = new Tensor(
|
|
1200
|
-
'int32',
|
|
1201
|
-
new Int32Array(height * width).fill(-1),
|
|
1202
|
-
[height, width]
|
|
1203
|
-
)
|
|
1204
|
-
toReturn.push({
|
|
1205
|
-
segmentation: segmentation,
|
|
1206
|
-
segments_info: []
|
|
1207
|
-
});
|
|
1208
|
-
continue;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
// Get segmentation map and segment information of batch item
|
|
1213
|
-
let [segmentation, segments] = this.compute_segments(
|
|
1214
|
-
mask_probs_item,
|
|
1215
|
-
pred_scores_item,
|
|
1216
|
-
pred_labels_item,
|
|
1217
|
-
mask_threshold,
|
|
1218
|
-
overlap_mask_area_threshold,
|
|
1219
|
-
label_ids_to_fuse,
|
|
1220
|
-
target_size,
|
|
1221
|
-
)
|
|
1260
|
+
export class MaskFormerFeatureExtractor extends ImageFeatureExtractor {
|
|
1222
1261
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
})
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
return toReturn;
|
|
1262
|
+
/** @type {typeof post_process_panoptic_segmentation} */
|
|
1263
|
+
post_process_panoptic_segmentation(...args) {
|
|
1264
|
+
return post_process_panoptic_segmentation(...args);
|
|
1230
1265
|
}
|
|
1231
1266
|
|
|
1232
1267
|
post_process_instance_segmentation() {
|
|
@@ -1235,8 +1270,9 @@ export class DetrFeatureExtractor extends ImageFeatureExtractor {
|
|
|
1235
1270
|
}
|
|
1236
1271
|
}
|
|
1237
1272
|
|
|
1273
|
+
|
|
1238
1274
|
export class YolosFeatureExtractor extends ImageFeatureExtractor {
|
|
1239
|
-
/** @type {post_process_object_detection} */
|
|
1275
|
+
/** @type {typeof post_process_object_detection} */
|
|
1240
1276
|
post_process_object_detection(...args) {
|
|
1241
1277
|
return post_process_object_detection(...args);
|
|
1242
1278
|
}
|
|
@@ -2520,14 +2556,17 @@ export class AutoProcessor {
|
|
|
2520
2556
|
ConvNextFeatureExtractor,
|
|
2521
2557
|
ConvNextImageProcessor,
|
|
2522
2558
|
SegformerFeatureExtractor,
|
|
2559
|
+
SapiensFeatureExtractor,
|
|
2523
2560
|
BitImageProcessor,
|
|
2524
2561
|
DPTImageProcessor,
|
|
2525
2562
|
DPTFeatureExtractor,
|
|
2563
|
+
PvtImageProcessor,
|
|
2526
2564
|
GLPNFeatureExtractor,
|
|
2527
2565
|
BeitFeatureExtractor,
|
|
2528
2566
|
DeiTFeatureExtractor,
|
|
2529
2567
|
DetrFeatureExtractor,
|
|
2530
2568
|
RTDetrImageProcessor,
|
|
2569
|
+
MaskFormerFeatureExtractor,
|
|
2531
2570
|
YolosFeatureExtractor,
|
|
2532
2571
|
DonutFeatureExtractor,
|
|
2533
2572
|
NougatImageProcessor,
|