1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//! Autoexposure: deciding per-channel exposures before a scan
use crate::{
error::Error,
protocol::{
caps::other::DataTypes,
data::Rect,
decode::{Image, Samples},
window::Window,
},
scan::{
autoexpose::{AutoExposure, Exposures, prescan_windows},
pass::Progress,
window::Recipe,
},
session::Session,
};
use std::{ops::ControlFlow, time::Duration};
use tracing::*;
/// Long enough for a low-resolution pass over a whole frame
const PASS_TIMEOUT: Duration = Duration::from_secs(300);
impl Session {
/// Meter `frame` and answer the exposures to scan it with
///
/// Builds its own metering windows from `recipe`, so the exposures come off
/// the frame rather than off whatever the last pass left in the unit, and
/// cover the channels `recipe` will scan. `lock_white_balance` keeps the
/// channels in the ratio the unit calls neutral, which is what a scan that
/// has to stay comparable to the next one wants.
pub fn autoexpose_frame(
&mut self,
frame: Rect,
recipe: &Recipe,
lock_white_balance: bool,
) -> Result<Exposures, Error> {
self.autoexpose_frame_with(frame, recipe, lock_white_balance, |_, _| {
ControlFlow::Continue(())
})
}
/// The same as [`Self::autoexpose_frame`], with `on` given which metering pass is running
/// (counting from one) and letting it cancel by returning `Break`
pub fn autoexpose_frame_with(
&mut self,
frame: Rect,
recipe: &Recipe,
lock_white_balance: bool,
on: impl FnMut(usize, Progress) -> ControlFlow<()>,
) -> Result<Exposures, Error> {
let windows = recipe
.metering(self.capabilities())
.windows(self.capabilities(), frame)?;
self.autoexpose_with(&windows, lock_white_balance, on)
}
/// Meter `windows` and answer their new exposures
///
/// A set is what gets metered rather than a channel: one descriptor per
/// channel, one exposure in each, and a lock that decides them against one
/// another. Which mechanism runs is the unit's to advertise.
pub fn autoexpose(
&mut self,
windows: &[Window],
lock_white_balance: bool,
) -> Result<Exposures, Error> {
self.autoexpose_with(
windows,
lock_white_balance,
|_, _| ControlFlow::Continue(()),
)
}
/// The same as [`Self::autoexpose`], letting `on` cancel by returning `Break`
///
/// A unit that meters for itself takes a pass we never read, so `on` never
/// runs and cannot cancel that mechanism
pub fn autoexpose_with(
&mut self,
windows: &[Window],
lock_white_balance: bool,
mut on: impl FnMut(usize, Progress) -> ControlFlow<()>,
) -> Result<Exposures, Error> {
let mechanism = AutoExposure::choose(self.capabilities(), lock_white_balance)?;
debug!(?mechanism, "metering");
match mechanism {
AutoExposure::Unit(kind) => {
// The unit meters during a pass of its own. It writes the result
// into the descriptors, so GET WINDOW is what reports it
let mut metering = windows.to_vec();
for w in &mut metering {
w.scanning_kind = kind;
}
// Its own numbers are the point, not the image, so the pass is
// stopped rather than read out
self.start_pass(&metering, PASS_TIMEOUT)?;
self.abort()?;
// Only the channels we asked it to meter: the unit holds a
// descriptor for every channel it has, metered or not
let held = self.windows()?;
let mut exposures = Exposures::read(windows);
for w in windows {
if let Some(metered) = held.iter().find(|h| h.id == w.id) {
exposures.set(w.channel(), metered.exposure);
}
}
Ok(exposures)
}
AutoExposure::Host(metering) => {
// Exposures persist in the unit across sessions, so metering from
// whatever is in the descriptors compounds run over run.
// `DataType::WhiteBalanceExposure` is the unit's own neutral,
// measured at start-up, so we start there every time.
let seeded = self.seed_white_balance(windows)?;
let mut windows = prescan_windows(self.capabilities(), &seeded);
let mut samples = Samples::default();
let mut layout = None;
let mut n = 0;
loop {
let taken = self
.scan_pass_with(&windows, PASS_TIMEOUT, &mut samples, |p| on(n + 1, p))?;
let layout = layout.insert(taken.layout);
let image = Image::new(layout, &samples)?;
n += 1;
// Correct from what this pass measured, whether or not
// another one follows: the exposures the scan gets are the
// ones the last pass asked for, never the ones it ran at
let next = metering.apply(self.capabilities(), &image, &windows)?;
for (w, exposure) in windows.iter_mut().zip(next) {
w.exposure = exposure;
}
// A level below full scale says exactly what exposure lands
// on target, so confirming it costs a pass to learn nothing.
// Only a clipped channel, whose correction is a retreat
// rather than a measurement, is worth another
let measured = metering.measured(&image, &windows);
debug!(pass = n, measured, "metering pass");
if measured {
break;
}
if n >= metering.max_passes.max(1) {
debug!(passes = n, "metering never came off full scale");
break;
}
}
let layout = layout.expect("the loop runs at least once");
let measured = metering.measure(&Image::new(&layout, &samples)?, &windows);
// `setup` is a diagnostic - nothing below reads it - and it is
// the one command in a pass the unit can be slow about: a whole
// PROBE_TIMEOUT per channel on some holders, which reads as the
// scan having hung. Nikon Scan never asks for it at all, so it
// stays behind the log level that would print it
let compare = enabled!(Level::TRACE)
&& self
.capabilities()
.features
.data_types
.contains(DataTypes::SETUP_READ);
for (n, (window, level)) in windows.iter().zip(&measured).enumerate() {
let unit = compare.then(|| self.setup(window.id).ok()).flatten();
let image = unit.as_ref().and_then(|s| s.images.first());
debug!(
channel = n,
id = window.id,
ours = level,
base_level = unit.as_ref().map(|s| s.base_level),
unit_min = image.map(|i| i.min),
unit_max = image.map(|i| i.max),
"metering levels"
);
}
Ok(Exposures::read(&windows))
}
}
}
/// The same windows with the unit's start-up exposures in them
///
/// Any pass wants this, not just a metered one: the channels do not read
/// neutral at equal exposures, so a descriptor left at 0 comes back with a
/// cast. Infrared is seeded the same way, from the same record: metering
/// scales the exposure it finds, so a channel left at 0 stays at 0 however
/// many passes it gets, and the mask comes back at whatever the unit
/// defaults to rather than at the level the film base asks for.
///
/// A channel the unit has no reading for keeps what it came with
pub fn seed_white_balance(&mut self, windows: &[Window]) -> Result<Vec<Window>, Error> {
let mut seeded = Vec::with_capacity(windows.len());
for w in windows {
let mut w = w.clone();
match self.white_balance(w.channel()) {
Ok(exposure) => w.exposure = exposure,
Err(e) => debug!(id = w.id, %e, "no start-up exposure for this channel"),
}
seeded.push(w);
}
Ok(seeded)
}
}