Skip to content

Commit 07ea9c5

Browse files
committed
feat: expose image attribute as expression
1 parent 0129008 commit 07ea9c5

File tree

18 files changed

+288
-11
lines changed

18 files changed

+288
-11
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

daft/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def refresh_logger() -> None:
6969
from_pylist,
7070
from_ray_dataset,
7171
)
72-
from daft.daft import ImageFormat, ImageMode, ResourceRequest
72+
from daft.daft import ImageFormat, ImageMode, ImageProperty, ResourceRequest
7373
from daft.dataframe import DataFrame
7474
from daft.schema import Schema
7575
from daft.datatype import DataType, TimeUnit
@@ -148,6 +148,7 @@ def refresh_logger() -> None:
148148
"Identifier",
149149
"ImageFormat",
150150
"ImageMode",
151+
"ImageProperty",
151152
"ResourceRequest",
152153
"Schema",
153154
"Series",

daft/daft/__init__.pyi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,17 @@ class ImageMode(Enum):
7676
"""
7777
...
7878

79+
class ImageProperty(Enum):
80+
"""Supported image properties for Daft's image type."""
81+
82+
Height = 1
83+
Width = 2
84+
Channel = 3
85+
Mode = 4
86+
87+
@staticmethod
88+
def from_property_string(attr: str) -> ImageProperty: ...
89+
7990
class PyWindowBoundary:
8091
"""Represents a window frame boundary in window functions."""
8192

daft/expressions/expressions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
CountMode,
2222
ImageFormat,
2323
ImageMode,
24+
ImageProperty,
2425
ResourceRequest,
2526
initialize_udfs,
2627
resolved_col,
@@ -5217,6 +5218,20 @@ def to_mode(self, mode: str | ImageMode) -> Expression:
52175218
f = native.get_function_from_registry("to_mode")
52185219
return Expression._from_pyexpr(f(self._expr, mode=image_mode))
52195220

5221+
def attribute(self, name: Literal["width", "height", "channel", "mode"] | ImageProperty) -> Expression:
5222+
"""Get a property of the image, such as 'width', 'height', 'channel', or 'mode'.
5223+
5224+
Args:
5225+
name (str): The name of the property to retrieve.
5226+
5227+
Returns:
5228+
Expression: An Expression representing the requested property.
5229+
"""
5230+
if isinstance(name, str):
5231+
name = ImageProperty.from_property_string(name)
5232+
f = native.get_function_from_registry("image_attribute")
5233+
return Expression._from_pyexpr(f(self._expr, lit(name)._expr))
5234+
52205235

52215236
class ExpressionPartitioningNamespace(ExpressionNamespace):
52225237
"""The following methods are available under the `expr.partition` attribute."""

src/daft-core/src/array/image_array.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,39 @@ impl ImageArray {
3636
}
3737

3838
pub fn channel_array(&self) -> &arrow2::array::UInt16Array {
39-
let array = self.physical.children.get(Self::IMAGE_CHANNEL_IDX).unwrap();
40-
array.u16().unwrap().as_arrow()
39+
self.channels().as_arrow()
4140
}
4241

4342
pub fn height_array(&self) -> &arrow2::array::UInt32Array {
44-
let array = self.physical.children.get(Self::IMAGE_HEIGHT_IDX).unwrap();
45-
array.u32().unwrap().as_arrow()
43+
self.heights().as_arrow()
4644
}
4745

4846
pub fn width_array(&self) -> &arrow2::array::UInt32Array {
49-
let array = self.physical.children.get(Self::IMAGE_WIDTH_IDX).unwrap();
50-
array.u32().unwrap().as_arrow()
47+
self.widths().as_arrow()
5148
}
5249

5350
pub fn mode_array(&self) -> &arrow2::array::UInt8Array {
51+
self.modes().as_arrow()
52+
}
53+
54+
pub fn channels(&self) -> &DataArray<UInt16Type> {
55+
let array = self.physical.children.get(Self::IMAGE_CHANNEL_IDX).unwrap();
56+
array.u16().unwrap()
57+
}
58+
59+
pub fn heights(&self) -> &DataArray<UInt32Type> {
60+
let array = self.physical.children.get(Self::IMAGE_HEIGHT_IDX).unwrap();
61+
array.u32().unwrap()
62+
}
63+
64+
pub fn widths(&self) -> &DataArray<UInt32Type> {
65+
let array = self.physical.children.get(Self::IMAGE_WIDTH_IDX).unwrap();
66+
array.u32().unwrap()
67+
}
68+
69+
pub fn modes(&self) -> &DataArray<UInt8Type> {
5470
let array = self.physical.children.get(Self::IMAGE_MODE_IDX).unwrap();
55-
array.u8().unwrap().as_arrow()
71+
array.u8().unwrap()
5672
}
5773

5874
pub fn from_list_array(

src/daft-core/src/lit/conversions.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use super::{deserializer::LiteralDeserializer, FromLiteral, Literal};
88
use crate::python::{PyDataType, PyTimeUnit};
99
use crate::{
1010
datatypes::IntervalValue,
11-
prelude::{CountMode, DataType, ImageFormat, ImageMode, TimeUnit},
11+
prelude::{CountMode, DataType, ImageFormat, ImageMode, ImageProperty, TimeUnit},
1212
series::Series,
1313
};
1414

@@ -219,7 +219,8 @@ impl_float_fromliteral!(f32);
219219
impl_float_fromliteral!(f64);
220220
impl_pyobj_fromliteral!(IOConfig, common_io_config::python::IOConfig);
221221
impl_pyobj_fromliteral!(ImageMode, ImageMode);
222-
impl_pyobj_fromliteral!(ImageFormat, ImageFormat);
222+
impl_pyobj_fromliteral!(ImageProperty, ImageProperty);
223223
impl_pyobj_fromliteral!(CountMode, CountMode);
224224
impl_pyobj_fromliteral!(TimeUnit, PyTimeUnit);
225225
impl_pyobj_fromliteral!(DataType, PyDataType);
226+
impl_pyobj_fromliteral!(ImageFormat, ImageFormat);

src/daft-core/src/prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
// Re-export arrow2 bitmap
66
pub use arrow2::bitmap;
7+
pub use daft_schema::image_property::ImageProperty;
78
// Re-export core series structures
89
pub use daft_schema::schema::{Schema, SchemaRef};
910

src/daft-image/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ common-error = {path = "../common/error", default-features = false}
55
common-image = {workspace = true}
66
daft-core = {path = "../daft-core", default-features = false}
77
daft-dsl = {path = "../daft-dsl", default-features = false}
8+
daft-schema = {path = "../daft-schema", default-features = false}
89
log = {workspace = true}
910
serde = {workspace = true}
1011
typetag = {workspace = true}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use common_error::{DaftError, DaftResult};
2+
use daft_core::prelude::*;
3+
use daft_dsl::{
4+
functions::{FunctionArgs, ScalarUDF},
5+
ExprRef,
6+
};
7+
use daft_schema::image_property::ImageProperty;
8+
use serde::{Deserialize, Serialize};
9+
10+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
11+
pub struct ImageAttribute;
12+
13+
#[derive(FunctionArgs)]
14+
struct ImageAttributeArgs<T> {
15+
input: T,
16+
attr: ImageProperty,
17+
}
18+
19+
#[typetag::serde]
20+
impl ScalarUDF for ImageAttribute {
21+
fn call(&self, inputs: FunctionArgs<Series>) -> DaftResult<Series> {
22+
let ImageAttributeArgs { input, attr } = inputs.try_into()?;
23+
crate::series::attribute(&input, attr)
24+
}
25+
26+
fn name(&self) -> &'static str {
27+
"image_attribute"
28+
}
29+
30+
fn get_return_field(
31+
&self,
32+
inputs: FunctionArgs<ExprRef>,
33+
schema: &Schema,
34+
) -> DaftResult<Field> {
35+
let ImageAttributeArgs { input, .. } = inputs.try_into()?;
36+
37+
let input_field = input.to_field(schema)?;
38+
match input_field.dtype {
39+
DataType::Image(_) | DataType::FixedShapeImage(..) => {
40+
Ok(Field::new(input_field.name, DataType::UInt32))
41+
}
42+
_ => Err(DaftError::TypeError(format!(
43+
"Image attribute can only be retrieved from ImageArrays, got {}",
44+
input_field.dtype
45+
))),
46+
}
47+
}
48+
49+
fn docstring(&self) -> &'static str {
50+
"Extracts metadata attributes from image series (height/width/channels/mode)"
51+
}
52+
}

src/daft-image/src/functions/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use daft_dsl::functions::FunctionModule;
22

3+
pub mod attribute;
34
pub mod crop;
45
pub mod decode;
56
pub mod encode;
@@ -15,5 +16,6 @@ impl FunctionModule for ImageFunctions {
1516
parent.add_fn(encode::ImageEncode);
1617
parent.add_fn(resize::ImageResize);
1718
parent.add_fn(to_mode::ImageToMode);
19+
parent.add_fn(attribute::ImageAttribute);
1820
}
1921
}

0 commit comments

Comments
 (0)