call method

Future<SegmentationMask> call(
  1. Mat image, {
  2. Float32List? buffer,
})

Segments an image to separate foreground (person) from background.

The image parameter is a cv.Mat in BGR or BGRA format. It is NOT disposed by this method -- caller is responsible for disposal.

The optional buffer parameter allows reusing a pre-allocated Float32List for the tensor conversion to reduce GC pressure.

Returns a SegmentationMask with per-pixel probabilities at model output resolution.

Throws SegmentationException on:

Example:

final mat = cv.imdecode(bytes, cv.IMREAD_COLOR);
final mask = await segmenter.call(mat);
mat.dispose();

Implementation

Future<SegmentationMask> call(cv.Mat image, {Float32List? buffer}) async {
  if (_disposed) {
    throw StateError('Cannot use SelfieSegmentation after dispose()');
  }

  if (image.isEmpty) {
    throw SegmentationException(
      SegmentationError.imageDecodeFailed,
      'Input Mat is empty',
    );
  }

  if (image.cols < kMinSegmentationInputSize ||
      image.rows < kMinSegmentationInputSize) {
    throw SegmentationException(
      SegmentationError.imageTooSmall,
      'Mat ${image.cols}x${image.rows} is smaller than minimum '
      '${kMinSegmentationInputSize}x$kMinSegmentationInputSize',
    );
  }

  final ImageTensor pack = convertImageToTensor(
    image,
    outW: _inW,
    outH: _inH,
    buffer: buffer ?? _matTensorBuffer,
  );

  final CompiledModel? compiledModel = _compiledModel;
  if (compiledModel != null) {
    // Copying runAsync is the official LiteRT pattern for host-side data
    // (the C++ Write/Read API is lock+memcpy+unlock); the Metal accelerator
    // only supports MetalBufferPacked tensor buffers, so host zero-copy is
    // not available on the GPU path.
    final Float32List rawOutput;
    try {
      final List<Float32List> outputs = await compiledModel.runAsync([
        pack.tensorNHWC,
      ]);
      rawOutput = outputs[0];
    } catch (e) {
      throw SegmentationException(
        SegmentationError.inferenceFailed,
        'Inference failed: $e',
        e,
      );
    }
    return _buildMask(rawOutput, image.cols, image.rows, pack.padding);
  }

  final Float32List rawOutput;
  try {
    if (_iso == null) {
      _inputBuf.setAll(0, pack.tensorNHWC);
      _itp!.invoke();
      rawOutput = Float32List.fromList(_outputBuf);
    } else {
      fillNHWC4D(pack.tensorNHWC, _input4dCache, _inH, _inW);
      final List<List<List<List<List<double>>>>> inputs = [_input4dCache];

      final Map<int, Object> outputs = <int, Object>{0: _output4dCache};

      await _iso!.runForMultipleInputs(inputs, outputs);
      rawOutput = flattenDynamicTensor(outputs[0]);
    }
  } catch (e) {
    if (!_delegateFailed && _delegate != null) {
      _delegateFailed = true;
    }
    throw SegmentationException(
      SegmentationError.inferenceFailed,
      'Inference failed: $e',
      e,
    );
  }

  return _buildMask(rawOutput, image.cols, image.rows, pack.padding);
}