ascom_alpaca/api/camera/image_array/
server.rs

1use super::{
2    ImageArray, ImageArrayRank, ImageBytesMetadata, ImageElementType, TransmissionElementType,
3    COLOUR_AXIS, IMAGE_BYTES_TYPE,
4};
5use crate::server::ResponseWithTransaction;
6use crate::ASCOMResult;
7use axum::response::{IntoResponse, Response};
8use bytemuck::{bytes_of, Zeroable};
9use http::header::{HeaderMap, ACCEPT, CONTENT_TYPE};
10use serde::{Serialize, Serializer};
11use std::mem::size_of;
12
13pub(crate) struct ImageBytesResponse(pub(crate) ImageArray);
14
15impl IntoResponse for ResponseWithTransaction<ASCOMResult<ImageBytesResponse>> {
16    fn into_response(self) -> Response {
17        let mut metadata = ImageBytesMetadata {
18            metadata_version: 1,
19            data_start: i32::try_from(size_of::<ImageBytesMetadata>())
20                .expect("internal error: metadata size is too large"),
21            client_transaction_id: self.transaction.client_transaction_id,
22            server_transaction_id: Some(self.transaction.server_transaction_id),
23            ..Zeroable::zeroed()
24        };
25        let bytes = match &self.response {
26            Ok(ImageBytesResponse(img_array)) => {
27                metadata.image_element_type = ImageElementType::I32.into();
28                metadata.transmission_element_type = img_array.transmission_element_type.into();
29                let dims = <[_; 3]>::from(img_array.dim())
30                    .map(|dim| i32::try_from(dim).expect("dimension is too large"));
31                metadata.dimension_1 = dims[0];
32                metadata.dimension_2 = dims[1];
33                metadata.rank = match dims[2] {
34                    1_i32 => ImageArrayRank::Rank2,
35                    n => {
36                        metadata.dimension_3 = n;
37                        ImageArrayRank::Rank3
38                    }
39                }
40                .into();
41                let mut bytes = Vec::with_capacity(
42                    size_of::<ImageBytesMetadata>()
43                        + img_array.len()
44                            * match img_array.transmission_element_type {
45                                TransmissionElementType::I32 => size_of::<i32>(),
46                                TransmissionElementType::U8 => size_of::<u8>(),
47                                TransmissionElementType::I16 => size_of::<i16>(),
48                                TransmissionElementType::U16 => size_of::<u16>(),
49                            },
50                );
51                bytes.extend_from_slice(bytes_of(&metadata));
52                #[expect(
53                    clippy::as_conversions,
54                    clippy::cast_possible_truncation,
55                    clippy::cast_sign_loss
56                )]
57                match img_array.transmission_element_type {
58                    TransmissionElementType::I32 => {
59                        bytes.extend(img_array.iter().flat_map(|&i| i.to_le_bytes()));
60                    }
61                    TransmissionElementType::U8 => {
62                        bytes.extend(img_array.iter().map(|&i| i as u8));
63                    }
64                    TransmissionElementType::I16 => {
65                        bytes.extend(img_array.iter().flat_map(|&i| (i as i16).to_le_bytes()));
66                    }
67                    TransmissionElementType::U16 => {
68                        bytes.extend(img_array.iter().flat_map(|&i| (i as u16).to_le_bytes()));
69                    }
70                }
71                bytes
72            }
73            Err(err) => {
74                metadata.error_number = err.code.raw().into();
75                let mut bytes =
76                    Vec::with_capacity(size_of::<ImageBytesMetadata>() + err.message.len());
77                bytes.extend_from_slice(bytes_of(&metadata));
78                bytes.extend_from_slice(err.message.as_bytes());
79                bytes
80            }
81        };
82        ([(CONTENT_TYPE, IMAGE_BYTES_TYPE)], bytes).into_response()
83    }
84}
85
86impl Serialize for ImageArray {
87    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
88        #[derive(Serialize)]
89        #[serde(rename_all = "PascalCase")]
90        struct JsonImageArray<'img> {
91            #[serde(rename = "Type")]
92            type_: ImageElementType,
93            rank: ImageArrayRank,
94            value: Value<'img>,
95        }
96
97        #[derive(Serialize)]
98        #[serde(untagged)]
99        enum Value<'img> {
100            Rank2(#[serde(with = "serde_ndim")] ndarray::ArrayView2<'img, i32>),
101            Rank3(#[serde(with = "serde_ndim")] ndarray::ArrayView3<'img, i32>),
102        }
103
104        let view = self.data.view();
105
106        JsonImageArray {
107            type_: ImageElementType::I32,
108            rank: self.rank(),
109            value: match self.rank() {
110                ImageArrayRank::Rank2 => Value::Rank2(view.remove_axis(COLOUR_AXIS)),
111                ImageArrayRank::Rank3 => Value::Rank3(view),
112            },
113        }
114        .serialize(serializer)
115    }
116}
117
118impl ImageArray {
119    pub(crate) fn is_accepted(headers: &HeaderMap) -> bool {
120        use mediatype::{names, MediaType, MediaTypeList, Name};
121
122        const MEDIA_TYPE: MediaType<'static> =
123            MediaType::new(names::APPLICATION, Name::new_unchecked("imagebytes"));
124
125        headers
126            .get_all(ACCEPT)
127            .iter()
128            .filter_map(|value| value.to_str().ok())
129            .flat_map(MediaTypeList::new)
130            .filter_map(Result::ok)
131            .any(|media_type| media_type.essence() == MEDIA_TYPE)
132    }
133}