Skip to content

Commit 1237708

Browse files
authored
Merge pull request #5 from Clinical-Support-Systems/MLKitInfer-NewChanges
Refactor MLKitInfer
2 parents add5244 + a70c679 commit 1237708

1 file changed

Lines changed: 128 additions & 21 deletions

File tree

src/Plugin.Maui.ML/Platforms/Android/MLKitInfer.cs

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
#if ANDROID
1+
#if ANDROID
22
using Java.Nio;
3+
using Java.Nio.Channels;
34
using Microsoft.ML.OnnxRuntime.Tensors;
45
using System.Reflection;
56
using Xamarin.TensorFlow.Lite;
@@ -14,6 +15,9 @@ public sealed class MLKitInfer : IMLInfer, IDisposable
1415
private bool _disposed;
1516
private Interpreter? _interpreter;
1617
private byte[]? _modelBytes;
18+
#pragma warning disable CS0414 // Field is assigned intentionally to keep the mapped model buffer alive for the interpreter lifetime.
19+
private ByteBuffer? _modelBuffer;
20+
#pragma warning restore CS0414
1721

1822
public void Dispose()
1923
{
@@ -44,21 +48,55 @@ public async Task LoadModelAsync(Stream modelStream, CancellationToken cancellat
4448
Initialize(ms.ToArray());
4549
}
4650

47-
public async Task LoadModelFromAssetAsync(string assetName, CancellationToken cancellationToken = default)
51+
public Task LoadModelFromAssetAsync(string assetName, CancellationToken cancellationToken = default)
4852
{
4953
if (string.IsNullOrWhiteSpace(assetName))
5054
throw new ArgumentException("Asset name cannot be null or empty", nameof(assetName));
51-
try
52-
{
53-
await using var assetStream = Application.Context.Assets?.Open(assetName)
54-
?? throw new FileNotFoundException(
55-
$"Asset '{assetName}' not found in Android assets.");
56-
await LoadModelAsync(assetStream, cancellationToken).ConfigureAwait(false);
57-
}
58-
catch (Exception ex)
55+
56+
return Task.Run(() =>
5957
{
60-
throw new InvalidOperationException($"Failed to load TFLite model asset '{assetName}': {ex.Message}", ex);
61-
}
58+
cancellationToken.ThrowIfCancellationRequested();
59+
60+
try
61+
{
62+
var assets = Application.Context.Assets
63+
?? throw new InvalidOperationException("Android AssetManager is unavailable.");
64+
65+
try
66+
{
67+
using var afd = assets.OpenFd(assetName)
68+
?? throw new FileNotFoundException(
69+
$"Asset '{assetName}' not found in Android assets.");
70+
71+
using var inputStream = new Java.IO.FileInputStream(afd.FileDescriptor);
72+
73+
var channel = inputStream.Channel
74+
?? throw new InvalidOperationException("Could not get FileChannel for asset.");
75+
76+
var mappedBuffer = channel.Map(
77+
FileChannel.MapMode.ReadOnly!,
78+
afd.StartOffset,
79+
afd.DeclaredLength);
80+
81+
Initialize(mappedBuffer);
82+
}
83+
catch (Exception ex) when (ex is not OperationCanceledException)
84+
{
85+
cancellationToken.ThrowIfCancellationRequested();
86+
87+
using var assetStream = assets.Open(assetName)
88+
?? throw new FileNotFoundException($"Asset '{assetName}' not found in Android assets.", ex);
89+
using var ms = new System.IO.MemoryStream();
90+
assetStream.CopyTo(ms);
91+
Initialize(ms.ToArray());
92+
}
93+
}
94+
catch (Exception ex) when (ex is not OperationCanceledException)
95+
{
96+
throw new InvalidOperationException(
97+
$"Failed to load TFLite model asset '{assetName}': {ex.Message}", ex);
98+
}
99+
}, cancellationToken);
62100
}
63101

64102
public Task<Dictionary<string, Tensor<float>>> RunInferenceAsync(Dictionary<string, Tensor<float>> inputs,
@@ -126,20 +164,71 @@ public Task<Dictionary<string, Tensor<float>>> RunInferenceAsync(Dictionary<stri
126164
}
127165

128166
public Task<Dictionary<string, Tensor<float>>> RunInferenceLongInputsAsync(Dictionary<string, Tensor<long>> inputs,
129-
CancellationToken cancellationToken = default)
167+
CancellationToken cancellationToken = default)
130168
{
169+
if (!IsModelLoaded) throw new InvalidOperationException("No TFLite model loaded. Call LoadModelAsync first.");
131170
if (inputs == null || inputs.Count == 0)
132171
throw new ArgumentException("Inputs cannot be null or empty", nameof(inputs));
133-
var floatInputs = new Dictionary<string, Tensor<float>>();
134-
foreach (var (k, v) in inputs)
172+
173+
return Task.Run(() =>
135174
{
136-
var cast = new DenseTensor<float>(v.Dimensions.ToArray());
137-
var arr = v.ToArray();
138-
for (var i = 0; i < arr.Length; i++) cast.Buffer.Span[i] = arr[i];
139-
floatInputs[k] = cast;
140-
}
175+
lock (_sync)
176+
{
177+
if (_interpreter == null) throw new InvalidOperationException("Interpreter disposed.");
178+
179+
var first = inputs.First();
180+
var inputTensor = first.Value;
181+
var inputIndex = 0; // assume single input
182+
183+
var flat = inputTensor.ToArray();
184+
var inputShape = inputTensor.Dimensions.ToArray();
185+
186+
try
187+
{
188+
_interpreter.ResizeInput(inputIndex, inputShape);
189+
}
190+
catch
191+
{
192+
/* ignore if immutable */
193+
}
194+
195+
_interpreter.AllocateTensors();
196+
197+
var nativeOrder = ByteOrder.NativeOrder() ?? ByteOrder.BigEndian;
198+
199+
// INT64 input buffer
200+
var inputData = ByteBuffer.AllocateDirect(flat.Length * 8).Order(nativeOrder!);
201+
inputData.AsLongBuffer()!.Put(flat);
202+
inputData.Rewind();
203+
204+
var outputTensor = _interpreter.GetOutputTensor(0);
205+
if (outputTensor == null)
206+
throw new InvalidOperationException("Failed to retrieve output tensor.");
207+
208+
var oshape = outputTensor.Shape();
209+
if (oshape == null)
210+
throw new InvalidOperationException("Failed to retrieve output shape.");
211+
212+
var outCount = 1;
213+
foreach (var d in oshape) outCount *= d;
141214

142-
return RunInferenceAsync(floatInputs, cancellationToken);
215+
// FLOAT32 output buffer
216+
var outputData = ByteBuffer.AllocateDirect(outCount * 4).Order(nativeOrder!);
217+
218+
_interpreter.Run(inputData, outputData);
219+
220+
outputData.Rewind();
221+
222+
var outputArray = new float[outCount];
223+
outputData.AsFloatBuffer()!.Get(outputArray);
224+
225+
var dense = new DenseTensor<float>(oshape.Select(i => i).ToArray());
226+
var span = dense.Buffer.Span;
227+
for (var i = 0; i < outputArray.Length; i++) span[i] = outputArray[i];
228+
229+
return new Dictionary<string, Tensor<float>> { ["output0"] = dense };
230+
}
231+
}, cancellationToken);
143232
}
144233

145234
public Dictionary<string, MLNodeMetadata> GetInputMetadata()
@@ -174,6 +263,7 @@ public void UnloadModel()
174263
_interpreter?.Dispose();
175264
_interpreter = null;
176265
_modelBytes = null;
266+
_modelBuffer = null;
177267
}
178268
}
179269

@@ -193,6 +283,23 @@ private void Initialize(byte[] bytes)
193283
_interpreter.AllocateTensors();
194284
}
195285
}
286+
private void Initialize(ByteBuffer modelBuffer)
287+
{
288+
lock (_sync)
289+
{
290+
UnloadModel();
291+
292+
_modelBytes = null;
293+
_modelBuffer = modelBuffer;
294+
295+
_interpreter = new Interpreter(
296+
modelBuffer,
297+
(Interpreter.Options?)new Interpreter.Options().SetNumThreads(
298+
Math.Max(1, Environment.ProcessorCount - 1)));
299+
300+
_interpreter.AllocateTensors();
301+
}
302+
}
196303

197304
private static Dictionary<string, MLNodeMetadata> GetTensorMetadata(Interpreter interpreter, bool isInput)
198305
{

0 commit comments

Comments
 (0)