Skip to main content

tract_core/ops/array/
pad.rs

1use crate::internal::*;
2
3/// How a padded region is filled. A `Constant` value carries a single element
4/// in the datum type the model builder gave it, f32 for a float graph, and no
5/// pass retypes it: a consumer casts it to the datum type of the tensor it pads.
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
7pub enum PadMode {
8    Constant(Arc<Tensor>),
9    Reflect,
10    Edge,
11}
12
13impl Default for PadMode {
14    fn default() -> PadMode {
15        PadMode::Constant(Arc::new(0.0f32.into()))
16    }
17}
18
19#[derive(Debug, Clone, new, Default, Hash, PartialEq, Eq)]
20pub struct Pad {
21    pub pads: Vec<(usize, usize)>,
22    pub mode: PadMode,
23}
24
25impl Pad {
26    fn eval_t<T>(&self, input_tensor: TValue) -> TractResult<TValue>
27    where
28        T: Copy + Datum,
29    {
30        use tract_ndarray::*;
31        let input = input_tensor.to_plain_array_view::<T>()?;
32        let output_shape: Vec<usize> =
33            input.shape().iter().zip(self.pads.iter()).map(|(&d, &(a, b))| d + a + b).collect();
34        let element = match &self.mode {
35            PadMode::Constant(f) => f.cast_to_scalar::<T>()?,
36            _ => T::default(),
37        };
38        let mut output = ArrayD::<T>::from_elem(output_shape, element);
39        let slice_spec: Vec<SliceInfoElem> = self
40            .pads
41            .iter()
42            .map(|&(a, b)| SliceInfoElem::Slice {
43                start: a as isize,
44                end: if b != 0 { Some(-(b as isize)) } else { None },
45                step: 1,
46            })
47            .collect();
48        let slice_info = SliceInfo::<_, IxDyn, IxDyn>::try_from(slice_spec).unwrap();
49        output.slice_mut(slice_info.as_ref()).assign(&input);
50        if self.mode == PadMode::Reflect || self.mode == PadMode::Edge {
51            for (ax, &(bef, aft)) in self.pads.iter().enumerate() {
52                let axis = Axis(ax);
53                let dim = output.shape()[ax];
54                {
55                    let (mut pad, data) = output.view_mut().split_at(axis, bef);
56                    for i in 0..bef {
57                        let mut target = pad.slice_axis_mut(axis, Slice::from(i..i + 1));
58                        let source_slice = match self.mode {
59                            PadMode::Edge => 0,
60                            PadMode::Reflect => bef - i,
61                            _ => panic!(),
62                        };
63                        let source =
64                            data.slice_axis(axis, Slice::from(source_slice..source_slice + 1));
65                        target.assign(&source);
66                    }
67                }
68                {
69                    let (data, mut pad) = output.view_mut().split_at(axis, dim - aft);
70                    for i in 0..aft {
71                        let mut target = pad.slice_axis_mut(axis, Slice::from(i..i + 1));
72                        let source_slice = match self.mode {
73                            PadMode::Edge => dim - aft - 1,
74                            PadMode::Reflect => dim - aft - 2 - i,
75                            _ => panic!(),
76                        };
77                        let source =
78                            data.slice_axis(axis, Slice::from(source_slice..source_slice + 1));
79                        target.assign(&source);
80                    }
81                }
82            }
83        }
84        let mut output = output.into_tensor();
85        unsafe { output.set_datum_type(input_tensor.datum_type()) }
86        Ok(output.into_tvalue())
87    }
88}
89
90impl Op for Pad {
91    fn name(&self) -> StaticName {
92        "Pad".into()
93    }
94
95    fn info(&self) -> TractResult<Vec<String>> {
96        Ok(vec![format!("Mode: {:?}, pads: {:?})", self.mode, self.pads,)])
97    }
98
99    op_as_typed_op!();
100}
101
102impl EvalOp for Pad {
103    op_out_of_plan!();
104
105    fn eval(&self, _ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
106        let input = args_1!(inputs);
107        Ok(tvec!(dispatch_numbers!(Self::eval_t(input.datum_type())(self, input))?))
108    }
109}
110
111impl TypedOp for Pad {
112    as_op!();
113
114    fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
115        let mut fact = inputs[0].without_value();
116        if self.pads.len() != fact.rank() {
117            bail!("Inconsistent pad: input of rank {}, pads are: {:?}", fact.rank(), self.pads);
118        }
119        for (ix, (b, e)) in self.pads.iter().enumerate() {
120            fact.shape.set(ix, fact.shape[ix].clone() + *b + *e);
121        }
122        Ok(tvec!(fact))
123    }
124
125    fn input_roi(
126        &self,
127        model: &TypedModel,
128        node: &TypedNode,
129    ) -> TractResult<Option<TVec<Option<TDim>>>> {
130        let output_fact = model.outlet_fact(OutletId::new(node.id, 0))?;
131        rule_if_some!(roi = &output_fact.region_of_interest);
132        // For each padded axis, substitute 🎯axis → 🎯axis - before
133        let mut input_roi = roi.clone();
134        for (axis, &(before, _)) in self.pads.iter().enumerate() {
135            if before == 0 {
136                continue;
137            }
138            if let Some(sym) = input_roi
139                .symbols()
140                .into_iter()
141                .find(|s| crate::ops::logic::sym_to_coord_axis(s) == Some(axis))
142            {
143                let shifted = TDim::Sym(sym.clone()) - TDim::Val(before as i64);
144                input_roi = input_roi.substitute(&sym, &shifted).unwrap_or(input_roi);
145            }
146        }
147        Ok(Some(tvec![Some(input_roi)]))
148    }
149
150    fn axes_mapping(
151        &self,
152        inputs: &[&TypedFact],
153        outputs: &[&TypedFact],
154    ) -> TractResult<AxesMapping> {
155        let mut result = AxesMapping::disconnected(inputs, outputs)?;
156        for (ix, pads) in self.pads.iter().enumerate() {
157            if pads == &(0, 0) {
158                result = result.linking((InOut::In(0), ix), (InOut::Out(0), ix))?;
159            }
160        }
161        Ok(result)
162    }
163
164    fn change_axes(
165        &self,
166        model: &TypedModel,
167        node: &TypedNode,
168        io: InOut,
169        change: &AxisOp,
170    ) -> TractResult<Option<AxisChangeConsequence>> {
171        let mut new_op = self.clone();
172        if let (InOut::In(0), AxisOp::Rm(ix)) = (io, change)
173            && new_op.pads.remove(*ix) == (0, 0)
174        {
175            return Ok(Some(AxisChangeConsequence::new(
176                model,
177                node,
178                Some(Box::new(new_op)),
179                change,
180            )));
181        }
182        if let (InOut::In(0), AxisOp::Add(ix)) = (io, change) {
183            new_op.pads.insert(*ix, (0, 0));
184            return Ok(Some(AxisChangeConsequence::new(
185                model,
186                node,
187                Some(Box::new(new_op)),
188                change,
189            )));
190        }
191        Ok(None)
192    }
193
194    fn declutter(
195        &self,
196        model: &TypedModel,
197        node: &TypedNode,
198    ) -> TractResult<Option<TypedModelPatch>> {
199        if self.pads.iter().all(|p| p.0 == 0 && p.1 == 0) {
200            TypedModelPatch::shunt_one_op(model, node)
201        } else {
202            Ok(None)
203        }
204    }
205}