ascom_alpaca/api/camera/image_array/
server.rs1use 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}