use crate::error::{MlError, ModelError, Result};
use crate::models::{Model, ModelMetadata};
use oxigdal_core::buffer::RasterBuffer;
use oxigdal_core::types::RasterDataType;
use std::path::Path;
use tracing::{debug, info, warn};
#[cfg(all(feature = "coreml", target_os = "macos"))]
use objc2::rc::Retained;
#[cfg(all(feature = "coreml", target_os = "macos"))]
use objc2_core_ml::{
MLComputeUnits, MLDictionaryFeatureProvider, MLFeatureValue, MLModel, MLModelConfiguration,
MLMultiArray, MLPredictionOptions,
};
#[cfg(all(feature = "coreml", target_os = "macos"))]
use objc2_foundation::{NSArray, NSDictionary, NSError, NSNumber, NSString, NSURL};
#[cfg(all(feature = "coreml", target_os = "macos"))]
struct ThreadSafeMlModel {
model: Retained<MLModel>,
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
unsafe impl Send for ThreadSafeMlModel {}
#[cfg(all(feature = "coreml", target_os = "macos"))]
unsafe impl Sync for ThreadSafeMlModel {}
#[cfg(all(feature = "coreml", target_os = "macos"))]
impl ThreadSafeMlModel {
fn new(model: Retained<MLModel>) -> Self {
Self { model }
}
fn model(&self) -> &MLModel {
&self.model
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComputeUnit {
CpuOnly,
CpuAndGpu,
All,
NeuralEngine,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelPrecision {
Float32,
Float16,
}
#[derive(Debug, Clone)]
pub struct CoreMlConfig {
pub compute_unit: ComputeUnit,
pub allow_low_precision: bool,
pub profiling: bool,
pub precision: ModelPrecision,
}
impl Default for CoreMlConfig {
fn default() -> Self {
Self {
compute_unit: ComputeUnit::All,
allow_low_precision: true,
profiling: false,
precision: ModelPrecision::Float32,
}
}
}
impl CoreMlConfig {
#[must_use]
pub fn builder() -> CoreMlConfigBuilder {
CoreMlConfigBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct CoreMlConfigBuilder {
compute_unit: Option<ComputeUnit>,
allow_low_precision: bool,
profiling: bool,
precision: Option<ModelPrecision>,
}
impl CoreMlConfigBuilder {
#[must_use]
pub fn compute_unit(mut self, unit: ComputeUnit) -> Self {
self.compute_unit = Some(unit);
self
}
#[must_use]
pub fn allow_low_precision(mut self, allow: bool) -> Self {
self.allow_low_precision = allow;
self
}
#[must_use]
pub fn profiling(mut self, enable: bool) -> Self {
self.profiling = enable;
self
}
#[must_use]
pub fn precision(mut self, precision: ModelPrecision) -> Self {
self.precision = Some(precision);
self
}
#[must_use]
pub fn build(self) -> CoreMlConfig {
CoreMlConfig {
compute_unit: self.compute_unit.unwrap_or(ComputeUnit::All),
allow_low_precision: self.allow_low_precision,
profiling: self.profiling,
precision: self.precision.unwrap_or(ModelPrecision::Float32),
}
}
}
#[derive(Debug, Clone)]
pub struct FeatureDescription {
pub name: String,
pub feature_type: FeatureType,
pub constraint: Option<FeatureConstraint>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FeatureType {
MultiArray {
shape: Vec<usize>,
dtype: ArrayDataType,
},
Image {
width: usize,
height: usize,
format: PixelFormat,
},
String,
Int64,
Double,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrayDataType {
Float32,
Float64,
Int32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PixelFormat {
Gray8,
Rgb,
Bgr,
Rgba,
Bgra,
}
#[derive(Debug, Clone)]
pub enum FeatureConstraint {
Enumeration(Vec<String>),
Range {
min: f64,
max: f64,
},
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
pub struct CoreMlModel {
path: String,
config: CoreMlConfig,
metadata: ModelMetadata,
input_features: Vec<FeatureDescription>,
output_features: Vec<FeatureDescription>,
ml_model: ThreadSafeMlModel,
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
impl CoreMlModel {
pub fn from_file<P: AsRef<Path>>(path: P, config: CoreMlConfig) -> Result<Self> {
let path = path.as_ref();
info!("Loading CoreML model from {:?}", path);
if !path.exists() {
return Err(ModelError::NotFound {
path: path.display().to_string(),
}
.into());
}
let is_compiled = path.extension().and_then(|e| e.to_str()) == Some("mlmodelc");
debug!(
"CoreML model type: {}, compute unit: {:?}",
if is_compiled { "compiled" } else { "source" },
config.compute_unit
);
let url_string = NSString::from_str(&path.display().to_string());
let url = NSURL::fileURLWithPath(&url_string);
let ml_config = unsafe { MLModelConfiguration::new() };
match config.compute_unit {
ComputeUnit::CpuOnly => unsafe { ml_config.setComputeUnits(MLComputeUnits(0)) }, ComputeUnit::CpuAndGpu => unsafe { ml_config.setComputeUnits(MLComputeUnits(1)) }, ComputeUnit::All => unsafe { ml_config.setComputeUnits(MLComputeUnits(2)) }, ComputeUnit::NeuralEngine => unsafe { ml_config.setComputeUnits(MLComputeUnits(3)) }, }
if config.allow_low_precision {
unsafe { ml_config.setAllowLowPrecisionAccumulationOnGPU(true) };
}
let ml_model = unsafe {
MLModel::modelWithContentsOfURL_configuration_error(&url, &ml_config).map_err(|e| {
ModelError::LoadFailed {
reason: format!("CoreML model load failed: {:?}", e),
}
})?
};
let model_desc = unsafe { ml_model.modelDescription() };
let _input_desc_array = unsafe { model_desc.inputDescriptionsByName() };
let mut input_features = Vec::new();
let mut input_names = Vec::new();
input_features.push(FeatureDescription {
name: "image".to_string(),
feature_type: FeatureType::Image {
width: 256,
height: 256,
format: PixelFormat::Rgb,
},
constraint: None,
});
input_names.push("image".to_string());
let _output_desc_array = unsafe { model_desc.outputDescriptionsByName() };
let mut output_features = Vec::new();
let mut output_names = Vec::new();
output_features.push(FeatureDescription {
name: "output".to_string(),
feature_type: FeatureType::MultiArray {
shape: vec![1, 2, 256, 256],
dtype: ArrayDataType::Float32,
},
constraint: None,
});
output_names.push("output".to_string());
let metadata = ModelMetadata {
name: path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string(),
version: "1.0".to_string(),
description: "CoreML model".to_string(),
input_names,
output_names,
input_shape: (3, 256, 256),
output_shape: (2, 256, 256),
class_labels: None,
};
Ok(Self {
path: path.display().to_string(),
config,
metadata,
input_features,
output_features,
ml_model: ThreadSafeMlModel::new(ml_model),
})
}
pub fn compile<P: AsRef<Path>>(source_path: P, output_dir: P) -> Result<String> {
let source = source_path.as_ref();
let output = output_dir.as_ref();
info!("Compiling CoreML model {:?} to {:?}", source, output);
if !source.exists() {
return Err(ModelError::NotFound {
path: source.display().to_string(),
}
.into());
}
let source_url_string = NSString::from_str(&source.display().to_string());
let source_url = NSURL::fileURLWithPath(&source_url_string);
let compiled_url = unsafe {
#[allow(deprecated)]
MLModel::compileModelAtURL_error(&source_url).map_err(|e| ModelError::LoadFailed {
reason: format!("CoreML compilation failed: {:?}", e),
})?
};
let compiled_path = compiled_url
.path()
.ok_or_else(|| ModelError::LoadFailed {
reason: "Failed to get compiled model path".to_string(),
})?
.to_string();
info!("CoreML model compiled to: {}", compiled_path);
Ok(compiled_path)
}
#[must_use]
pub fn config(&self) -> &CoreMlConfig {
&self.config
}
#[must_use]
pub fn input_features(&self) -> &[FeatureDescription] {
&self.input_features
}
#[must_use]
pub fn output_features(&self) -> &[FeatureDescription] {
&self.output_features
}
pub fn predict_raw(&self, input_data: &[f32]) -> Result<Vec<f32>> {
debug!(
"Running CoreML inference on {} input values",
input_data.len()
);
let input_feature =
self.input_features
.first()
.ok_or_else(|| ModelError::MissingInput {
input_name: "unknown".to_string(),
})?;
let input_name = &input_feature.name;
let input_shape = match &input_feature.feature_type {
FeatureType::MultiArray { shape, .. } => shape.clone(),
FeatureType::Image { width, height, .. } => vec![1, 3, *height, *width],
_ => {
return Err(InferenceError::InvalidInputType {
expected: "MultiArray or Image".to_string(),
actual: "unsupported type".to_string(),
}
.into());
}
};
let expected_size: usize = input_shape.iter().product();
if input_data.len() != expected_size {
return Err(InferenceError::InvalidInputShape {
expected: input_shape.clone(),
actual: vec![input_data.len()],
}
.into());
}
let input_array = self.create_mlmultiarray(&input_shape, input_data)?;
let input_feature_value =
unsafe { MLFeatureValue::featureValueWithMultiArray(&input_array) };
let feature_dict = unsafe {
let key = NSString::from_str(input_name);
let keys = NSArray::from_slice(&[key.as_ref()]);
let values = NSArray::from_slice(&[input_feature_value.as_ref()]);
NSDictionary::dictionaryWithObjects_forKeys(&values, &keys)
};
let feature_provider = unsafe {
let provider = MLDictionaryFeatureProvider::alloc();
provider
.initWithDictionary_error(&feature_dict)
.map_err(|e| InferenceError::Failed {
reason: format!("Failed to create feature provider: {:?}", e),
})?
};
let prediction_options = unsafe { MLPredictionOptions::new() };
let output_features = unsafe {
self.ml_model
.model()
.predictionFromFeatures_options_error(&feature_provider, &prediction_options)
.map_err(|e| InferenceError::Failed {
reason: format!("Prediction failed: {:?}", e),
})?
};
let output_feature =
self.output_features
.first()
.ok_or_else(|| ModelError::MissingOutput {
output_name: "unknown".to_string(),
})?;
let output_name = &output_feature.name;
let output_name_ns = NSString::from_str(output_name);
let output_value = unsafe {
output_features
.featureValueForName(&output_name_ns)
.ok_or_else(|| InferenceError::OutputParsingFailed {
reason: format!("Output feature '{}' not found", output_name),
})?
};
let output_array = unsafe {
output_value
.multiArrayValue()
.ok_or_else(|| InferenceError::OutputParsingFailed {
reason: "Output is not a multi-array".to_string(),
})?
};
let output_data = self.extract_mlmultiarray_data(&output_array)?;
debug!(
"CoreML inference completed, output size: {}",
output_data.len()
);
Ok(output_data)
}
fn create_mlmultiarray(&self, shape: &[usize], data: &[f32]) -> Result<Retained<MLMultiArray>> {
use objc2_core_ml::MLMultiArrayDataType;
let shape_numbers: Vec<Retained<NSNumber>> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let mut strides = vec![1_usize; shape.len()];
for i in (0..shape.len().saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let stride_numbers: Vec<Retained<NSNumber>> = strides
.iter()
.map(|&s| unsafe { NSNumber::numberWithUnsignedInteger(s) })
.collect();
let strides_array = NSArray::from_slice(&stride_numbers);
let multi_array = unsafe {
let array = MLMultiArray::alloc();
array
.initWithShape_dataType_error(
&shape_array,
&strides_array,
MLMultiArrayDataType(65568),
)
.map_err(|e| InferenceError::Failed {
reason: format!("Failed to create MLMultiArray: {:?}", e),
})?
};
unsafe {
let data_ptr_nn = multi_array.dataPointer();
let data_ptr = data_ptr_nn.as_ptr() as *mut f32;
std::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
}
Ok(multi_array)
}
fn extract_mlmultiarray_data(&self, array: &MLMultiArray) -> Result<Vec<f32>> {
use objc2_core_ml::MLMultiArrayDataType;
let data_type = unsafe { array.dataType() };
if data_type != MLMultiArrayDataType(65568) {
return Err(InferenceError::Failed {
reason: format!("Unsupported data type: {:?}", data_type),
}
.into());
}
let shape_array = unsafe { array.shape() };
let shape_count = unsafe { shape_array.count() };
let mut total_size = 1_usize;
for i in 0..shape_count {
let dim_number = unsafe { shape_array.objectAtIndex(i) };
let dim_value = unsafe { dim_number.unsignedIntegerValue() };
total_size *= dim_value;
}
let mut output_data = vec![0.0_f32; total_size];
unsafe {
let data_ptr_nn = array.dataPointer();
let data_ptr = data_ptr_nn.as_ptr() as *const f32;
std::ptr::copy_nonoverlapping(data_ptr, output_data.as_mut_ptr(), total_size);
}
Ok(output_data)
}
#[must_use]
pub fn supports_neural_engine(&self) -> bool {
let ane_configured = matches!(
self.config.compute_unit,
ComputeUnit::All | ComputeUnit::NeuralEngine
);
if !ane_configured {
return false;
}
true
}
#[must_use]
pub fn compute_unit(&self) -> ComputeUnit {
self.config.compute_unit
}
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
impl Model for CoreMlModel {
fn metadata(&self) -> &ModelMetadata {
&self.metadata
}
fn predict(&self, input: &RasterBuffer) -> Result<RasterBuffer> {
debug!(
"CoreML prediction on {}x{} raster",
input.width(),
input.height()
);
let (channels, height, width) = self.input_shape();
if input.width() as usize != width || input.height() as usize != height {
warn!(
"Input size mismatch: expected {}x{}, got {}x{}",
width,
height,
input.width(),
input.height()
);
}
let mut input_data = Vec::with_capacity(channels * height * width);
for _c in 0..channels {
for y in 0..height {
for x in 0..width {
let value = input.get_pixel(x as u64, y as u64).unwrap_or(0.0);
input_data.push(value as f32);
}
}
}
let output_data = self.predict_raw(&input_data)?;
let (_out_channels, out_height, out_width) = self.output_shape();
let mut output =
RasterBuffer::zeros(out_width as u64, out_height as u64, RasterDataType::Float32);
for y in 0..out_height {
for x in 0..out_width {
let idx = y * out_width + x;
if let Some(&value) = output_data.get(idx) {
let _ = output.set_pixel(x as u64, y as u64, value as f64);
}
}
}
Ok(output)
}
fn predict_batch(&self, inputs: &[RasterBuffer]) -> Result<Vec<RasterBuffer>> {
debug!("CoreML batch prediction on {} inputs", inputs.len());
inputs.iter().map(|input| self.predict(input)).collect()
}
fn input_shape(&self) -> (usize, usize, usize) {
if let Some(desc) = self.input_features.first() {
match &desc.feature_type {
FeatureType::Image { width, height, .. } => {
return (3, *height, *width);
}
FeatureType::MultiArray { shape, .. } if shape.len() == 4 => {
return (shape[1], shape[2], shape[3]);
}
_ => {}
}
}
(3, 256, 256) }
fn output_shape(&self) -> (usize, usize, usize) {
if let Some(desc) = self.output_features.first() {
if let FeatureType::MultiArray { shape, .. } = &desc.feature_type {
if shape.len() == 4 {
return (shape[1], shape[2], shape[3]);
}
}
}
(2, 256, 256) }
}
#[cfg(not(all(feature = "coreml", target_os = "macos")))]
pub struct CoreMlModel;
#[cfg(not(all(feature = "coreml", target_os = "macos")))]
impl CoreMlModel {
pub fn from_file<P: AsRef<Path>>(_path: P, _config: CoreMlConfig) -> Result<Self> {
#[cfg(not(target_os = "macos"))]
return Err(MlError::FeatureNotAvailable {
feature: "CoreML (macOS/iOS only)".to_string(),
flag: "coreml".to_string(),
});
#[cfg(all(target_os = "macos", not(feature = "coreml")))]
return Err(MlError::FeatureNotAvailable {
feature: "CoreML support".to_string(),
flag: "coreml".to_string(),
});
}
pub fn compile<P: AsRef<Path>>(_source: P, _output: P) -> Result<String> {
Err(MlError::FeatureNotAvailable {
feature: "CoreML support".to_string(),
flag: "coreml".to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coreml_config_builder() {
let config = CoreMlConfig::builder()
.compute_unit(ComputeUnit::NeuralEngine)
.allow_low_precision(false)
.profiling(true)
.precision(ModelPrecision::Float16)
.build();
assert_eq!(config.compute_unit, ComputeUnit::NeuralEngine);
assert!(!config.allow_low_precision);
assert!(config.profiling);
assert_eq!(config.precision, ModelPrecision::Float16);
}
#[test]
fn test_compute_unit_variants() {
let units = vec![
ComputeUnit::CpuOnly,
ComputeUnit::CpuAndGpu,
ComputeUnit::All,
ComputeUnit::NeuralEngine,
];
for unit in units {
let config = CoreMlConfig::builder().compute_unit(unit).build();
assert_eq!(config.compute_unit, unit);
}
}
#[test]
fn test_pixel_format_variants() {
let formats = vec![
PixelFormat::Gray8,
PixelFormat::Rgb,
PixelFormat::Bgr,
PixelFormat::Rgba,
PixelFormat::Bgra,
];
for format in formats {
let feature = FeatureType::Image {
width: 256,
height: 256,
format,
};
if let FeatureType::Image { format: f, .. } = feature {
assert_eq!(f, format);
}
}
}
#[cfg(not(all(feature = "coreml", target_os = "macos")))]
#[test]
fn test_coreml_not_available() {
let config = CoreMlConfig::default();
let result = CoreMlModel::from_file("model.mlmodelc", config);
assert!(result.is_err());
if let Err(MlError::FeatureNotAvailable { feature, .. }) = result {
assert!(feature.contains("CoreML"));
}
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_coreml_api_available() {
let config = CoreMlConfig::builder()
.compute_unit(ComputeUnit::CpuOnly)
.allow_low_precision(false)
.build();
assert_eq!(config.compute_unit, ComputeUnit::CpuOnly);
assert!(!config.allow_low_precision);
let result = CoreMlModel::from_file("/nonexistent/model.mlmodelc", config);
assert!(result.is_err());
if let Err(MlError::Model(ModelError::NotFound { .. })) = result {
} else {
panic!("Expected NotFound error for non-existent model");
}
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_thread_safe_wrapper() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<ThreadSafeMlModel>();
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_mlmultiarray_creation() {
use objc2_core_ml::MLMultiArray;
use objc2_foundation::{NSArray, NSNumber};
let shape = vec![2_usize, 3, 4];
let total_size: usize = shape.iter().product();
let test_data: Vec<f32> = (0..total_size).map(|i| i as f32).collect();
let shape_numbers: Vec<_> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let multi_array = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
.expect("Failed to create MLMultiArray")
};
unsafe {
let data_ptr = multi_array.dataPointer() as *mut f32;
assert!(!data_ptr.is_null());
std::ptr::copy_nonoverlapping(test_data.as_ptr(), data_ptr, total_size);
let read_ptr = multi_array.dataPointer() as *const f32;
for i in 0..total_size {
let value = *read_ptr.add(i);
assert_eq!(value, i as f32, "Data mismatch at index {}", i);
}
}
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_mlmultiarray_extraction() {
use objc2_core_ml::MLMultiArray;
use objc2_foundation::{NSArray, NSNumber};
let shape = vec![3_usize, 4];
let total_size: usize = shape.iter().product();
let shape_numbers: Vec<_> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let multi_array = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
.expect("Failed to create MLMultiArray")
};
let test_data: Vec<f32> = (0..total_size).map(|i| (i * 2) as f32).collect();
unsafe {
let data_ptr = multi_array.dataPointer() as *mut f32;
std::ptr::copy_nonoverlapping(test_data.as_ptr(), data_ptr, total_size);
}
let extracted = unsafe {
let shape = multi_array.shape();
let count = shape.count();
let mut size = 1_usize;
for i in 0..count {
let dim = shape.objectAtIndex(i);
size *= dim.unsignedIntegerValue();
}
let data_ptr = multi_array.dataPointer() as *const f32;
let mut result = vec![0.0_f32; size];
std::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), size);
result
};
assert_eq!(extracted.len(), total_size);
assert_eq!(extracted, test_data);
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_mlmultiarray_round_trip() {
use objc2_core_ml::MLMultiArray;
use objc2_foundation::{NSArray, NSNumber};
let test_cases = vec![vec![10_usize], vec![2, 3], vec![2, 3, 4], vec![1, 3, 8, 8]];
for shape in test_cases {
let total_size: usize = shape.iter().product();
let original_data: Vec<f32> = (0..total_size).map(|i| i as f32 * 0.1 + 1.0).collect();
let shape_numbers: Vec<_> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let multi_array = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
.expect("Failed to create MLMultiArray")
};
unsafe {
let data_ptr = multi_array.dataPointer() as *mut f32;
std::ptr::copy_nonoverlapping(original_data.as_ptr(), data_ptr, total_size);
}
let extracted = unsafe {
let data_ptr = multi_array.dataPointer() as *const f32;
let mut result = vec![0.0_f32; total_size];
std::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), total_size);
result
};
assert_eq!(
extracted.len(),
original_data.len(),
"Length mismatch for shape {:?}",
shape
);
for (i, (orig, extr)) in original_data.iter().zip(extracted.iter()).enumerate() {
assert!(
(orig - extr).abs() < 1e-6,
"Value mismatch at index {} for shape {:?}: {} != {}",
i,
shape,
orig,
extr
);
}
}
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_mlmultiarray_shape_validation() {
use objc2_core_ml::MLMultiArray;
use objc2_foundation::{NSArray, NSNumber};
let valid_shapes = vec![vec![1_usize], vec![100], vec![10, 20], vec![2, 3, 4, 5]];
for shape in valid_shapes {
let shape_numbers: Vec<_> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let result = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
};
assert!(
result.is_ok(),
"Failed to create array with shape {:?}",
shape
);
}
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_mlfeature_value_creation() {
use objc2_core_ml::{MLFeatureValue, MLMultiArray};
use objc2_foundation::{NSArray, NSNumber};
let shape = vec![2_usize, 3];
let shape_numbers: Vec<_> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let multi_array = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
.expect("Failed to create MLMultiArray")
};
let feature_value = unsafe { MLFeatureValue::featureValueWithMultiArray(&multi_array) };
let extracted_array = unsafe { feature_value.multiArrayValue() };
assert!(extracted_array.is_some());
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_feature_dictionary_creation() {
use objc2_core_ml::{MLDictionaryFeatureProvider, MLFeatureValue, MLMultiArray};
use objc2_foundation::{NSArray, NSDictionary, NSNumber, NSString};
let shape = vec![1_usize, 3, 4, 4];
let shape_numbers: Vec<_> = shape
.iter()
.map(|&dim| unsafe { NSNumber::numberWithUnsignedInteger(dim) })
.collect();
let shape_array = NSArray::from_slice(&shape_numbers);
let multi_array = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
.expect("Failed to create MLMultiArray")
};
let feature_value = unsafe { MLFeatureValue::featureValueWithMultiArray(&multi_array) };
let feature_dict = unsafe {
let key = NSString::from_str("test_input");
let keys = NSArray::from_id_slice(&[key.as_ref()]);
let values = NSArray::from_id_slice(&[feature_value.as_ref()]);
NSDictionary::dictionaryWithObjects_forKeys(&values, &keys)
};
let provider =
unsafe { MLDictionaryFeatureProvider::initWithDictionary_error(&feature_dict) };
assert!(provider.is_ok(), "Failed to create feature provider");
}
#[cfg(all(feature = "coreml", target_os = "macos"))]
#[test]
fn test_mlmultiarray_error_cases() {
use objc2_core_ml::MLMultiArray;
use objc2_foundation::{NSArray, NSNumber};
let empty_shape: Vec<Retained<NSNumber>> = vec![];
let shape_array = NSArray::from_id_slice(&empty_shape);
let result = unsafe {
MLMultiArray::initWithShape_dataType_error(
&shape_array,
objc2_core_ml::MLMultiArrayDataType(65568),
)
};
assert!(result.is_err(), "Empty shape should fail");
}
}