diff --git a/.cargo/config.toml b/.cargo/config.toml index 3ffd7fa..d492598 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,11 +1,14 @@ [build] -target = "thumbv6m-none-eabi" +target = "thumbv8m.main-none-eabihf" +# target = "thumbv6m-none-eabi" [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "debug" -[target.thumbv6m-none-eabi] -runner = 'probe-rs run --chip STM32G0B1RE' +# [target.thumbv6m-none-eabi] +[target.thumbv8m.main-none-eabihf] +# runner = 'probe-rs run --chip STM32G0B1RE' +runner = 'probe-rs run --chip STM32U585CI' rustflags = [ "-C", "link-arg=--nmagic", "-C", "link-arg=-Tlink.x", diff --git a/Cargo.toml b/Cargo.toml index e5b48cb..8144119 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ embassy-stm32 = { "rt", "defmt", "memory-x", - "stm32g0b1re", + # "stm32g0b1re", + "stm32u585ci", "time-driver-any", "exti", "unstable-pac", diff --git a/heatmap.py b/heatmap.py index b87764d..a692367 100644 --- a/heatmap.py +++ b/heatmap.py @@ -1,27 +1,52 @@ #!/usr/bin/env python3 -"""Render a sensor dump (one bracketed row of numbers per line) as a heatmap. +"""Render a sensor dump as a heatmap. Usage: python heatmap.py [input.txt] [-o out.png] [--cmap inferno] -Each line is expected to look like: - [55, 192, 131, ..., 138] -Brackets/commas are optional, so plain whitespace-separated rows also work. +Accepts either: + - Raw device log lines as printed by the firmware, e.g.: + 5.464019 [INFO ] row 0: [71.6, 75.2, ..., 82.1] (tts_test tts-test/src/main.rs:212) + Only the "row N: [...]" part of each line is used; everything else + (timestamps, log level, source location, other info! lines like the + ambient temperature/heatmap range) is ignored. You can paste the whole + console log as-is. + - Plain rows of numbers, one row per line, brackets/commas optional: + [55, 192, 131, ..., 138] """ import argparse import re import sys +_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m") +_ROW_RE = re.compile(r"row\s*(\d+)\s*:\s*\[([^\]]*)\]", re.IGNORECASE) + def parse_grid(text): - """Parse lines of numbers into a 2D list of floats.""" - grid = [] - for line in text.splitlines(): - # strip brackets and split on commas/whitespace - nums = re.findall(r"-?\d+(?:\.\d+)?", line) + """Parse a device log or a plain number dump into a 2D list of floats.""" + text = _ANSI_RE.sub("", text) + + # Prefer explicit "row N: [...]" entries (as printed by the firmware's + # info! logs) so that timestamps, log levels and source locations on the + # same line don't get mistaken for pixel data. + rows = {} + for match in _ROW_RE.finditer(text): + idx = int(match.group(1)) + nums = re.findall(r"-?\d+(?:\.\d+)?", match.group(2)) if nums: - grid.append([float(n) for n in nums]) + rows[idx] = [float(n) for n in nums] + + if rows: + grid = [rows[i] for i in sorted(rows)] + else: + # fall back: one row of numbers per line + grid = [] + for line in text.splitlines(): + nums = re.findall(r"-?\d+(?:\.\d+)?", line) + if nums: + grid.append([float(n) for n in nums]) + if not grid: raise ValueError("No numeric data found in input") width = len(grid[0]) diff --git a/src/calibration.rs b/src/calibration.rs new file mode 100644 index 0000000..2d63295 --- /dev/null +++ b/src/calibration.rs @@ -0,0 +1,172 @@ +//! Reads and stores every calibration constant the sensor needs: both the +//! trim-register values used to reproduce the factory calibration, and the +//! per-pixel compensation data used to turn raw ADC counts into a real +//! temperature (datasheet section 12). + +use crate::eeprom::{read_f32, read_word}; +use crate::Bus; + +/// The sensor's pixel array is 16x16. +pub const GRID: usize = 16; + +/// A full 16x16 per-pixel calibration grid (`ThGrad`, `ThOffset`, `Pij`). +pub type Grid16 = [[T; GRID]; GRID]; + +/// A per-row-profile calibration grid used for electrical offset and VDD +/// compensation. Only 8 row profiles are calibrated; each is reused for two +/// of the array's 16 physical rows, see [`crate::thermal::row_profile`]. +pub type Grid8 = [[i16; GRID]; 8]; + +/// All calibration data read from the sensor's EEPROM at startup. +pub struct Calibration { + /// Trim register values used during factory calibration; must be + /// re-applied so the sensor's analog behaviour matches the calibration + /// data (datasheet section 11.3). + pub mbit: u8, + pub bias: u8, + pub clk: u8, + pub bpa: u8, + pub pu: u8, + + /// Ambient temperature from PTAT (datasheet 12.1). + pub ptat_gradient: f32, + pub ptat_offset: f32, + + /// Thermal offset compensation (datasheet 12.2). + pub gradient_scale_div: i32, // 2^gradScale + pub thermal_gradient: Grid16, // ThGrad + pub thermal_offset: Grid16, // ThOffset + + /// VDD compensation (datasheet 12.4). + pub vdd_comp_gradient: Grid8, + pub vdd_comp_offset: Grid8, + pub vdd_scale_gradient_div: i32, // 2^VddScGrad + pub vdd_scale_offset_div: i32, // 2^VddScOff + pub vdd_at_calibration: [i32; 2], // VddTh1, VddTh2 + pub ptat_at_calibration: [i32; 2], // PtatTh1, PtatTh2 + + /// Sensitivity / object temperature (datasheet 12.5). + pub pixc_min: f32, + pub pixc_max: f32, + pub epsilon: f32, + pub global_gain: f32, + pub sensitivity: Grid16, // Pij + + /// Identifies which calibration table (see [`crate::lookup_table`]) + /// this sensor was factory-calibrated against. + pub table_number: u16, +} + +impl Calibration { + /// Read the full calibration set from the sensor's EEPROM. + pub async fn read(i2c: &mut Bus) -> Self { + let mbit = read_word(i2c, 0x001A).await as u8; + let bias = read_word(i2c, 0x001B).await as u8; + let clk = read_word(i2c, 0x001C).await as u8; + let bpa = read_word(i2c, 0x001D).await as u8; + let pu = read_word(i2c, 0x001E).await as u8; + + let ptat_gradient = read_f32(i2c, 0x0034, 0x0035).await; + let ptat_offset = read_f32(i2c, 0x0036, 0x0037).await; + + let gradient_scale_div = 1i32 << read_word(i2c, 0x0008).await as u8; + let thermal_gradient = read_reversed_grid16_signed(i2c, 0x0100).await; + let thermal_offset = read_reversed_grid16_signed(i2c, 0x0200).await; + + let vdd_comp_gradient = read_packed_grid8(i2c, 0x0040).await; + let vdd_comp_offset = read_packed_grid8(i2c, 0x00A0).await; + let vdd_scale_gradient_div = 1i32 << read_word(i2c, 0x003E).await as u8; + let vdd_scale_offset_div = 1i32 << read_word(i2c, 0x003F).await as u8; + let vdd_at_calibration = [ + read_word(i2c, 0x0025).await as i32, + read_word(i2c, 0x0026).await as i32, + ]; + let ptat_at_calibration = [ + read_word(i2c, 0x002C).await as i32, + read_word(i2c, 0x002D).await as i32, + ]; + + let pixc_min = read_f32(i2c, 0x0000, 0x0001).await; + let pixc_max = read_f32(i2c, 0x0002, 0x0003).await; + let global_gain = read_word(i2c, 0x0009).await as f32; + let epsilon = read_word(i2c, 0x000D).await as f32; + let sensitivity = read_reversed_grid16_raw(i2c, 0x0300).await; + + let table_number = read_word(i2c, 0x000C).await; + + Calibration { + mbit, + bias, + clk, + bpa, + pu, + ptat_gradient, + ptat_offset, + gradient_scale_div, + thermal_gradient, + thermal_offset, + vdd_comp_gradient, + vdd_comp_offset, + vdd_scale_gradient_div, + vdd_scale_offset_div, + vdd_at_calibration, + ptat_at_calibration, + pixc_min, + pixc_max, + epsilon, + global_gain, + sensitivity, + table_number, + } + } +} + +/// Datasheet Fig. 11 layout shared by `ThGrad`, `ThOffset` and `Pij`: 256 +/// consecutive words, stored as two 8-row halves where the second half is +/// stored *row-reversed* (its first stored row is the array's last row). +async fn read_reversed_grid16_raw(i2c: &mut Bus, base: u16) -> Grid16 { + let mut grid: Grid16 = [[0u16; GRID]; GRID]; + for row in 0..8 { + for col in 0..GRID { + grid[row][col] = read_word(i2c, base + (col + row * GRID) as u16).await; + } + } + for row in 0..8 { + for col in 0..GRID { + grid[GRID - 1 - row][col] = read_word(i2c, base + 128 + (col + row * GRID) as u16).await; + } + } + grid +} + +/// [`read_reversed_grid16_raw`], reinterpreted as signed values (`ThGrad`, +/// `ThOffset` are stored as 16-bit signed integers). +async fn read_reversed_grid16_signed(i2c: &mut Bus, base: u16) -> Grid16 { + read_reversed_grid16_raw(i2c, base) + .await + .map(|row| row.map(|v| v as i16)) +} + +/// `VddCompGrad`/`VddCompOff` (datasheet 12.4): 128 values (8 row profiles x +/// 16 columns) packed as signed 12-bit values with an `0x800` bias, 4 values +/// per 3 EEPROM words. +async fn read_packed_grid8(i2c: &mut Bus, base: u16) -> Grid8 { + let mut grid: Grid8 = [[0i16; GRID]; 8]; + let mut index = 0usize; + for group in 0..(GRID * 8 / 4) { + let w0 = read_word(i2c, base + (group * 3) as u16).await; + let w1 = read_word(i2c, base + (group * 3 + 1) as u16).await; + let w2 = read_word(i2c, base + (group * 3 + 2) as u16).await; + let packed_values = [ + w0 & 0x0FFF, + (w0 >> 12) | ((w1 & 0x00FF) << 4), + (w1 >> 8) | ((w2 & 0x000F) << 8), + w2 >> 4, + ]; + for value in packed_values { + grid[index / GRID][index % GRID] = value as i16 - 0x800; + index += 1; + } + } + grid +} diff --git a/src/eeprom.rs b/src/eeprom.rs new file mode 100644 index 0000000..009630e --- /dev/null +++ b/src/eeprom.rs @@ -0,0 +1,36 @@ +//! Low-level helpers for reading the sensor's calibration EEPROM over I2C. +//! +//! The EEPROM is addressed in 16-bit words. Every read follows the same +//! read-only sequence described in the datasheet (section 11.4): +//! SET_ADDRESS -> NORMAL_READ -> GET_DATA -> ACTIVE. Nothing in this module +//! ever writes to the calibration area itself. + +use crate::Bus; + +const EEPROM_ADDR: u8 = 0x1B; + +const SET_ADDRESS: u8 = 0x09; +const NORMAL_READ: u8 = 0x06; +const GET_DATA: u8 = 0x0B; +const ACTIVE: u8 = 0x01; + +/// Read a single 16-bit word from the EEPROM at `addr`. +pub async fn read_word(i2c: &mut Bus, addr: u16) -> u16 { + i2c.write(EEPROM_ADDR, &[SET_ADDRESS, (addr >> 8) as u8, addr as u8]) + .await + .unwrap(); + i2c.write(EEPROM_ADDR, &[NORMAL_READ]).await.unwrap(); + let mut bytes = [0u8; 2]; + i2c.write_read(EEPROM_ADDR, &[GET_DATA], &mut bytes).await.unwrap(); + i2c.write(EEPROM_ADDR, &[ACTIVE]).await.unwrap(); + u16::from_le_bytes(bytes) +} + +/// Reconstruct a little-endian `f32` stored across two consecutive EEPROM +/// words (low word first, matching how `PTATGrad`/`PTATOff`/`PixCmin`/ +/// `PixCmax` etc. are stored). +pub async fn read_f32(i2c: &mut Bus, low_addr: u16, high_addr: u16) -> f32 { + let low = read_word(i2c, low_addr).await as u32; + let high = read_word(i2c, high_addr).await as u32; + f32::from_bits(high << 16 | low) +} diff --git a/src/lookup_table.rs b/src/lookup_table.rs new file mode 100644 index 0000000..ea71acc --- /dev/null +++ b/src/lookup_table.rs @@ -0,0 +1,84 @@ +//! Generic bilinear-interpolation lookup against a Heimann "Table.c"-style +//! object-temperature calibration table (datasheet section 12.5). +//! +//! Heimann calibrates each sensor/optics/gain combination with its own +//! table, selected via a "table number" stored in the sensor's EEPROM. Only +//! tables using an equidistant signal axis (`EQUIADTABLE` in Heimann's +//! reference code) are supported here, which turns the row lookup into a +//! plain shift instead of a per-row search. All of the reference tables we +//! have for the 16x16 sensor use this layout. +//! +//! Both lookups below clamp out-of-range inputs to the nearest edge of the +//! table (linear extrapolation) instead of failing, so a pixel briefly +//! outside the calibrated range just gets a best-effort estimate rather than +//! an error. + +/// The ambient-temperature column to use for a whole frame, located once via +/// [`LookupTable::locate_ambient_column`] since it doesn't vary pixel to +/// pixel. +pub struct AmbientColumn { + index: usize, + delta_dk: i32, +} + +pub struct LookupTable { + /// Heimann's table number (EEPROM `TN`) that this table was made for. + pub table_number: u16, + /// `values[row][column]` is the object temperature in deci-Kelvin. + /// `0` marks an entry outside the sensor's factory-calibrated range. + pub values: &'static [[u16; COLS]; ROWS], + /// Ambient temperature (deci-Kelvin) of each column. + pub ta_columns_dk: [u16; COLS], + /// Scale used to turn a sensitivity-compensated pixel signal into + /// "digits" comparable to this table (Heimann's `PCSCALEVAL`). + pub pixel_signal_scale: f32, + /// Signal digits between two adjacent rows (`ADEQUIDISTANCE`). + pub row_step_digits: i32, + /// `log2(row_step_digits)`; turns a signal into a row index with a shift + /// instead of a division (`ADEXPBITS`). + pub row_exp_bits: u32, + /// Added to a signal before locating its row, so that signals near zero + /// still map to a valid (non-negative) row index (`TABLEOFFSET`). + pub row_offset_digits: i32, + /// Deci-Kelvin between two adjacent columns (`TAEQUIDISTANCE`). + pub column_step_dk: i32, +} + +impl LookupTable { + /// Locate the ambient-temperature column for `ambient_dk`, clamped to + /// the table's calibrated range. + pub fn locate_ambient_column(&self, ambient_dk: i32) -> AmbientColumn { + let last_selectable_column = COLS - 2; // `index + 1` must stay in bounds + let index = (0..=last_selectable_column) + .rev() + .find(|&i| self.ta_columns_dk[i] as i32 <= ambient_dk) + .unwrap_or(0); + AmbientColumn { + index, + delta_dk: ambient_dk - self.ta_columns_dk[index] as i32, + } + } + + /// Bi-linear interpolation of the object temperature (deci-Kelvin) for + /// one pixel's sensitivity-compensated signal (in digits). + pub fn lookup(&self, column: &AmbientColumn, signal_digits: i32) -> i32 { + let shifted_signal = (signal_digits + self.row_offset_digits) >> self.row_exp_bits; + let row = shifted_signal.clamp(0, (ROWS - 2) as i32) as usize; + + let c = column.index; + let top_left = self.values[row][c] as i32; + let top_right = self.values[row][c + 1] as i32; + let bottom_left = self.values[row + 1][c] as i32; + let bottom_right = self.values[row + 1][c + 1] as i32; + + // interpolate along the ambient-temperature axis, at this row and the next + let at_row = (top_right - top_left) * column.delta_dk / self.column_step_dk + top_left; + let at_next_row = + (bottom_right - bottom_left) * column.delta_dk / self.column_step_dk + bottom_left; + + // interpolate along the signal axis, between those two results + let row_start_digits = row as i32 * self.row_step_digits; + let position_in_row = (signal_digits + self.row_offset_digits) - row_start_digits; + (at_next_row - at_row) * position_in_row / self.row_step_digits + at_row + } +} diff --git a/src/main.rs b/src/main.rs index 421f7b0..f968280 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,156 +1,90 @@ #![no_main] #![no_std] +mod calibration; +mod eeprom; +mod lookup_table; +mod sensor; +mod table_tn114; +mod thermal; + use defmt::*; use embassy_executor::Spawner; +use embassy_stm32::dma; use embassy_stm32::mode::Async; -use embassy_stm32::{Config, bind_interrupts, dma::InterruptHandler, i2c::{self, ErrorInterruptHandler, EventInterruptHandler, I2c, Master}, peripherals::{DMA1_CH1, DMA1_CH2, I2C2}}; -use embassy_time::Timer; +use embassy_stm32::peripherals::{GPDMA1_CH0, GPDMA1_CH1, I2C2}; +use embassy_stm32::{bind_interrupts, i2c, rcc, time, Config}; use defmt_rtt as _; use panic_probe as _; bind_interrupts!(struct Irqs { - DMA1_CHANNEL2_3 => InterruptHandler; - DMA1_CHANNEL1 => InterruptHandler; - I2C2_3 => EventInterruptHandler, ErrorInterruptHandler; + GPDMA1_CHANNEL0 => dma::InterruptHandler; + GPDMA1_CHANNEL1 => dma::InterruptHandler; + I2C2_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; }); -const SENSOR: u8 = 0x1A; -const EEPROM: u8 = 0x1B; - -type Bus = I2c<'static, Async, Master>; - -/// Read a 16-bit word from the sensor EEPROM (read-only sequence, never writes). -async fn eeprom_word(i2c: &mut Bus, addr: u16) -> u16 { - i2c.write(EEPROM, &[0x09, (addr >> 8) as u8, addr as u8]).await.unwrap(); // SET_ADDRESS - i2c.write(EEPROM, &[0x06]).await.unwrap(); // NORMAL_READ - let mut b = [0u8; 2]; - i2c.write_read(EEPROM, &[0x0B], &mut b).await.unwrap(); // GET_DATA - i2c.write(EEPROM, &[0x01]).await.unwrap(); // ACTIVE - (b[1] as u16) << 8 | b[0] as u16 -} - -/// Reconstruct a little-endian float stored across two EEPROM words. -async fn eeprom_f32(i2c: &mut Bus, lo_addr: u16, hi_addr: u16) -> f32 { - let lo = eeprom_word(i2c, lo_addr).await as u32; - let hi = eeprom_word(i2c, hi_addr).await as u32; - f32::from_bits(hi << 16 | lo) -} - -/// Run one conversion with the given configuration byte and read both halves. -async fn read_block(i2c: &mut Bus, config: u8) -> ([u8; 130], [u8; 130]) { - i2c.write(SENSOR, &[0x01, config]).await.unwrap(); - let mut status = [0u8; 1]; - loop { - i2c.write_read(SENSOR, &[0x02], &mut status).await.unwrap(); - if status[0] & 0x01 != 0 { - break; - } - } - let mut top = [0u8; 130]; - let mut bottom = [0u8; 130]; - i2c.write_read(SENSOR, &[0x0A], &mut top).await.unwrap(); - i2c.write_read(SENSOR, &[0x0B], &mut bottom).await.unwrap(); - (top, bottom) -} - -/// Pixel `j` (0..63) of a half-block buffer: word0 is PTAT/VDD, pixels start at byte 2. -fn px(buf: &[u8; 130], j: usize) -> u16 { - (buf[2 * j + 2] as u16) << 8 | buf[2 * j + 3] as u16 -} +/// Shared I2C bus type used by every module that talks to the sensor. +pub type Bus = i2c::I2c<'static, Async, i2c::Master>; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Config::default()); + let mut config = Config::default(); + configure_clocks(&mut config); + let p = embassy_stm32::init(config); let mut i2c_cfg = i2c::Config::default(); i2c_cfg.sda_pullup = false; i2c_cfg.scl_pullup = false; - let mut i2c = I2c::new(p.I2C2, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2, Irqs, i2c_cfg); + let mut i2c = i2c::I2c::new(p.I2C2, p.PB13, p.PB14, p.GPDMA1_CH0, p.GPDMA1_CH1, Irqs, i2c_cfg); - // --- read calibration data from EEPROM (read-only) --- - let mbit = eeprom_word(&mut i2c, 0x001A).await as u8; - let bias = eeprom_word(&mut i2c, 0x001B).await as u8; - let clk = eeprom_word(&mut i2c, 0x001C).await as u8; - let bpa = eeprom_word(&mut i2c, 0x001D).await as u8; - let pu = eeprom_word(&mut i2c, 0x001E).await as u8; - let gradscale = eeprom_word(&mut i2c, 0x0008).await as u8; - let ptatgr = eeprom_f32(&mut i2c, 0x0034, 0x0035).await; - let ptatoff = eeprom_f32(&mut i2c, 0x0036, 0x0037).await; - - // ThGrad and ThOffset (16x16 signed), bottom half is stored row-reversed. - let mut thgrad = [[0i16; 16]; 16]; - let mut thoffset = [[0i16; 16]; 16]; - for (base, dst) in [(0x0100u16, &mut thgrad), (0x0200u16, &mut thoffset)] { - for m in 0..8 { - for n in 0..16 { - dst[m][n] = eeprom_word(&mut i2c, base + (n + m * 16) as u16).await as i16; - } - } - for k in 0..8 { - for n in 0..16 { - dst[15 - k][n] = eeprom_word(&mut i2c, base + 128 + (n + k * 16) as u16).await as i16; - } - } + let cal = calibration::Calibration::read(&mut i2c).await; + info!("sensor table number (TN): {}", cal.table_number); + if cal.table_number != table_tn114::TABLE.table_number { + warn!( + "sensor's TableNumber ({}) does not match the embedded calibration table ({}); \ + object temperatures will be inaccurate", + cal.table_number, + table_tn114::TABLE.table_number + ); } - // --- wake up sensor and load calibration into trim registers --- - i2c.write(SENSOR, &[0x01, 0x01]).await.unwrap(); - Timer::after_millis(30).await; - for (reg, val) in [(0x03, mbit), (0x04, bias), (0x05, bias), (0x06, clk), (0x07, bpa), (0x08, bpa), (0x09, pu)] { - i2c.write(SENSOR, &[reg, val]).await.unwrap(); - Timer::after_millis(5).await; - } + sensor::wake_up_and_load_trim(&mut i2c, &cal).await; - // --- read both pixel blocks (each carries a PTAT value + its pixels) --- - let (top0, bot0) = read_block(&mut i2c, 0x09).await; // block 0, PTAT - let (top1, bot1) = read_block(&mut i2c, 0x19).await; // block 1, PTAT - // --- electrical offset (blind measurement) --- - let (eo_top, eo_bot) = read_block(&mut i2c, 0x0B).await; + let frame = sensor::Frame::capture(&mut i2c).await; + let heatmap = thermal::process_frame(&cal, &frame, &table_tn114::TABLE); - // ambient temperature from averaged PTAT (datasheet 11.1) - let ptat = |b: &[u8; 130]| (b[0] as u32) << 8 | b[1] as u32; - let ptat_av = ((ptat(&top0) + ptat(&top1) + ptat(&bot0) + ptat(&bot1)) / 4) as f32; - let ambient_dk = ptat_av * ptatgr + ptatoff; - let ambient_c = ambient_dk / 10.0 - 273.15; - - // sort raw pixels into a 16x16 image (datasheet ordering) - let mut image = [[0i32; 16]; 16]; - let gradscale_div = 1i32 << gradscale; - for n in 0..16 { - let raw = [ - px(&top0, n), px(&top0, n + 16), px(&top0, n + 32), px(&top0, n + 48), - px(&top1, n), px(&top1, n + 16), px(&top1, n + 32), px(&top1, n + 48), - px(&bot1, n + 48), px(&bot1, n + 32), px(&bot1, n + 16), px(&bot1, n), - px(&bot0, n + 48), px(&bot0, n + 32), px(&bot0, n + 16), px(&bot0, n), - ]; - let eo = [ - px(&eo_top, n), px(&eo_top, n + 16), px(&eo_top, n + 32), px(&eo_top, n + 48), - px(&eo_bot, n + 48), px(&eo_bot, n + 32), px(&eo_bot, n + 16), px(&eo_bot, n), - ]; - for m in 0..16 { - // thermal-offset compensation (11.2) + electrical-offset compensation (11.3) - let comp = raw[m] as i32 - - (thgrad[m][n] as i32 * ptat_av as i32) / gradscale_div - - thoffset[m][n] as i32; - let eo_row = if m < 8 { m % 4 } else { m % 4 + 4 }; - image[m][n] = comp - eo[eo_row] as i32; - } - } - - let mut min = i32::MAX; - let mut max = i32::MIN; - for row in &image { - for &v in row { - min = min.min(v); - max = max.max(v); - } - } - - info!("ambient temperature: {} C (PTAT_av={})", ambient_c, ptat_av); - info!("compensated image range: {}..{}", min, max); - for m in 0..16 { - info!("row {}: {}", m, image[m]); + info!("ambient temperature: {} C", heatmap.ambient_c); + info!("heatmap range: {}..{} C", heatmap.min_c, heatmap.max_c); + for row in 0..calibration::GRID { + info!("row {}: {}", row, heatmap.temperature_c[row]); } } + +/// Configure the system clock to run at its maximum from the board's 25 MHz +/// HSE: PLL1 = (25 MHz / 5) * 64 / 2 = 160 MHz, used as SYSCLK. Also enables +/// the board's 32.768 kHz LSE for the RTC. +fn configure_clocks(config: &mut Config) { + config.rcc.hse = Some(rcc::Hse { + freq: time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV5, + mul: rcc::PllMul::MUL64, + divp: Some(rcc::PllDiv::DIV2), + divq: Some(rcc::PllDiv::DIV2), + divr: Some(rcc::PllDiv::DIV2), // 160 MHz + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; + + config.rcc.ls.lse = Some(rcc::LseConfig { + frequency: time::Hertz(32_768), + mode: rcc::LseMode::Oscillator(rcc::LseDrive::MediumHigh), + peripherals_clocked: false, + }); + config.rcc.ls.rtc = rcc::RtcClockSource::LSE; +} diff --git a/src/sensor.rs b/src/sensor.rs new file mode 100644 index 0000000..e477d79 --- /dev/null +++ b/src/sensor.rs @@ -0,0 +1,117 @@ +//! Talks to the sensor's pixel array (as opposed to its calibration +//! EEPROM, see [`crate::eeprom`]): waking it up, loading its trim +//! registers, and reading raw pixel/PTAT/VDD data. + +use embassy_time::Timer; + +use crate::calibration::Calibration; +use crate::Bus; + +const SENSOR_ADDR: u8 = 0x1A; + +const CONFIGURATION_REGISTER: u8 = 0x01; +const STATUS_REGISTER: u8 = 0x02; +const TOP_HALF: u8 = 0x0A; +const BOTTOM_HALF: u8 = 0x0B; +const END_OF_CONVERSION: u8 = 0x01; + +/// Configuration register bits (datasheet Table 6). +mod config_bit { + pub const WAKEUP: u8 = 1 << 0; + pub const BLIND: u8 = 1 << 1; // sample electrical offsets instead of the pixels + pub const VDD_MEAS: u8 = 1 << 2; // measure VDD instead of PTAT + pub const START: u8 = 1 << 3; + pub const BLOCK1: u8 = 1 << 4; // select block 1 instead of block 0 +} + +/// One raw half-array conversion result: a 2-byte header word (PTAT or VDD, +/// depending on which config bits were used) followed by 64 pixel words. +pub type RawHalf = [u8; 130]; + +/// Wake the sensor up and load the trim registers used during its factory +/// calibration (datasheet section 11.3). Must be called before capturing any +/// frames. +pub async fn wake_up_and_load_trim(i2c: &mut Bus, cal: &Calibration) { + i2c.write(SENSOR_ADDR, &[CONFIGURATION_REGISTER, config_bit::WAKEUP]) + .await + .unwrap(); + Timer::after_millis(30).await; + + let trim_registers = [ + (0x03, cal.mbit), + (0x04, cal.bias), + (0x05, cal.bias), + (0x06, cal.clk), + (0x07, cal.bpa), + (0x08, cal.bpa), + (0x09, cal.pu), + ]; + for (register, value) in trim_registers { + i2c.write(SENSOR_ADDR, &[register, value]).await.unwrap(); + Timer::after_millis(5).await; + } +} + +/// Everything read from the sensor for one temperature calculation: the two +/// pixel/PTAT blocks, the two VDD blocks, and the electrical-offset (blind) +/// block. +pub struct Frame { + pub pixel_top: [RawHalf; 2], + pub pixel_bottom: [RawHalf; 2], + pub electrical_offset: (RawHalf, RawHalf), + pub vdd_top: [RawHalf; 2], + pub vdd_bottom: [RawHalf; 2], +} + +impl Frame { + /// Trigger the five conversions needed for one temperature calculation + /// and read all of their results back. + pub async fn capture(i2c: &mut Bus) -> Self { + use config_bit::*; + + let (top0, bottom0) = read_halves(i2c, WAKEUP | START).await; + let (top1, bottom1) = read_halves(i2c, WAKEUP | START | BLOCK1).await; + let electrical_offset = read_halves(i2c, WAKEUP | START | BLIND).await; + let (vdd_top0, vdd_bottom0) = read_halves(i2c, WAKEUP | START | VDD_MEAS).await; + let (vdd_top1, vdd_bottom1) = read_halves(i2c, WAKEUP | START | VDD_MEAS | BLOCK1).await; + + Frame { + pixel_top: [top0, top1], + pixel_bottom: [bottom0, bottom1], + electrical_offset, + vdd_top: [vdd_top0, vdd_top1], + vdd_bottom: [vdd_bottom0, vdd_bottom1], + } + } +} + +/// Trigger one conversion with the given configuration bits and read back +/// both halves of the array once it completes. +async fn read_halves(i2c: &mut Bus, config: u8) -> (RawHalf, RawHalf) { + i2c.write(SENSOR_ADDR, &[CONFIGURATION_REGISTER, config]).await.unwrap(); + + let mut status = [0u8; 1]; + loop { + i2c.write_read(SENSOR_ADDR, &[STATUS_REGISTER], &mut status).await.unwrap(); + if status[0] & END_OF_CONVERSION != 0 { + break; + } + } + + let mut top = [0u8; 130]; + let mut bottom = [0u8; 130]; + i2c.write_read(SENSOR_ADDR, &[TOP_HALF], &mut top).await.unwrap(); + i2c.write_read(SENSOR_ADDR, &[BOTTOM_HALF], &mut bottom).await.unwrap(); + (top, bottom) +} + +/// Header word (bytes 0/1) of a half-array buffer: PTAT or VDD, depending on +/// which config bits were used for the conversion that produced it. +pub fn header_word(buf: &RawHalf) -> u32 { + ((buf[0] as u32) << 8) | buf[1] as u32 +} + +/// Pixel `index` (0..63) of a half-array buffer. +pub fn pixel(buf: &RawHalf, index: usize) -> u16 { + (buf[2 * index + 2] as u16) << 8 | buf[2 * index + 3] as u16 +} diff --git a/src/table_tn114.rs b/src/table_tn114.rs new file mode 100644 index 0000000..8aa5ecd --- /dev/null +++ b/src/table_tn114.rs @@ -0,0 +1,1625 @@ +//! Factory calibration table for the HTPA16x16dR2 "L2.1 HiSi F5.0 Gain 3k3" +//! optics/gain variant -- Heimann table number 114. +//! +//! Extracted from Heimann's reference Table.c (the +//! HTPA16x16dR2L2_1HiSiF5_0_Gain3k3 block, from +//! `_reference/Thermopile IR Arrays/Software/old/SampleCode16x16dR2/Table.c`). +//! +//! `VALUES[row][column]` is the object temperature in deci-Kelvin for a given +//! sensitivity-compensated pixel signal (row) and ambient temperature (column). +//! `0` marks a cell outside the sensor's factory-calibrated range. + +use crate::lookup_table::LookupTable; + +const ROWS: usize = 1595; +const COLS: usize = 7; + +pub static TABLE: LookupTable = LookupTable { + table_number: 114, + values: &VALUES, + ta_columns_dk: [2782, 2882, 2982, 3082, 3182, 3282, 3382], + pixel_signal_scale: 100_000_000.0, // PCSCALEVAL + row_step_digits: 64, // ADEQUIDISTANCE + row_exp_bits: 6, // ADEXPBITS + row_offset_digits: 1024, // TABLEOFFSET + column_step_dk: 100, // TAEQUIDISTANCE +}; + +#[rustfmt::skip] +static VALUES: [[u16; COLS]; ROWS] = [ + [0, 0, 1210, 1841, 2175, 2427, 2638], + [0, 0, 1586, 2001, 2282, 2508, 2703], + [0, 1222, 1810, 2132, 2377, 2582, 2765], + [0, 1592, 1976, 2244, 2462, 2651, 2822], + [1321, 1814, 2111, 2343, 2540, 2716, 2877], + [1643, 1979, 2226, 2431, 2612, 2776, 2929], + [1850, 2114, 2327, 2512, 2679, 2833, 2978], + [2008, 2228, 2417, 2586, 2741, 2887, 3026], + [2138, 2329, 2499, 2655, 2800, 2939, 3071], + [2250, 2419, 2574, 2719, 2856, 2988, 3115], + [2348, 2500, 2643, 2779, 2909, 3035, 3157], + [2436, 2575, 2708, 2836, 2959, 3080, 3198], + [2516, 2645, 2769, 2890, 3008, 3123, 3237], + [2590, 2709, 2826, 2941, 3054, 3165, 3275], + [2658, 2770, 2881, 2990, 3098, 3205, 3312], + [2722, 2828, 2933, 3037, 3141, 3244, 3347], + [2782, 2882, 2982, 3082, 3182, 3282, 3382], + [2839, 2934, 3029, 3125, 3222, 3319, 3416], + [2893, 2983, 3075, 3167, 3260, 3354, 3449], + [2944, 3030, 3118, 3207, 3297, 3389, 3481], + [2993, 3075, 3160, 3246, 3334, 3422, 3512], + [3039, 3119, 3201, 3284, 3369, 3455, 3543], + [3084, 3161, 3240, 3320, 3403, 3487, 3572], + [3127, 3201, 3278, 3356, 3436, 3518, 3601], + [3169, 3240, 3314, 3390, 3468, 3548, 3630], + [3209, 3278, 3350, 3424, 3500, 3578, 3658], + [3248, 3315, 3385, 3457, 3531, 3607, 3685], + [3286, 3351, 3418, 3488, 3561, 3636, 3712], + [3322, 3385, 3451, 3519, 3590, 3663, 3738], + [3358, 3419, 3483, 3550, 3619, 3691, 3764], + [3392, 3452, 3514, 3580, 3647, 3717, 3790], + [3426, 3484, 3545, 3609, 3675, 3743, 3814], + [3458, 3515, 3575, 3637, 3702, 3769, 3839], + [3490, 3545, 3604, 3665, 3728, 3794, 3863], + [3521, 3575, 3632, 3692, 3754, 3819, 3887], + [3551, 3604, 3660, 3719, 3780, 3844, 3910], + [3581, 3633, 3687, 3745, 3805, 3868, 3933], + [3610, 3661, 3714, 3770, 3829, 3891, 3955], + [3638, 3688, 3740, 3796, 3854, 3914, 3977], + [3666, 3715, 3766, 3820, 3877, 3937, 3999], + [3693, 3741, 3791, 3845, 3901, 3959, 4021], + [3720, 3767, 3816, 3869, 3924, 3982, 4042], + [3746, 3792, 3841, 3892, 3946, 4003, 4063], + [3772, 3817, 3865, 3915, 3969, 4025, 4083], + [3797, 3841, 3888, 3938, 3991, 4046, 4104], + [3822, 3865, 3912, 3961, 4012, 4067, 4124], + [3846, 3889, 3934, 3983, 4034, 4087, 4144], + [3870, 3912, 3957, 4004, 4055, 4108, 4163], + [3893, 3935, 3979, 4026, 4075, 4128, 4182], + [3917, 3957, 4001, 4047, 4096, 4147, 4201], + [3939, 3979, 4022, 4068, 4116, 4167, 4220], + [3962, 4001, 4043, 4088, 4136, 4186, 4239], + [3984, 4023, 4064, 4109, 4156, 4205, 4257], + [4006, 4044, 4085, 4129, 4175, 4224, 4275], + [4027, 4065, 4105, 4148, 4194, 4243, 4293], + [4048, 4085, 4125, 4168, 4213, 4261, 4311], + [4069, 4106, 4145, 4187, 4232, 4279, 4329], + [4089, 4126, 4165, 4206, 4250, 4297, 4346], + [4110, 4145, 4184, 4225, 4268, 4315, 4363], + [4130, 4165, 4203, 4243, 4287, 4332, 4380], + [4149, 4184, 4222, 4262, 4304, 4349, 4397], + [4169, 4203, 4240, 4280, 4322, 4367, 4414], + [4188, 4222, 4259, 4298, 4339, 4384, 4430], + [4207, 4241, 4277, 4315, 4357, 4400, 4447], + [4226, 4259, 4295, 4333, 4374, 4417, 4463], + [4244, 4277, 4313, 4350, 4391, 4433, 4479], + [4263, 4295, 4330, 4367, 4407, 4450, 4495], + [4281, 4313, 4347, 4384, 4424, 4466, 4511], + [4299, 4330, 4365, 4401, 4440, 4482, 4527], + [4316, 4348, 4382, 4418, 4456, 4498, 4543], + [4334, 4365, 4398, 4434, 4473, 4513, 4560], + [4351, 4382, 4415, 4450, 4489, 4529, 4576], + [4368, 4399, 4431, 4467, 4504, 4545, 4593], + [4385, 4415, 4448, 4483, 4519, 4561, 4610], + [4402, 4432, 4464, 4499, 4534, 4577, 4626], + [4419, 4448, 4480, 4513, 4550, 4593, 4643], + [4435, 4464, 4497, 4528, 4565, 4609, 4660], + [4451, 4480, 4511, 4543, 4581, 4625, 4677], + [4467, 4500, 4525, 4557, 4596, 4642, 4694], + [4483, 4513, 4539, 4572, 4612, 4658, 4711], + [4505, 4526, 4554, 4588, 4628, 4675, 4729], + [4517, 4539, 4568, 4603, 4644, 4692, 4745], + [4530, 4553, 4582, 4618, 4660, 4709, 4761], + [4542, 4567, 4597, 4633, 4676, 4726, 4776], + [4555, 4581, 4612, 4649, 4693, 4742, 4792], + [4568, 4595, 4627, 4665, 4709, 4758, 4807], + [4582, 4609, 4642, 4681, 4726, 4773, 4822], + [4595, 4623, 4657, 4696, 4742, 4789, 4837], + [4609, 4637, 4672, 4713, 4758, 4804, 4852], + [4622, 4652, 4687, 4729, 4773, 4819, 4867], + [4636, 4667, 4703, 4745, 4788, 4834, 4882], + [4650, 4682, 4719, 4760, 4804, 4849, 4896], + [4664, 4697, 4734, 4776, 4819, 4864, 4911], + [4679, 4712, 4750, 4791, 4834, 4879, 4925], + [4693, 4727, 4766, 4806, 4849, 4893, 4940], + [4708, 4743, 4781, 4821, 4864, 4908, 4954], + [4723, 4758, 4796, 4836, 4879, 4923, 4969], + [4738, 4774, 4812, 4851, 4893, 4937, 4983], + [4753, 4789, 4827, 4866, 4908, 4951, 4997], + [4769, 4804, 4842, 4881, 4922, 4966, 5011], + [4784, 4819, 4857, 4896, 4937, 4980, 5025], + [4799, 4834, 4871, 4910, 4951, 4994, 5039], + [4814, 4849, 4886, 4925, 4966, 5008, 5053], + [4830, 4864, 4901, 4939, 4980, 5022, 5066], + [4845, 4879, 4915, 4954, 4994, 5036, 5080], + [4860, 4894, 4930, 4968, 5008, 5050, 5094], + [4874, 4908, 4944, 4982, 5022, 5064, 5107], + [4889, 4923, 4959, 4996, 5036, 5077, 5121], + [4904, 4937, 4973, 5010, 5050, 5091, 5134], + [4918, 4952, 4987, 5024, 5064, 5104, 5147], + [4933, 4966, 5001, 5038, 5077, 5118, 5161], + [4947, 4980, 5015, 5052, 5091, 5131, 5174], + [4962, 4994, 5029, 5066, 5104, 5145, 5187], + [4976, 5008, 5043, 5080, 5118, 5158, 5200], + [4990, 5022, 5057, 5093, 5131, 5171, 5213], + [5004, 5036, 5071, 5107, 5145, 5184, 5226], + [5018, 5050, 5084, 5120, 5158, 5197, 5239], + [5032, 5064, 5098, 5133, 5171, 5210, 5251], + [5046, 5078, 5111, 5147, 5184, 5223, 5264], + [5060, 5091, 5125, 5160, 5197, 5236, 5277], + [5073, 5105, 5138, 5173, 5210, 5249, 5289], + [5087, 5118, 5151, 5186, 5223, 5262, 5302], + [5100, 5132, 5165, 5199, 5236, 5274, 5315], + [5114, 5145, 5178, 5212, 5249, 5287, 5327], + [5127, 5158, 5191, 5225, 5262, 5300, 5339], + [5141, 5171, 5204, 5238, 5274, 5312, 5352], + [5154, 5185, 5217, 5251, 5287, 5325, 5364], + [5167, 5198, 5230, 5264, 5299, 5337, 5376], + [5180, 5211, 5243, 5276, 5312, 5349, 5388], + [5193, 5224, 5255, 5289, 5324, 5362, 5400], + [5206, 5236, 5268, 5302, 5337, 5374, 5412], + [5219, 5249, 5281, 5314, 5349, 5386, 5424], + [5232, 5262, 5293, 5327, 5361, 5398, 5436], + [5245, 5275, 5306, 5339, 5374, 5410, 5448], + [5258, 5287, 5318, 5351, 5386, 5422, 5460], + [5271, 5300, 5331, 5364, 5398, 5434, 5472], + [5283, 5312, 5343, 5376, 5410, 5446, 5484], + [5296, 5325, 5355, 5388, 5422, 5458, 5495], + [5308, 5337, 5368, 5400, 5434, 5470, 5507], + [5321, 5350, 5380, 5412, 5446, 5481, 5519], + [5333, 5362, 5392, 5424, 5458, 5493, 5530], + [5346, 5374, 5404, 5436, 5470, 5505, 5542], + [5358, 5386, 5416, 5448, 5481, 5516, 5553], + [5370, 5398, 5428, 5460, 5493, 5528, 5565], + [5382, 5410, 5440, 5472, 5505, 5539, 5576], + [5394, 5422, 5452, 5483, 5516, 5551, 5587], + [5407, 5434, 5464, 5495, 5528, 5562, 5599], + [5419, 5446, 5476, 5507, 5539, 5574, 5610], + [5431, 5458, 5487, 5518, 5551, 5585, 5621], + [5442, 5470, 5499, 5530, 5562, 5596, 5632], + [5454, 5482, 5511, 5541, 5574, 5608, 5643], + [5466, 5493, 5522, 5553, 5585, 5619, 5654], + [5478, 5505, 5534, 5564, 5596, 5630, 5665], + [5490, 5517, 5545, 5576, 5608, 5641, 5676], + [5501, 5528, 5557, 5587, 5619, 5652, 5687], + [5513, 5540, 5568, 5598, 5630, 5663, 5698], + [5525, 5551, 5580, 5609, 5641, 5674, 5709], + [5536, 5563, 5591, 5621, 5652, 5685, 5720], + [5548, 5574, 5602, 5632, 5663, 5696, 5731], + [5559, 5585, 5613, 5643, 5674, 5707, 5741], + [5570, 5597, 5624, 5654, 5685, 5718, 5752], + [5582, 5608, 5636, 5665, 5696, 5728, 5763], + [5593, 5619, 5647, 5676, 5707, 5739, 5773], + [5604, 5630, 5658, 5687, 5718, 5750, 5784], + [5615, 5641, 5669, 5698, 5728, 5761, 5794], + [5627, 5652, 5680, 5709, 5739, 5771, 5805], + [5638, 5663, 5691, 5719, 5750, 5782, 5815], + [5649, 5674, 5701, 5730, 5760, 5792, 5826], + [5660, 5685, 5712, 5741, 5771, 5803, 5836], + [5671, 5696, 5723, 5752, 5782, 5813, 5846], + [5682, 5707, 5734, 5762, 5792, 5824, 5857], + [5693, 5718, 5745, 5773, 5803, 5834, 5867], + [5704, 5729, 5755, 5783, 5813, 5844, 5877], + [5714, 5739, 5766, 5794, 5824, 5855, 5888], + [5725, 5750, 5777, 5804, 5834, 5865, 5898], + [5736, 5761, 5787, 5815, 5844, 5875, 5908], + [5747, 5771, 5798, 5825, 5855, 5886, 5918], + [5757, 5782, 5808, 5836, 5865, 5896, 5928], + [5768, 5793, 5819, 5846, 5875, 5906, 5938], + [5779, 5803, 5829, 5856, 5885, 5916, 5948], + [5789, 5813, 5839, 5867, 5896, 5926, 5958], + [5800, 5824, 5850, 5877, 5906, 5936, 5968], + [5810, 5834, 5860, 5887, 5916, 5946, 5978], + [5821, 5845, 5870, 5897, 5926, 5956, 5988], + [5831, 5855, 5880, 5907, 5936, 5966, 5998], + [5841, 5865, 5891, 5918, 5946, 5976, 6007], + [5852, 5876, 5901, 5928, 5956, 5986, 6017], + [5862, 5886, 5911, 5938, 5966, 5996, 6027], + [5872, 5896, 5921, 5948, 5976, 6005, 6037], + [5882, 5906, 5931, 5958, 5986, 6015, 6046], + [5893, 5916, 5941, 5968, 5996, 6025, 6056], + [5903, 5926, 5951, 5978, 6005, 6035, 6066], + [5913, 5936, 5961, 5987, 6015, 6044, 6075], + [5923, 5946, 5971, 5997, 6025, 6054, 6085], + [5933, 5956, 5981, 6007, 6035, 6064, 6094], + [5943, 5966, 5991, 6017, 6044, 6073, 6104], + [5953, 5976, 6001, 6027, 6054, 6083, 6113], + [5963, 5986, 6010, 6036, 6064, 6092, 6123], + [5973, 5996, 6020, 6046, 6073, 6102, 6132], + [5983, 6006, 6030, 6056, 6083, 6111, 6142], + [5993, 6015, 6040, 6065, 6092, 6121, 6151], + [6003, 6025, 6049, 6075, 6102, 6130, 6160], + [6012, 6035, 6059, 6084, 6111, 6140, 6170], + [6022, 6045, 6069, 6094, 6121, 6149, 6179], + [6032, 6054, 6078, 6103, 6130, 6158, 6188], + [6042, 6064, 6088, 6113, 6140, 6168, 6197], + [6051, 6074, 6097, 6122, 6149, 6177, 6207], + [6061, 6083, 6107, 6132, 6158, 6186, 6216], + [6070, 6093, 6116, 6141, 6168, 6196, 6225], + [6080, 6102, 6126, 6151, 6177, 6205, 6234], + [6090, 6112, 6135, 6160, 6186, 6214, 6243], + [6099, 6121, 6144, 6169, 6195, 6223, 6252], + [6109, 6131, 6154, 6179, 6205, 6232, 6261], + [6118, 6140, 6163, 6188, 6214, 6241, 6270], + [6128, 6149, 6172, 6197, 6223, 6250, 6279], + [6137, 6159, 6182, 6206, 6232, 6259, 6288], + [6146, 6168, 6191, 6215, 6241, 6268, 6297], + [6156, 6177, 6200, 6225, 6250, 6277, 6306], + [6165, 6186, 6209, 6234, 6259, 6286, 6315], + [6174, 6196, 6219, 6243, 6268, 6295, 6324], + [6184, 6205, 6228, 6252, 6277, 6304, 6333], + [6193, 6214, 6237, 6261, 6286, 6313, 6341], + [6202, 6223, 6246, 6270, 6295, 6322, 6350], + [6211, 6232, 6255, 6279, 6304, 6331, 6359], + [6220, 6241, 6264, 6288, 6313, 6340, 6368], + [6229, 6251, 6273, 6297, 6322, 6348, 6376], + [6239, 6260, 6282, 6306, 6331, 6357, 6385], + [6248, 6269, 6291, 6315, 6340, 6366, 6394], + [6257, 6278, 6300, 6323, 6348, 6375, 6402], + [6266, 6287, 6309, 6332, 6357, 6383, 6411], + [6275, 6296, 6318, 6341, 6366, 6392, 6420], + [6284, 6304, 6326, 6350, 6375, 6401, 6428], + [6293, 6313, 6335, 6359, 6383, 6409, 6437], + [6302, 6322, 6344, 6367, 6392, 6418, 6445], + [6310, 6331, 6353, 6376, 6401, 6427, 6454], + [6319, 6340, 6362, 6385, 6409, 6435, 6462], + [6328, 6349, 6370, 6393, 6418, 6444, 6471], + [6337, 6357, 6379, 6402, 6426, 6452, 6479], + [6346, 6366, 6388, 6411, 6435, 6461, 6488], + [6355, 6375, 6396, 6419, 6444, 6469, 6496], + [6363, 6384, 6405, 6428, 6452, 6478, 6505], + [6372, 6392, 6414, 6437, 6461, 6486, 6513], + [6381, 6401, 6422, 6445, 6469, 6494, 6521], + [6390, 6410, 6431, 6454, 6478, 6503, 6530], + [6398, 6418, 6439, 6462, 6486, 6511, 6538], + [6407, 6427, 6448, 6471, 6494, 6520, 6546], + [6415, 6435, 6457, 6479, 6503, 6528, 6554], + [6424, 6444, 6465, 6487, 6511, 6536, 6563], + [6433, 6452, 6473, 6496, 6520, 6545, 6571], + [6441, 6461, 6482, 6504, 6528, 6553, 6579], + [6450, 6469, 6490, 6513, 6536, 6561, 6587], + [6458, 6478, 6499, 6521, 6544, 6569, 6595], + [6467, 6486, 6507, 6529, 6553, 6577, 6604], + [6475, 6495, 6515, 6538, 6561, 6586, 6612], + [6484, 6503, 6524, 6546, 6569, 6594, 6620], + [6492, 6511, 6532, 6554, 6577, 6602, 6628], + [6500, 6520, 6540, 6562, 6586, 6610, 6636], + [6509, 6528, 6549, 6571, 6594, 6618, 6644], + [6517, 6536, 6557, 6579, 6602, 6626, 6652], + [6525, 6545, 6565, 6587, 6610, 6634, 6660], + [6534, 6553, 6573, 6595, 6618, 6642, 6668], + [6542, 6561, 6582, 6603, 6626, 6650, 6676], + [6550, 6569, 6590, 6611, 6634, 6658, 6684], + [6559, 6578, 6598, 6619, 6642, 6666, 6692], + [6567, 6586, 6606, 6628, 6650, 6674, 6700], + [6575, 6594, 6614, 6636, 6658, 6682, 6708], + [6583, 6602, 6622, 6644, 6666, 6690, 6715], + [6591, 6610, 6630, 6652, 6674, 6698, 6723], + [6600, 6618, 6638, 6660, 6682, 6706, 6731], + [6608, 6626, 6646, 6668, 6690, 6714, 6739], + [6616, 6634, 6654, 6676, 6698, 6722, 6747], + [6624, 6643, 6662, 6684, 6706, 6730, 6755], + [6632, 6651, 6670, 6691, 6714, 6737, 6762], + [6640, 6659, 6678, 6699, 6722, 6745, 6770], + [6648, 6667, 6686, 6707, 6730, 6753, 6778], + [6656, 6675, 6694, 6715, 6737, 6761, 6786], + [6664, 6682, 6702, 6723, 6745, 6769, 6793], + [6672, 6690, 6710, 6731, 6753, 6776, 6801], + [6680, 6698, 6718, 6739, 6761, 6784, 6809], + [6688, 6706, 6726, 6746, 6768, 6792, 6816], + [6696, 6714, 6734, 6754, 6776, 6799, 6824], + [6704, 6722, 6741, 6762, 6784, 6807, 6831], + [6712, 6730, 6749, 6770, 6792, 6815, 6839], + [6719, 6738, 6757, 6778, 6799, 6822, 6847], + [6727, 6745, 6765, 6785, 6807, 6830, 6854], + [6735, 6753, 6772, 6793, 6815, 6838, 6862], + [6743, 6761, 6780, 6801, 6822, 6845, 6869], + [6751, 6769, 6788, 6808, 6830, 6853, 6877], + [6758, 6776, 6796, 6816, 6837, 6860, 6884], + [6766, 6784, 6803, 6824, 6845, 6868, 6892], + [6774, 6792, 6811, 6831, 6853, 6875, 6899], + [6782, 6800, 6819, 6839, 6860, 6883, 6907], + [6789, 6807, 6826, 6846, 6868, 6890, 6914], + [6797, 6815, 6834, 6854, 6875, 6898, 6921], + [6805, 6822, 6841, 6861, 6883, 6905, 6929], + [6812, 6830, 6849, 6869, 6890, 6913, 6936], + [6820, 6838, 6857, 6876, 6898, 6920, 6944], + [6828, 6845, 6864, 6884, 6905, 6927, 6951], + [6835, 6853, 6872, 6891, 6913, 6935, 6958], + [6843, 6860, 6879, 6899, 6920, 6942, 6966], + [6850, 6868, 6887, 6906, 6927, 6950, 6973], + [6858, 6875, 6894, 6914, 6935, 6957, 6980], + [6866, 6883, 6902, 6921, 6942, 6964, 6988], + [6873, 6890, 6909, 6929, 6949, 6972, 6995], + [6881, 6898, 6916, 6936, 6957, 6979, 7002], + [6888, 6905, 6924, 6943, 6964, 6986, 7009], + [6896, 6913, 6931, 6951, 6971, 6993, 7017], + [6903, 6920, 6939, 6958, 6979, 7001, 7024], + [6910, 6928, 6946, 6965, 6986, 7008, 7031], + [6918, 6935, 6953, 6973, 6993, 7015, 7038], + [6925, 6942, 6961, 6980, 7001, 7022, 7045], + [6933, 6950, 6968, 6987, 7008, 7030, 7052], + [6940, 6957, 6975, 6995, 7015, 7037, 7060], + [6947, 6964, 6983, 7002, 7022, 7044, 7067], + [6955, 6972, 6990, 7009, 7029, 7051, 7074], + [6962, 6979, 6997, 7016, 7037, 7058, 7081], + [6969, 6986, 7004, 7023, 7044, 7065, 7088], + [6977, 6994, 7012, 7031, 7051, 7072, 7095], + [6984, 7001, 7019, 7038, 7058, 7079, 7102], + [6991, 7008, 7026, 7045, 7065, 7087, 7109], + [6998, 7015, 7033, 7052, 7072, 7094, 7116], + [7006, 7022, 7040, 7059, 7079, 7101, 7123], + [7013, 7030, 7047, 7066, 7086, 7108, 7130], + [7020, 7037, 7055, 7074, 7094, 7115, 7137], + [7027, 7044, 7062, 7081, 7101, 7122, 7144], + [7035, 7051, 7069, 7088, 7108, 7129, 7151], + [7042, 7058, 7076, 7095, 7115, 7136, 7158], + [7049, 7065, 7083, 7102, 7122, 7143, 7165], + [7056, 7073, 7090, 7109, 7129, 7150, 7172], + [7063, 7080, 7097, 7116, 7136, 7157, 7179], + [7070, 7087, 7104, 7123, 7143, 7164, 7186], + [7077, 7094, 7111, 7130, 7150, 7170, 7192], + [7084, 7101, 7118, 7137, 7157, 7177, 7199], + [7092, 7108, 7125, 7144, 7163, 7184, 7206], + [7099, 7115, 7132, 7151, 7170, 7191, 7213], + [7106, 7122, 7139, 7158, 7177, 7198, 7220], + [7113, 7129, 7146, 7165, 7184, 7205, 7227], + [7120, 7136, 7153, 7172, 7191, 7212, 7233], + [7127, 7143, 7160, 7178, 7198, 7219, 7240], + [7134, 7150, 7167, 7185, 7205, 7225, 7247], + [7141, 7157, 7174, 7192, 7212, 7232, 7254], + [7148, 7164, 7181, 7199, 7218, 7239, 7261], + [7155, 7171, 7188, 7206, 7225, 7246, 7267], + [7161, 7178, 7195, 7213, 7232, 7252, 7274], + [7168, 7184, 7201, 7220, 7239, 7259, 7281], + [7175, 7191, 7208, 7226, 7246, 7266, 7287], + [7182, 7198, 7215, 7233, 7252, 7273, 7294], + [7189, 7205, 7222, 7240, 7259, 7279, 7301], + [7196, 7212, 7229, 7247, 7266, 7286, 7307], + [7203, 7219, 7236, 7254, 7273, 7293, 7314], + [7210, 7225, 7242, 7260, 7279, 7299, 7321], + [7216, 7232, 7249, 7267, 7286, 7306, 7327], + [7223, 7239, 7256, 7274, 7293, 7313, 7334], + [7230, 7246, 7263, 7280, 7299, 7319, 7341], + [7237, 7253, 7269, 7287, 7306, 7326, 7347], + [7244, 7259, 7276, 7294, 7313, 7333, 7354], + [7250, 7266, 7283, 7301, 7319, 7339, 7360], + [7257, 7273, 7289, 7307, 7326, 7346, 7367], + [7264, 7280, 7296, 7314, 7333, 7352, 7373], + [7271, 7286, 7303, 7320, 7339, 7359, 7380], + [7277, 7293, 7310, 7327, 7346, 7366, 7386], + [7284, 7300, 7316, 7334, 7352, 7372, 7393], + [7291, 7306, 7323, 7340, 7359, 7379, 7400], + [7297, 7313, 7329, 7347, 7366, 7385, 7406], + [7304, 7320, 7336, 7354, 7372, 7392, 7412], + [7311, 7326, 7343, 7360, 7379, 7398, 7419], + [7317, 7333, 7349, 7367, 7385, 7405, 7425], + [7324, 7339, 7356, 7373, 7392, 7411, 7432], + [7331, 7346, 7362, 7380, 7398, 7418, 7438], + [7337, 7353, 7369, 7386, 7405, 7424, 7445], + [7344, 7359, 7375, 7393, 7411, 7431, 7451], + [7351, 7366, 7382, 7399, 7418, 7437, 7458], + [7357, 7372, 7389, 7406, 7424, 7443, 7464], + [7364, 7379, 7395, 7412, 7431, 7450, 7470], + [7370, 7385, 7402, 7419, 7437, 7456, 7477], + [7377, 7392, 7408, 7425, 7443, 7463, 7483], + [7383, 7398, 7414, 7432, 7450, 7469, 7489], + [7390, 7405, 7421, 7438, 7456, 7475, 7496], + [7396, 7411, 7427, 7444, 7463, 7482, 7502], + [7403, 7418, 7434, 7451, 7469, 7488, 7508], + [7409, 7424, 7440, 7457, 7475, 7494, 7515], + [7416, 7431, 7447, 7464, 7482, 7501, 7521], + [7422, 7437, 7453, 7470, 7488, 7507, 7527], + [7429, 7444, 7460, 7476, 7494, 7513, 7534], + [7435, 7450, 7466, 7483, 7501, 7520, 7540], + [7442, 7456, 7472, 7489, 7507, 7526, 7546], + [7448, 7463, 7479, 7495, 7513, 7532, 7552], + [7454, 7469, 7485, 7502, 7520, 7539, 7559], + [7461, 7476, 7491, 7508, 7526, 7545, 7565], + [7467, 7482, 7498, 7514, 7532, 7551, 7571], + [7474, 7488, 7504, 7521, 7538, 7557, 7577], + [7480, 7495, 7510, 7527, 7545, 7564, 7583], + [7486, 7501, 7517, 7533, 7551, 7570, 7590], + [7493, 7507, 7523, 7540, 7557, 7576, 7596], + [7499, 7514, 7529, 7546, 7563, 7582, 7602], + [7505, 7520, 7535, 7552, 7570, 7588, 7608], + [7512, 7526, 7542, 7558, 7576, 7595, 7614], + [7518, 7532, 7548, 7565, 7582, 7601, 7620], + [7524, 7539, 7554, 7571, 7588, 7607, 7626], + [7530, 7545, 7560, 7577, 7594, 7613, 7633], + [7537, 7551, 7567, 7583, 7601, 7619, 7639], + [7543, 7557, 7573, 7589, 7607, 7625, 7645], + [7549, 7564, 7579, 7595, 7613, 7631, 7651], + [7555, 7570, 7585, 7602, 7619, 7637, 7657], + [7562, 7576, 7591, 7608, 7625, 7644, 7663], + [7568, 7582, 7598, 7614, 7631, 7650, 7669], + [7574, 7588, 7604, 7620, 7637, 7656, 7675], + [7580, 7595, 7610, 7626, 7644, 7662, 7681], + [7586, 7601, 7616, 7632, 7650, 7668, 7687], + [7593, 7607, 7622, 7638, 7656, 7674, 7693], + [7599, 7613, 7628, 7645, 7662, 7680, 7699], + [7605, 7619, 7634, 7651, 7668, 7686, 7705], + [7611, 7625, 7641, 7657, 7674, 7692, 7711], + [7617, 7631, 7647, 7663, 7680, 7698, 7717], + [7623, 7638, 7653, 7669, 7686, 7704, 7723], + [7630, 7644, 7659, 7675, 7692, 7710, 7729], + [7636, 7650, 7665, 7681, 7698, 7716, 7735], + [7642, 7656, 7671, 7687, 7704, 7722, 7741], + [7648, 7662, 7677, 7693, 7710, 7728, 7747], + [7654, 7668, 7683, 7699, 7716, 7734, 7753], + [7660, 7674, 7689, 7705, 7722, 7740, 7759], + [7666, 7680, 7695, 7711, 7728, 7746, 7765], + [7672, 7686, 7701, 7717, 7734, 7752, 7771], + [7678, 7692, 7707, 7723, 7740, 7758, 7777], + [7684, 7698, 7713, 7729, 7746, 7764, 7782], + [7690, 7704, 7719, 7735, 7752, 7769, 7788], + [7696, 7710, 7725, 7741, 7758, 7775, 7794], + [7702, 7716, 7731, 7747, 7764, 7781, 7800], + [7708, 7722, 7737, 7753, 7769, 7787, 7806], + [7714, 7728, 7743, 7759, 7775, 7793, 7812], + [7720, 7734, 7749, 7765, 7781, 7799, 7818], + [7726, 7740, 7755, 7770, 7787, 7805, 7823], + [7732, 7746, 7761, 7776, 7793, 7811, 7829], + [7738, 7752, 7767, 7782, 7799, 7816, 7835], + [7744, 7758, 7772, 7788, 7805, 7822, 7841], + [7750, 7764, 7778, 7794, 7811, 7828, 7847], + [7756, 7770, 7784, 7800, 7816, 7834, 7852], + [7762, 7776, 7790, 7806, 7822, 7840, 7858], + [7768, 7781, 7796, 7812, 7828, 7845, 7864], + [7774, 7787, 7802, 7817, 7834, 7851, 7870], + [7780, 7793, 7808, 7823, 7840, 7857, 7875], + [7785, 7799, 7814, 7829, 7845, 7863, 7881], + [7791, 7805, 7819, 7835, 7851, 7869, 7887], + [7797, 7811, 7825, 7841, 7857, 7874, 7893], + [7803, 7817, 7831, 7846, 7863, 7880, 7898], + [7809, 7822, 7837, 7852, 7868, 7886, 7904], + [7815, 7828, 7843, 7858, 7874, 7891, 7910], + [7820, 7834, 7848, 7864, 7880, 7897, 7915], + [7826, 7840, 7854, 7869, 7886, 7903, 7921], + [7832, 7846, 7860, 7875, 7891, 7909, 7927], + [7838, 7851, 7866, 7881, 7897, 7914, 7932], + [7844, 7857, 7871, 7887, 7903, 7920, 7938], + [7850, 7863, 7877, 7892, 7909, 7926, 7944], + [7855, 7869, 7883, 7898, 7914, 7931, 7949], + [7861, 7874, 7889, 7904, 7920, 7937, 7955], + [7867, 7880, 7894, 7910, 7926, 7943, 7961], + [7873, 7886, 7900, 7915, 7931, 7948, 7966], + [7878, 7892, 7906, 7921, 7937, 7954, 7972], + [7884, 7897, 7912, 7927, 7943, 7960, 7978], + [7890, 7903, 7917, 7932, 7948, 7965, 7983], + [7896, 7909, 7923, 7938, 7954, 7971, 7989], + [7901, 7914, 7929, 7944, 7960, 7976, 7994], + [7907, 7920, 7934, 7949, 7965, 7982, 8000], + [7913, 7926, 7940, 7955, 7971, 7988, 8005], + [7918, 7931, 7946, 7960, 7976, 7993, 8011], + [7924, 7937, 7951, 7966, 7982, 7999, 8017], + [7930, 7943, 7957, 7972, 7988, 8004, 8022], + [7935, 7948, 7962, 7977, 7993, 8010, 8028], + [7941, 7954, 7968, 7983, 7999, 8015, 8033], + [7947, 7960, 7974, 7989, 8004, 8021, 8039], + [7952, 7965, 7979, 7994, 8010, 8027, 8044], + [7958, 7971, 7985, 8000, 8015, 8032, 8050], + [7964, 7977, 7990, 8005, 8021, 8038, 8055], + [7969, 7982, 7996, 8011, 8026, 8043, 8061], + [7975, 7988, 8002, 8016, 8032, 8049, 8066], + [7980, 7993, 8007, 8022, 8038, 8054, 8072], + [7986, 7999, 8013, 8027, 8043, 8060, 8077], + [7992, 8004, 8018, 8033, 8049, 8065, 8083], + [7997, 8010, 8024, 8038, 8054, 8071, 8088], + [8003, 8016, 8029, 8044, 8060, 8076, 8093], + [8008, 8021, 8035, 8050, 8065, 8082, 8099], + [8014, 8027, 8040, 8055, 8071, 8087, 8104], + [8019, 8032, 8046, 8060, 8076, 8092, 8110], + [8025, 8038, 8051, 8066, 8081, 8098, 8115], + [8030, 8043, 8057, 8071, 8087, 8103, 8121], + [8036, 8049, 8062, 8077, 8092, 8109, 8126], + [8041, 8054, 8068, 8082, 8098, 8114, 8131], + [8047, 8060, 8073, 8088, 8103, 8120, 8137], + [8052, 8065, 8079, 8093, 8109, 8125, 8142], + [8058, 8071, 8084, 8099, 8114, 8130, 8148], + [8063, 8076, 8090, 8104, 8119, 8136, 8153], + [8069, 8082, 8095, 8110, 8125, 8141, 8158], + [8074, 8087, 8101, 8115, 8130, 8147, 8164], + [8080, 8093, 8106, 8120, 8136, 8152, 8169], + [8085, 8098, 8111, 8126, 8141, 8157, 8174], + [8091, 8103, 8117, 8131, 8146, 8163, 8180], + [8096, 8109, 8122, 8137, 8152, 8168, 8185], + [8102, 8114, 8128, 8142, 8157, 8173, 8190], + [8107, 8120, 8133, 8147, 8163, 8179, 8196], + [8113, 8125, 8138, 8153, 8168, 8184, 8201], + [8118, 8130, 8144, 8158, 8173, 8189, 8206], + [8123, 8136, 8149, 8163, 8179, 8195, 8212], + [8129, 8141, 8155, 8169, 8184, 8200, 8217], + [8134, 8147, 8160, 8174, 8189, 8205, 8222], + [8140, 8152, 8165, 8179, 8195, 8211, 8227], + [8145, 8157, 8171, 8185, 8200, 8216, 8233], + [8150, 8163, 8176, 8190, 8205, 8221, 8238], + [8156, 8168, 8181, 8195, 8210, 8226, 8243], + [8161, 8173, 8187, 8201, 8216, 8232, 8248], + [8166, 8179, 8192, 8206, 8221, 8237, 8254], + [8172, 8184, 8197, 8211, 8226, 8242, 8259], + [8177, 8189, 8203, 8217, 8232, 8247, 8264], + [8182, 8195, 8208, 8222, 8237, 8253, 8269], + [8188, 8200, 8213, 8227, 8242, 8258, 8275], + [8193, 8205, 8218, 8232, 8247, 8263, 8280], + [8198, 8211, 8224, 8238, 8253, 8268, 8285], + [8204, 8216, 8229, 8243, 8258, 8274, 8290], + [8209, 8221, 8234, 8248, 8263, 8279, 8295], + [8214, 8226, 8240, 8253, 8268, 8284, 8301], + [8220, 8232, 8245, 8259, 8273, 8289, 8306], + [8225, 8237, 8250, 8264, 8279, 8294, 8311], + [8230, 8242, 8255, 8269, 8284, 8300, 8316], + [8235, 8248, 8261, 8274, 8289, 8305, 8321], + [8241, 8253, 8266, 8280, 8294, 8310, 8326], + [8246, 8258, 8271, 8285, 8299, 8315, 8332], + [8251, 8263, 8276, 8290, 8305, 8320, 8337], + [8256, 8268, 8281, 8295, 8310, 8325, 8342], + [8262, 8274, 8287, 8300, 8315, 8331, 8347], + [8267, 8279, 8292, 8306, 8320, 8336, 8352], + [8272, 8284, 8297, 8311, 8325, 8341, 8357], + [8277, 8289, 8302, 8316, 8330, 8346, 8362], + [8282, 8294, 8307, 8321, 8336, 8351, 8367], + [8288, 8300, 8312, 8326, 8341, 8356, 8372], + [8293, 8305, 8318, 8331, 8346, 8361, 8378], + [8298, 8310, 8323, 8336, 8351, 8366, 8383], + [8303, 8315, 8328, 8342, 8356, 8371, 8388], + [8308, 8320, 8333, 8347, 8361, 8377, 8393], + [8314, 8325, 8338, 8352, 8366, 8382, 8398], + [8319, 8331, 8343, 8357, 8371, 8387, 8403], + [8324, 8336, 8348, 8362, 8376, 8392, 8408], + [8329, 8341, 8354, 8367, 8382, 8397, 8413], + [8334, 8346, 8359, 8372, 8387, 8402, 8418], + [8339, 8351, 8364, 8377, 8392, 8407, 8423], + [8344, 8356, 8369, 8382, 8397, 8412, 8428], + [8350, 8361, 8374, 8388, 8402, 8417, 8433], + [8355, 8366, 8379, 8393, 8407, 8422, 8438], + [8360, 8372, 8384, 8398, 8412, 8427, 8443], + [8365, 8377, 8389, 8403, 8417, 8432, 8448], + [8370, 8382, 8394, 8408, 8422, 8437, 8453], + [8375, 8387, 8399, 8413, 8427, 8442, 8458], + [8380, 8392, 8404, 8418, 8432, 8447, 8463], + [8385, 8397, 8410, 8423, 8437, 8452, 8468], + [8390, 8402, 8415, 8428, 8442, 8457, 8473], + [8395, 8407, 8420, 8433, 8447, 8462, 8478], + [8400, 8412, 8425, 8438, 8452, 8467, 8483], + [8405, 8417, 8430, 8443, 8457, 8472, 8488], + [8411, 8422, 8435, 8448, 8462, 8477, 8493], + [8416, 8427, 8440, 8453, 8467, 8482, 8498], + [8421, 8432, 8445, 8458, 8472, 8487, 8503], + [8426, 8437, 8450, 8463, 8477, 8492, 8508], + [8431, 8442, 8455, 8468, 8482, 8497, 8513], + [8436, 8447, 8460, 8473, 8487, 8502, 8518], + [8441, 8452, 8465, 8478, 8492, 8507, 8523], + [8446, 8457, 8470, 8483, 8497, 8512, 8527], + [8451, 8462, 8475, 8488, 8502, 8517, 8532], + [8456, 8467, 8480, 8493, 8507, 8522, 8537], + [8461, 8472, 8485, 8498, 8512, 8527, 8542], + [8466, 8477, 8490, 8503, 8517, 8531, 8547], + [8471, 8482, 8494, 8508, 8522, 8536, 8552], + [8476, 8487, 8499, 8513, 8526, 8541, 8557], + [8481, 8492, 8504, 8517, 8531, 8546, 8562], + [8486, 8497, 8509, 8522, 8536, 8551, 8567], + [8491, 8502, 8514, 8527, 8541, 8556, 8571], + [8495, 8507, 8519, 8532, 8546, 8561, 8576], + [8500, 8512, 8524, 8537, 8551, 8566, 8581], + [8505, 8517, 8529, 8542, 8556, 8571, 8586], + [8510, 8522, 8534, 8547, 8561, 8575, 8591], + [8515, 8527, 8539, 8552, 8566, 8580, 8596], + [8520, 8532, 8544, 8557, 8570, 8585, 8601], + [8525, 8536, 8549, 8562, 8575, 8590, 8605], + [8530, 8541, 8553, 8566, 8580, 8595, 8610], + [8535, 8546, 8558, 8571, 8585, 8600, 8615], + [8540, 8551, 8563, 8576, 8590, 8604, 8620], + [8545, 8556, 8568, 8581, 8595, 8609, 8625], + [8550, 8561, 8573, 8586, 8600, 8614, 8629], + [8554, 8566, 8578, 8591, 8604, 8619, 8634], + [8559, 8571, 8583, 8596, 8609, 8624, 8639], + [8564, 8575, 8588, 8600, 8614, 8628, 8644], + [8569, 8580, 8592, 8605, 8619, 8633, 8649], + [8574, 8585, 8597, 8610, 8624, 8638, 8653], + [8579, 8590, 8602, 8615, 8628, 8643, 8658], + [8584, 8595, 8607, 8620, 8633, 8648, 8663], + [8588, 8600, 8612, 8624, 8638, 8652, 8668], + [8593, 8605, 8616, 8629, 8643, 8657, 8672], + [8598, 8609, 8621, 8634, 8648, 8662, 8677], + [8603, 8614, 8626, 8639, 8652, 8667, 8682], + [8608, 8619, 8631, 8644, 8657, 8671, 8687], + [8613, 8624, 8636, 8648, 8662, 8676, 8691], + [8617, 8629, 8640, 8653, 8667, 8681, 8696], + [8622, 8633, 8645, 8658, 8671, 8686, 8701], + [8627, 8638, 8650, 8663, 8676, 8690, 8706], + [8632, 8643, 8655, 8667, 8681, 8695, 8710], + [8637, 8648, 8660, 8672, 8686, 8700, 8715], + [8641, 8653, 8664, 8677, 8690, 8705, 8720], + [8646, 8657, 8669, 8682, 8695, 8709, 8724], + [8651, 8662, 8674, 8686, 8700, 8714, 8729], + [8656, 8667, 8679, 8691, 8705, 8719, 8734], + [8661, 8672, 8683, 8696, 8709, 8723, 8738], + [8665, 8676, 8688, 8701, 8714, 8728, 8743], + [8670, 8681, 8693, 8705, 8719, 8733, 8748], + [8675, 8686, 8698, 8710, 8723, 8737, 8752], + [8680, 8691, 8702, 8715, 8728, 8742, 8757], + [8684, 8695, 8707, 8719, 8733, 8747, 8762], + [8689, 8700, 8712, 8724, 8737, 8752, 8766], + [8694, 8705, 8716, 8729, 8742, 8756, 8771], + [8698, 8709, 8721, 8734, 8747, 8761, 8776], + [8703, 8714, 8726, 8738, 8751, 8765, 8780], + [8708, 8719, 8730, 8743, 8756, 8770, 8785], + [8713, 8724, 8735, 8748, 8761, 8775, 8790], + [8717, 8728, 8740, 8752, 8765, 8779, 8794], + [8722, 8733, 8745, 8757, 8770, 8784, 8799], + [8727, 8738, 8749, 8762, 8775, 8789, 8803], + [8731, 8742, 8754, 8766, 8779, 8793, 8808], + [8736, 8747, 8759, 8771, 8784, 8798, 8813], + [8741, 8752, 8763, 8776, 8789, 8803, 8817], + [8745, 8756, 8768, 8780, 8793, 8807, 8822], + [8750, 8761, 8772, 8785, 8798, 8812, 8826], + [8755, 8766, 8777, 8789, 8803, 8816, 8831], + [8759, 8770, 8782, 8794, 8807, 8821, 8836], + [8764, 8775, 8786, 8799, 8812, 8826, 8840], + [8769, 8780, 8791, 8803, 8816, 8830, 8845], + [8773, 8784, 8796, 8808, 8821, 8835, 8849], + [8778, 8789, 8800, 8813, 8826, 8839, 8854], + [8783, 8793, 8805, 8817, 8830, 8844, 8858], + [8787, 8798, 8810, 8822, 8835, 8848, 8863], + [8792, 8803, 8814, 8826, 8839, 8853, 8868], + [8797, 8807, 8819, 8831, 8844, 8858, 8872], + [8801, 8812, 8823, 8835, 8848, 8862, 8877], + [8806, 8816, 8828, 8840, 8853, 8867, 8881], + [8810, 8821, 8832, 8845, 8858, 8871, 8886], + [8815, 8826, 8837, 8849, 8862, 8876, 8890], + [8820, 8830, 8842, 8854, 8867, 8880, 8895], + [8824, 8835, 8846, 8858, 8871, 8885, 8899], + [8829, 8839, 8851, 8863, 8876, 8889, 8904], + [8833, 8844, 8855, 8867, 8880, 8894, 8908], + [8838, 8849, 8860, 8872, 8885, 8898, 8913], + [8843, 8853, 8864, 8877, 8889, 8903, 8917], + [8847, 8858, 8869, 8881, 8894, 8907, 8922], + [8852, 8862, 8874, 8886, 8898, 8912, 8926], + [8856, 8867, 8878, 8890, 8903, 8916, 8931], + [8861, 8871, 8883, 8895, 8907, 8921, 8935], + [8865, 8876, 8887, 8899, 8912, 8925, 8940], + [8870, 8880, 8892, 8904, 8916, 8930, 8944], + [8874, 8885, 8896, 8908, 8921, 8934, 8949], + [8879, 8889, 8901, 8913, 8925, 8939, 8953], + [8883, 8894, 8905, 8917, 8930, 8943, 8958], + [8888, 8899, 8910, 8922, 8934, 8948, 8962], + [8893, 8903, 8914, 8926, 8939, 8952, 8967], + [8897, 8908, 8919, 8931, 8943, 8957, 8971], + [8902, 8912, 8923, 8935, 8948, 8961, 8975], + [8906, 8917, 8928, 8940, 8952, 8966, 8980], + [8911, 8921, 8932, 8944, 8957, 8970, 8984], + [8915, 8926, 8937, 8949, 8961, 8975, 8989], + [8920, 8930, 8941, 8953, 8966, 8979, 8993], + [8924, 8935, 8946, 8957, 8970, 8983, 8998], + [8929, 8939, 8950, 8962, 8975, 8988, 9002], + [8933, 8943, 8955, 8966, 8979, 8992, 9006], + [8938, 8948, 8959, 8971, 8983, 8997, 9011], + [8942, 8952, 8963, 8975, 8988, 9001, 9015], + [8946, 8957, 8968, 8980, 8992, 9006, 9020], + [8951, 8961, 8972, 8984, 8997, 9010, 9024], + [8955, 8966, 8977, 8989, 9001, 9014, 9028], + [8960, 8970, 8981, 8993, 9005, 9019, 9033], + [8964, 8975, 8986, 8997, 9010, 9023, 9037], + [8969, 8979, 8990, 9002, 9014, 9028, 9042], + [8973, 8984, 8995, 9006, 9019, 9032, 9046], + [8978, 8988, 8999, 9011, 9023, 9036, 9050], + [8982, 8992, 9003, 9015, 9027, 9041, 9055], + [8987, 8997, 9008, 9019, 9032, 9045, 9059], + [8991, 9001, 9012, 9024, 9036, 9049, 9063], + [8995, 9006, 9017, 9028, 9041, 9054, 9068], + [9000, 9010, 9021, 9033, 9045, 9058, 9072], + [9004, 9014, 9025, 9037, 9049, 9063, 9076], + [9009, 9019, 9030, 9041, 9054, 9067, 9081], + [9013, 9023, 9034, 9046, 9058, 9071, 9085], + [9017, 9028, 9039, 9050, 9062, 9076, 9089], + [9022, 9032, 9043, 9055, 9067, 9080, 9094], + [9026, 9036, 9047, 9059, 9071, 9084, 9098], + [9031, 9041, 9052, 9063, 9076, 9089, 9102], + [9035, 9045, 9056, 9068, 9080, 9093, 9107], + [9039, 9050, 9060, 9072, 9084, 9097, 9111], + [9044, 9054, 9065, 9076, 9089, 9102, 9115], + [9048, 9058, 9069, 9081, 9093, 9106, 9120], + [9053, 9063, 9073, 9085, 9097, 9110, 9124], + [9057, 9067, 9078, 9089, 9102, 9115, 9128], + [9061, 9071, 9082, 9094, 9106, 9119, 9133], + [9066, 9076, 9086, 9098, 9110, 9123, 9137], + [9070, 9080, 9091, 9102, 9114, 9127, 9141], + [9074, 9084, 9095, 9107, 9119, 9132, 9145], + [9079, 9089, 9099, 9111, 9123, 9136, 9150], + [9083, 9093, 9104, 9115, 9127, 9140, 9154], + [9087, 9097, 9108, 9120, 9132, 9145, 9158], + [9092, 9102, 9112, 9124, 9136, 9149, 9162], + [9096, 9106, 9117, 9128, 9140, 9153, 9167], + [9100, 9110, 9121, 9132, 9145, 9157, 9171], + [9105, 9115, 9125, 9137, 9149, 9162, 9175], + [9109, 9119, 9130, 9141, 9153, 9166, 9180], + [9113, 9123, 9134, 9145, 9157, 9170, 9184], + [9118, 9128, 9138, 9150, 9162, 9174, 9188], + [9122, 9132, 9142, 9154, 9166, 9179, 9192], + [9126, 9136, 9147, 9158, 9170, 9183, 9196], + [9130, 9140, 9151, 9162, 9174, 9187, 9201], + [9135, 9145, 9155, 9167, 9179, 9191, 9205], + [9139, 9149, 9160, 9171, 9183, 9196, 9209], + [9143, 9153, 9164, 9175, 9187, 9200, 9213], + [9148, 9157, 9168, 9179, 9191, 9204, 9218], + [9152, 9162, 9172, 9184, 9196, 9208, 9222], + [9156, 9166, 9177, 9188, 9200, 9213, 9226], + [9160, 9170, 9181, 9192, 9204, 9217, 9230], + [9165, 9175, 9185, 9196, 9208, 9221, 9234], + [9169, 9179, 9189, 9201, 9213, 9225, 9239], + [9173, 9183, 9194, 9205, 9217, 9229, 9243], + [9177, 9187, 9198, 9209, 9221, 9234, 9247], + [9182, 9192, 9202, 9213, 9225, 9238, 9251], + [9186, 9196, 9206, 9217, 9229, 9242, 9255], + [9190, 9200, 9210, 9222, 9234, 9246, 9260], + [9194, 9204, 9215, 9226, 9238, 9250, 9264], + [9199, 9208, 9219, 9230, 9242, 9255, 9268], + [9203, 9213, 9223, 9234, 9246, 9259, 9272], + [9207, 9217, 9227, 9238, 9250, 9263, 9276], + [9211, 9221, 9232, 9243, 9255, 9267, 9280], + [9216, 9225, 9236, 9247, 9259, 9271, 9285], + [9220, 9229, 9240, 9251, 9263, 9275, 9289], + [9224, 9234, 9244, 9255, 9267, 9280, 9293], + [9228, 9238, 9248, 9259, 9271, 9284, 9297], + [9232, 9242, 9252, 9264, 9275, 9288, 9301], + [9237, 9246, 9257, 9268, 9280, 9292, 9305], + [9241, 9250, 9261, 9272, 9284, 9296, 9309], + [9245, 9255, 9265, 9276, 9288, 9300, 9314], + [9249, 9259, 9269, 9280, 9292, 9304, 9318], + [9253, 9263, 9273, 9284, 9296, 9309, 9322], + [9257, 9267, 9278, 9289, 9300, 9313, 9326], + [9262, 9271, 9282, 9293, 9304, 9317, 9330], + [9266, 9275, 9286, 9297, 9309, 9321, 9334], + [9270, 9280, 9290, 9301, 9313, 9325, 9338], + [9274, 9284, 9294, 9305, 9317, 9329, 9342], + [9278, 9288, 9298, 9309, 9321, 9333, 9346], + [9282, 9292, 9302, 9313, 9325, 9337, 9351], + [9287, 9296, 9307, 9318, 9329, 9342, 9355], + [9291, 9300, 9311, 9322, 9333, 9346, 9359], + [9295, 9305, 9315, 9326, 9337, 9350, 9363], + [9299, 9309, 9319, 9330, 9341, 9354, 9367], + [9303, 9313, 9323, 9334, 9346, 9358, 9371], + [9307, 9317, 9327, 9338, 9350, 9362, 9375], + [9311, 9321, 9331, 9342, 9354, 9366, 9379], + [9316, 9325, 9335, 9346, 9358, 9370, 9383], + [9320, 9329, 9340, 9350, 9362, 9374, 9387], + [9324, 9333, 9344, 9354, 9366, 9378, 9391], + [9328, 9338, 9348, 9359, 9370, 9382, 9395], + [9332, 9342, 9352, 9363, 9374, 9386, 9399], + [9336, 9346, 9356, 9367, 9378, 9391, 9403], + [9340, 9350, 9360, 9371, 9382, 9395, 9408], + [9344, 9354, 9364, 9375, 9386, 9399, 9412], + [9349, 9358, 9368, 9379, 9390, 9403, 9416], + [9353, 9362, 9372, 9383, 9395, 9407, 9420], + [9357, 9366, 9376, 9387, 9399, 9411, 9424], + [9361, 9370, 9380, 9391, 9403, 9415, 9428], + [9365, 9374, 9384, 9395, 9407, 9419, 9432], + [9369, 9378, 9389, 9399, 9411, 9423, 9436], + [9373, 9382, 9393, 9403, 9415, 9427, 9440], + [9377, 9387, 9397, 9407, 9419, 9431, 9444], + [9381, 9391, 9401, 9411, 9423, 9435, 9448], + [9385, 9395, 9405, 9415, 9427, 9439, 9452], + [9389, 9399, 9409, 9420, 9431, 9443, 9456], + [9393, 9403, 9413, 9424, 9435, 9447, 9460], + [9397, 9407, 9417, 9428, 9439, 9451, 9464], + [9401, 9411, 9421, 9432, 9443, 9455, 9468], + [9406, 9415, 9425, 9436, 9447, 9459, 9472], + [9410, 9419, 9429, 9440, 9451, 9463, 9476], + [9414, 9423, 9433, 9444, 9455, 9467, 9480], + [9418, 9427, 9437, 9448, 9459, 9471, 9484], + [9422, 9431, 9441, 9452, 9463, 9475, 9488], + [9426, 9435, 9445, 9456, 9467, 9479, 9492], + [9430, 9439, 9449, 9460, 9471, 9483, 9496], + [9434, 9443, 9453, 9464, 9475, 9487, 9500], + [9438, 9447, 9457, 9468, 9479, 9491, 9504], + [9442, 9451, 9461, 9472, 9483, 9495, 9508], + [9446, 9455, 9465, 9476, 9487, 9499, 9512], + [9450, 9459, 9469, 9480, 9491, 9503, 9516], + [9454, 9463, 9473, 9484, 9495, 9507, 9520], + [9458, 9467, 9477, 9488, 9499, 9511, 9523], + [9462, 9471, 9481, 9492, 9503, 9515, 9527], + [9466, 9475, 9485, 9496, 9507, 9519, 9531], + [9470, 9479, 9489, 9500, 9511, 9523, 9535], + [9474, 9483, 9493, 9504, 9515, 9527, 9539], + [9478, 9487, 9497, 9508, 9519, 9531, 9543], + [9482, 9491, 9501, 9511, 9523, 9535, 9547], + [9486, 9495, 9505, 9515, 9527, 9539, 9551], + [9490, 9499, 9509, 9519, 9531, 9542, 9555], + [9494, 9503, 9513, 9523, 9535, 9546, 9559], + [9498, 9507, 9517, 9527, 9538, 9550, 9563], + [9502, 9511, 9521, 9531, 9542, 9554, 9567], + [9506, 9515, 9525, 9535, 9546, 9558, 9571], + [9510, 9519, 9529, 9539, 9550, 9562, 9575], + [9514, 9523, 9533, 9543, 9554, 9566, 9578], + [9518, 9527, 9537, 9547, 9558, 9570, 9582], + [9522, 9531, 9541, 9551, 9562, 9574, 9586], + [9525, 9535, 9544, 9555, 9566, 9578, 9590], + [9529, 9539, 9548, 9559, 9570, 9582, 9594], + [9533, 9543, 9552, 9563, 9574, 9586, 9598], + [9537, 9546, 9556, 9567, 9578, 9589, 9602], + [9541, 9550, 9560, 9571, 9582, 9593, 9606], + [9545, 9554, 9564, 9574, 9586, 9597, 9610], + [9549, 9558, 9568, 9578, 9589, 9601, 9614], + [9553, 9562, 9572, 9582, 9593, 9605, 9617], + [9557, 9566, 9576, 9586, 9597, 9609, 9621], + [9561, 9570, 9580, 9590, 9601, 9613, 9625], + [9565, 9574, 9584, 9594, 9605, 9617, 9629], + [9569, 9578, 9588, 9598, 9609, 9621, 9633], + [9573, 9582, 9591, 9602, 9613, 9624, 9637], + [9577, 9586, 9595, 9606, 9617, 9628, 9641], + [9580, 9590, 9599, 9610, 9620, 9632, 9644], + [9584, 9593, 9603, 9613, 9624, 9636, 9648], + [9588, 9597, 9607, 9617, 9628, 9640, 9652], + [9592, 9601, 9611, 9621, 9632, 9644, 9656], + [9596, 9605, 9615, 9625, 9636, 9648, 9660], + [9600, 9609, 9619, 9629, 9640, 9651, 9664], + [9604, 9613, 9622, 9633, 9644, 9655, 9668], + [9608, 9617, 9626, 9637, 9648, 9659, 9671], + [9612, 9621, 9630, 9640, 9651, 9663, 9675], + [9615, 9624, 9634, 9644, 9655, 9667, 9679], + [9619, 9628, 9638, 9648, 9659, 9671, 9683], + [9623, 9632, 9642, 9652, 9663, 9674, 9687], + [9627, 9636, 9646, 9656, 9667, 9678, 9690], + [9631, 9640, 9650, 9660, 9671, 9682, 9694], + [9635, 9644, 9653, 9664, 9674, 9686, 9698], + [9639, 9648, 9657, 9667, 9678, 9690, 9702], + [9643, 9651, 9661, 9671, 9682, 9694, 9706], + [9646, 9655, 9665, 9675, 9686, 9697, 9710], + [9650, 9659, 9669, 9679, 9690, 9701, 9713], + [9654, 9663, 9673, 9683, 9694, 9705, 9717], + [9658, 9667, 9676, 9687, 9697, 9709, 9721], + [9662, 9671, 9680, 9690, 9701, 9713, 9725], + [9666, 9675, 9684, 9694, 9705, 9716, 9729], + [9669, 9678, 9688, 9698, 9709, 9720, 9732], + [9673, 9682, 9692, 9702, 9713, 9724, 9736], + [9677, 9686, 9695, 9706, 9716, 9728, 9740], + [9681, 9690, 9699, 9709, 9720, 9732, 9744], + [9685, 9694, 9703, 9713, 9724, 9735, 9747], + [9689, 9697, 9707, 9717, 9728, 9739, 9751], + [9692, 9701, 9711, 9721, 9732, 9743, 9755], + [9696, 9705, 9715, 9725, 9735, 9747, 9759], + [9700, 9709, 9718, 9728, 9739, 9750, 9763], + [9704, 9713, 9722, 9732, 9743, 9754, 9766], + [9708, 9716, 9726, 9736, 9747, 9758, 9770], + [9711, 9720, 9730, 9740, 9750, 9762, 9774], + [9715, 9724, 9733, 9744, 9754, 9766, 9778], + [9719, 9728, 9737, 9747, 9758, 9769, 9781], + [9723, 9732, 9741, 9751, 9762, 9773, 9785], + [9727, 9735, 9745, 9755, 9765, 9777, 9789], + [9730, 9739, 9749, 9759, 9769, 9781, 9793], + [9734, 9743, 9752, 9762, 9773, 9784, 9796], + [9738, 9747, 9756, 9766, 9777, 9788, 9800], + [9742, 9751, 9760, 9770, 9781, 9792, 9804], + [9746, 9754, 9764, 9774, 9784, 9796, 9807], + [9749, 9758, 9767, 9777, 9788, 9799, 9811], + [9753, 9762, 9771, 9781, 9792, 9803, 9815], + [9757, 9766, 9775, 9785, 9795, 9807, 9819], + [9761, 9769, 9779, 9789, 9799, 9810, 9822], + [9764, 9773, 9782, 9792, 9803, 9814, 9826], + [9768, 9777, 9786, 9796, 9807, 9818, 9830], + [9772, 9781, 9790, 9800, 9810, 9822, 9833], + [9776, 9784, 9794, 9804, 9814, 9825, 9837], + [9779, 9788, 9797, 9807, 9818, 9829, 9841], + [9783, 9792, 9801, 9811, 9822, 9833, 9845], + [9787, 9796, 9805, 9815, 9825, 9836, 9848], + [9791, 9799, 9809, 9818, 9829, 9840, 9852], + [9794, 9803, 9812, 9822, 9833, 9844, 9856], + [9798, 9807, 9816, 9826, 9836, 9848, 9859], + [9802, 9811, 9820, 9830, 9840, 9851, 9863], + [9806, 9814, 9823, 9833, 9844, 9855, 9867], + [9809, 9818, 9827, 9837, 9848, 9859, 9870], + [9813, 9822, 9831, 9841, 9851, 9862, 9874], + [9817, 9825, 9835, 9844, 9855, 9866, 9878], + [9821, 9829, 9838, 9848, 9859, 9870, 9881], + [9824, 9833, 9842, 9852, 9862, 9873, 9885], + [9828, 9837, 9846, 9856, 9866, 9877, 9889], + [9832, 9840, 9849, 9859, 9870, 9881, 9892], + [9835, 9844, 9853, 9863, 9873, 9884, 9896], + [9839, 9848, 9857, 9867, 9877, 9888, 9900], + [9843, 9851, 9861, 9870, 9881, 9892, 9903], + [9846, 9855, 9864, 9874, 9884, 9895, 9907], + [9850, 9859, 9868, 9878, 9888, 9899, 9911], + [9854, 9862, 9872, 9881, 9892, 9903, 9914], + [9858, 9866, 9875, 9885, 9895, 9906, 9918], + [9861, 9870, 9879, 9889, 9899, 9910, 9922], + [9865, 9873, 9883, 9892, 9903, 9914, 9925], + [9869, 9877, 9886, 9896, 9906, 9917, 9929], + [9872, 9881, 9890, 9900, 9910, 9921, 9933], + [9876, 9885, 9894, 9903, 9914, 9925, 9936], + [9880, 9888, 9897, 9907, 9917, 9928, 9940], + [9883, 9892, 9901, 9911, 9921, 9932, 9944], + [9887, 9896, 9905, 9914, 9925, 9936, 9947], + [9891, 9899, 9908, 9918, 9928, 9939, 9951], + [9894, 9903, 9912, 9922, 9932, 9943, 9954], + [9898, 9906, 9916, 9925, 9936, 9946, 9958], + [9902, 9910, 9919, 9929, 9939, 9950, 9962], + [9905, 9914, 9923, 9933, 9943, 9954, 9965], + [9909, 9917, 9926, 9936, 9946, 9957, 9969], + [9913, 9921, 9930, 9940, 9950, 9961, 9972], + [9916, 9925, 9934, 9943, 9954, 9965, 9976], + [9920, 9928, 9937, 9947, 9957, 9968, 9980], + [9924, 9932, 9941, 9951, 9961, 9972, 9983], + [9927, 9936, 9945, 9954, 9965, 9975, 9987], + [9931, 9939, 9948, 9958, 9968, 9979, 9991], + [9934, 9943, 9952, 9962, 9972, 9983, 9994], + [9938, 9947, 9956, 9965, 9975, 9986, 9998], + [9942, 9950, 9959, 9969, 9979, 9990, 10001], + [9945, 9954, 9963, 9972, 9983, 9993, 10005], + [9949, 9957, 9966, 9976, 9986, 9997, 10008], + [9953, 9961, 9970, 9980, 9990, 10001, 10012], + [9956, 9965, 9974, 9983, 9993, 10004, 10016], + [9960, 9968, 9977, 9987, 9997, 10008, 10019], + [9963, 9972, 9981, 9990, 10001, 10011, 10023], + [9967, 9975, 9984, 9994, 10004, 10015, 10026], + [9971, 9979, 9988, 9998, 10008, 10018, 10030], + [9974, 9983, 9992, 10001, 10011, 10022, 10033], + [9978, 9986, 9995, 10005, 10015, 10026, 10037], + [9982, 9990, 9999, 10008, 10018, 10029, 10041], + [9985, 9993, 10002, 10012, 10022, 10033, 10044], + [9989, 9997, 10006, 10015, 10026, 10036, 10048], + [9992, 10001, 10010, 10019, 10029, 10040, 10051], + [9996, 10004, 10013, 10023, 10033, 10043, 10055], + [10000, 10008, 10017, 10026, 10036, 10047, 10058], + [10003, 10011, 10020, 10030, 10040, 10051, 10062], + [10007, 10015, 10024, 10033, 10043, 10054, 10065], + [10010, 10019, 10027, 10037, 10047, 10058, 10069], + [10014, 10022, 10031, 10040, 10051, 10061, 10073], + [10017, 10026, 10035, 10044, 10054, 10065, 10076], + [10021, 10029, 10038, 10048, 10058, 10068, 10080], + [10025, 10033, 10042, 10051, 10061, 10072, 10083], + [10028, 10036, 10045, 10055, 10065, 10075, 10087], + [10032, 10040, 10049, 10058, 10068, 10079, 10090], + [10035, 10044, 10052, 10062, 10072, 10082, 10094], + [10039, 10047, 10056, 10065, 10075, 10086, 10097], + [10042, 10051, 10059, 10069, 10079, 10089, 10101], + [10046, 10054, 10063, 10072, 10082, 10093, 10104], + [10050, 10058, 10067, 10076, 10086, 10097, 10108], + [10053, 10061, 10070, 10079, 10089, 10100, 10111], + [10057, 10065, 10074, 10083, 10093, 10104, 10115], + [10060, 10068, 10077, 10087, 10097, 10107, 10118], + [10064, 10072, 10081, 10090, 10100, 10111, 10122], + [10067, 10075, 10084, 10094, 10104, 10114, 10125], + [10071, 10079, 10088, 10097, 10107, 10118, 10129], + [10074, 10083, 10091, 10101, 10111, 10121, 10132], + [10078, 10086, 10095, 10104, 10114, 10125, 10136], + [10081, 10090, 10098, 10108, 10118, 10128, 10139], + [10085, 10093, 10102, 10111, 10121, 10132, 10143], + [10088, 10097, 10105, 10115, 10125, 10135, 10146], + [10092, 10100, 10109, 10118, 10128, 10139, 10150], + [10095, 10104, 10112, 10122, 10132, 10142, 10153], + [10099, 10107, 10116, 10125, 10135, 10146, 10157], + [10103, 10111, 10119, 10129, 10139, 10149, 10160], + [10106, 10114, 10123, 10132, 10142, 10153, 10164], + [10110, 10118, 10126, 10136, 10146, 10156, 10167], + [10113, 10121, 10130, 10139, 10149, 10160, 10171], + [10117, 10125, 10133, 10143, 10153, 10163, 10174], + [10120, 10128, 10137, 10146, 10156, 10166, 10178], + [10124, 10132, 10140, 10150, 10160, 10170, 10181], + [10127, 10135, 10144, 10153, 10163, 10173, 10185], + [10131, 10139, 10147, 10157, 10166, 10177, 10188], + [10134, 10142, 10151, 10160, 10170, 10180, 10191], + [10138, 10146, 10154, 10164, 10173, 10184, 10195], + [10141, 10149, 10158, 10167, 10177, 10187, 10198], + [10145, 10153, 10161, 10171, 10180, 10191, 10202], + [10148, 10156, 10165, 10174, 10184, 10194, 10205], + [10152, 10160, 10168, 10177, 10187, 10198, 10209], + [10155, 10163, 10172, 10181, 10191, 10201, 10212], + [10159, 10167, 10175, 10184, 10194, 10205, 10216], + [10162, 10170, 10179, 10188, 10198, 10208, 10219], + [10165, 10174, 10182, 10191, 10201, 10211, 10222], + [10169, 10177, 10186, 10195, 10205, 10215, 10226], + [10172, 10180, 10189, 10198, 10208, 10218, 10229], + [10176, 10184, 10193, 10202, 10211, 10222, 10233], + [10179, 10187, 10196, 10205, 10215, 10225, 10236], + [10183, 10191, 10199, 10209, 10218, 10229, 10240], + [10186, 10194, 10203, 10212, 10222, 10232, 10243], + [10190, 10198, 10206, 10215, 10225, 10236, 10246], + [10193, 10201, 10210, 10219, 10229, 10239, 10250], + [10197, 10205, 10213, 10222, 10232, 10242, 10253], + [10200, 10208, 10217, 10226, 10235, 10246, 10257], + [10204, 10212, 10220, 10229, 10239, 10249, 10260], + [10207, 10215, 10224, 10233, 10242, 10253, 10264], + [10210, 10218, 10227, 10236, 10246, 10256, 10267], + [10214, 10222, 10230, 10239, 10249, 10259, 10270], + [10217, 10225, 10234, 10243, 10253, 10263, 10274], + [10221, 10229, 10237, 10246, 10256, 10266, 10277], + [10224, 10232, 10241, 10250, 10259, 10270, 10281], + [10228, 10236, 10244, 10253, 10263, 10273, 10284], + [10231, 10239, 10248, 10257, 10266, 10276, 10287], + [10234, 10242, 10251, 10260, 10270, 10280, 10291], + [10238, 10246, 10254, 10263, 10273, 10283, 10294], + [10241, 10249, 10258, 10267, 10276, 10287, 10298], + [10245, 10253, 10261, 10270, 10280, 10290, 10301], + [10248, 10256, 10265, 10274, 10283, 10293, 10304], + [10252, 10260, 10268, 10277, 10287, 10297, 10308], + [10255, 10263, 10271, 10280, 10290, 10300, 10311], + [10258, 10266, 10275, 10284, 10293, 10304, 10314], + [10262, 10270, 10278, 10287, 10297, 10307, 10318], + [10265, 10273, 10282, 10291, 10300, 10310, 10321], + [10269, 10277, 10285, 10294, 10304, 10314, 10325], + [10272, 10280, 10288, 10297, 10307, 10317, 10328], + [10275, 10283, 10292, 10301, 10310, 10320, 10331], + [10279, 10287, 10295, 10304, 10314, 10324, 10335], + [10282, 10290, 10299, 10308, 10317, 10327, 10338], + [10286, 10294, 10302, 10311, 10320, 10331, 10341], + [10289, 10297, 10305, 10314, 10324, 10334, 10345], + [10292, 10300, 10309, 10318, 10327, 10337, 10348], + [10296, 10304, 10312, 10321, 10331, 10341, 10351], + [10299, 10307, 10315, 10324, 10334, 10344, 10355], + [10303, 10310, 10319, 10328, 10337, 10347, 10358], + [10306, 10314, 10322, 10331, 10341, 10351, 10361], + [10309, 10317, 10326, 10335, 10344, 10354, 10365], + [10313, 10321, 10329, 10338, 10347, 10357, 10368], + [10316, 10324, 10332, 10341, 10351, 10361, 10371], + [10319, 10327, 10336, 10345, 10354, 10364, 10375], + [10323, 10331, 10339, 10348, 10357, 10367, 10378], + [10326, 10334, 10342, 10351, 10361, 10371, 10382], + [10330, 10337, 10346, 10355, 10364, 10374, 10385], + [10333, 10341, 10349, 10358, 10367, 10378, 10388], + [10336, 10344, 10352, 10361, 10371, 10381, 10391], + [10340, 10347, 10356, 10365, 10374, 10384, 10395], + [10343, 10351, 10359, 10368, 10377, 10388, 10398], + [10346, 10354, 10363, 10371, 10381, 10391, 10401], + [10350, 10358, 10366, 10375, 10384, 10394, 10405], + [10353, 10361, 10369, 10378, 10387, 10397, 10408], + [10356, 10364, 10373, 10381, 10391, 10401, 10411], + [10360, 10368, 10376, 10385, 10394, 10404, 10415], + [10363, 10371, 10379, 10388, 10397, 10407, 10418], + [10367, 10374, 10383, 10391, 10401, 10411, 10421], + [10370, 10378, 10386, 10395, 10404, 10414, 10425], + [10373, 10381, 10389, 10398, 10407, 10417, 10428], + [10377, 10384, 10393, 10401, 10411, 10421, 10431], + [10380, 10388, 10396, 10405, 10414, 10424, 10435], + [10383, 10391, 10399, 10408, 10417, 10427, 10438], + [10387, 10394, 10402, 10411, 10421, 10431, 10441], + [10390, 10398, 10406, 10415, 10424, 10434, 10444], + [10393, 10401, 10409, 10418, 10427, 10437, 10448], + [10397, 10404, 10412, 10421, 10431, 10441, 10451], + [10400, 10408, 10416, 10425, 10434, 10444, 10454], + [10403, 10411, 10419, 10428, 10437, 10447, 10458], + [10406, 10414, 10422, 10431, 10440, 10450, 10461], + [10410, 10417, 10426, 10434, 10444, 10454, 10464], + [10413, 10421, 10429, 10438, 10447, 10457, 10467], + [10416, 10424, 10432, 10441, 10450, 10460, 10471], + [10420, 10427, 10436, 10444, 10454, 10464, 10474], + [10423, 10431, 10439, 10448, 10457, 10467, 10477], + [10426, 10434, 10442, 10451, 10460, 10470, 10481], + [10430, 10437, 10445, 10454, 10464, 10473, 10484], + [10433, 10441, 10449, 10458, 10467, 10477, 10487], + [10436, 10444, 10452, 10461, 10470, 10480, 10490], + [10440, 10447, 10455, 10464, 10473, 10483, 10494], + [10443, 10450, 10459, 10467, 10477, 10486, 10497], + [10446, 10454, 10462, 10471, 10480, 10490, 10500], + [10449, 10457, 10465, 10474, 10483, 10493, 10503], + [10453, 10460, 10468, 10477, 10486, 10496, 10507], + [10456, 10464, 10472, 10480, 10490, 10500, 10510], + [10459, 10467, 10475, 10484, 10493, 10503, 10513], + [10463, 10470, 10478, 10487, 10496, 10506, 10516], + [10466, 10473, 10482, 10490, 10499, 10509, 10520], + [10469, 10477, 10485, 10494, 10503, 10513, 10523], + [10472, 10480, 10488, 10497, 10506, 10516, 10526], + [10476, 10483, 10491, 10500, 10509, 10519, 10529], + [10479, 10487, 10495, 10503, 10513, 10522, 10533], + [10482, 10490, 10498, 10507, 10516, 10526, 10536], + [10485, 10493, 10501, 10510, 10519, 10529, 10539], + [10489, 10496, 10504, 10513, 10522, 10532, 10542], + [10492, 10500, 10508, 10516, 10526, 10535, 10546], + [10495, 10503, 10511, 10520, 10529, 10539, 10549], + [10499, 10506, 10514, 10523, 10532, 10542, 10552], + [10502, 10509, 10517, 10526, 10535, 10545, 10555], + [10505, 10513, 10521, 10529, 10538, 10548, 10559], + [10508, 10516, 10524, 10533, 10542, 10551, 10562], + [10512, 10519, 10527, 10536, 10545, 10555, 10565], + [10515, 10522, 10530, 10539, 10548, 10558, 10568], + [10518, 10526, 10534, 10542, 10551, 10561, 10571], + [10521, 10529, 10537, 10545, 10555, 10564, 10575], + [10525, 10532, 10540, 10549, 10558, 10568, 10578], + [10528, 10535, 10543, 10552, 10561, 10571, 10581], + [10531, 10539, 10547, 10555, 10564, 10574, 10584], + [10534, 10542, 10550, 10558, 10568, 10577, 10587], + [10538, 10545, 10553, 10562, 10571, 10580, 10591], + [10541, 10548, 10556, 10565, 10574, 10584, 10594], + [10544, 10552, 10560, 10568, 10577, 10587, 10597], + [10547, 10555, 10563, 10571, 10580, 10590, 10600], + [10550, 10558, 10566, 10574, 10584, 10593, 10603], + [10554, 10561, 10569, 10578, 10587, 10596, 10607], + [10557, 10564, 10572, 10581, 10590, 10600, 10610], + [10560, 10568, 10576, 10584, 10593, 10603, 10613], + [10563, 10571, 10579, 10587, 10596, 10606, 10616], + [10567, 10574, 10582, 10591, 10600, 10609, 10619], + [10570, 10577, 10585, 10594, 10603, 10612, 10623], + [10573, 10580, 10588, 10597, 10606, 10616, 10626], + [10576, 10584, 10592, 10600, 10609, 10619, 10629], + [10579, 10587, 10595, 10603, 10612, 10622, 10632], + [10583, 10590, 10598, 10607, 10616, 10625, 10635], + [10586, 10593, 10601, 10610, 10619, 10628, 10639], + [10589, 10597, 10604, 10613, 10622, 10632, 10642], + [10592, 10600, 10608, 10616, 10625, 10635, 10645], + [10595, 10603, 10611, 10619, 10628, 10638, 10648], + [10599, 10606, 10614, 10623, 10632, 10641, 10651], + [10602, 10609, 10617, 10626, 10635, 10644, 10654], + [10605, 10612, 10620, 10629, 10638, 10647, 10658], + [10608, 10616, 10624, 10632, 10641, 10651, 10661], + [10611, 10619, 10627, 10635, 10644, 10654, 10664], + [10615, 10622, 10630, 10638, 10647, 10657, 10667], + [10618, 10625, 10633, 10642, 10651, 10660, 10670], + [10621, 10628, 10636, 10645, 10654, 10663, 10673], + [10624, 10632, 10640, 10648, 10657, 10666, 10677], + [10627, 10635, 10643, 10651, 10660, 10670, 10680], + [10631, 10638, 10646, 10654, 10663, 10673, 10683], + [10634, 10641, 10649, 10657, 10666, 10676, 10686], + [10637, 10644, 10652, 10661, 10670, 10679, 10689], + [10640, 10647, 10655, 10664, 10673, 10682, 10692], + [10643, 10651, 10659, 10667, 10676, 10685, 10695], + [10646, 10654, 10662, 10670, 10679, 10688, 10699], + [10650, 10657, 10665, 10673, 10682, 10692, 10702], + [10653, 10660, 10668, 10676, 10685, 10695, 10705], + [10656, 10663, 10671, 10680, 10688, 10698, 10708], + [10659, 10666, 10674, 10683, 10692, 10701, 10711], + [10662, 10670, 10677, 10686, 10695, 10704, 10714], + [10665, 10673, 10681, 10689, 10698, 10707, 10717], + [10669, 10676, 10684, 10692, 10701, 10710, 10720], + [10672, 10679, 10687, 10695, 10704, 10714, 10724], + [10675, 10682, 10690, 10698, 10707, 10717, 10727], + [10678, 10685, 10693, 10702, 10710, 10720, 10730], + [10681, 10689, 10696, 10705, 10714, 10723, 10733], + [10684, 10692, 10700, 10708, 10717, 10726, 10736], + [10688, 10695, 10703, 10711, 10720, 10729, 10739], + [10691, 10698, 10706, 10714, 10723, 10732, 10742], + [10694, 10701, 10709, 10717, 10726, 10735, 10745], + [10697, 10704, 10712, 10720, 10729, 10739, 10749], + [10700, 10707, 10715, 10724, 10732, 10742, 10752], + [10703, 10711, 10718, 10727, 10735, 10745, 10755], + [10706, 10714, 10721, 10730, 10739, 10748, 10758], + [10710, 10717, 10725, 10733, 10742, 10751, 10761], + [10713, 10720, 10728, 10736, 10745, 10754, 10764], + [10716, 10723, 10731, 10739, 10748, 10757, 10767], + [10719, 10726, 10734, 10742, 10751, 10760, 10770], + [10722, 10729, 10737, 10745, 10754, 10764, 10773], + [10725, 10732, 10740, 10748, 10757, 10767, 10777], + [10728, 10736, 10743, 10752, 10760, 10770, 10780], + [10731, 10739, 10746, 10755, 10763, 10773, 10783], + [10735, 10742, 10750, 10758, 10767, 10776, 10786], + [10738, 10745, 10753, 10761, 10770, 10779, 10789], + [10741, 10748, 10756, 10764, 10773, 10782, 10792], + [10744, 10751, 10759, 10767, 10776, 10785, 10795], + [10747, 10754, 10762, 10770, 10779, 10788, 10798], + [10750, 10757, 10765, 10773, 10782, 10791, 10801], + [10753, 10760, 10768, 10776, 10785, 10794, 10804], + [10756, 10764, 10771, 10780, 10788, 10798, 10807], + [10759, 10767, 10774, 10783, 10791, 10801, 10810], + [10763, 10770, 10777, 10786, 10794, 10804, 10814], + [10766, 10773, 10781, 10789, 10798, 10807, 10817], + [10769, 10776, 10784, 10792, 10801, 10810, 10820], + [10772, 10779, 10787, 10795, 10804, 10813, 10823], + [10775, 10782, 10790, 10798, 10807, 10816, 10826], + [10778, 10785, 10793, 10801, 10810, 10819, 10829], + [10781, 10788, 10796, 10804, 10813, 10822, 10832], + [10784, 10791, 10799, 10807, 10816, 10825, 10835], + [10787, 10795, 10802, 10810, 10819, 10828, 10838], + [10790, 10798, 10805, 10813, 10822, 10831, 10841], + [10794, 10801, 10808, 10817, 10825, 10834, 10844], + [10797, 10804, 10811, 10820, 10828, 10838, 10847], + [10800, 10807, 10815, 10823, 10831, 10841, 10850], + [10803, 10810, 10818, 10826, 10834, 10844, 10853], + [10806, 10813, 10821, 10829, 10837, 10847, 10856], + [10809, 10816, 10824, 10832, 10841, 10850, 10860], + [10812, 10819, 10827, 10835, 10844, 10853, 10863], + [10815, 10822, 10830, 10838, 10847, 10856, 10866], + [10818, 10825, 10833, 10841, 10850, 10859, 10869], + [10821, 10828, 10836, 10844, 10853, 10862, 10872], + [10824, 10831, 10839, 10847, 10856, 10865, 10875], + [10827, 10835, 10842, 10850, 10859, 10868, 10878], + [10830, 10838, 10845, 10853, 10862, 10871, 10881], + [10834, 10841, 10848, 10856, 10865, 10874, 10884], + [10837, 10844, 10851, 10859, 10868, 10877, 10887], + [10840, 10847, 10854, 10862, 10871, 10880, 10890], + [10843, 10850, 10857, 10865, 10874, 10883, 10893], + [10846, 10853, 10860, 10869, 10877, 10886, 10896], + [10849, 10856, 10864, 10872, 10880, 10889, 10899], + [10852, 10859, 10867, 10875, 10883, 10892, 10902], + [10855, 10862, 10870, 10878, 10886, 10895, 10905], + [10858, 10865, 10873, 10881, 10889, 10898, 10908], + [10861, 10868, 10876, 10884, 10892, 10901, 10911], + [10864, 10871, 10879, 10887, 10895, 10904, 10914], + [10867, 10874, 10882, 10890, 10898, 10908, 10917], + [10870, 10877, 10885, 10893, 10901, 10911, 10920], + [10873, 10880, 10888, 10896, 10904, 10914, 10923], + [10876, 10883, 10891, 10899, 10907, 10917, 10926], + [10879, 10886, 10894, 10902, 10910, 10920, 10929], + [10882, 10889, 10897, 10905, 10914, 10923, 10932], + [10885, 10892, 10900, 10908, 10917, 10926, 10935], + [10888, 10895, 10903, 10911, 10920, 10929, 10938], + [10891, 10898, 10906, 10914, 10923, 10932, 10941], + [10894, 10902, 10909, 10917, 10926, 10935, 10944], + [10898, 10905, 10912, 10920, 10929, 10938, 10947], + [10901, 10908, 10915, 10923, 10932, 10941, 10950], + [10904, 10911, 10918, 10926, 10935, 10944, 10953], + [10907, 10914, 10921, 10929, 10938, 10947, 10956], + [10910, 10917, 10924, 10932, 10941, 10950, 10959], + [10913, 10920, 10927, 10935, 10944, 10953, 10962], + [10916, 10923, 10930, 10938, 10947, 10956, 10965], + [10919, 10926, 10933, 10941, 10950, 10959, 10968], + [10922, 10929, 10936, 10944, 10953, 10962, 10971], + [10925, 10932, 10939, 10947, 10956, 10965, 10974], + [10928, 10935, 10942, 10950, 10959, 10968, 10977], + [10931, 10938, 10945, 10953, 10962, 10971, 10980], + [10934, 10941, 10948, 10956, 10965, 10974, 10983], + [10937, 10944, 10951, 10959, 10968, 10977, 10986], + [10940, 10947, 10954, 10962, 10971, 10980, 10989], + [10943, 10950, 10957, 10965, 10974, 10983, 10992], + [10946, 10953, 10960, 10968, 10977, 10986, 10995], + [10949, 10956, 10963, 10971, 10980, 10989, 10998], + [10952, 10959, 10966, 10974, 10983, 10992, 11001], + [10955, 10962, 10969, 10977, 10986, 10995, 11004], + [10958, 10965, 10972, 10980, 10989, 10997, 11007], + [10961, 10968, 10975, 10983, 10992, 11000, 11010], + [10964, 10971, 10978, 10986, 10994, 11003, 11013], + [10967, 10974, 10981, 10989, 10997, 11006, 11016], + [10970, 10977, 10984, 10992, 11000, 11009, 11019], + [10973, 10980, 10987, 10995, 11003, 11012, 11022], + [10976, 10983, 10990, 10998, 11006, 11015, 11025], + [10979, 10986, 10993, 11001, 11009, 11018, 11028], + [10982, 10989, 10996, 11004, 11012, 11021, 11031], + [10985, 10992, 10999, 11007, 11015, 11024, 11034], + [10988, 10995, 11002, 11010, 11018, 11027, 11037], + [10991, 10998, 11005, 11013, 11021, 11030, 11040], + [10994, 11001, 11008, 11016, 11024, 11033, 11043], + [10997, 11004, 11011, 11019, 11027, 11036, 11045], + [11000, 11006, 11014, 11022, 11030, 11039, 11048], + [11003, 11009, 11017, 11025, 11033, 11042, 11051], + [11006, 11012, 11020, 11028, 11036, 11045, 11054], + [11008, 11015, 11023, 11031, 11039, 11048, 11057], + [11011, 11018, 11026, 11034, 11042, 11051, 11060], + [11014, 11021, 11029, 11037, 11045, 11054, 11063], + [11017, 11024, 11032, 11039, 11048, 11057, 11066], + [11020, 11027, 11035, 11042, 11051, 11060, 11069], + [11023, 11030, 11038, 11045, 11054, 11063, 11072], + [11026, 11033, 11040, 11048, 11057, 11066, 11075], + [11029, 11036, 11043, 11051, 11060, 11068, 11078], + [11032, 11039, 11046, 11054, 11063, 11071, 11081], + [11035, 11042, 11049, 11057, 11065, 11074, 11084], + [11038, 11045, 11052, 11060, 11068, 11077, 11087], + [11041, 11048, 11055, 11063, 11071, 11080, 11090], + [11044, 11051, 11058, 11066, 11074, 11083, 11092], + [11047, 11054, 11061, 11069, 11077, 11086, 11095], + [11050, 11057, 11064, 11072, 11080, 11089, 11098], + [11053, 11060, 11067, 11075, 11083, 11092, 11101], + [11056, 11063, 11070, 11078, 11086, 11095, 11104], + [11059, 11066, 11073, 11081, 11089, 11098, 11107], + [11062, 11069, 11076, 11084, 11092, 11101, 11110], + [11065, 11071, 11079, 11087, 11095, 11104, 11113], + [11068, 11074, 11082, 11089, 11098, 11107, 11116], + [11071, 11077, 11085, 11092, 11101, 11109, 11119], + [11073, 11080, 11088, 11095, 11104, 11112, 11122], + [11076, 11083, 11090, 11098, 11106, 11115, 11125], + [11079, 11086, 11093, 11101, 11109, 11118, 11127], + [11082, 11089, 11096, 11104, 11112, 11121, 11130], + [11085, 11092, 11099, 11107, 11115, 11124, 11133], + [11088, 11095, 11102, 11110, 11118, 11127, 11136], + [11091, 11098, 11105, 11113, 11121, 11130, 11139], + [11094, 11101, 11108, 11116, 11124, 11133, 11142], + [11097, 11104, 11111, 11119, 11127, 11136, 11145], + [11100, 11107, 11114, 11122, 11130, 11139, 11148], + [11103, 11110, 11117, 11124, 11133, 11141, 11151], + [11106, 11112, 11120, 11127, 11136, 11144, 11154], + [11109, 11115, 11123, 11130, 11138, 11147, 11156], + [11111, 11118, 11125, 11133, 11141, 11150, 11159], + [11114, 11121, 11128, 11136, 11144, 11153, 11162], + [11117, 11124, 11131, 11139, 11147, 11156, 11165], + [11120, 11127, 11134, 11142, 11150, 11159, 11168], + [11123, 11130, 11137, 11145, 11153, 11162, 11171], + [11126, 11133, 11140, 11148, 11156, 11165, 11174], + [11129, 11136, 11143, 11151, 11159, 11167, 11177], + [11132, 11139, 11146, 11153, 11162, 11170, 11180], + [11135, 11141, 11149, 11156, 11165, 11173, 11182], + [11138, 11144, 11152, 11159, 11167, 11176, 11185], + [11141, 11147, 11154, 11162, 11170, 11179, 11188], + [11143, 11150, 11157, 11165, 11173, 11182, 11191], + [11146, 11153, 11160, 11168, 11176, 11185, 11194], + [11149, 11156, 11163, 11171, 11179, 11188, 11197], + [11152, 11159, 11166, 11174, 11182, 11191, 11200], + [11155, 11162, 11169, 11177, 11185, 11193, 11203], + [11158, 11165, 11172, 11179, 11188, 11196, 11205], + [11161, 11168, 11175, 11182, 11190, 11199, 11208], + [11164, 11170, 11178, 11185, 11193, 11202, 11211], + [11167, 11173, 11180, 11188, 11196, 11205, 11214], + [11169, 11176, 11183, 11191, 11199, 11208, 11217], + [11172, 11179, 11186, 11194, 11202, 11211, 11220], + [11175, 11182, 11189, 11197, 11205, 11213, 11223], + [11178, 11185, 11192, 11200, 11208, 11216, 11225], + [11181, 11188, 11195, 11202, 11211, 11219, 11228], + [11184, 11191, 11198, 11205, 11213, 11222, 11231], + [11187, 11193, 11201, 11208, 11216, 11225, 11234], + [11190, 11196, 11203, 11211, 11219, 11228, 11237], + [11193, 11199, 11206, 11214, 11222, 11231, 11240], + [11195, 11202, 11209, 11217, 11225, 11233, 11243], + [11198, 11205, 11212, 11220, 11228, 11236, 11245], + [11201, 11208, 11215, 11223, 11231, 11239, 11248], + [11204, 11211, 11218, 11225, 11233, 11242, 11251], + [11207, 11214, 11221, 11228, 11236, 11245, 11254], + [11210, 11216, 11224, 11231, 11239, 11248, 11257], + [11213, 11219, 11226, 11234, 11242, 11251, 11260], + [11215, 11222, 11229, 11237, 11245, 11253, 11263], + [11218, 11225, 11232, 11240, 11248, 11256, 11265], + [11221, 11228, 11235, 11242, 11251, 11259, 11268], + [11224, 11231, 11238, 11245, 11253, 11262, 11271], + [11227, 11234, 11241, 11248, 11256, 11265, 11274], + [11230, 11236, 11243, 11251, 11259, 11268, 11277], + [11233, 11239, 11246, 11254, 11262, 11270, 11280], + [11235, 11242, 11249, 11257, 11265, 11273, 11282], + [11238, 11245, 11252, 11260, 11268, 11276, 11285], + [11241, 11248, 11255, 11262, 11270, 11279, 11288], + [11244, 11251, 11258, 11265, 11273, 11282, 11291], + [11247, 11253, 11261, 11268, 11276, 11285, 11294], + [11250, 11256, 11263, 11271, 11279, 11287, 11296], + [11253, 11259, 11266, 11274, 11282, 11290, 11299], + [11255, 11262, 11269, 11277, 11285, 11293, 11302], + [11258, 11265, 11272, 11279, 11287, 11296, 11305], + [11261, 11268, 11275, 11282, 11290, 11299, 11308], + [11264, 11271, 11278, 11285, 11293, 11302, 11311], + [11267, 11273, 11280, 11288, 11296, 11304, 11313], + [11270, 11276, 11283, 11291, 11299, 11307, 11316], + [11272, 11279, 11286, 11294, 11302, 11310, 11319], + [11275, 11282, 11289, 11296, 11304, 11313, 11322], + [11278, 11285, 11292, 11299, 11307, 11316, 11325], + [11281, 11288, 11295, 11302, 11310, 11318, 11327], + [11284, 11290, 11297, 11305, 11313, 11321, 11330], + [11287, 11293, 11300, 11308, 11316, 11324, 11333], + [11289, 11296, 11303, 11310, 11318, 11327, 11336], + [11292, 11299, 11306, 11313, 11321, 11330, 11339], + [11295, 11302, 11309, 11316, 11324, 11332, 11341], + [11298, 11304, 11311, 11319, 11327, 11335, 11344], + [11301, 11307, 11314, 11322, 11330, 11338, 11347], + [11304, 11310, 11317, 11325, 11332, 11341, 11350], + [11306, 11313, 11320, 11327, 11335, 11344, 11353], + [11309, 11316, 11323, 11330, 11338, 11347, 11355], + [11312, 11319, 11325, 11333, 11341, 11349, 11358], + [11315, 11321, 11328, 11336, 11344, 11352, 11361], + [11318, 11324, 11331, 11339, 11346, 11355, 11364], + [11320, 11327, 11334, 11341, 11349, 11358, 11367], + [11323, 11330, 11337, 11344, 11352, 11360, 11369], + [11326, 11333, 11340, 11347, 11355, 11363, 11372], + [11329, 11335, 11342, 11350, 11358, 11366, 11375], + [11332, 11338, 11345, 11353, 11360, 11369, 11378], + [11334, 11341, 11348, 11355, 11363, 11372, 11381], + [11337, 11344, 11351, 11358, 11366, 11374, 11383], + [11340, 11347, 11354, 11361, 11369, 11377, 11386], + [11343, 11349, 11356, 11364, 11372, 11380, 11389], + [11346, 11352, 11359, 11367, 11374, 11383, 11392], + [11348, 11355, 11362, 11369, 11377, 11386, 11394], + [11351, 11358, 11365, 11372, 11380, 11388, 11397], + [11354, 11361, 11367, 11375, 11383, 11391, 11400], + [11357, 11363, 11370, 11378, 11386, 11394, 11403], + [11360, 11366, 11373, 11380, 11388, 11397, 11406], + [11362, 11369, 11376, 11383, 11391, 11399, 11408], + [11365, 11372, 11379, 11386, 11394, 11402, 11411], + [11368, 11374, 11381, 11389, 11397, 11405, 11414], + [11371, 11377, 11384, 11392, 11399, 11408, 11417], + [11374, 11380, 11387, 11394, 11402, 11410, 11419], + [11376, 11383, 11390, 11397, 11405, 11413, 11422], + [11379, 11386, 11393, 11400, 11408, 11416, 11425], + [11382, 11388, 11395, 11403, 11410, 11419, 11428], + [11385, 11391, 11398, 11405, 11413, 11422, 11430], + [11387, 11394, 11401, 11408, 11416, 11424, 11433], + [11390, 11397, 11404, 11411, 11419, 11427, 11436], + [11393, 11399, 11406, 11414, 11422, 11430, 11439], + [11396, 11402, 11409, 11416, 11424, 11433, 11441], + [11399, 11405, 11412, 11419, 11427, 11435, 11444], + [11401, 11408, 11415, 11422, 11430, 11438, 11447], + [11404, 11411, 11417, 11425, 11433, 11441, 11450], + [11407, 11413, 11420, 11428, 11435, 11444, 11452], + [11410, 11416, 11423, 11430, 11438, 11446, 11455], + [11412, 11419, 11426, 11433, 11441, 11449, 11458], + [11415, 11422, 11428, 11436, 11444, 11452, 11461], + [11418, 11424, 11431, 11439, 11446, 11455, 11463], + [11421, 11427, 11434, 11441, 11449, 11457, 11466], + [11423, 11430, 11437, 11444, 11452, 11460, 11469], + [11426, 11433, 11439, 11447, 11455, 11463, 11472], + [11429, 11435, 11442, 11450, 11457, 11466, 11474], + [11432, 11438, 11445, 11452, 11460, 11468, 11477], + [11435, 11441, 11448, 11455, 11463, 11471, 11480], + [11437, 11444, 11450, 11458, 11466, 11474, 11483], + [11440, 11446, 11453, 11461, 11468, 11477, 11485], + [11443, 11449, 11456, 11463, 11471, 11479, 11488], + [11446, 11452, 11459, 11466, 11474, 11482, 11491], + [11448, 11455, 11461, 11469, 11476, 11485, 11493], + [11451, 11457, 11464, 11471, 11479, 11487, 11496], + [11454, 11460, 11467, 11474, 11482, 11490, 11499], + [11457, 11463, 11470, 11477, 11485, 11493, 11502], + [11459, 11466, 11472, 11480, 11487, 11496, 11504], + [11462, 11468, 11475, 11482, 11490, 11498, 11507], + [11465, 11471, 11478, 11485, 11493, 11501, 11510], + [11467, 11474, 11481, 11488, 11496, 11504, 11513], + [11470, 11477, 11483, 11491, 11498, 11507, 11515], + [11473, 11479, 11486, 11493, 11501, 11509, 11518], + [11476, 11482, 11489, 11496, 11504, 11512, 11521], + [11478, 11485, 11492, 11499, 11507, 11515, 11523], + [11481, 11488, 11494, 11502, 11509, 11517, 11526], + [11484, 11490, 11497, 11504, 11512, 11520, 11529], + [11487, 11493, 11500, 11507, 11515, 11523, 11532], + [11489, 11496, 11502, 11510, 11517, 11526, 11534], + [11492, 11498, 11505, 11512, 11520, 11528, 11537], + [11495, 11501, 11508, 11515, 11523, 11531, 11540], + [11498, 11504, 11511, 11518, 11526, 11534, 11542], + [11500, 11507, 11513, 11521, 11528, 11536, 11545], + [11503, 11509, 11516, 11523, 11531, 11539, 11548], + [11506, 11512, 11519, 11526, 11534, 11542, 11550], + [11508, 11515, 11522, 11529, 11536, 11545, 11553], + [11511, 11517, 11524, 11531, 11539, 11547, 11556], + [11514, 11520, 11527, 11534, 11542, 11550, 11559], + [11517, 11523, 11530, 11537, 11544, 11553, 11561], + [11519, 11526, 11532, 11540, 11547, 11555, 11564], + [11522, 11528, 11535, 11542, 11550, 11558, 11567], + [11525, 11531, 11538, 11545, 11553, 11561, 11569], + [11527, 11534, 11540, 11548, 11555, 11563, 11572], + [11530, 11536, 11543, 11550, 11558, 11566, 11575], + [11533, 11539, 11546, 11553, 11561, 11569, 11577], + [11536, 11542, 11549, 11556, 11563, 11572, 11580], + [11538, 11545, 11551, 11558, 11566, 11574, 11583], + [11541, 11547, 11554, 11561, 11569, 11577, 11585], + [11544, 11550, 11557, 11564, 11571, 11580, 11588], + [11546, 11553, 11559, 11567, 11574, 11582, 11591], + [11549, 11555, 11562, 11569, 11577, 11585, 11594], + [11552, 11558, 11565, 11572, 11580, 11588, 11596], + [11555, 11561, 11567, 11575, 11582, 11590, 11599], + [11557, 11563, 11570, 11577, 11585, 11593, 11602], + [11560, 11566, 11573, 11580, 11588, 11596, 11604], + [11563, 11569, 11576, 11583, 11590, 11598, 11607], + [11565, 11572, 11578, 11585, 11593, 11601, 11610], + [11568, 11574, 11581, 11588, 11596, 11604, 11612], + [11571, 11577, 11584, 11591, 11598, 11606, 11615], + [11573, 11580, 11586, 11593, 11601, 11609, 11618], + [11576, 11582, 11589, 11596, 11604, 11612, 11620], + [11579, 11585, 11592, 11599, 11606, 11614, 11623], + [11581, 11588, 11594, 11601, 11609, 11617, 11626], + [11584, 11590, 11597, 11604, 11612, 11620, 11628], + [11587, 11593, 11600, 11607, 11614, 11622, 11631], + [11590, 11596, 11602, 11609, 11617, 11625, 11634], + [11592, 11598, 11605, 11612, 11620, 11628, 11636], + [11595, 11601, 11608, 11615, 11622, 11630, 11639], + [11598, 11604, 11610, 11618, 11625, 11633, 11642], + [11600, 11606, 11613, 11620, 11628, 11636, 11644], + [11603, 11609, 11616, 11623, 11630, 11638, 11647], + [11606, 11612, 11618, 11626, 11633, 11641, 11650], + [11608, 11614, 11621, 11628, 11636, 11644, 11652], + [11611, 11617, 11624, 11631, 11638, 11646, 11655], + [11614, 11620, 11626, 11634, 11641, 11649, 11658], + [11616, 11622, 11629, 11636, 11644, 11652, 11660], + [11619, 11625, 11632, 11639, 11646, 11654, 11663], + [11622, 11628, 11634, 11641, 11649, 11657, 11665], + [11624, 11630, 11637, 11644, 11652, 11660, 11668], + [11627, 11633, 11640, 11647, 11654, 11662, 11671], + [11630, 11636, 11642, 11649, 11657, 11665, 11673], + [11632, 11638, 11645, 11652, 11660, 11668, 11676], + [11635, 11641, 11648, 11655, 11662, 11670, 11679], + [11638, 11644, 11650, 11657, 11665, 11673, 11681], + [11640, 11646, 11653, 11660, 11668, 11676, 11684], + [11643, 11649, 11656, 11663, 11670, 11678, 11687], + [11646, 11652, 11658, 11665, 11673, 11681, 11689], + [11648, 11654, 11661, 11668, 11676, 11683, 11692], + [11651, 11657, 11664, 11671, 11678, 11686, 11695], + [11654, 11660, 11666, 11673, 11681, 11689, 11697], + [11656, 11662, 11669, 11676, 11683, 11691, 11700], + [11659, 11665, 11672, 11679, 11686, 11694, 11702], + [11662, 11668, 11674, 11681, 11689, 11697, 11705], + [11664, 11670, 11677, 11684, 11691, 11699, 11708], + [11667, 11673, 11680, 11687, 11694, 11702, 11710], + [11669, 11676, 11682, 11689, 11697, 11705, 11713], + [11672, 11678, 11685, 11692, 11699, 11707, 11716], + [11675, 11681, 11687, 11694, 11702, 11710, 11718], + [11677, 11684, 11690, 11697, 11705, 11712, 11721], + [11680, 11686, 11693, 11700, 11707, 11715, 11723], + [11683, 11689, 11695, 11702, 11710, 11718, 11726], + [11685, 11691, 11698, 11705, 11712, 11720, 11729], + [11688, 11694, 11701, 11708, 11715, 11723, 11731], + [11691, 11697, 11703, 11710, 11718, 11726, 11734], + [11693, 11699, 11706, 11713, 11720, 11728, 11737], + [11696, 11702, 11709, 11716, 11723, 11731, 11739], + [11699, 11705, 11711, 11718, 11726, 11733, 11742], + [11701, 11707, 11714, 11721, 11728, 11736, 11744], + [11704, 11710, 11716, 11723, 11731, 11739, 11747], + [11706, 11713, 11719, 11726, 11733, 11741, 11750], + [11709, 11715, 11722, 11729, 11736, 11744, 11752], + [11712, 11718, 11724, 11731, 11739, 11747, 11755], + [11714, 11720, 11727, 11734, 11741, 11749, 11757], + [11717, 11723, 11730, 11736, 11744, 11752, 11760], + [11720, 11726, 11732, 11739, 11747, 11754, 11763], + [11722, 11728, 11735, 11742, 11749, 11757, 11765], + [11725, 11731, 11737, 11744, 11752, 11760, 11768], + [11727, 11734, 11740, 11747, 11754, 11762, 11771], + [11730, 11736, 11743, 11750, 11757, 11765, 11773], + [11733, 11739, 11745, 11752, 11760, 11767, 11776], + [11735, 11741, 11748, 11755, 11762, 11770, 11778], + [11738, 11744, 11750, 11757, 11765, 11773, 11781], + [11741, 11747, 11753, 11760, 11767, 11775, 11784], + [11743, 11749, 11756, 11763, 11770, 11778, 11786], + [11746, 11752, 11758, 11765, 11773, 11780, 11789], + [11748, 11754, 11761, 11768, 11775, 11783, 11791], + [11751, 11757, 11764, 11770, 11778, 11786, 11794], + [11754, 11760, 11766, 11773, 11780, 11788, 11796], + [11756, 11762, 11769, 11776, 11783, 11791, 11799], + [11759, 11765, 11771, 11778, 11786, 11793, 11802], + [11761, 11767, 11774, 11781, 11788, 11796, 11804], + [11764, 11770, 11777, 11783, 11791, 11799, 11807], + [11767, 11773, 11779, 11786, 11793, 11801, 11809], + [11769, 11775, 11782, 11789, 11796, 11804, 11812], + [11772, 11778, 11784, 11791, 11799, 11806, 11815], + [11774, 11780, 11787, 11794, 11801, 11809, 11817], + [11777, 11783, 11790, 11796, 11804, 11812, 11820], + [11780, 11786, 11792, 11799, 11806, 11814, 11822], + [11782, 11788, 11795, 11802, 11809, 11817, 11825], + [11785, 11791, 11797, 11804, 11811, 11819, 11827], + [11787, 11793, 11800, 11807, 11814, 11822, 11830], + [11790, 11796, 11802, 11809, 11817, 11824, 11833], + [11793, 11799, 11805, 11812, 11819, 11827, 11835], + [11795, 11801, 11808, 11815, 11822, 11830, 11838], + [11798, 11804, 11810, 11817, 11824, 11832, 11840], + [11800, 11806, 11813, 11820, 11827, 11835, 11843], + [11803, 11809, 11815, 11822, 11830, 11837, 11846], + [11806, 11812, 11818, 11825, 11832, 11840, 11848], + [11808, 11814, 11821, 11827, 11835, 11842, 11851], + [11811, 11817, 11823, 11830, 11837, 11845, 11853], + [11813, 11819, 11826, 11833, 11840, 11848, 11856], + [11816, 11822, 11828, 11835, 11842, 11850, 11858], + [11818, 11824, 11831, 11838, 11845, 11853, 11861], + [11821, 11827, 11833, 11840, 11848, 11855, 11863], + [11824, 11830, 11836, 11843, 11850, 11858, 11866], + [11826, 11832, 11839, 11845, 11853, 11860, 11869], + [11829, 11835, 11841, 11848, 11855, 11863, 11871], + [11831, 11837, 11844, 11851, 11858, 11866, 11874], + [11834, 11840, 11846, 11853, 11860, 11868, 11876], + [11837, 11843, 11849, 11856, 11863, 11871, 11879], + [11839, 11845, 11851, 11858, 11866, 11873, 11881], + [11842, 11848, 11854, 11861, 11868, 11876, 11884], + [11844, 11850, 11857, 11863, 11871, 11878, 11887], + [11847, 11853, 11859, 11866, 11873, 11881, 11889], + [11849, 11855, 11862, 11869, 11876, 11883, 11892], + [11852, 11858, 11864, 11871, 11878, 11886, 11894], + [11855, 11860, 11867, 11874, 11881, 11889, 11897], + [11857, 11863, 11869, 11876, 11883, 11891, 11899], + [11860, 11866, 11872, 11879, 11886, 11894, 11902], + [11862, 11868, 11875, 11881, 11889, 11896, 11904], + [11865, 11871, 11877, 11884, 11891, 11899, 11907], + [11867, 11873, 11880, 11886, 11894, 11901, 11909], + [11870, 11876, 11882, 11889, 11896, 11904, 11912], + [11872, 11878, 11885, 11892, 11899, 11906, 11915], + [11875, 11881, 11887, 11894, 11901, 11909, 11917], + [11878, 11884, 11890, 11897, 11904, 11911, 11920], + [11880, 11886, 11892, 11899, 11906, 11914, 11922], + [11883, 11889, 11895, 11902, 11909, 11917, 11925], + [11885, 11891, 11897, 11904, 11911, 11919, 11927], + [11888, 11894, 11900, 11907, 11914, 11922, 11930], + [11890, 11896, 11903, 11909, 11917, 11924, 11932], + [11893, 11899, 11905, 11912, 11919, 11927, 11935], + [11895, 11901, 11908, 11914, 11922, 11929, 11937], + [11898, 11904, 11910, 11917, 11924, 11932, 11940], + [11901, 11906, 11913, 11920, 11927, 11934, 11942], + [11903, 11909, 11915, 11922, 11929, 11937, 11945], + [11906, 11912, 11918, 11925, 11932, 11939, 11947], + [11908, 11914, 11920, 11927, 11934, 11942, 11950], + [11911, 11917, 11923, 11930, 11937, 11944, 11953], + [11913, 11919, 11925, 11932, 11939, 11947, 11955], + [11916, 11922, 11928, 11935, 11942, 11949, 11958], + [11918, 11924, 11931, 11937, 11944, 11952, 11960], + [11921, 11927, 11933, 11940, 11947, 11955, 11963], + [11923, 11929, 11936, 11942, 11949, 11957, 11965], + [11926, 11932, 11938, 11945, 11952, 11960, 11968], + [11928, 11934, 11941, 11947, 11955, 11962, 11970], + [11931, 11937, 11943, 11950, 11957, 11965, 11973], + [11934, 11939, 11946, 11952, 11960, 11967, 11975], + [11936, 11942, 11948, 11955, 11962, 11970, 11978], + [11939, 11944, 11951, 11957, 11965, 11972, 11980], + [11941, 11947, 11953, 11960, 11967, 11975, 11983], + [11944, 11950, 11956, 11963, 11970, 11977, 11985], + [11946, 11952, 11958, 11965, 11972, 11980, 11988], + [11949, 11955, 11961, 11968, 11975, 11982, 11990], + [11951, 11957, 11963, 11970, 11977, 11985, 11993], + [11954, 11960, 11966, 11973, 11980, 11987, 11995], + [11956, 11962, 11968, 11975, 11982, 11990, 11998], + [11959, 11965, 11971, 11978, 11985, 11992, 12000], + [11961, 11967, 11973, 11980, 11987, 11995, 12003], + [11964, 11970, 11976, 11983, 11990, 11997, 12005], + [11966, 11972, 11978, 11985, 11992, 12000, 12008], +]; diff --git a/src/thermal.rs b/src/thermal.rs new file mode 100644 index 0000000..4aacc37 --- /dev/null +++ b/src/thermal.rs @@ -0,0 +1,171 @@ +//! Turns one raw sensor [`Frame`] plus [`Calibration`] data into a 16x16 +//! object-temperature heatmap, following the compensation pipeline described +//! in the HTPA16x16dR2 datasheet, section 12: +//! +//! 1. ambient temperature, from PTAT (12.1) +//! 2. thermal offset compensation (12.2) +//! 3. electrical offset compensation (12.3) +//! 4. VDD compensation (12.4) +//! 5. sensitivity (PixC) compensation + lookup table (12.5) + +use crate::calibration::{Calibration, GRID}; +use crate::lookup_table::LookupTable; +use crate::sensor::{header_word, pixel, Frame, RawHalf}; + +/// The computed heatmap for one frame, plus a couple of handy summary stats. +pub struct Heatmap { + pub ambient_c: f32, + pub min_c: f32, + pub max_c: f32, + pub temperature_c: [[f32; GRID]; GRID], +} + +/// Compensate one raw frame into a full object-temperature heatmap. +pub fn process_frame( + cal: &Calibration, + frame: &Frame, + table: &LookupTable, +) -> Heatmap { + let ptat_av = average_header_word(&frame.pixel_top, &frame.pixel_bottom); + let vdd_av = average_header_word(&frame.vdd_top, &frame.vdd_bottom); + + let ambient_dk = ptat_av as f32 * cal.ptat_gradient + cal.ptat_offset; + let ambient_c = dk_to_c(ambient_dk); + let ambient_column = table.locate_ambient_column(ambient_dk as i32); + + let mut temperature_c = [[0f32; GRID]; GRID]; + let mut min_c = f32::MAX; + let mut max_c = f32::MIN; + + for col in 0..GRID { + let raw_pixels = assemble_raw_column(frame, col); + let electrical_offsets = electrical_offset_column(&frame.electrical_offset, col); + + for row in 0..GRID { + let profile = row_profile(row); + + let thermal_compensated = + raw_pixels[row] - thermal_offset_correction(cal, row, col, ptat_av); + let electrical_compensated = + thermal_compensated - electrical_offsets[profile] as i32; + let vdd_compensated = electrical_compensated + - vdd_compensation(cal, profile, col, ptat_av, vdd_av); + + let pixc = sensitivity_coefficient(cal, row, col); + let signal_digits = (vdd_compensated as f32 * table.pixel_signal_scale / pixc) as i32; + + let object_dk = table.lookup(&ambient_column, signal_digits); + let object_c = dk_to_c(object_dk as f32); + + temperature_c[row][col] = object_c; + min_c = min_c.min(object_c); + max_c = max_c.max(object_c); + } + } + + Heatmap { ambient_c, min_c, max_c, temperature_c } +} + +/// Convert deci-Kelvin (as used throughout the datasheet's calibration data) +/// to degrees Celsius. +fn dk_to_c(dk: f32) -> f32 { + dk / 10.0 - 273.15 +} + +/// Which of the 8 stored calibration row-profiles a physical row uses. +/// Electrical offset and VDD compensation are only calibrated for 8 rows; +/// each profile is reused for two of the array's 16 physical rows (the +/// bottom half is wired up in reverse row order, see the datasheet's +/// "Readout Order" figure). +fn row_profile(row: usize) -> usize { + if row < 8 { + row % 4 + } else { + row % 4 + 4 + } +} + +/// Average of the header word (PTAT or VDD) across a block-0/block-1 pair of +/// top/bottom half buffers (datasheet 12.1/12.4 both recommend averaging all +/// four samples for a more stable reading). +fn average_header_word(top: &[RawHalf; 2], bottom: &[RawHalf; 2]) -> i32 { + let sum = header_word(&top[0]) + header_word(&top[1]) + header_word(&bottom[0]) + header_word(&bottom[1]); + (sum / 4) as i32 +} + +/// Reconstruct one column (16 rows) of raw pixel digits from the pixel +/// blocks, in the physical row order described in the datasheet's "Readout +/// Order" figure: block 0 and block 1 each contribute 8 rows, with the +/// bottom half read out column-major and row-reversed. +fn assemble_raw_column(frame: &Frame, col: usize) -> [i32; GRID] { + let top0 = &frame.pixel_top[0]; + let top1 = &frame.pixel_top[1]; + let bottom1 = &frame.pixel_bottom[1]; + let bottom0 = &frame.pixel_bottom[0]; + + [ + pixel(top0, col) as i32, + pixel(top0, col + 16) as i32, + pixel(top0, col + 32) as i32, + pixel(top0, col + 48) as i32, + pixel(top1, col) as i32, + pixel(top1, col + 16) as i32, + pixel(top1, col + 32) as i32, + pixel(top1, col + 48) as i32, + pixel(bottom1, col + 48) as i32, + pixel(bottom1, col + 32) as i32, + pixel(bottom1, col + 16) as i32, + pixel(bottom1, col) as i32, + pixel(bottom0, col + 48) as i32, + pixel(bottom0, col + 32) as i32, + pixel(bottom0, col + 16) as i32, + pixel(bottom0, col) as i32, + ] +} + +/// One column of the 8 electrical-offset row profiles (datasheet 12.3). +fn electrical_offset_column(halves: &(RawHalf, RawHalf), col: usize) -> [u16; 8] { + let (top, bottom) = halves; + [ + pixel(top, col), + pixel(top, col + 16), + pixel(top, col + 32), + pixel(top, col + 48), + pixel(bottom, col + 48), + pixel(bottom, col + 32), + pixel(bottom, col + 16), + pixel(bottom, col), + ] +} + +/// Thermal offset correction to subtract from one pixel (datasheet 12.2): +/// `ThGrad * Ta / 2^gradScale + ThOffset`. +fn thermal_offset_correction(cal: &Calibration, row: usize, col: usize, ptat_av: i32) -> i32 { + (cal.thermal_gradient[row][col] as i32 * ptat_av) / cal.gradient_scale_div + + cal.thermal_offset[row][col] as i32 +} + +/// VDD compensation to subtract from one pixel (datasheet 12.4). Compares +/// the measured VDD against what it should be at the current ambient +/// temperature (interpolated between the two calibration points), scaled by +/// this pixel's VDD sensitivity. +fn vdd_compensation(cal: &Calibration, profile: usize, col: usize, ptat_av: i32, vdd_av: i32) -> i32 { + let gradient = cal.vdd_comp_gradient[profile][col] as i32; + let offset = cal.vdd_comp_offset[profile][col] as i32; + + let [vdd_at_cal1, vdd_at_cal2] = cal.vdd_at_calibration; + let [ptat_at_cal1, ptat_at_cal2] = cal.ptat_at_calibration; + let expected_vdd = vdd_at_cal1 + + (vdd_at_cal2 - vdd_at_cal1) * (ptat_av - ptat_at_cal1) / (ptat_at_cal2 - ptat_at_cal1); + let vdd_delta = vdd_av - expected_vdd; + + let scaled_gradient = gradient * ptat_av / cal.vdd_scale_gradient_div + offset; + scaled_gradient * vdd_delta / cal.vdd_scale_offset_div +} + +/// Per-pixel sensitivity coefficient, PixC (datasheet 12.5): +/// `(Pij * (PixCmax - PixCmin) / 65535 + PixCmin) * epsilon/100 * GlobalGain/10000`. +fn sensitivity_coefficient(cal: &Calibration, row: usize, col: usize) -> f32 { + let scaled = cal.sensitivity[row][col] as f32 * (cal.pixc_max - cal.pixc_min) / 65535.0 + cal.pixc_min; + scaled * (cal.epsilon / 100.0) * (cal.global_gain / 10000.0) +}