fix: align Rust ML service with verified cargo build
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e2e3addd66
commit
e03c8a60fe
Generated
+2274
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ edition = "2021"
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
axum = { version = "0.7", features = ["multipart"] }
|
axum = { version = "0.7", features = ["multipart"] }
|
||||||
image = "0.25"
|
image = "0.25"
|
||||||
ndarray = "0.15"
|
ndarray = "0.17"
|
||||||
ort = { version = "2.0.0-rc.10", features = ["download-binaries"] }
|
ort = { version = "2.0.0-rc.10", features = ["download-binaries", "ndarray"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "net"] }
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "net"] }
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ use std::io::Cursor;
|
|||||||
pub fn preprocess_image(bytes: &[u8], input_size: u32) -> Result<Array4<f32>, ServiceError> {
|
pub fn preprocess_image(bytes: &[u8], input_size: u32) -> Result<Array4<f32>, ServiceError> {
|
||||||
// Decode image from bytes
|
// Decode image from bytes
|
||||||
let cursor = Cursor::new(bytes);
|
let cursor = Cursor::new(bytes);
|
||||||
let reader = ImageReader::new(cursor)
|
let image = ImageReader::new(cursor)
|
||||||
.map_err(|_| ServiceError::BadRequest("Uploaded file is not a valid image".to_string()))?;
|
.with_guessed_format()
|
||||||
|
.map_err(|_| ServiceError::BadRequest("Uploaded file is not a valid image".to_string()))?
|
||||||
let image = reader
|
|
||||||
.decode()
|
.decode()
|
||||||
.map_err(|_| ServiceError::BadRequest("Uploaded file is not a valid image".to_string()))?;
|
.map_err(|_| ServiceError::BadRequest("Uploaded file is not a valid image".to_string()))?;
|
||||||
|
|
||||||
@@ -91,18 +90,9 @@ mod tests {
|
|||||||
// After resizing 2x1 to 2x2, we expect interpolation
|
// After resizing 2x1 to 2x2, we expect interpolation
|
||||||
// Check that first pixel channel values are present (at least the first row)
|
// Check that first pixel channel values are present (at least the first row)
|
||||||
// The exact values depend on interpolation, but we can verify the structure
|
// The exact values depend on interpolation, but we can verify the structure
|
||||||
let first_batch = &array.slice(ndarray::s![0, .., .., ..]);
|
assert_eq!(array[[0, 0, 0, 0]], 10.0);
|
||||||
assert_eq!(first_batch.shape(), &[2, 2, 3]);
|
assert_eq!(array[[0, 0, 0, 1]], 20.0);
|
||||||
|
assert_eq!(array[[0, 0, 0, 2]], 30.0);
|
||||||
// Verify first pixel (0, 0) has 3 channels
|
|
||||||
let pixel_0_0 = &first_batch.slice(ndarray::s![0, 0, ..]);
|
|
||||||
assert_eq!(pixel_0_0.len(), 3);
|
|
||||||
|
|
||||||
// The first pixel should be close to [10, 20, 30] (may be interpolated)
|
|
||||||
// We'll just verify it's in a reasonable range
|
|
||||||
assert!(pixel_0_0[0] >= 5.0 && pixel_0_0[0] <= 25.0);
|
|
||||||
assert!(pixel_0_0[1] >= 15.0 && pixel_0_0[1] <= 35.0);
|
|
||||||
assert!(pixel_0_0[2] >= 25.0 && pixel_0_0[2] <= 45.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::config::LABELS;
|
use crate::config::LABELS;
|
||||||
use crate::error::ServiceError;
|
use crate::error::ServiceError;
|
||||||
use ndarray::Array4;
|
use ndarray::Array4;
|
||||||
use ort::Session;
|
use ort::{session::Session, value::TensorRef};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -35,7 +35,7 @@ impl ModelService {
|
|||||||
pub fn new(model_path: &Path, input_size: u32) -> Self {
|
pub fn new(model_path: &Path, input_size: u32) -> Self {
|
||||||
let session = Session::builder()
|
let session = Session::builder()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|builder| builder.commit_from_file(model_path).ok())
|
.and_then(|mut builder| builder.commit_from_file(model_path).ok())
|
||||||
.map(Mutex::new);
|
.map(Mutex::new);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -71,21 +71,21 @@ impl ModelService {
|
|||||||
.ok_or_else(|| ServiceError::ModelUnavailable("Model is not loaded".to_string()))?;
|
.ok_or_else(|| ServiceError::ModelUnavailable("Model is not loaded".to_string()))?;
|
||||||
|
|
||||||
// Lock the session for thread-safe access
|
// Lock the session for thread-safe access
|
||||||
let session_guard = session
|
let mut session_guard = session
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
||||||
|
|
||||||
// Run inference
|
let input = TensorRef::from_array_view(&input)
|
||||||
|
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
||||||
let outputs = session_guard
|
let outputs = session_guard
|
||||||
.run(ort::inputs![input]?)
|
.run(ort::inputs![input])
|
||||||
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
||||||
|
|
||||||
// Extract output as f32 vector
|
|
||||||
let output_tensor = outputs[0]
|
let output_tensor = outputs[0]
|
||||||
.try_extract_tensor::<f32>()
|
.try_extract_tensor::<f32>()
|
||||||
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
.map_err(|_| ServiceError::PredictionFailed("Prediction failed".to_string()))?;
|
||||||
|
|
||||||
let probabilities: Vec<f32> = output_tensor.iter().copied().collect();
|
let probabilities: Vec<f32> = output_tensor.1.iter().copied().collect();
|
||||||
|
|
||||||
Self::prediction_from_probabilities(&probabilities)
|
Self::prediction_from_probabilities(&probabilities)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use crate::error::ServiceError;
|
|||||||
use crate::image::preprocess_image;
|
use crate::image::preprocess_image;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{State, Multipart},
|
extract::{State, Multipart},
|
||||||
http::StatusCode,
|
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user