fractal_analysis/lib.rs
1//! Welcome to the `fractal-analysis` crate! Here you will find necessary tools
2//! for the analysis of fractal sets, based on the “Z-box merging” algorithm.
3//!
4//! # How to use
5//! I have tried to create ergonomic functions for the following data-sets:
6//! * [Images, colour or gray-scale](mod@img_funs)
7//! * [Sounds](mod@snd_funs) (to be added later)
8//! * [Electro-encephalograms](mod@eeg_19c_funs) (to be added later)
9//! * [MRI images](mod@mri_funs) (to be added later)
10//!
11//! For each of those, you will find ready-made functions that can give you
12//! useful results; just eg say `example_code_here` and you will get the result.
13//!
14//! # Caveat
15//! Currently (March 2021) the documentation and testing are still unfinished.
16//! They are both provided on a best-effort basis; the user's understanding is
17//! appreciated.
18//!
19//! # How to extend to other uses
20//! Oh, how I _wish_ there was a way for me to design an API that said “Oh, just
21//! do `zbox_merge(input)` for whatever input and it'll work!”.
22//!
23//! Sadly, it is no-where nearly as simple as that. The
24//! basic library will get you a long way
25//! along, but there is serious work you'll need to do before-hand. The simplest
26//! way would be to just
27//! [send the crate maintainer an e-mail](mailto:velona@ahiru.eu)
28//! and ask directly, but in case s/he is unreachable, here is what you'll
29//! need to do:
30//!
31//! ### Choose your dependent and independent dimensions
32//! First, decide what counts as an “separate dimension” for your use-case.
33//! Is each channel of your EEG a separate dimension that depends on the
34//! time-stamp, or are they a sampling
35//! of a two-dimensional scalp? What about the colour channels of your image?
36//! That will give you a first idea of what to do, and denote the limits of the
37//! result you will end up with.
38//!
39//! Of particular note: You will want the number of dimensions to be independent
40//! of the measurement method. For instance: For the colour channels, we chose
41//! each to be a separate dimension in part because all colour images have those
42//! exact same 3 channels. But for the EEGs we chose to consider them as a 2-D
43//! grid, or else the result would depend on the number of electrodes and
44//! measurements between different setups would have no immediate way to be
45//! compared. As a result, about half the mental effort was spent in deciding
46//! exactly how to subdivide the electrode placement into quadrants!
47//!
48//! ### Count the number of bits you have available
49//! For each coordinate, count the amount of values it can take. Take the
50//! smallest of those amounts, find its base-2 logarithm,
51//! and throw away all the decimal digits. The result
52//! is the number of bits to which each of your coordinates will need to be
53//! normalised.
54//!
55//! ### Choose your data-types
56//! This is only important if you _really_ care about efficiency. The default
57//! choices ought to be optimal for most cases, but under special circumstances
58//! they _might_ not be.
59//!
60//! **Example 1:** If you have 5 keys of 12 bits each, each `Coor` will be an
61//! `u16`, and as a result the default `Key` will be an `u128`. But they can
62//! actually fit within an `u64`; the compiler just doesn't know that. In that
63//! case, you might prefer to explicitly cast all keys to `u64`s.
64//!
65//! **Example 2:** If you need 48 bits for your key, and you're running the
66//! code on a 16-bit architecture CPU, you might prefer to implement
67//! custom-made 48 bit data-types (3*16) instead of letting the code use `u64`s
68//! by default. The [`lindel`](lindel) crate permits that.
69//!
70//! ### Create a “key-extraction” function
71//! The key-extraction is comprised of two parts: Normalisation and
72//! linearisation.
73//!
74//! The normalisation is already provided for you, and only
75//! requires you to provide, for each co-ordinate, the min/max values and the
76//! amount of bits to which they will be normalised. However, please bear in
77//! mind that you will need to extract the independent coordinates together
78//! with the dependent ones!
79//!
80//! As for the linearisation, two separate methods are already provided, thanks
81//! to the [`lindel`](lindel) crate. Those are Morton encoding (“z-index”) and Hilbert
82//! encoding (“Hilbert index”). If you care about speed and/or have
83//! at least one independent coordinate, the z-index will serve you better.
84//! If you have no independent coordinates, and you can afford the small
85//! performance hit, a Hilbert index will easily allow you to examine almost
86//! every possible subdivision of your data-set independently. Still, please
87//! note the following:
88//! 1. You will need to do that manually, as the program can't perform this
89//! operation automatically.
90//! 2. You could theoretically use a Hilbert index everywhere, but in
91//! subdividing it you might end up omitting parts of the independent
92//! coordinates whose values are too large.
93//! 3. While a z-index is much quicker to compute than a Hilbert index, for
94//! large data-sets the most expensive operation is the sorting, so perhaps the
95//! difference won't be important for the `O(N)` part of the program.
96//!
97//! ### Choose a sorting algorithm
98//! If you don't have a great concern for speed, you could choose the default
99//! methods defined in `std` or `rayon`. If you need to squeeze every last bit
100//! of performance, on the other hand, you might prefer to implement a radix
101//! sort or something to that extent.
102//!
103//! ### And you're finished!
104//! …finished with the preliminary work, that is. Now all that's left
105//! is to write the actual code: extract the keys, sort, extract the
106//! amounts of common leading bits between each consecutive pair,
107//! get the results from them, drain the useless bits, and use a
108//! regression function to get the result. Here is some example code,
109//! copied from the `img_funs` module:
110//! ```should_not_compile
111//!let width = input.width() as usize;
112//!let height = input.height() as usize;
113//!
114//!let get_key_from_sample = |(x, y, rgb): (u32, u32, &image::Rgb<u8>)| -> u64 {
115//! let norm_x = create_normaliser::<u32, u8>(0, input.width()).unwrap();
116//! // The provided normaliser simplifies things slightly.
117//! let norm_y = create_normaliser::<u32, u8>(0, input.height()).unwrap();
118//! let arr = [norm_x(x), norm_y(y), rgb[0], rgb[1], rgb[2]];
119//! // Extract both independent and dependent coordinates
120//! morton_encoding::morton_encode(arr)
121//! // Linearise using `morton_encoding`
122//!};
123//!
124//!let clzs = get_clzs(input.enumerate_pixels(), get_key_from_sample);
125//!let (tmp, lacun) = get_results_from_clzs(clzs);
126//!let tmp = drain_useless_bits::<_, 64, 40>(tmp);
127//!let lacun = drain_useless_bits::<_, 64, 40>(lacun);
128//!finalise_results(tmp, lacun, width*height, 8)
129//! ```
130//!
131//! Unless of course you need more than 256 bits for each key, in which case
132//! you'll need to change the rest of the functions so that they operate on
133//! `u16`s instead.
134//!
135//! # Interpreting the results
136//! ## Fractal dimension
137//! _The first_ and most important _thing to understand_ with regards to the
138//! fractal dimension is the limits inside which its
139//! log-log plot will fall. Those create a quadrilateral, whose four edges
140//! are defined as follows:
141//! 1. Edge 1 is vertical, for `x` equal to the amount of bits you have
142//! available. You can't subdivide your data-set more then the resolution that
143//! each coordinate has.
144//! 2. Edge 2 is horizontal, for `y` equal to the amount of samples you have
145//! available. Subdivide all you might, you will never get more populated
146//! boxes than the amount of samples (eg pixels) that exist in the data-set.
147//! 3. Edge 3 is diagonal, and has a slope equal to the amount of independent
148//! coordinates, if any.
149//! 4. Edge 4 is diagonal as well; its slope is equal to the total amount
150//! of coordinates available, independent as well as dependent. A data-set
151//! constrained within `N` dimensions can never have a fractal dimension
152//! above `N`.
153//!
154//! The most immediate result of these four constraints is that, especially if
155//! the log-log plot rises abruptly at first, it will encounter a plateau as
156//! soon as it reaches the total amount of samples, past which it will be
157//! horizontal. As such, the horizontal part of the plot _must_ be excised
158//! before calculating the fractal dimension, else it will be artificially low.
159//!
160//! _The second thing to understand_ is that, although an ideally fractal
161//! shape's log-log plot will be linear, in practice data-sets will be
162//! scale-dependent, leading to non-linearities in the log-log plot. In every
163//! such instance we've found, the log-log plot is concave downward. Therefore,
164//! the user has two choices:
165//! 1. To take the simple linear regression of the log-log plot, and interpret
166//! its slope as the fractal dimension. The scale-dependence, if any, may be
167//! found from the mean square error between the line and the actual plot.
168//! 2. To take a parabolic regression of the log-log plot, interpret the linear
169//! parametre as the fractal dimension, and the second-order parametre as the
170//! scale-dependence.
171//!
172//! Neither has been tried with any real rigour; the user is cordially invited
173//! to share the results, if any.
174//!
175//! ## Lacunarity
176//! The way the lacunarity was defined in the available literature, it appears
177//! to be a measure that's different for each scale. It's measured here for the
178//! user's convenience, but the interpretation of the results must necessarily
179//! be left up to the user.
180//!
181//!
182
183#![cfg_attr(not(feature = "std"), no_std)]
184
185use core::ops::{Add, BitXor, Div, Sub};
186use num_traits::{CheckedShl, CheckedSub, PrimInt, Zero};
187use arrayvec::ArrayVec;
188
189
190pub mod img_funs;
191//pub mod snd_funs;
192//pub mod eeg_19c_funs;
193
194pub use img_funs::*;
195//pub use snd_funs::*;
196//pub use eeg_19c_funs::*;
197
198#[cfg(feature = "time_things")]
199macro_rules! time {
200 ($x: expr) => {{
201 eprintln!("Measuring expression: {}", stringify!($x));
202 let begin = std::time::Instant::now();
203 let result = $x;
204 eprintln!("Time elapsed: {:#?}\n", begin.elapsed());
205 result
206 }};
207}
208
209#[cfg(not(feature = "time_things"))]
210macro_rules! time {
211 ($x: expr) => {{
212 $x
213 }};
214}
215
216/// A convenience function that takes an arbitrary value, along with its minimum and maximum value, and normalises it to the limits of the coordinate data-type it's given.
217/// ```rust
218/// # use fractal_analysis::normalise;
219/// let x = 1024u32;
220/// let norm_x: u8 = normalise(x, 0, 65536);
221/// assert_eq!(norm_x, 4);
222/// ```
223/// Please bear in mind:
224/// 1. This function assumes that the minimum will be inclusive and the maximum exclusive.
225/// 2. This function will silently return a zero if `maximum * (Coordinate::MAX + 1)` overflows, or if the value provided is outside `minimum..maximum`.
226pub fn normalise<Input, Coordinate>(x: Input, minimum: Input, maximum: Input) -> Coordinate
227where
228 Input: Add<Output = Input>
229 + Sub<Output = Input>
230 + Div<Output = Input>
231 + core::convert::TryInto<Coordinate>
232 + CheckedShl
233 + CheckedSub
234 + Copy,
235 Coordinate: Zero,
236{
237 let spread: Input = maximum - minimum;
238 let coor_bits = core::mem::size_of::<Coordinate>() * 8;
239 x.checked_sub(&minimum)
240 .and_then(|x| x.checked_shl(coor_bits as u32))
241 .map(|x| x / spread)
242 .and_then(|x| x.try_into().ok())
243 .unwrap_or(Coordinate::zero())
244}
245
246/// A convenience function that takes a minimum and maximum value, inclusive and exclusive respectively, and returns a function to normalise values to the limits of the coordinate data-type it's given.
247/// ```rust
248/// # use fractal_analysis::create_normaliser;
249/// let norm_fn = create_normaliser::<u32, u8>(0u32, 8192).unwrap();
250/// assert_eq!(norm_fn(31), 0);
251/// assert_eq!(norm_fn(32), 1);
252/// assert_eq!(norm_fn(250), 7);
253/// ```
254/// Please note:
255/// 1. This function returns `None` if `maximum * (Coordinate::MAX + 1)` overflows.
256/// 2. The output function will return a zero if given a value that's outside bounds.
257pub fn create_normaliser<Input, Coordinate>(
258 minimum: Input,
259 maximum: Input,
260) -> Option<impl Fn(Input) -> Coordinate>
261where
262 Input: Add<Output = Input>
263 + Sub<Output = Input>
264 + Div<Output = Input>
265 + core::convert::TryInto<Coordinate>
266 + CheckedShl
267 + CheckedSub
268 + Copy,
269 Coordinate: Zero,
270{
271 let coor_bits = core::mem::size_of::<Coordinate>() * 8;
272 let _ = maximum.checked_shl(coor_bits as u32)?;
273 Some(move |x| normalise(x, minimum, maximum))
274}
275
276pub fn map_sampler<'a, H: 'a, Smp, Key: 'a, Set>(
277 set: Set,
278 get_key_from_sample: H,
279) -> impl Iterator<Item = Key> + 'a
280where
281 H: Fn(Smp) -> Key,
282 Key: PrimInt,
283 Set: IntoIterator<Item = Smp> + 'a,
284{
285 set.into_iter().map(get_key_from_sample)
286}
287
288#[cfg(feature = "std")]
289pub fn iterate_sorted_pairs<Key: Ord + Copy>(
290 input: impl Iterator<Item = Key>,
291) -> impl Iterator<Item = (Key, Key)> {
292 let mut imp = input.collect::<Vec<_>>();
293 imp.sort_unstable();
294 (0..imp.len().saturating_sub(1)).map(move |i| (imp[i], imp[i + 1]))
295}
296
297#[cfg(not(feature = "std"))]
298pub fn iterate_sorted_pairs<'a, Key: Ord + Copy>(
299 input: impl Iterator<Item = Key>,
300 buffer: &'a mut [Key],
301) -> impl Iterator<Item = (Key, Key)> + 'a
302{
303 buffer.iter_mut()
304 .zip(input)
305 .for_each(|(a, b)| *a = b);
306 buffer.sort_unstable();
307 (0..buffer.len().saturating_sub(1)).map(move |i| (buffer[i], buffer[i + 1]))
308}
309
310// TODO: Do this as a method to allow chaining
311
312#[cfg(feature = "std")]
313pub fn get_clzs<'a, H: 'a, Smp: 'a, Key: 'a, Set: 'a>(
314 set: Set,
315 get_key_from_sample: H,
316) -> impl Iterator<Item = u8> + 'a
317where
318 H: Fn(Smp) -> Key,
319 Key: PrimInt + BitXor<Output = Key>,
320 Set: IntoIterator<Item = Smp>,
321{
322 let keys = time!(map_sampler(set, get_key_from_sample));
323 let sorted_pairs_of_keys = time!(iterate_sorted_pairs(keys));
324 time!(sorted_pairs_of_keys
325 .map(|(a, b)| a ^ b)
326 .map(|x| x.leading_zeros() as u8))
327}
328
329#[cfg(not(feature = "std"))]
330pub fn get_clzs<'a, H: 'a, Smp: 'a, Key: 'a, Set: 'a>(
331 set: Set,
332 get_key_from_sample: H,
333 buffer: &'a mut [Key]
334) -> impl Iterator<Item = u8> + 'a
335where
336 H: Fn(Smp) -> Key,
337 Key: PrimInt + BitXor<Output = Key>,
338 Set: IntoIterator<Item = Smp>,
339{
340 let keys = time!(map_sampler(set, get_key_from_sample));
341 let sorted_pairs_of_keys = time!(iterate_sorted_pairs(keys, buffer));
342 time!(sorted_pairs_of_keys
343 .map(|(a, b)| a ^ b)
344 .map(|x| x.leading_zeros() as u8))
345}
346
347pub fn get_results_from_clzs<const KEY_BIT_AMT: usize>(
348 input: impl Iterator<Item = u8>,
349) -> (ArrayVec<u32, KEY_BIT_AMT>, ArrayVec<u64, KEY_BIT_AMT>) {
350 let mut s = ArrayVec::from([0u32; KEY_BIT_AMT]);
351 let mut prevs = ArrayVec::from([0usize; KEY_BIT_AMT]);
352 let mut squares = ArrayVec::from([0u64; KEY_BIT_AMT]);
353 for (i, x) in input.into_iter().chain(Some(0).into_iter()).enumerate() {
354 for b_i in (x as usize)..(KEY_BIT_AMT) {
355 s[b_i] += 1;
356 squares[b_i] += (i - prevs[b_i]) as u64 * (i - prevs[b_i]) as u64;
357 prevs[b_i] = i;
358 }
359 }
360 (s, squares)
361}
362
363pub fn get_results_from_clzs_functional<const KEY_BIT_AMT: usize>(
364 input: impl Iterator<Item = u8> + Clone,
365) -> (ArrayVec<u32, KEY_BIT_AMT>, ArrayVec<usize, KEY_BIT_AMT>)
366{
367 let mut s = ArrayVec::from([0u32; KEY_BIT_AMT]);
368 let mut squares = ArrayVec::from([0usize; KEY_BIT_AMT]);
369 let smaller_clz_positions = |x| input.clone().enumerate().filter(move |(_, a)| *a <= x).map(|x| x.0);
370
371 s.iter_mut().enumerate().for_each(|(i, x)| {
372 *x = (smaller_clz_positions(i as u8)).count() as u32
373 });
374
375 squares.iter_mut().enumerate().for_each(|(i, a)| {
376 let poss_1 = smaller_clz_positions(i as u8);
377 let poss_2 = smaller_clz_positions(i as u8).chain(Some(0).into_iter()).skip(1);
378 *a = poss_2.zip(poss_1).map(|x| x.0 - x.1).map(|x| x*x).sum();
379 });
380
381 (s, squares)
382}
383
384//const fn get_results_from_clzs = get_results_from_clzs_imperative;
385
386pub fn get_inclination(input: &[f64]) -> f64 {
387 let length = input.iter().count() as f64;
388 let avy: f64 = input.iter().sum::<f64>() / length;
389 let avx: f64 = length * (length - 1.0) / (2.0 * length);
390 let num_inc = |(i, x): (usize, f64)| -> f64 { (x - avy) * (i as f64 - avx) };
391 let denom_inc = |(i, _): (usize, f64)| -> f64 { (i as f64 - avx) * (i as f64 - avx) };
392 let num: f64 = input
393 .iter()
394 .enumerate()
395 .map(|(a, b): (usize, &f64)| num_inc((a, *b)))
396 .sum();
397 let denom: f64 = input
398 .iter()
399 .enumerate()
400 .map(|(a, b): (usize, &f64)| denom_inc((a, *b)))
401 .sum();
402 num / denom
403}
404
405pub fn finalise_results<const KEY_BIT_AMT: usize>(
406 s: ArrayVec<u32, KEY_BIT_AMT>,
407 squares: ArrayVec<u64, KEY_BIT_AMT>,
408 sample_size: usize,
409 coor_bit_amt: u8,
410) -> (f64, ArrayVec<f64, KEY_BIT_AMT>, ArrayVec<f64, KEY_BIT_AMT>) {
411 let step = KEY_BIT_AMT / (coor_bit_amt as usize);
412 let result_2 = s
413 .iter()
414 .skip(step - 1)
415 .step_by(step)
416 .map(|&x| f64::from(x).log2())
417 .collect::<ArrayVec<_, KEY_BIT_AMT>>();
418 let result_3 = squares
419 .iter()
420 .zip(s.iter())
421 .skip(step - 1)
422 .step_by(step)
423 .map(|(&a, &b)| (a as f64) * (b as f64) / (sample_size as f64 * sample_size as f64) - 1.0)
424 .collect::<ArrayVec<_, KEY_BIT_AMT>>();
425 let cap = (sample_size as f64).log2();
426 let result_1_lim = result_2
427 .iter()
428 .position(|x| *x > (0.9) * cap)
429 .unwrap_or(coor_bit_amt as usize);
430 let result_1 = get_inclination(&result_2[0..result_1_lim]);
431 (result_1, result_2, result_3)
432}
433
434#[cfg(feature = "std")]
435pub fn zbox_merge<H, Smp, Key, Set, const KEY_BIT_AMT: usize>(set: Set, get_key_from_sample: H, sample_size: usize, coor_bits: u8) -> (f64, ArrayVec<f64, KEY_BIT_AMT>, ArrayVec<f64, KEY_BIT_AMT>)
436where
437 H: Fn(Smp) -> Key,
438 Key: PrimInt,
439 Set: IntoIterator<Item = Smp>,
440{
441 let clzs = get_clzs(set, get_key_from_sample);
442 let (s, squares) = get_results_from_clzs(clzs);
443 finalise_results(s, squares, sample_size, coor_bits)
444}
445
446pub fn drain_useless_bits<T, const TOTAL_BITS: usize, const USEFUL_BITS: usize>(mut input: ArrayVec<T, TOTAL_BITS>) -> ArrayVec<T, USEFUL_BITS> {
447 let useless_bits = TOTAL_BITS - USEFUL_BITS;
448 input.drain(..useless_bits);
449 input.into_iter().collect()
450}
451
452
453
454
455
456
457#[cfg(feature = "parallel")]
458use rayon::prelude::*;
459
460#[cfg(feature = "parallel")]
461pub fn get_clzs_par<'a, H:'a, Smp:'a, Key:'a, Set:'a>(set: Set, get_key_from_sample: H) -> impl rayon::iter::ParallelIterator<Item = u8>
462where
463 H: Fn(Smp) -> Key + std::marker::Sync + std::marker::Send + Copy,
464 Key: num_traits::int::PrimInt + std::marker::Sync + std::marker::Send,
465 [Key] : rayon::prelude::ParallelSliceMut<Key>,
466 Set : rayon::iter::IntoParallelIterator<Item = Smp>,
467 Smp: std::marker::Sync + std::marker::Send
468{
469 let mut buffer = time! (set
470 .into_par_iter()
471 .map(get_key_from_sample)
472 .collect::<Vec<_>>());
473
474 time!(
475 buffer.par_sort_unstable()
476 );
477
478 time!(
479 (0..buffer.len().saturating_sub(1))
480 .into_par_iter()
481 .map(move |i| (buffer[i], buffer[i + 1]))
482 .map(|x| x.0 ^ x.1)
483 .map(|x| x.leading_zeros() as u8)
484 )
485}
486
487#[cfg(feature = "parallel")]
488pub fn get_results_from_clzs_parallel<const KEY_BIT_AMT: usize>(
489 input: impl Iterator<Item = u8> + Clone + Sync,
490) -> ([u32; KEY_BIT_AMT], [usize; KEY_BIT_AMT])
491{
492 let mut s = [0u32; KEY_BIT_AMT];
493 let mut squares = [0usize; KEY_BIT_AMT];
494 let smaller_clz_positions = |x| input.clone().enumerate().filter(move |(_, a)| *a <= x).map(|x| x.0);
495
496 s.par_iter_mut().enumerate().for_each(|(i, x)| {
497 *x = (smaller_clz_positions(i as u8)).count() as u32
498 });
499
500 squares.par_iter_mut().enumerate().for_each(|(i, a)| {
501 let poss_1 = smaller_clz_positions(i as u8);
502 let poss_2 = smaller_clz_positions(i as u8).chain(Some(0).into_iter()).skip(1);
503 *a = poss_2.zip(poss_1).map(|x| x.0 - x.1).map(|x| x*x).sum();
504 });
505
506 (s, squares)
507}
508
509#[cfg(feature = "parallel")]
510pub fn zbox_merge_par<H, Smp, Key, Set, const KEY_BIT_AMT: usize>(set: Set, get_key_from_sample: H, sample_size: usize, coor_bits: u8) -> (f64, ArrayVec<f64, KEY_BIT_AMT>, ArrayVec<f64, KEY_BIT_AMT>)
511where
512 H: Fn(Smp) -> Key + std::marker::Sync + std::marker::Send + Copy,
513 Key: num_traits::int::PrimInt + std::marker::Sync + std::marker::Send,
514 [Key] : rayon::prelude::ParallelSliceMut<Key>,
515 Set : rayon::iter::IntoParallelIterator<Item = Smp>,
516 Smp: std::marker::Sync + std::marker::Send
517{
518 let fnoo = get_results_from_clzs;
519 let clzs = get_clzs_par(set, get_key_from_sample).collect::<Vec<_>>();
520 let (s, squares) = fnoo(clzs.into_iter());
521 finalise_results(s, squares, sample_size, coor_bits)
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
529 fn wtf() {
530 assert!(true);
531 }
532
533 #[test]
534 fn img_sorta() {
535 use itertools::Itertools;
536 use noise::utils::*;
537 use noise::Fbm;
538 use noise::MultiFractal;
539
540 let mut dur = std::time::Duration::from_millis(0);
541 for x in 0..10 {
542 let clamp = |x, lower, upper| {
543 if x < lower {
544 lower
545 } else if x > upper {
546 upper
547 } else {
548 x
549 }
550 };
551 let size_x = 1024;
552 let size_y = size_x;
553 let fbm = Fbm::new().set_octaves(32).set_persistence(x as f64 / 10.);
554 let pmb = PlaneMapBuilder::new(&fbm)
555 .set_size(size_x as usize, size_y as usize)
556 .build();
557 let pix_span = (0..size_x as usize).cartesian_product(0..size_y as usize);
558 let mut result = image::GrayImage::new(size_x as u32, size_y as u32);
559 pix_span
560 .clone()
561 .map(|(xx, yy)| pmb.get_value(xx, yy))
562 .map(|val| (val + 1.0) * 128.0)
563 .map(|val| (clamp(val, 0.0, 255.99)) as u8)
564 .zip(pix_span)
565 .for_each(|(val, (xx, yy))| {
566 result.put_pixel(xx as u32, yy as u32, image::Luma([val]))
567 });
568 drop((pmb, fbm));
569
570 let begin = std::time::Instant::now();
571 let (hrm, _, _) = measure_lum_2d_u8_image(result);
572 dur += begin.elapsed();
573 dbg!(hrm);
574 }
575 println!("Time elapsed: {:?}", dur);
576
577 use noise::Seedable;
578 let mut dur = std::time::Duration::from_millis(0);
579 for x in 0..10 {
580 let clamp = |x, lower, upper| {
581 if x < lower {
582 lower
583 } else if x > upper {
584 upper
585 } else {
586 x
587 }
588 };
589 let size_x = 1 << 10;
590 let size_y = size_x;
591 let fbm_1 = Fbm::new()
592 .set_octaves(32)
593 .set_persistence(x as f64 / 10.)
594 .set_seed(0);
595 let fbm_2 = Fbm::new()
596 .set_octaves(32)
597 .set_persistence(x as f64 / 10.)
598 .set_seed(1);
599 let fbm_3 = Fbm::new()
600 .set_octaves(32)
601 .set_persistence(x as f64 / 10.)
602 .set_seed(2);
603 let pmb_1 = PlaneMapBuilder::new(&fbm_1)
604 .set_size(size_x as usize, size_y as usize)
605 .build();
606 let pmb_2 = PlaneMapBuilder::new(&fbm_2)
607 .set_size(size_x as usize, size_y as usize)
608 .build();
609 let pmb_3 = PlaneMapBuilder::new(&fbm_3)
610 .set_size(size_x as usize, size_y as usize)
611 .build();
612 let pix_span = (0..size_x as usize).cartesian_product(0..size_y as usize);
613 let mut result = image::RgbImage::new(size_x as u32, size_y as u32);
614 let map_fn = |val: f64| (val + 1.0) * 128.0;
615 let map_fn = |val: f64| clamp(map_fn(val), 0.0, 255.99) as u8;
616 let map_fn = |x: [f64; 3]| [map_fn(x[0]), map_fn(x[1]), map_fn(x[2])];
617 pix_span
618 .clone()
619 .map(|(xx, yy)| {
620 [
621 pmb_1.get_value(xx, yy),
622 pmb_2.get_value(xx, yy),
623 pmb_3.get_value(xx, yy),
624 ]
625 })
626 .map(map_fn)
627 .zip(pix_span)
628 .for_each(|(val, (xx, yy))| {
629 result.put_pixel(xx as u32, yy as u32, image::Rgb(val))
630 });
631 drop((pmb_1, fbm_1, pmb_2, fbm_2, pmb_3, fbm_3));
632
633 let begin = std::time::Instant::now();
634 let (hrm, _, _) = measure_rgb_2d_u8_image(result);
635 dur += begin.elapsed();
636 dbg!(hrm);
637 }
638 println!("Time elapsed: {:?}", dur);
639 }
640}