@@ -325,3 +325,187 @@ def test_points_converter_functionality():
325325 result = to_numpy (points_obj )
326326 assert isinstance (result , np .ndarray )
327327 np .testing .assert_array_equal (result , np .array ([[10.0 , 20.0 , 2.0 ], [30.0 , 40.0 , 1.0 ]]))
328+
329+
330+ def test_typed_numpy_array_basic ():
331+ """Test basic typed numpy array conversion from Polars data."""
332+ import numpy .typing as npt
333+ import polars as pl
334+
335+ # Test Float32 typed array
336+ NDArrayFloat32 = npt .NDArray [np .float32 ]
337+ df = pl .DataFrame ({"data" : [[0.8 , 0.9 ]]}, schema = {"data" : pl .List (pl .Float32 )})
338+ result = from_polars_data (df ["data" ][0 ], NDArrayFloat32 )
339+
340+ assert isinstance (result , np .ndarray )
341+ assert result .dtype == np .float32
342+ np .testing .assert_array_almost_equal (result , np .array ([0.8 , 0.9 ], dtype = np .float32 ))
343+
344+ # Test Int32 typed array
345+ NDArrayInt32 = npt .NDArray [np .int32 ]
346+ df = pl .DataFrame ({"data" : [[1 , 2 , 3 ]]}, schema = {"data" : pl .List (pl .Int32 )})
347+ result = from_polars_data (df ["data" ][0 ], NDArrayInt32 )
348+
349+ assert isinstance (result , np .ndarray )
350+ assert result .dtype == np .int32
351+ np .testing .assert_array_equal (result , np .array ([1 , 2 , 3 ], dtype = np .int32 ))
352+
353+ # Test Float64 typed array
354+ NDArrayFloat64 = npt .NDArray [np .float64 ]
355+ df = pl .DataFrame ({"data" : [[1.5 , 2.5 ]]}, schema = {"data" : pl .List (pl .Float64 )})
356+ result = from_polars_data (df ["data" ][0 ], NDArrayFloat64 )
357+
358+ assert isinstance (result , np .ndarray )
359+ assert result .dtype == np .float64
360+ np .testing .assert_array_almost_equal (result , np .array ([1.5 , 2.5 ], dtype = np .float64 ))
361+
362+
363+ def test_typed_numpy_array_dtype_conversion ():
364+ """Test that typed numpy arrays trigger dtype conversion when needed."""
365+ import numpy .typing as npt
366+ import polars as pl
367+
368+ # Test conversion from float64 to float32
369+ NDArrayFloat32 = npt .NDArray [np .float32 ]
370+ df = pl .DataFrame ({"data" : [[1.0 , 2.0 ]]}, schema = {"data" : pl .List (pl .Float64 )})
371+ result = from_polars_data (df ["data" ][0 ], NDArrayFloat32 )
372+
373+ assert result .dtype == np .float32 , f"Expected float32 but got { result .dtype } "
374+ np .testing .assert_array_almost_equal (result , np .array ([1.0 , 2.0 ], dtype = np .float32 ))
375+
376+ # Test conversion from int64 to int32
377+ NDArrayInt32 = npt .NDArray [np .int32 ]
378+ df = pl .DataFrame ({"data" : [[10 , 20 ]]}, schema = {"data" : pl .List (pl .Int64 )})
379+ result = from_polars_data (df ["data" ][0 ], NDArrayInt32 )
380+
381+ assert result .dtype == np .int32 , f"Expected int32 but got { result .dtype } "
382+ np .testing .assert_array_equal (result , np .array ([10 , 20 ], dtype = np .int32 ))
383+
384+
385+ def test_typed_numpy_array_optional ():
386+ """Test optional typed numpy arrays (Type | None)."""
387+ import numpy .typing as npt
388+ import polars as pl
389+
390+ NDArrayFloat32 = npt .NDArray [np .float32 ]
391+ OptionalFloat32 = NDArrayFloat32 | None if sys .version_info >= (3 , 10 ) else Optional [NDArrayFloat32 ]
392+
393+ # Test with None
394+ result = from_polars_data (None , OptionalFloat32 )
395+ assert result is None
396+
397+ # Test with actual data
398+ df = pl .DataFrame ({"data" : [[0.8 , 0.9 ]]}, schema = {"data" : pl .List (pl .Float32 )})
399+ result = from_polars_data (df ["data" ][0 ], OptionalFloat32 )
400+
401+ assert isinstance (result , np .ndarray )
402+ assert result .dtype == np .float32
403+ np .testing .assert_array_almost_equal (result , np .array ([0.8 , 0.9 ], dtype = np .float32 ))
404+
405+
406+ def test_typed_numpy_array_preserves_dtype ():
407+ """Test that typed numpy arrays preserve dtype from Polars when types match."""
408+ import numpy .typing as npt
409+ import polars as pl
410+
411+ # When Polars dtype matches the type annotation, no conversion should occur
412+ NDArrayFloat32 = npt .NDArray [np .float32 ]
413+ df = pl .DataFrame ({"data" : [[0.5 , 0.7 ]]}, schema = {"data" : pl .List (pl .Float32 )})
414+ result = from_polars_data (df ["data" ][0 ], NDArrayFloat32 )
415+
416+ assert result .dtype == np .float32
417+ # Values should be exact (no float precision loss)
418+ np .testing .assert_array_equal (result , np .array ([0.5 , 0.7 ], dtype = np .float32 ))
419+
420+
421+ def test_typed_numpy_array_various_dtypes ():
422+ """Test typed numpy arrays with various numpy dtypes."""
423+ import numpy .typing as npt
424+ import polars as pl
425+
426+ # Test uint8
427+ NDArrayUInt8 = npt .NDArray [np .uint8 ]
428+ df = pl .DataFrame ({"data" : [[1 , 2 , 3 ]]}, schema = {"data" : pl .List (pl .UInt8 )})
429+ result = from_polars_data (df ["data" ][0 ], NDArrayUInt8 )
430+ assert result .dtype == np .uint8
431+
432+ # Test int64
433+ NDArrayInt64 = npt .NDArray [np .int64 ]
434+ df = pl .DataFrame ({"data" : [[100 , 200 ]]}, schema = {"data" : pl .List (pl .Int64 )})
435+ result = from_polars_data (df ["data" ][0 ], NDArrayInt64 )
436+ assert result .dtype == np .int64
437+
438+ # Test uint16
439+ NDArrayUInt16 = npt .NDArray [np .uint16 ]
440+ df = pl .DataFrame ({"data" : [[1000 , 2000 ]]}, schema = {"data" : pl .List (pl .UInt16 )})
441+ result = from_polars_data (df ["data" ][0 ], NDArrayUInt16 )
442+ assert result .dtype == np .uint16
443+
444+
445+ def test_typed_numpy_array_helper_function ():
446+ """Test the _apply_numpy_dtype_from_type_annotation helper function directly."""
447+ import numpy .typing as npt
448+
449+ from datumaro .experimental .type_registry import _apply_numpy_dtype_from_type_annotation
450+
451+ # Test dtype conversion
452+ NDArrayFloat32 = npt .NDArray [np .float32 ]
453+ arr = np .array ([1.0 , 2.0 ], dtype = np .float64 )
454+ result = _apply_numpy_dtype_from_type_annotation (arr , NDArrayFloat32 )
455+ assert result .dtype == np .float32
456+
457+ # Test no conversion when dtype already matches
458+ arr_f32 = np .array ([1.0 , 2.0 ], dtype = np .float32 )
459+ result = _apply_numpy_dtype_from_type_annotation (arr_f32 , NDArrayFloat32 )
460+ assert result .dtype == np .float32
461+
462+ # Test with generic np.ndarray (should not convert)
463+ arr_f64 = np .array ([1.0 , 2.0 ], dtype = np .float64 )
464+ result = _apply_numpy_dtype_from_type_annotation (arr_f64 , np .ndarray )
465+ assert result .dtype == np .float64 # Should remain unchanged
466+
467+
468+ def test_typed_numpy_array_round_trip ():
469+ """Test round-trip conversion: numpy -> polars -> typed numpy."""
470+ import numpy .typing as npt
471+ import polars as pl
472+
473+ NDArrayFloat32 = npt .NDArray [np .float32 ]
474+
475+ # Original typed array
476+ original = np .array ([0.8 , 0.95 , 0.87 ], dtype = np .float32 )
477+
478+ # Convert to polars-compatible format
479+ from datumaro .experimental .type_registry import to_numpy
480+
481+ polars_ready = to_numpy (original , pl .Float32 )
482+
483+ # Create polars series
484+ series = pl .Series ("scores" , [polars_ready ], dtype = pl .List (pl .Float32 ))
485+
486+ # Extract back from polars
487+ polars_data = series [0 ]
488+
489+ # Convert back to typed numpy array
490+ result = from_polars_data (polars_data , NDArrayFloat32 )
491+
492+ # Verify dtype and values are preserved
493+ assert result .dtype == np .float32
494+ np .testing .assert_array_almost_equal (original , result )
495+
496+
497+ def test_typed_numpy_array_multidimensional ():
498+ """Test typed numpy arrays with multidimensional data."""
499+ import numpy .typing as npt
500+ import polars as pl
501+
502+ NDArrayInt32 = npt .NDArray [np .int32 ]
503+
504+ # Test with nested lists (2D array)
505+ # Note: Polars List type is for 1D arrays, so we test with flattened data
506+ df = pl .DataFrame ({"data" : [[10 , 15 , 30 , 35 ]]}, schema = {"data" : pl .List (pl .Int32 )})
507+ result = from_polars_data (df ["data" ][0 ], NDArrayInt32 )
508+
509+ assert result .dtype == np .int32
510+ assert result .shape == (4 ,)
511+ np .testing .assert_array_equal (result , np .array ([10 , 15 , 30 , 35 ], dtype = np .int32 ))
0 commit comments