From 266c939ad236c16503b088d2faa4500362912fef Mon Sep 17 00:00:00 2001 From: John Lindsay Date: Wed, 6 Feb 2019 18:13:58 -0500 Subject: [PATCH] Numerous bug fixes and documentation improvements Numerous bug fixes and documentation improvements --- .../extract_raster_values_at_points.rs | 174 +++------ src/tools/gis_analysis/highest_pos.rs | 16 +- src/tools/gis_analysis/lowest_pos.rs | 16 +- src/tools/gis_analysis/max_abs_overlay.rs | 12 +- src/tools/gis_analysis/max_overlay.rs | 11 +- src/tools/gis_analysis/min_abs_overlay.rs | 12 +- src/tools/gis_analysis/min_overlay.rs | 11 +- src/tools/gis_analysis/percent_equal_to.rs | 18 +- .../gis_analysis/percent_greater_than.rs | 18 +- src/tools/gis_analysis/percent_less_than.rs | 18 +- src/tools/gis_analysis/sum_overlay.rs | 6 +- .../balance_contrast_enhancement.rs | 2 +- .../image_analysis/change_vector_analysis.rs | 8 +- .../image_analysis/create_colour_composite.rs | 2 +- .../direct_decorrelation_stretch.rs | 2 +- .../edge_preserving_mean_filter.rs | 19 +- src/tools/image_analysis/gamma_correction.rs | 45 +-- .../image_analysis/histogram_equalization.rs | 2 +- .../image_analysis/histogram_matching.rs | 2 +- .../histogram_matching_two_images.rs | 2 +- src/tools/image_analysis/ihs_to_rgb.rs | 327 ++++++++++++----- .../image_analysis/image_stack_profile.rs | 16 +- src/tools/image_analysis/integral_image.rs | 13 +- src/tools/image_analysis/line_thin.rs | 2 +- src/tools/image_analysis/mean_filter.rs | 63 ++-- .../min_max_contrast_stretch.rs | 2 +- .../modified_k_means_clustering.rs | 4 +- src/tools/image_analysis/mosaic.rs | 4 +- .../percentage_contrast_stretch.rs | 4 +- src/tools/image_analysis/resample.rs | 2 +- src/tools/image_analysis/rgb_to_ihs.rs | 339 ++++++++++++------ .../sigmoidal_contrast_stretch.rs | 24 +- src/tools/image_analysis/sobel_filter.rs | 4 +- .../image_analysis/split_colour_composite.rs | 36 +- .../image_analysis/stdev_contrast_stretch.rs | 18 +- src/tools/image_analysis/thicken_line.rs | 18 +- .../write_func_memory_insertion.rs | 23 +- .../lidar_analysis/clip_lidar_to_polygon.rs | 11 + .../erase_polygon_from_lidar.rs | 11 + src/tools/lidar_analysis/lidar_join.rs | 8 +- src/tools/lidar_analysis/lidar_tile.rs | 30 +- .../lidar_analysis/lidar_tile_footprint.rs | 13 +- .../principal_component_analysis.rs | 82 ++++- .../stream_network_analysis/horton_order.rs | 2 +- .../stream_network_analysis/long_profile.rs | 26 +- .../long_profile_from_points.rs | 2 +- .../raster_streams_to_vector.rs | 3 + .../rasterize_streams.rs | 2 +- .../circular_variance_of_aspect.rs | 12 +- .../drainage_preserving_smoothing.rs | 51 +-- src/tools/terrain_analysis/elev_percentile.rs | 2 +- .../feature_preserving_denoise.rs | 55 +-- .../terrain_analysis/percent_elev_range.rs | 4 +- src/tools/terrain_analysis/profile.rs | 13 +- src/tools/terrain_analysis/relative_aspect.rs | 17 + .../relative_topographic_position.rs | 31 +- whitebox_plugin_generator.py | 3 +- whitebox_tools.py | 70 ++-- 58 files changed, 1190 insertions(+), 553 deletions(-) diff --git a/src/tools/gis_analysis/extract_raster_values_at_points.rs b/src/tools/gis_analysis/extract_raster_values_at_points.rs index b416f3fa5..8b5be9256 100644 --- a/src/tools/gis_analysis/extract_raster_values_at_points.rs +++ b/src/tools/gis_analysis/extract_raster_values_at_points.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 17/06/2018 -Last Modified: 17/06/2018 +Last Modified: 30/01/2019 License: MIT */ @@ -14,7 +14,17 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; -/// Extracts the values of raster(s) at vector point locations. +/// This tool can be used to extract the values of one or more rasters (`--inputs`) at the sites of a set of vector points. +/// By default, the data is output to the attribute table of the input points (`--points`) vector; however, +/// if the `--out_text` parameter is specified, the tool will additionally output point values as text data +/// to standard output (*stdout*). Attribute fields will be added to the table of the points file, with field +/// names, *VALUE1*, *VALUE2*, *VALUE3*, etc. each corresponding to the order of input rasters. +/// +/// If you need to plot a chart of values from a raster stack at a set of points, the `ImageStackProfile` may be +/// more suitable for this application. +/// +/// # See Also +/// `ImageStackProfile`, `FindLowestOrHighestPoints` pub struct ExtractRasterValuesAtPoints { name: String, description: String, @@ -51,6 +61,17 @@ impl ExtractRasterValuesAtPoints { optional: false, }); + parameters.push(ToolParameter { + name: "Output text?".to_owned(), + flags: vec!["--out_text".to_owned()], + description: + "Output point values as text? Otherwise, the only output is to to the points file's attribute table." + .to_owned(), + parameter_type: ParameterType::Boolean, + default_value: Some("false".to_string()), + optional: true, + }); + let sep: String = path::MAIN_SEPARATOR.to_string(); let p = format!("{}", env::current_dir().unwrap().display()); let e = format!("{}", env::current_exe().unwrap().display()); @@ -117,6 +138,7 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { ) -> Result<(), Error> { let mut input_files = String::new(); let mut points_file = String::new(); + let mut output_text = false; if args.len() == 0 { return Err(Error::new( @@ -134,24 +156,23 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { keyval = true; } let flag_val = vec[0].to_lowercase().replace("--", "-"); - if vec[0].to_lowercase() == "-i" || vec[0].to_lowercase() == "--inputs" { - if keyval { - input_files = vec[1].to_string(); + if flag_val == "-i" || flag_val == "-inputs" { + input_files = if keyval { + vec[1].to_string() } else { - input_files = args[i + 1].to_string(); - } + args[i + 1].to_string() + }; } else if flag_val == "-points" { points_file = if keyval { vec[1].to_string() } else { args[i + 1].to_string() }; + } else if flag_val.contains("-out_text") { + output_text = true; } } - // let mut progress: usize; - // let mut old_progress: usize = 1; - if verbose { println!("***************{}", "*".repeat(self.get_tool_name().len())); println!("* Welcome to {} *", self.get_tool_name()); @@ -163,12 +184,12 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { let start = Instant::now(); let mut cmd = input_files.split(";"); - let mut vec = cmd.collect::>(); - if vec.len() == 1 { + let mut v = cmd.collect::>(); + if v.len() == 1 { cmd = input_files.split(","); - vec = cmd.collect::>(); + v = cmd.collect::>(); } - let num_files = vec.len(); + let num_files = v.len(); if num_files < 1 { return Err(Error::new(ErrorKind::InvalidInput, "There is something incorrect about the input files. At least one input is required to operate this tool.")); @@ -187,8 +208,9 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { } let (mut row, mut col): (isize, isize); - let mut x_vals = vec![]; - let mut y_vals = vec![]; + let mut x_vals = Vec::with_capacity(num_records); + let mut y_vals = Vec::with_capacity(num_records); + let mut raster_values = vec![vec![0f64; num_files]; num_records]; for record_num in 0..num_records { let record = points.get_record(record_num); y_vals.push(record.points[0].y); @@ -196,8 +218,8 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { } // add the attributes for each raster - for i in 0..vec.len() { - if !vec[i].trim().is_empty() { + for i in 0..num_files { + if !v[i].trim().is_empty() { let val = AttributeField::new(&format!("VALUE{}", i + 1), FieldDataType::Real, 12u8, 4u8); points.attributes.add_field(&val); @@ -206,7 +228,7 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { let mut z: f64; let mut i = 1; - for value in vec { + for value in v { if !value.trim().is_empty() { if verbose { println!("Reading data...") @@ -227,114 +249,22 @@ impl WhiteboxTool for ExtractRasterValuesAtPoints { &format!("VALUE{}", i), FieldData::Real(z), ); + + if output_text { + raster_values[record_num][i - 1] = z; + } } i += 1; } } - // drop(attributes); - - // let start = time::now(); - // let rows = input.configs.rows as isize; - // let columns = input.configs.columns as isize; - // let nodata = input.configs.nodata; - - // // loop through the raster, locating the min/max - // let rows_completed = Arc::new(Mutex::new(0..rows)); - // let old_progress = Arc::new(Mutex::new(1)); - // let num_procs = num_cpus::get() as isize; - // let (tx, rx) = mpsc::channel(); - // for tid in 0..num_procs { - // let input = input.clone(); - // let rows_completed = rows_completed.clone(); - // let old_progress = old_progress.clone(); - // let tx = tx.clone(); - // thread::spawn(move || { - // let mut low_z = f64::INFINITY; - // let mut low_row = 0isize; - // let mut low_col = 0isize; - // let mut high_z = f64::NEG_INFINITY; - // let mut high_row = 0isize; - // let mut high_col = 0isize; - // let mut progress: usize; - // // let mut old_progress: usize = 1; - // for row in (0..rows).filter(|r| r % num_procs == tid) { - // for col in 0..columns { - // z = input.get_value(row, col); - // if z != nodata { - // if z < low_z { - // low_z = z; - // low_col = col; - // low_row = row; - // } - // if z > high_z { - // high_z = z; - // high_col = col; - // high_row = row; - // } - // } - // } - // let r = match rows_completed.lock().unwrap().next() { - // Some(val) => val, - // None => 0, // There are no more tiles to interpolate - // }; - // if verbose { - // progress = (100.0_f64 * r as f64 / (rows - 1) as f64) as usize; - // let mut p = old_progress.lock().unwrap(); - // if progress != *p { - // println!("Progress: {}%", progress); - // *p = progress; - // } - // } - // } - // tx.send((low_z, low_col, low_row, high_z, high_col, high_row)) - // .unwrap(); - // }); - // } - - // let mut low_z = f64::INFINITY; - // let mut low_row = 0isize; - // let mut low_col = 0isize; - // let mut high_z = f64::NEG_INFINITY; - // let mut high_row = 0isize; - // let mut high_col = 0isize; - // for _ in 0..num_procs { - // let data = rx.recv().unwrap(); - // if data.0 < low_z { - // low_z = data.0; - // low_col = data.1; - // low_row = data.2; - // } - // if data.3 > high_z { - // high_z = data.3; - // high_col = data.4; - // high_row = data.5; - // } - // } - - // // add the vector record(s) - // let mut rec_num = 1i32; - // if out_type == "lowest" || out_type == "both" { - // output.add_point_record( - // input.get_x_from_column(low_col), - // input.get_y_from_row(low_row), - // ); - // output - // .attributes - // .add_record(vec![FieldData::Int(rec_num), FieldData::Real(low_z)], false); - // rec_num += 1i32; - // } - - // if out_type == "highest" || out_type == "both" { - // output.add_point_record( - // input.get_x_from_column(high_col), - // input.get_y_from_row(high_row), - // ); - // output.attributes.add_record( - // vec![FieldData::Int(rec_num), FieldData::Real(high_z)], - // false, - // ); - // } + + if output_text { + println!("Point values:"); + for record_num in 0..num_records { + println!("Point {} values: {:?}", record_num+1, raster_values[record_num]); + } + } let elapsed_time = get_formatted_elapsed_time(start); diff --git a/src/tools/gis_analysis/highest_pos.rs b/src/tools/gis_analysis/highest_pos.rs index c7fbb7aef..28936ea3a 100644 --- a/src/tools/gis_analysis/highest_pos.rs +++ b/src/tools/gis_analysis/highest_pos.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 +Created: 22/06/2017 Last Modified: 13/10/2018 License: MIT */ @@ -14,6 +14,20 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool identifies the stack position (index) of the maximum value within a raster stack on a cell-by-cell +/// basis. For example, if five raster images (`--inputs`) are input to the tool, the output raster (`--output`) +/// would show which of the five input rasters contained the highest value for each grid cell. The index value in +/// the output raster is the zero-order number of the raster stack, i.e. if the highest value in the stack is +/// contained in the first image, the output value would be 0; if the highest stack value were the second image, +/// the output value would be 1, and so on. If any of the cell values within the stack is NoData, the output raster +/// will contain the NoData value for the corresponding grid cell. The index value is related to the order of the +/// input images. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `LowestPosition`, `PickFromList` pub struct HighestPosition { name: String, description: String, diff --git a/src/tools/gis_analysis/lowest_pos.rs b/src/tools/gis_analysis/lowest_pos.rs index 4b76d3e8e..58b53c9db 100644 --- a/src/tools/gis_analysis/lowest_pos.rs +++ b/src/tools/gis_analysis/lowest_pos.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 4, 2017 +Created: 04/07/2017 Last Modified: 13/10/2018 License: MIT */ @@ -14,6 +14,20 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool identifies the stack position (index) of the minimum value within a raster stack on a cell-by-cell +/// basis. For example, if five raster images (`--inputs`) are input to the tool, the output raster (`--output`) +/// would show which of the five input rasters contained the lowest value for each grid cell. The index value in +/// the output raster is the zero-order number of the raster stack, i.e. if the lowest value in the stack is +/// contained in the first image, the output value would be 0; if the lowest stack value were the second image, +/// the output value would be 1, and so on. If any of the cell values within the stack is NoData, the output raster +/// will contain the NoData value for the corresponding grid cell. The index value is related to the order of the +/// input images. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `HighestPosition`, `PickFromList` pub struct LowestPosition { name: String, description: String, diff --git a/src/tools/gis_analysis/max_abs_overlay.rs b/src/tools/gis_analysis/max_abs_overlay.rs index 28ab6ce2a..31edd0410 100644 --- a/src/tools/gis_analysis/max_abs_overlay.rs +++ b/src/tools/gis_analysis/max_abs_overlay.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 +Created: 22/06/2017 Last Modified: 13/10/2018 License: MIT */ @@ -13,6 +13,16 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to find the maximum absolute (non-negative) value in each cell of a grid from a set of +/// input images (`--inputs`). NoData values in any of the input images will result in a NoData pixel in the output +/// image. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows +/// and columns. +/// +/// # See Also +/// `MaxOverlay`, `MinAbsoluteOverlay`, `MinOverlay` pub struct MaxAbsoluteOverlay { name: String, description: String, diff --git a/src/tools/gis_analysis/max_overlay.rs b/src/tools/gis_analysis/max_overlay.rs index 1cc113589..e46848751 100644 --- a/src/tools/gis_analysis/max_overlay.rs +++ b/src/tools/gis_analysis/max_overlay.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 +Created: 22/06/2017 Last Modified: 13/10/2018 License: MIT */ @@ -13,6 +13,15 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to find the maximum value in each cell of a grid from a set of input images (`--inputs`). +/// NoData values in any of the input images will result in a NoData pixel in the output image (`--output`). It is +/// similar to the `Max` mathematical tool, except that it will accept more than two input images. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `MinOverlay`, `MaxAbsoluteOverlay`, `MinAbsoluteOverlay`, `Max` pub struct MaxOverlay { name: String, description: String, diff --git a/src/tools/gis_analysis/min_abs_overlay.rs b/src/tools/gis_analysis/min_abs_overlay.rs index d76c422ed..874bcc97c 100644 --- a/src/tools/gis_analysis/min_abs_overlay.rs +++ b/src/tools/gis_analysis/min_abs_overlay.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 +Created: 22/06/2017 Last Modified: 13/10/2018 License: MIT */ @@ -13,6 +13,16 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to find the minimum absolute (non-negative) value in each cell of a grid from a set of +/// input images (`--inputs`). NoData values in any of the input images will result in a NoData pixel in the output +/// image. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows +/// and columns. +/// +/// # See Also +/// `MinOverlay`, `MaxAbsoluteOverlay`, `MaxOverlay` pub struct MinAbsoluteOverlay { name: String, description: String, diff --git a/src/tools/gis_analysis/min_overlay.rs b/src/tools/gis_analysis/min_overlay.rs index b38e556f2..7030ab90a 100644 --- a/src/tools/gis_analysis/min_overlay.rs +++ b/src/tools/gis_analysis/min_overlay.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 +Created: 22/06/2017 Last Modified: 13/10/2018 License: MIT */ @@ -13,6 +13,15 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to find the minimum value in each cell of a grid from a set of input images (`--inputs`). +/// NoData values in any of the input images will result in a NoData pixel in the output image (`--output`). It is +/// similar to the `Min` mathematical tool, except that it will accept more than two input images. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `MaxOverlay`, `MaxAbsoluteOverlay`, `MinAbsoluteOverlay`, `Min` pub struct MinOverlay { name: String, description: String, diff --git a/src/tools/gis_analysis/percent_equal_to.rs b/src/tools/gis_analysis/percent_equal_to.rs index b46ec4a2b..5b2a48389 100644 --- a/src/tools/gis_analysis/percent_equal_to.rs +++ b/src/tools/gis_analysis/percent_equal_to.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 -Last Modified: 13/10/2018 +Created: 22/06/2017 +Last Modified: 31/01/2019 License: MIT */ @@ -14,6 +14,19 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool calculates the percentage of a raster stack (`--inputs`) that have cell values equal to an input *comparison* +/// raster. The user must specify the name of the value raster (`--comparison`), the names of the raster files contained +/// in the stack, and an output raster file name (`--output`). The tool, working on a cell-by-cell basis, will count the +/// number of rasters within the stack that have the same grid cell value as the corresponding grid cell in the *comparison* +/// raster. This count is then expressed as a percentage of the number of rasters contained within the stack and output. +/// If any of the rasters within the stack contain the NoData value, the corresponding grid cell in the output raster will +/// be assigned NoData. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `PercentGreaterThan`, `PercentLessThan` pub struct PercentEqualTo { name: String, description: String, @@ -194,6 +207,7 @@ impl WhiteboxTool for PercentEqualTo { let nodata = comparison.configs.nodata; let mut output = Raster::initialize_using_file(&output_file, &comparison); + output.configs.data_type = DataType::F32; let mut n_images: Array2D = Array2D::new(rows, columns, 0, -1)?; let mut in_nodata: f64; diff --git a/src/tools/gis_analysis/percent_greater_than.rs b/src/tools/gis_analysis/percent_greater_than.rs index 2cd5ebb5f..cb5234bfc 100644 --- a/src/tools/gis_analysis/percent_greater_than.rs +++ b/src/tools/gis_analysis/percent_greater_than.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 -Last Modified: 13/10/2018 +Created: 22/06/2017 +Last Modified: 31/01/2019 License: MIT */ @@ -14,6 +14,19 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool calculates the percentage of a raster stack (`--inputs`) that have cell values greater than an input *comparison* +/// raster. The user must specify the name of the value raster (`--comparison`), the names of the raster files contained +/// in the stack, and an output raster file name (`--output`). The tool, working on a cell-by-cell basis, will count the +/// number of rasters within the stack with larger grid cell values greater than the corresponding grid cell in the *comparison* +/// raster. This count is then expressed as a percentage of the number of rasters contained within the stack and output. +/// If any of the rasters within the stack contain the NoData value, the corresponding grid cell in the output raster will +/// be assigned NoData. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `PercentLessThan`, `PercentEqualTo` pub struct PercentGreaterThan { name: String, description: String, @@ -194,6 +207,7 @@ impl WhiteboxTool for PercentGreaterThan { let nodata = comparison.configs.nodata; let mut output = Raster::initialize_using_file(&output_file, &comparison); + output.configs.data_type = DataType::F32; let mut n_images: Array2D = Array2D::new(rows, columns, 0, -1)?; let mut in_nodata: f64; diff --git a/src/tools/gis_analysis/percent_less_than.rs b/src/tools/gis_analysis/percent_less_than.rs index 7603e06dd..e932e8116 100644 --- a/src/tools/gis_analysis/percent_less_than.rs +++ b/src/tools/gis_analysis/percent_less_than.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 22 2017 -Last Modified: 13/10/2018 +Created: 22/06/2017 +Last Modified: 31/01/2019 License: MIT */ @@ -14,6 +14,19 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool calculates the percentage of a raster stack (`--inputs`) that have cell values less than an input *comparison* +/// raster. The user must specify the name of the value raster (`--comparison`), the names of the raster files contained +/// in the stack, and an output raster file name (`--output`). The tool, working on a cell-by-cell basis, will count the +/// number of rasters within the stack with larger grid cell values less than the corresponding grid cell in the *comparison* +/// raster. This count is then expressed as a percentage of the number of rasters contained within the stack and output. +/// If any of the rasters within the stack contain the NoData value, the corresponding grid cell in the output raster will +/// be assigned NoData. +/// +/// # Warning +/// Each of the input rasters must have the same spatial extent and number of rows and columns. +/// +/// # See Also +/// `PercentGreaterThan`, `PercentEqualTo` pub struct PercentLessThan { name: String, description: String, @@ -194,6 +207,7 @@ impl WhiteboxTool for PercentLessThan { let nodata = comparison.configs.nodata; let mut output = Raster::initialize_using_file(&output_file, &comparison); + output.configs.data_type = DataType::F32; let mut n_images: Array2D = Array2D::new(rows, columns, 0, -1)?; let mut in_nodata: f64; diff --git a/src/tools/gis_analysis/sum_overlay.rs b/src/tools/gis_analysis/sum_overlay.rs index a3391bdb0..f77ad4fbc 100644 --- a/src/tools/gis_analysis/sum_overlay.rs +++ b/src/tools/gis_analysis/sum_overlay.rs @@ -13,11 +13,11 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; -/// This tool calculates the sum for each grid cell from a group of raster images. +/// This tool calculates the sum for each grid cell from a group of raster images (`--inputs`). NoData values in any of the input +/// images will result in a NoData pixel in the output image (`--output`). /// /// # Warning -/// Each of the input rasters must have the same spatial extent and number of rows -/// and columns. +/// Each of the input rasters must have the same spatial extent and number of rows and columns. /// /// # See Also /// `WeightedSum` diff --git a/src/tools/image_analysis/balance_contrast_enhancement.rs b/src/tools/image_analysis/balance_contrast_enhancement.rs index bf00a0d01..6d7f3b909 100644 --- a/src/tools/image_analysis/balance_contrast_enhancement.rs +++ b/src/tools/image_analysis/balance_contrast_enhancement.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 19, 2017 +Created: 19/07/2017 Last Modified: 13/10/2018 License: MIT */ diff --git a/src/tools/image_analysis/change_vector_analysis.rs b/src/tools/image_analysis/change_vector_analysis.rs index 46b0f71ba..ad89ce31a 100644 --- a/src/tools/image_analysis/change_vector_analysis.rs +++ b/src/tools/image_analysis/change_vector_analysis.rs @@ -37,7 +37,13 @@ use std::path; /// It is common to apply a simple thresholding operation on the magnitude data to /// determine 'actual' change (i.e. change above some assumed level of error). The type /// of change (qualitatively) is then defined according to the corresponding sector code. -/// Jensen (2005) provides a useful description of this approach to change detection. +/// Jensen (2015) provides a useful description of this approach to change detection. +/// +/// # Reference +/// Jensen, J. R. (2015). Introductory Digital Image Processing: A Remote Sensing Perspective. +/// +/// # See Also +/// `WriteFunctionMemoryInsertion` pub struct ChangeVectorAnalysis { name: String, description: String, diff --git a/src/tools/image_analysis/create_colour_composite.rs b/src/tools/image_analysis/create_colour_composite.rs index 848de2a3f..fe06d8660 100644 --- a/src/tools/image_analysis/create_colour_composite.rs +++ b/src/tools/image_analysis/create_colour_composite.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 19, 2017 +Created: 19/07/2017 Last Modified: 06/01/2019 License: MIT */ diff --git a/src/tools/image_analysis/direct_decorrelation_stretch.rs b/src/tools/image_analysis/direct_decorrelation_stretch.rs index 84d776289..f0bd89dab 100644 --- a/src/tools/image_analysis/direct_decorrelation_stretch.rs +++ b/src/tools/image_analysis/direct_decorrelation_stretch.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 21, 2017 +Created: 21/07/2017 Last Modified: 13/10/2018 License: MIT */ diff --git a/src/tools/image_analysis/edge_preserving_mean_filter.rs b/src/tools/image_analysis/edge_preserving_mean_filter.rs index 7335a5e9b..5f28b2d6e 100644 --- a/src/tools/image_analysis/edge_preserving_mean_filter.rs +++ b/src/tools/image_analysis/edge_preserving_mean_filter.rs @@ -17,6 +17,23 @@ use std::path; use std::sync::mpsc; use std::sync::Arc; use std::thread; + +/// This tool performs a type of edge-preserving mean filter operation on an input image (`--input`). The filter, a +/// type of low-pass filter, can be used to emphasize the longer-range variability in an image, effectively acting to +/// smooth the image and to reduce noise in the image. The algorithm calculates the average value in a moving window +/// centred on each grid cell, including in the averaging only the set of neighbouring values for which the absolute +/// value difference with the centre value is less than a specified threshold value (`--threshold`). It is, therefore, +/// similar to the `BilateralFilter`, except all neighbours within the threshold difference are equally weighted and +/// neighbour distance is not accounted for. Filter kernels are always square, and filter size, is specified using +/// the `--filter` parameter. This dimensions should be odd, positive integer values, e.g. 3, 5, 7, 9... +/// +/// This tool works with both greyscale and red-green-blue (RGB) input images. RGB images are decomposed into +/// intensity-hue-saturation (IHS) and the filter is applied to the intensity channel. If an RGB image is input, the +/// threshold value must be in the range 0.0-1.0 (more likely less than 0.15), where a value of 1.0 would result in an ordinary mean filter +/// (`MeanFilter`). NoData values in the input image are ignored during filtering. +/// +/// # See Also +/// `MeanFilter`, `BilateralFilter`, `EdgePreservingMeanFilter`, `GaussianFilter`, `MedianFilter`, `RgbToIhs` pub struct EdgePreservingMeanFilter { name: String, description: String, @@ -268,7 +285,7 @@ impl WhiteboxTool for EdgePreservingMeanFilter { let mut dx = vec![0isize; num_pixels_in_filter]; let mut dy = vec![0isize; num_pixels_in_filter]; - // fill the filter d_x and d_y values and the distance-weights + // fill the filter d_x and d_y values let midpoint: isize = (filter_size as f64 / 2f64).floor() as isize; let mut a = 0; for row in 0..filter_size { diff --git a/src/tools/image_analysis/gamma_correction.rs b/src/tools/image_analysis/gamma_correction.rs index d3cb365d8..78125cda1 100644 --- a/src/tools/image_analysis/gamma_correction.rs +++ b/src/tools/image_analysis/gamma_correction.rs @@ -1,12 +1,9 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 13, 2017 +Created: 13/07/2017 Last Modified: 13/10/2018 License: MIT - -NOTES: 1. The tool should be updated to take multiple file inputs. - 2. Unlike the original Whitebox GAT tool that this is based on, */ use crate::raster::*; @@ -21,6 +18,13 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; +/// This tool performs a gamma colour correction transform on an input image (`--input`), such that each +/// input pixel value (zin) is mapped to the corresponding output value (zout) as: +/// +/// > zout = zin`gamma` +/// +/// The user must specify the value of the `gamma` parameter. The input image may be of either a greyscale or RGB colour +/// composite data type. pub struct GammaCorrection { name: String, description: String, @@ -34,7 +38,7 @@ impl GammaCorrection { // public constructor let name = "GammaCorrection".to_string(); let toolbox = "Image Processing Tools/Image Enhancement".to_string(); - let description = "Performs a sigmoidal contrast stretch on input images.".to_string(); + let description = "Performs a gamma correction on an input images.".to_string(); let mut parameters = vec![]; parameters.push(ToolParameter { @@ -144,24 +148,25 @@ impl WhiteboxTool for GammaCorrection { if vec.len() > 1 { keyval = true; } - if vec[0].to_lowercase() == "-i" || vec[0].to_lowercase() == "--input" { - if keyval { - input_file = vec[1].to_string(); + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() } else { - input_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-o" || vec[0].to_lowercase() == "--output" { - if keyval { - output_file = vec[1].to_string(); + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() } else { - output_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-gamma" || vec[0].to_lowercase() == "--gamma" { - if keyval { - gamma = vec[1].to_string().parse::().unwrap(); + args[i + 1].to_string() + }; + } else if flag_val == "-gamma" { + gamma = if keyval { + vec[1].to_string().parse::().unwrap() } else { - gamma = args[i + 1].to_string().parse::().unwrap(); - } + args[i + 1].to_string().parse::().unwrap() + }; } } diff --git a/src/tools/image_analysis/histogram_equalization.rs b/src/tools/image_analysis/histogram_equalization.rs index eeeebd763..1579e02aa 100644 --- a/src/tools/image_analysis/histogram_equalization.rs +++ b/src/tools/image_analysis/histogram_equalization.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: August 26, 2017 +Created: 26/08/2017 Last Modified: 13/10/2018 License: MIT diff --git a/src/tools/image_analysis/histogram_matching.rs b/src/tools/image_analysis/histogram_matching.rs index 3154dde45..86c855589 100644 --- a/src/tools/image_analysis/histogram_matching.rs +++ b/src/tools/image_analysis/histogram_matching.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: September 14, 2017 +Created: 14/09/2017 Last Modified: 13/10/2018 License: MIT */ diff --git a/src/tools/image_analysis/histogram_matching_two_images.rs b/src/tools/image_analysis/histogram_matching_two_images.rs index 715778e80..30689f892 100644 --- a/src/tools/image_analysis/histogram_matching_two_images.rs +++ b/src/tools/image_analysis/histogram_matching_two_images.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: August 31, 2017 +Created: 31/08/2017 Last Modified: 13/10/2018 License: MIT */ diff --git a/src/tools/image_analysis/ihs_to_rgb.rs b/src/tools/image_analysis/ihs_to_rgb.rs index 5b809f913..5b73fd511 100644 --- a/src/tools/image_analysis/ihs_to_rgb.rs +++ b/src/tools/image_analysis/ihs_to_rgb.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 25, 2017 -Last Modified: 13/10/2018 +Created: 25/07/2017 +Last Modified: 05/02/2019 License: MIT */ @@ -11,13 +11,43 @@ use crate::tools::*; use num_cpus; use std::env; use std::f64; +use std::f64::consts::PI; use std::io::{Error, ErrorKind}; use std::path; use std::sync::mpsc; use std::sync::Arc; use std::thread; -/// Converts intensity, hue, and saturation (IHS) images into red, green, and blue (RGB) images. +/// This tool transforms three intensity, hue, and saturation (IHS; sometimes HSI or HIS) raster images into three +/// equivalent multispectral images corresponding with the red, green, and blue channels of an RGB composite. Intensity +/// refers to the brightness of a color, hue is related to the dominant wavelength of light and is perceived as color, +/// and saturation is the purity of the color (Koutsias et al., 2000). There are numerous algorithms for performing a +/// red-green-blue (RGB) to IHS transformation. This tool uses the transformation described by Haydn (1982). Note that, +/// based on this transformation, the input IHS values must follow the ranges: +/// +/// > 0 < I < 1 +/// > +/// > 0 < H < 2PI +/// > +/// > 0 < S < 1 +/// +/// The output red, green, and blue images will have values ranging from 0 to 255. The user must specify the names of the +/// intensity, hue, and saturation images (`--intensity`, `--hue`, `--saturation`). These images will generally be created using +/// the `RgbToIhs` tool. The user must also specify the names of the output red, green, and blue images (`--red`, `--green`, +/// `--blue`). Image enhancements, such as contrast stretching, are often performed on the individual IHS components, which are +/// then inverse transformed back in RGB components using this tool. The output RGB components can then be used to create an +/// improved color composite image. +/// +/// # References +/// Haydn, R., Dalke, G.W. and Henkel, J. (1982) Application of the IHS color transform to the processing of multisensor +/// data and image enhancement. Proc. of the Inter- national Symposium on Remote Sensing of Arid and Semiarid Lands, +/// Cairo, 599-616. +/// +/// Koutsias, N., Karteris, M., and Chuvico, E. (2000). The use of intensity-hue-saturation transformation of Landsat-5 Thematic +/// Mapper data for burned land mapping. Photogrammetric Engineering and Remote Sensing, 66(7), 829-840. +/// +/// # See Also +/// `RgbToIhs`, `BalanceContrastEnhancement`, `DirectDecorrelationStretch` pub struct IhsToRgb { name: String, description: String, @@ -190,61 +220,44 @@ impl WhiteboxTool for IhsToRgb { if vec.len() > 1 { keyval = true; } - if vec[0].to_lowercase() == "-red" || vec[0].to_lowercase() == "--red" { + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-r" || flag_val == "-red" { if keyval { red_file = vec[1].to_string(); } else { red_file = args[i + 1].to_string(); } - } else if vec[0].to_lowercase() == "-g" - || vec[0].to_lowercase() == "-green" - || vec[0].to_lowercase() == "--green" - { + } else if flag_val == "-g" || flag_val == "-green" { if keyval { green_file = vec[1].to_string(); } else { green_file = args[i + 1].to_string(); } - } else if vec[0].to_lowercase() == "-b" - || vec[0].to_lowercase() == "-blue" - || vec[0].to_lowercase() == "--blue" - { + } else if flag_val == "-b" || flag_val == "-blue" { if keyval { blue_file = vec[1].to_string(); } else { blue_file = args[i + 1].to_string(); } - } else if vec[0].to_lowercase() == "-i" - || vec[0].to_lowercase() == "-intensity" - || vec[0].to_lowercase() == "--intensity" - { + } else if flag_val == "-i" || flag_val == "-intensity" { if keyval { intensity_file = vec[1].to_string(); } else { intensity_file = args[i + 1].to_string(); } - } else if vec[0].to_lowercase() == "-h" - || vec[0].to_lowercase() == "-hue" - || vec[0].to_lowercase() == "--hue" - { + } else if flag_val == "-h" || flag_val == "-hue" { if keyval { hue_file = vec[1].to_string(); } else { hue_file = args[i + 1].to_string(); } - } else if vec[0].to_lowercase() == "-s" - || vec[0].to_lowercase() == "-saturation" - || vec[0].to_lowercase() == "--saturation" - { + } else if flag_val == "-s" || flag_val == "-saturation" { if keyval { saturation_file = vec[1].to_string(); } else { saturation_file = args[i + 1].to_string(); } - } else if vec[0].to_lowercase() == "-o" - || vec[0].to_lowercase() == "-composite" - || vec[0].to_lowercase() == "--composite" - { + } else if flag_val == "-o" || flag_val == "-composite" || flag_val == "-output" { if keyval { composite_file = vec[1].to_string(); } else { @@ -265,14 +278,20 @@ impl WhiteboxTool for IhsToRgb { let mut progress: usize; let mut old_progress: usize = 1; - if !red_file.contains(&sep) && !red_file.contains("/") { - red_file = format!("{}{}", working_directory, red_file); - } - if !green_file.contains(&sep) && !green_file.contains("/") { - green_file = format!("{}{}", working_directory, green_file); - } - if !blue_file.contains(&sep) && !blue_file.contains("/") { - blue_file = format!("{}{}", working_directory, blue_file); + if !use_composite { + if !red_file.contains(&sep) && !red_file.contains("/") { + red_file = format!("{}{}", working_directory, red_file); + } + if !green_file.contains(&sep) && !green_file.contains("/") { + green_file = format!("{}{}", working_directory, green_file); + } + if !blue_file.contains(&sep) && !blue_file.contains("/") { + blue_file = format!("{}{}", working_directory, blue_file); + } + } else { + if !composite_file.contains(&sep) && !composite_file.contains("/") { + composite_file = format!("{}{}", working_directory, composite_file); + } } if !intensity_file.contains(&sep) && !intensity_file.contains("/") { intensity_file = format!("{}{}", working_directory, intensity_file); @@ -324,50 +343,37 @@ impl WhiteboxTool for IhsToRgb { } let num_procs = num_cpus::get() as isize; - let (tx, rx) = mpsc::channel(); - for tid in 0..num_procs { - let input_i = input_i.clone(); - let input_h = input_h.clone(); - let input_s = input_s.clone(); - let tx = tx.clone(); - thread::spawn(move || { - let (mut r, mut g, mut b): (f64, f64, f64); - let (mut i, mut h, mut s): (f64, f64, f64); - for row in (0..rows).filter(|r| r % num_procs == tid) { - let mut red_data = vec![nodata_i; columns as usize]; - let mut green_data = vec![nodata_i; columns as usize]; - let mut blue_data = vec![nodata_i; columns as usize]; - for col in 0..columns { - i = input_i[(row, col)]; - h = input_h[(row, col)]; - s = input_s[(row, col)]; - if i != nodata_i && h != nodata_h && s != nodata_s { - if h <= 1f64 { - r = i * (1f64 + 2f64 * s - 3f64 * s * h) / 3f64; - g = i * (1f64 - s + 3f64 * s * h) / 3f64; - b = i * (1f64 - s) / 3f64; - } else if h <= 2f64 { - r = i * (1f64 - s) / 3f64; - g = i * (1f64 + 2f64 * s - 3f64 * s * (h - 1f64)) / 3f64; - b = i * (1f64 - s + 3f64 * s * (h - 1f64)) / 3f64; - } else { - // h <= 3 - r = i * (1f64 - s + 3f64 * s * (h - 2f64)) / 3f64; - g = i * (1f64 - s) / 3f64; - b = i * (1f64 + 2f64 * s - 3f64 * s * (h - 2f64)) / 3f64; + if !use_composite { + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let input_i = input_i.clone(); + let input_h = input_h.clone(); + let input_s = input_s.clone(); + let tx = tx.clone(); + thread::spawn(move || { + // let (mut r, mut g, mut b): (f64, f64, f64); + let (mut i, mut h, mut s): (f64, f64, f64); + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut red_data = vec![nodata_i; columns as usize]; + let mut green_data = vec![nodata_i; columns as usize]; + let mut blue_data = vec![nodata_i; columns as usize]; + for col in 0..columns { + i = input_i[(row, col)]; + h = input_h[(row, col)]; + s = input_s[(row, col)]; + if i != nodata_i && h != nodata_h && s != nodata_s { + let (r, g, b) = hsi2rgb(h, s, i); + + red_data[col as usize] = r as f64; + green_data[col as usize] = g as f64; + blue_data[col as usize] = b as f64; } - - red_data[col as usize] = r; - green_data[col as usize] = g; - blue_data[col as usize] = b; } + tx.send((row, red_data, green_data, blue_data)).unwrap(); } - tx.send((row, red_data, green_data, blue_data)).unwrap(); - } - }); - } + }); + } - if !use_composite { let mut output_r = Raster::initialize_using_file(&red_file, &input_i); output_r.configs.photometric_interp = PhotometricInterpretation::Continuous; output_r.configs.data_type = DataType::F32; @@ -466,27 +472,48 @@ impl WhiteboxTool for IhsToRgb { Err(e) => return Err(e), }; - println!( - "{}", - &format!("Elapsed Time (excluding I/O): {}", elapsed_time).replace("PT", "") - ); + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time).replace("PT", "") + ); + } } else { + if verbose { + println!("Creating a colour composite output..."); + } + let (tx, rx) = mpsc::channel(); + for tid in 0..num_procs { + let input_i = input_i.clone(); + let input_h = input_h.clone(); + let input_s = input_s.clone(); + let tx = tx.clone(); + thread::spawn(move || { + let (mut i, mut h, mut s): (f64, f64, f64); + let mut value: f64; + for row in (0..rows).filter(|r| r % num_procs == tid) { + let mut data = vec![0f64; columns as usize]; + for col in 0..columns { + i = input_i[(row, col)]; + h = input_h[(row, col)]; + s = input_s[(row, col)]; + if i != nodata_i && h != nodata_h && s != nodata_s { + value = hsi2value(h, s, i); + data[col as usize] = value; + } + } + tx.send((row, data)).unwrap(); + } + }); + } + let mut output = Raster::initialize_using_file(&composite_file, &input_i); output.configs.photometric_interp = PhotometricInterpretation::RGB; - output.configs.data_type = DataType::RGB24; - let out_nodata = 0f64; - let (mut r, mut g, mut b): (u32, u32, u32); - let alpha_mask = (255 << 24) as u32; + output.configs.nodata = 0f64; + output.configs.data_type = DataType::RGBA32; for row in 0..rows { let data = rx.recv().unwrap(); - let mut out_data = vec![out_nodata; columns as usize]; - for col in 0..columns { - r = data.1[col as usize] as u32; - g = data.2[col as usize] as u32; - b = data.3[col as usize] as u32; - out_data[col as usize] = (alpha_mask | (b << 16) | (g << 8) | r) as f64; - } - output.set_row_data(data.0, out_data); + output.set_row_data(data.0, data.1); if verbose { progress = (100.0_f64 * row as f64 / (rows - 1) as f64) as usize; if progress != old_progress { @@ -508,7 +535,7 @@ impl WhiteboxTool for IhsToRgb { output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); if verbose { - println!("Saving red data...") + println!("Saving data...") }; let _ = match output.write() { Ok(_) => { @@ -529,3 +556,117 @@ impl WhiteboxTool for IhsToRgb { Ok(()) } } + +// #[inline] +// fn value2hsi(value: f64) -> (f64, f64, f64) { +// let r = (value as u32 & 0xFF) as f64 / 255f64; +// let g = ((value as u32 >> 8) & 0xFF) as f64 / 255f64; +// let b = ((value as u32 >> 16) & 0xFF) as f64 / 255f64; + +// let i = (r + g + b) / 3f64; + +// let rn = r / (r + g + b); +// let gn = g / (r + g + b); +// let bn = b / (r + g + b); + +// let mut h = if rn != gn || rn != bn { +// ((0.5 * ((rn - gn) + (rn - bn))) / ((rn - gn) * (rn - gn) + (rn - bn) * (gn - bn)).sqrt()) +// .acos() +// } else { +// 0f64 +// }; +// if b > g { +// h = 2f64 * PI - h; +// } + +// let s = 1f64 - 3f64 * rn.min(gn).min(bn); + +// (h, s, i) +// } + +#[inline] +fn hsi2value(h: f64, s: f64, i: f64) -> f64 { + let mut r: u32; + let mut g: u32; + let mut b: u32; + + let x = i * (1f64 - s); + + if h < 2f64 * PI / 3f64 { + let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); + let z = 3f64 * i - (x + y); + r = (y * 255f64).round() as u32; + g = (z * 255f64).round() as u32; + b = (x * 255f64).round() as u32; + } else if h < 4f64 * PI / 3f64 { + let h = h - 2f64 * PI / 3f64; + let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); + let z = 3f64 * i - (x + y); + r = (x * 255f64).round() as u32; + g = (y * 255f64).round() as u32; + b = (z * 255f64).round() as u32; + } else { + let h = h - 4f64 * PI / 3f64; + let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); + let z = 3f64 * i - (x + y); + r = (z * 255f64).round() as u32; + g = (x * 255f64).round() as u32; + b = (y * 255f64).round() as u32; + } + + if r > 255u32 { + r = 255u32; + } + if g > 255u32 { + g = 255u32; + } + if b > 255u32 { + b = 255u32; + } + + ((255 << 24) | (b << 16) | (g << 8) | r) as f64 +} + + +#[inline] +fn hsi2rgb(h: f64, s: f64, i: f64) -> (u32, u32, u32) { + let mut r: u32; + let mut g: u32; + let mut b: u32; + + let x = i * (1f64 - s); + + if h < 2f64 * PI / 3f64 { + let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); + let z = 3f64 * i - (x + y); + r = (y * 255f64).round() as u32; + g = (z * 255f64).round() as u32; + b = (x * 255f64).round() as u32; + } else if h < 4f64 * PI / 3f64 { + let h = h - 2f64 * PI / 3f64; + let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); + let z = 3f64 * i - (x + y); + r = (x * 255f64).round() as u32; + g = (y * 255f64).round() as u32; + b = (z * 255f64).round() as u32; + } else { + let h = h - 4f64 * PI / 3f64; + let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); + let z = 3f64 * i - (x + y); + r = (z * 255f64).round() as u32; + g = (x * 255f64).round() as u32; + b = (y * 255f64).round() as u32; + } + + if r > 255u32 { + r = 255u32; + } + if g > 255u32 { + g = 255u32; + } + if b > 255u32 { + b = 255u32; + } + + (r, g, b) +} \ No newline at end of file diff --git a/src/tools/image_analysis/image_stack_profile.rs b/src/tools/image_analysis/image_stack_profile.rs index 9cf007a79..c906011d9 100644 --- a/src/tools/image_analysis/image_stack_profile.rs +++ b/src/tools/image_analysis/image_stack_profile.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: March 15, 2018 +Created: 15/03/2018 Last Modified: 13/10/2018 License: MIT */ @@ -20,6 +20,20 @@ use std::io::{Error, ErrorKind}; use std::path; use std::process::Command; +/// This tool can be used to plot an image stack profile (i.e. a signature) for a set of points (`--points`) and +/// a multispectral image stack (`--inputs`). The tool outputs an interactive SVG line graph embedded in an +/// HTML document (`--output`). If the input points vector contains multiple points, each input point will +/// be associated with a single line in the output plot. The order of vertices in each signature line is +/// determined by the order of images specified in the `--inputs` parameter. At least two input images are +/// required to run this operation. Note that this tool does not require multispectral images as +/// inputs; other types of data may also be used as the image stack. Also note that the input images should be +/// single-band, continuous greytone rasters. RGB colour images are not good candidates for this tool. +/// +/// If you require the raster values to be saved in the vector points file's attribute table, or if you need +/// the raster values to be output as text, you may use the `ExtractRasterValuesAtPoints` tool instead. +/// +/// # See Also +/// `ExtractRasterValuesAtPoints` pub struct ImageStackProfile { name: String, description: String, diff --git a/src/tools/image_analysis/integral_image.rs b/src/tools/image_analysis/integral_image.rs index 22bb07c6c..c4fabbdee 100644 --- a/src/tools/image_analysis/integral_image.rs +++ b/src/tools/image_analysis/integral_image.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 26, 2017 +Created: 26/06/2017 Last Modified: 13/10/2018 License: MIT */ @@ -13,6 +13,17 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This tool transforms an input raster image into an integral image, or summed area table. Integral images are +/// the two-dimensional equivalent to a cumulative distribution function. Each pixel contains the sum of all +/// pixels contained within the enclosing rectangle above and to the left of a pixel. Images with a very large +/// number of grid cells will likely experience numerical overflow errors when converted to an integral image. +/// Integral images are used in a wide variety of computer vision and digital image processing applications, +/// including texture mapping. They allow for the efficient calculation of very large filters and are the +/// basis of several of *WhiteboxTools*'s image filters. +/// +/// # Reference +/// Crow, F. C. (1984, January). Summed-area tables for texture mapping. In ACM SIGGRAPH computer graphics +/// (Vol. 18, No. 3, pp. 207-212). ACM. pub struct IntegralImage { name: String, description: String, diff --git a/src/tools/image_analysis/line_thin.rs b/src/tools/image_analysis/line_thin.rs index 80b1fc8cf..29a668aac 100644 --- a/src/tools/image_analysis/line_thin.rs +++ b/src/tools/image_analysis/line_thin.rs @@ -30,7 +30,7 @@ use std::thread; /// the output raster must be read and written to during the same loop. /// /// # See Also -/// `RemoveSpurs` +/// `RemoveSpurs`, `ThickenRasterLine` pub struct LineThinning { name: String, description: String, diff --git a/src/tools/image_analysis/mean_filter.rs b/src/tools/image_analysis/mean_filter.rs index 8b1c59fb2..ae0dc34ed 100644 --- a/src/tools/image_analysis/mean_filter.rs +++ b/src/tools/image_analysis/mean_filter.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 25, 2017 -Last Modified: 13/10/2018 +Created: 25/06/2017 +Last Modified: 06/02/2019 License: MIT */ @@ -28,7 +28,7 @@ use crate::tools::*; /// useful for reducing the noise in an image. This tool utilizes an integral image approach (Crow, 1984) to ensure highly /// efficient filtering that is invariant to filter size. The algorithm operates by calculating the average value /// in a moving window centred on each grid cell. Neighbourhood size, or filter size, is specified in the x and y -/// dimensions using the `--filterx` and `--filtery`flags. These dimensions should be odd, positive integer values, +/// dimensions using the `--filterx` and `--filtery` flags. These dimensions should be odd, positive integer values, /// e.g. 3, 5, 7, 9... If the kernel filter size is the same in the x and y dimensions, the silent `--filter` flag /// may be used instead (command-line interface only). /// @@ -37,11 +37,15 @@ use crate::tools::*; /// filters such as the edge-preserving smoothing filters including the `BilateralFilter`, `MedianFilter`, `OlympicFilter`, /// `EdgePreservingMeanFilter` and even `GaussianFilter`. /// -/// `GaussianFilter` works with both greyscale and red-green-blue (RGB) images. RGB images are +/// This tool works with both greyscale and red-green-blue (RGB) images. RGB images are /// decomposed into intensity-hue-saturation (IHS) and the filter is applied to the intensity /// channel. NoData values in the input image are ignored during filtering. NoData values are assigned to all sites beyond /// the raster. /// +/// # Reference +/// Crow, F. C. (1984, January). Summed-area tables for texture mapping. In ACM SIGGRAPH computer graphics (Vol. 18, No. +/// 3, pp. 207-212). ACM. +/// /// # See Also /// `BilateralFilter`, `EdgePreservingMeanFilter`, `GaussianFilter`, `MedianFilter`, `RgbToIhs` pub struct MeanFilter { @@ -172,37 +176,38 @@ impl WhiteboxTool for MeanFilter { if vec.len() > 1 { keyval = true; } - if vec[0].to_lowercase() == "-i" || vec[0].to_lowercase() == "--input" { - if keyval { - input_file = vec[1].to_string(); + let flag_val = vec[0].to_lowercase().replace("--", "-"); + if flag_val == "-i" || flag_val == "-input" { + input_file = if keyval { + vec[1].to_string() } else { - input_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-o" || vec[0].to_lowercase() == "--output" { - if keyval { - output_file = vec[1].to_string(); + args[i + 1].to_string() + }; + } else if flag_val == "-o" || flag_val == "-output" { + output_file = if keyval { + vec[1].to_string() } else { - output_file = args[i + 1].to_string(); - } - } else if vec[0].to_lowercase() == "-filter" || vec[0].to_lowercase() == "--filter" { - if keyval { - filter_size_x = vec[1].to_string().parse::().unwrap() as usize; + args[i + 1].to_string() + }; + } else if flag_val == "-filter" { + filter_size_x = if keyval { + vec[1].to_string().parse::().unwrap() as usize } else { - filter_size_x = args[i + 1].to_string().parse::().unwrap() as usize; - } + args[i + 1].to_string().parse::().unwrap() as usize + }; filter_size_y = filter_size_x; - } else if vec[0].to_lowercase() == "-filterx" || vec[0].to_lowercase() == "--filterx" { - if keyval { - filter_size_x = vec[1].to_string().parse::().unwrap() as usize; + } else if flag_val == "-filterx" { + filter_size_x = if keyval { + vec[1].to_string().parse::().unwrap() as usize } else { - filter_size_x = args[i + 1].to_string().parse::().unwrap() as usize; - } - } else if vec[0].to_lowercase() == "-filtery" || vec[0].to_lowercase() == "--filtery" { - if keyval { - filter_size_y = vec[1].to_string().parse::().unwrap() as usize; + args[i + 1].to_string().parse::().unwrap() as usize + }; + } else if flag_val == "-filtery" { + filter_size_y = if keyval { + vec[1].to_string().parse::().unwrap() as usize } else { - filter_size_y = args[i + 1].to_string().parse::().unwrap() as usize; - } + args[i + 1].to_string().parse::().unwrap() as usize + }; } } diff --git a/src/tools/image_analysis/min_max_contrast_stretch.rs b/src/tools/image_analysis/min_max_contrast_stretch.rs index e8973d84b..8d409ef50 100644 --- a/src/tools/image_analysis/min_max_contrast_stretch.rs +++ b/src/tools/image_analysis/min_max_contrast_stretch.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 13, 2017 +Created: 13/07/2017 Last Modified: 13/10/2018 License: MIT diff --git a/src/tools/image_analysis/modified_k_means_clustering.rs b/src/tools/image_analysis/modified_k_means_clustering.rs index 5ccf92137..84f9288cd 100644 --- a/src/tools/image_analysis/modified_k_means_clustering.rs +++ b/src/tools/image_analysis/modified_k_means_clustering.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: Dec. 30, 2017 -Last Modified: Dec. 30, 2017 +Created: 30/12/2017 +Last Modified: 30/12/2017 License: MIT */ diff --git a/src/tools/image_analysis/mosaic.rs b/src/tools/image_analysis/mosaic.rs index 23006274b..13b0b7e26 100644 --- a/src/tools/image_analysis/mosaic.rs +++ b/src/tools/image_analysis/mosaic.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: January 2 2018 -Last Modified: January 2, 2018 +Created: 02/01/2018 +Last Modified: 02/01/2018 License: MIT */ diff --git a/src/tools/image_analysis/percentage_contrast_stretch.rs b/src/tools/image_analysis/percentage_contrast_stretch.rs index b823dbdd8..2a5e81c08 100644 --- a/src/tools/image_analysis/percentage_contrast_stretch.rs +++ b/src/tools/image_analysis/percentage_contrast_stretch.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 13, 2017 +Created: 13/07/2017 Last Modified: 13/10/2018 License: MIT @@ -83,7 +83,7 @@ impl PercentageContrastStretch { description: "Optional amount to clip the distribution tails by, in percent." .to_owned(), parameter_type: ParameterType::Float, - default_value: Some("0.0".to_owned()), + default_value: Some("1.0".to_owned()), optional: true, }); diff --git a/src/tools/image_analysis/resample.rs b/src/tools/image_analysis/resample.rs index 0dd594755..64109f1bf 100644 --- a/src/tools/image_analysis/resample.rs +++ b/src/tools/image_analysis/resample.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: January 1 2018 +Created: 01/01/2018 Last Modified: 13/10/2018 License: MIT */ diff --git a/src/tools/image_analysis/rgb_to_ihs.rs b/src/tools/image_analysis/rgb_to_ihs.rs index 21e1ca678..d527146c2 100644 --- a/src/tools/image_analysis/rgb_to_ihs.rs +++ b/src/tools/image_analysis/rgb_to_ihs.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 25, 2017 +Created: 25/07/2017 Last Modified: 13/10/2018 License: MIT */ @@ -11,13 +11,43 @@ use crate::tools::*; use num_cpus; use std::env; use std::f64; +use std::f64::consts::PI; use std::io::{Error, ErrorKind}; use std::path; use std::sync::mpsc; use std::sync::Arc; use std::thread; -/// Converts red, green, and blue (RGB) images into intensity, hue, and saturation (IHS) images. +/// This tool transforms three raster images of multispectral data (red, green, and blue channels) into their equivalent +/// intensity, hue, and saturation (IHS; sometimes HSI or HIS) images. Intensity refers to the brightness of a color, hue +/// is related to the dominant wavelength of light and is perceived as color, and saturation is the purity of the color +/// (Koutsias et al., 2000). There are numerous algorithms for performing a red-green-blue (RGB) to IHS transformation. +/// This tool uses the transformation described by Haydn (1982). Note that, based on this transformation, the output +/// IHS values follow the ranges: +/// +/// > 0 < I < 1 +/// > +/// > 0 < H < 2PI +/// > +/// > 0 < S < 1 +/// +/// The user must specify the names of the red, green, and blue images (`--red`, `--green`, `--blue`). Importantly, these +/// images need not necessarily correspond with the specific regions of the electromagnetic spectrum that are red, green, +/// and blue. Rather, the input images are three multispectral images that could be used to create a RGB color composite. +/// The user must also specify the names of the output intensity, hue, and saturation images (`--intensity`, `--hue`, +/// `--saturation`). Image enhancements, such as contrast stretching, are often performed on the IHS components, which are +/// then inverse transformed back in RGB components to then create an improved color composite image. +/// +/// # References +/// Haydn, R., Dalke, G.W. and Henkel, J. (1982) Application of the IHS color transform to the processing of multisensor +/// data and image enhancement. Proc. of the Inter- national Symposium on Remote Sensing of Arid and Semiarid Lands, +/// Cairo, 599-616. +/// +/// Koutsias, N., Karteris, M., and Chuvico, E. (2000). The use of intensity-hue-saturation transformation of Landsat-5 Thematic +/// Mapper data for burned land mapping. Photogrammetric Engineering and Remote Sensing, 66(7), 829-840. +/// +/// # See Also +/// `IhsToRgb`, `BalanceContrastEnhancement`, `DirectDecorrelationStretch` pub struct RgbToIhs { name: String, description: String, @@ -311,9 +341,9 @@ impl WhiteboxTool for RgbToIhs { let red_max = input_r.configs.display_max; let green_max = input_g.configs.display_max; let blue_max = input_b.configs.display_max; - let overall_min = red_min.min(green_min.min(blue_min)); - let overall_max = red_max.max(green_max.max(blue_max)); - let range = overall_max - overall_min; + // let overall_min = red_min.min(green_min.min(blue_min)); + // let overall_max = red_max.max(green_max.max(blue_max)); + // let range = overall_max - overall_min; let start = Instant::now(); @@ -338,68 +368,45 @@ impl WhiteboxTool for RgbToIhs { let input_b = input_b.clone(); let tx = tx.clone(); thread::spawn(move || { - let (mut r, mut g, mut b): (f64, f64, f64); - let (mut i, mut h, mut s, mut m): (f64, f64, f64, f64); + // let (mut r, mut g, mut b): (u32, u32, u32); + let (mut red, mut green, mut blue): (f64, f64, f64); + // let (mut i, mut h, mut s, mut m): (f64, f64, f64, f64); + // let mut value: f64; + let red_range = red_max - red_min; + let green_range = green_max - green_min; + let blue_range = blue_max - blue_min; for row in (0..rows).filter(|r| r % num_procs == tid) { let mut intensity_data = vec![nodata_r; columns as usize]; let mut hue_data = vec![nodata_r; columns as usize]; let mut saturation_data = vec![nodata_r; columns as usize]; for col in 0..columns { - r = input_r[(row, col)]; - g = input_g[(row, col)]; - b = input_b[(row, col)]; - if r != nodata_r && g != nodata_g && b != nodata_b { - r = (r - overall_min) / range; - if r < 0f64 { - r = 0f64; - } - if r > 1f64 { - r = 1f64; - } - - g = (g - overall_min) / range; - if g < 0f64 { - g = 0f64; - } - if g > 1f64 { - g = 1f64; - } - - b = (b - overall_min) / range; - if b < 0f64 { - b = 0f64; - } - if b > 1f64 { - b = 1f64; - } - - m = r.min(g.min(b)); - - i = r + g + b; + red = input_r[(row, col)]; + green = input_g[(row, col)]; + blue = input_b[(row, col)]; + if red != nodata_r && green != nodata_g && blue != nodata_b { + // r = ((red - red_min) / (red_max - red_min) * 255f64) as u32; + // if r > 255u32 { + // r = 255u32; + // } + + // g = ((green - green_min) / (green_max - green_min) * 255f64) as u32; + // if g > 255u32 { + // g = 255u32; + // } + + // b = ((blue - blue_min) / (blue_max - blue_min) * 255f64) as u32; + // if b > 255u32 { + // b = 255u32; + // } + + red = (red - red_min) / red_range; + green = (green - green_min) / green_range; + blue = (blue - blue_min) / blue_range; + let (h, s, i) = rgb2hsi(red, green, blue); - if i == 3f64 { - h = 0f64; - } else if m == b { - h = (g - b) / (i - 3f64 * b); - } else if m == r { - h = (b - r) / (i - 3f64 * r) + 1f64; - } else { - // m == g - h = (r - g) / (i - 3f64 * g) + 2f64; - } - - if h <= 1f64 { - s = (i - 3f64 * b) / i; - } else if h <= 2f64 { - s = (i - 3f64 * r) / i; - } else { - // H <= 3 - s = (i - 3f64 * g) / i; - } - - intensity_data[col as usize] = i; hue_data[col as usize] = h; saturation_data[col as usize] = s; + intensity_data[col as usize] = i; } } tx.send((row, intensity_data, hue_data, saturation_data)) @@ -495,10 +502,12 @@ impl WhiteboxTool for RgbToIhs { Err(e) => return Err(e), }; - println!( - "{}", - &format!("Elapsed Time (excluding I/O): {}", elapsed_time).replace("PT", "") - ); + if verbose { + println!( + "{}", + &format!("Elapsed Time (excluding I/O): {}", elapsed_time).replace("PT", "") + ); + } } else { if verbose { println!("Reading image data...") @@ -574,15 +583,16 @@ impl WhiteboxTool for RgbToIhs { } } - let range = overall_max - overall_min; + // let range = overall_max - overall_min; let (tx, rx) = mpsc::channel(); for tid in 0..num_procs { let input = input.clone(); let tx = tx.clone(); thread::spawn(move || { - let (mut r, mut g, mut b): (f64, f64, f64); - let (mut i, mut h, mut s, mut m): (f64, f64, f64, f64); + // let (mut r, mut g, mut b): (f64, f64, f64); + // let (mut i, mut h, mut s, mut m): (f64, f64, f64, f64); + // let (mut i, mut h, mut s): (f64, f64, f64); let mut z: f64; for row in (0..rows).filter(|r| r % num_procs == tid) { let mut intensity_data = vec![nodata; columns as usize]; @@ -591,57 +601,59 @@ impl WhiteboxTool for RgbToIhs { for col in 0..columns { z = input[(row, col)]; if z != nodata { - r = (z as u32 & 0xFF) as f64; - g = ((z as u32 >> 8) & 0xFF) as f64; - b = ((z as u32 >> 16) & 0xFF) as f64; - - r = (r - overall_min) / range; - if r < 0f64 { - r = 0f64; - } - if r > 1f64 { - r = 1f64; - } - - g = (g - overall_min) / range; - if g < 0f64 { - g = 0f64; - } - if g > 1f64 { - g = 1f64; - } - - b = (b - overall_min) / range; - if b < 0f64 { - b = 0f64; - } - if b > 1f64 { - b = 1f64; - } - - m = r.min(g.min(b)); - - i = r + g + b; - - if i == 3f64 { - h = 0f64; - } else if m == b { - h = (g - b) / (i - 3f64 * b); - } else if m == r { - h = (b - r) / (i - 3f64 * r) + 1f64; - } else { - // m == g - h = (r - g) / (i - 3f64 * g) + 2f64; - } - - if h <= 1f64 { - s = (i - 3f64 * b) / i; - } else if h <= 2f64 { - s = (i - 3f64 * r) / i; - } else { - // H <= 3 - s = (i - 3f64 * g) / i; - } + // r = (z as u32 & 0xFF) as f64; + // g = ((z as u32 >> 8) & 0xFF) as f64; + // b = ((z as u32 >> 16) & 0xFF) as f64; + + // r = (r - overall_min) / range; + // if r < 0f64 { + // r = 0f64; + // } + // if r > 1f64 { + // r = 1f64; + // } + + // g = (g - overall_min) / range; + // if g < 0f64 { + // g = 0f64; + // } + // if g > 1f64 { + // g = 1f64; + // } + + // b = (b - overall_min) / range; + // if b < 0f64 { + // b = 0f64; + // } + // if b > 1f64 { + // b = 1f64; + // } + + // m = r.min(g.min(b)); + + // i = r + g + b; + + // if i == 3f64 { + // h = 0f64; + // } else if m == b { + // h = (g - b) / (i - 3f64 * b); + // } else if m == r { + // h = (b - r) / (i - 3f64 * r) + 1f64; + // } else { + // // m == g + // h = (r - g) / (i - 3f64 * g) + 2f64; + // } + + // if h <= 1f64 { + // s = (i - 3f64 * b) / i; + // } else if h <= 2f64 { + // s = (i - 3f64 * r) / i; + // } else { + // // H <= 3 + // s = (i - 3f64 * g) / i; + // } + + let (h, s, i) = value2hsi(z); intensity_data[col as usize] = i; hue_data[col as usize] = h; @@ -755,3 +767,96 @@ impl WhiteboxTool for RgbToIhs { Ok(()) } } + +#[inline] +fn value2hsi(value: f64) -> (f64, f64, f64) { + let r = (value as u32 & 0xFF) as f64 / 255f64; + let g = ((value as u32 >> 8) & 0xFF) as f64 / 255f64; + let b = ((value as u32 >> 16) & 0xFF) as f64 / 255f64; + + let i = (r + g + b) / 3f64; + + let rn = r / (r + g + b); + let gn = g / (r + g + b); + let bn = b / (r + g + b); + + let mut h = if rn != gn || rn != bn { + ((0.5 * ((rn - gn) + (rn - bn))) / ((rn - gn) * (rn - gn) + (rn - bn) * (gn - bn)).sqrt()) + .acos() + } else { + 0f64 + }; + if b > g { + h = 2f64 * PI - h; + } + + let s = 1f64 - 3f64 * rn.min(gn).min(bn); + + (h, s, i) +} + +/// RGB values should be 0-1 +fn rgb2hsi(r: f64, g: f64, b: f64) -> (f64, f64, f64) { + let i = (r + g + b) / 3f64; + + let rn = r / (r + g + b); + let gn = g / (r + g + b); + let bn = b / (r + g + b); + + let mut h = if rn != gn || rn != bn { + ((0.5 * ((rn - gn) + (rn - bn))) / ((rn - gn) * (rn - gn) + (rn - bn) * (gn - bn)).sqrt()) + .acos() + } else { + 0f64 + }; + if b > g { + h = 2f64 * PI - h; + } + + let s = 1f64 - 3f64 * rn.min(gn).min(bn); + + (h, s, i) +} + +// #[inline] +// fn hsi2value(h: f64, s: f64, i: f64) -> f64 { +// let mut r: u32; +// let mut g: u32; +// let mut b: u32; + +// let x = i * (1f64 - s); + +// if h < 2f64 * PI / 3f64 { +// let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); +// let z = 3f64 * i - (x + y); +// r = (y * 255f64).round() as u32; +// g = (z * 255f64).round() as u32; +// b = (x * 255f64).round() as u32; +// } else if h < 4f64 * PI / 3f64 { +// let h = h - 2f64 * PI / 3f64; +// let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); +// let z = 3f64 * i - (x + y); +// r = (x * 255f64).round() as u32; +// g = (y * 255f64).round() as u32; +// b = (z * 255f64).round() as u32; +// } else { +// let h = h - 4f64 * PI / 3f64; +// let y = i * (1f64 + (s * h.cos()) / ((PI / 3f64 - h).cos())); +// let z = 3f64 * i - (x + y); +// r = (z * 255f64).round() as u32; +// g = (x * 255f64).round() as u32; +// b = (y * 255f64).round() as u32; +// } + +// if r > 255u32 { +// r = 255u32; +// } +// if g > 255u32 { +// g = 255u32; +// } +// if b > 255u32 { +// b = 255u32; +// } + +// ((255 << 24) | (b << 16) | (g << 8) | r) as f64 +// } diff --git a/src/tools/image_analysis/sigmoidal_contrast_stretch.rs b/src/tools/image_analysis/sigmoidal_contrast_stretch.rs index e590b4e16..7c8c6731c 100644 --- a/src/tools/image_analysis/sigmoidal_contrast_stretch.rs +++ b/src/tools/image_analysis/sigmoidal_contrast_stretch.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 13, 2017 +Created: 13/07/2017 Last Modified: 13/10/2018 License: MIT @@ -22,6 +22,28 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; +/// This tool performs a sigmoidal stretch on a raster image. This is a transformation where the input image value for a +/// grid cell (zin) is transformed to an output value zout such that: +/// +/// > zout = (1.0 / (1.0 + exp(*gain*(*cutoff* - z))) - *a* ) / *b* x *num_tones* +/// +/// where, +/// +/// > z = (zin - *MIN*) / *RANGE*, +/// +/// > *a* = 1.0 / (1.0 + exp(*gain* x *cutoff*)), +/// +/// > *b* = 1.0 / (1.0 + exp(*gain* x (*cutoff* - 1.0))) - 1.0 / (1.0 + exp(*gain* x *cutoff*)), +/// +/// *MIN* and *RANGE* are the minimum value and data range in the input image respectively and *gain* and *cutoff* are +/// user specified parameters (`--gain`, `--cutoff`). +/// +/// Like all of *WhiteboxTools*'s contrast enhancement tools, this operation will work on either greyscale or RGB input +/// images. +/// +/// # See Also +/// `GaussianContrastStretch`, `HistogramEqualization`, `MinMaxContrastStretch`, `PercentageContrastStretch`, +/// `StandardDeviationContrastStretch` pub struct SigmoidalContrastStretch { name: String, description: String, diff --git a/src/tools/image_analysis/sobel_filter.rs b/src/tools/image_analysis/sobel_filter.rs index 053ef38a9..6737ebd1d 100644 --- a/src/tools/image_analysis/sobel_filter.rs +++ b/src/tools/image_analysis/sobel_filter.rs @@ -341,7 +341,9 @@ impl WhiteboxTool for SobelFilter { } if clip_amount > 0.0 { - println!("Clipping output..."); + if verbose { + println!("Clipping output..."); + } output.clip_min_and_max_by_percent(clip_amount); } diff --git a/src/tools/image_analysis/split_colour_composite.rs b/src/tools/image_analysis/split_colour_composite.rs index 0f041509f..18ee90144 100644 --- a/src/tools/image_analysis/split_colour_composite.rs +++ b/src/tools/image_analysis/split_colour_composite.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 15, 2017 -Last Modified: 13/10/2018 +Created: 15/07/2017 +Last Modified: 06/02/2019 License: MIT */ @@ -12,11 +12,18 @@ use num_cpus; use std::env; use std::io::{Error, ErrorKind}; use std::path; +use std::path::Path; use std::sync::mpsc; use std::sync::Arc; use std::thread; -/// This tool splits an RGB colour composite image into seperate multispectral images. +/// This tool can be used to split a red-green-blue (RGB) colour-composite image into three separate bands of +/// multi-spectral imagery. The user must specify the input image (`--input`) and output image (`--output`). +/// The tool creates three output images, each based on the `--output` parameter and with the `_r`, `_g`, and `_b` +/// suffixes appended. +/// +/// # See Also +/// `CreateColourComposite` pub struct SplitColourComposite { name: String, description: String, @@ -47,7 +54,7 @@ impl SplitColourComposite { parameters.push(ToolParameter { name: "Output File".to_owned(), flags: vec!["-o".to_owned(), "--output".to_owned()], - description: "Output raster file (suffixes of '_r', '_g', and '_b' will be appended)." + description: "Output raster file (suffixes of _r, _g, and _b will be appended)." .to_owned(), parameter_type: ParameterType::NewFile(ParameterFileType::Raster), default_value: None, @@ -183,6 +190,7 @@ impl WhiteboxTool for SplitColourComposite { let rows = input.configs.rows as isize; let columns = input.configs.columns as isize; let nodata = input.configs.nodata; + let output_nodata = -32768f64; let num_procs = num_cpus::get() as isize; let (tx, rx) = mpsc::channel(); @@ -194,9 +202,9 @@ impl WhiteboxTool for SplitColourComposite { let mut val: u32; let (mut red, mut green, mut blue): (u32, u32, u32); for row in (0..rows).filter(|r| r % num_procs == tid) { - let mut data_r = vec![nodata; columns as usize]; - let mut data_g = vec![nodata; columns as usize]; - let mut data_b = vec![nodata; columns as usize]; + let mut data_r = vec![output_nodata; columns as usize]; + let mut data_g = vec![output_nodata; columns as usize]; + let mut data_b = vec![output_nodata; columns as usize]; for col in 0..columns { in_val = input.get_value(row, col); if in_val != nodata { @@ -214,20 +222,28 @@ impl WhiteboxTool for SplitColourComposite { }); } + let extension: String = match Path::new(&output_file).extension().unwrap().to_str() { + Some(n) => format!(".{}", n.to_string()), + None => "".to_string(), + }; + let mut output_r = - Raster::initialize_using_file(&output_file.replace(".dep", "_red.dep"), &input); + Raster::initialize_using_file(&output_file.replace(&extension, &format!("_r{}", extension)), &input); output_r.configs.photometric_interp = PhotometricInterpretation::Continuous; output_r.configs.data_type = DataType::F32; + output_r.configs.nodata = output_nodata; let mut output_g = - Raster::initialize_using_file(&output_file.replace(".dep", "_green.dep"), &input); + Raster::initialize_using_file(&output_file.replace(&extension, &format!("_g{}", extension)), &input); output_g.configs.photometric_interp = PhotometricInterpretation::Continuous; output_g.configs.data_type = DataType::F32; + output_g.configs.nodata = output_nodata; let mut output_b = - Raster::initialize_using_file(&output_file.replace(".dep", "_blue.dep"), &input); + Raster::initialize_using_file(&output_file.replace(&extension, &format!("_b{}", extension)), &input); output_b.configs.photometric_interp = PhotometricInterpretation::Continuous; output_b.configs.data_type = DataType::F32; + output_b.configs.nodata = output_nodata; for row in 0..rows { let data = rx.recv().unwrap(); diff --git a/src/tools/image_analysis/stdev_contrast_stretch.rs b/src/tools/image_analysis/stdev_contrast_stretch.rs index 42214d642..7d5ed115e 100644 --- a/src/tools/image_analysis/stdev_contrast_stretch.rs +++ b/src/tools/image_analysis/stdev_contrast_stretch.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 13, 2017 +Created: 13/07/2017 Last Modified: 13/10/2018 License: MIT @@ -22,6 +22,22 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; +/// This tool performs a standard deviation contrast stretch on a raster image. This operation maps each grid cell value +/// in the input raster image (zin) onto a new scale that ranges from a lower-tail clip value (`min_val`) +/// to the upper-tail clip value (`max_val`), with the user-specified number of tonal values (`num_tones`), such that: +/// +/// > zout = ((zin – min_val)/(max_val – min_val)) x num_tones +/// +/// where zout is the output value. The values of `min_val` and `max_val` are determined based on the image +/// mean and standard deviation. Specifically, the user must specify the number of standard deviations (`--clip` or +/// `--stdev`) to be used in determining the min and max clip values. The tool will then calculate the input image mean +/// and standard deviation and estimate the clip values from these statistics. +/// +/// This is the same kind of stretch that is used to display raster type data on the fly in many GIS software packages. +/// +/// # See Also +/// `GaussianContrastStretch`, `HistogramEqualization`, `MinMaxContrastStretch`, `PercentageContrastStretch`, +/// `SigmoidalContrastStretch` pub struct StandardDeviationContrastStretch { name: String, description: String, diff --git a/src/tools/image_analysis/thicken_line.rs b/src/tools/image_analysis/thicken_line.rs index 3d92e0f48..e08f67f34 100644 --- a/src/tools/image_analysis/thicken_line.rs +++ b/src/tools/image_analysis/thicken_line.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 4, 2017 +Created: 04/07/2017 Last Modified: 13/10/2018 License: MIT @@ -16,6 +16,22 @@ use std::f64; use std::io::{Error, ErrorKind}; use std::path; +/// This image processing tool can be used to thicken single-cell wide lines within a raster file along diagonal +/// sections of the lines. Because of the limitation of the raster data format, single-cell wide raster lines can +/// be traversed along diaganol sections without passing through a line grid cell. This causes problems for various +/// raster analysis functions for which lines are intended to be barriers. This tool will thicken raster lines, +/// such that it is impossible to cross a line without passing through a line grid cell. While this can also be +/// achieved using a maximum filter, unlike the filter approach, this tool will result in the smallest possible +/// thickening to achieve the desired result. +/// +/// All non-zero, positive values are considered to be foreground pixels while all zero valued cells or NoData cells +/// are considered background pixels. +/// +/// Note: Unlike other filter-based operations in *WhiteboxTools*, this algorithm can't easily be parallelized because +/// the output raster must be read and written to during the same loop. +/// +/// # See Also +/// `LineThinning` pub struct ThickenRasterLine { name: String, description: String, diff --git a/src/tools/image_analysis/write_func_memory_insertion.rs b/src/tools/image_analysis/write_func_memory_insertion.rs index d82a691e9..c400af4a7 100644 --- a/src/tools/image_analysis/write_func_memory_insertion.rs +++ b/src/tools/image_analysis/write_func_memory_insertion.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: July 18, 2017 +Created: 18/07/2017 Last Modified: 13/10/2018 License: MIT */ @@ -17,7 +17,26 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; -/// Performs a write function memory insertion for single-band multi-date change detection. +/// Jensen (2015) describes write function memory (WFM) insertion as a simple yet effective method of visualizing +/// land-cover change between two or three dates. WFM insertion may be used to qualitatively inspect change in any +/// type of registered, multi-date imagery. The technique operates by creating a red-green-blue (RGB) colour composite +/// image based on co-registered imagery from two or three dates. If two dates are input, the first date image will be +/// put into the red channel, while the second date image will be put into both the green and blue channels. The result +/// is an image where the areas of change are displayed as red (date 1 is brighter than date 2) and cyan (date 1 is +/// darker than date 2), and areas of little change are represented in grey-tones. The larger the change in pixel +/// brightness between dates, the more intense the resulting colour will be. +/// +/// If images from three dates are input, the resulting composite can contain many distinct colours. Again, more +/// intense the colours are indicative of areas of greater land-cover change among the dates, while areas of little +/// change are represented in grey-tones. Interpreting the direction of change is more difficult when three dates are +/// used. Note that for multi-spectral imagery, only one band from each date can be used for creating a WFM insertion +/// image. +/// +/// # Reference +/// Jensen, J. R. (2015). Introductory Digital Image Processing: A Remote Sensing Perspective. +/// +/// # See Also +/// `CreateColourComposite`, `ChangeVectorAnalysis` pub struct WriteFunctionMemoryInsertion { name: String, description: String, diff --git a/src/tools/lidar_analysis/clip_lidar_to_polygon.rs b/src/tools/lidar_analysis/clip_lidar_to_polygon.rs index 1db1ba038..530373041 100644 --- a/src/tools/lidar_analysis/clip_lidar_to_polygon.rs +++ b/src/tools/lidar_analysis/clip_lidar_to_polygon.rs @@ -15,6 +15,17 @@ use std::env; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to isolate, or clip, all of the LiDAR points in a LAS file (`--input`) contained within +/// one or more vector polygon features. The user must specify the name of the input clip file (--polygons), wich +/// must be a vector of a Polygon base shape type. The clip file may contain multiple polygon features and polygon hole +/// parts will be respected during clipping, i.e. LiDAR points within polygon holes will be removed from the output LAS +/// file. +/// +/// Use the `ErasePolygonFromLidar` tool to perform the complementary operation of removing points from a LAS file +/// that are contained within a set of polygons. +/// +/// # See Also +/// `ErasePolygonFromLidar`, `Clip`, `ClipRasterToPolygon` pub struct ClipLidarToPolygon { name: String, description: String, diff --git a/src/tools/lidar_analysis/erase_polygon_from_lidar.rs b/src/tools/lidar_analysis/erase_polygon_from_lidar.rs index 3de70b4ac..8679485cb 100644 --- a/src/tools/lidar_analysis/erase_polygon_from_lidar.rs +++ b/src/tools/lidar_analysis/erase_polygon_from_lidar.rs @@ -15,6 +15,17 @@ use std::env; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to remove, or erase, all of the LiDAR points in a LAS file (`--input`) contained within +/// one or more vector polygon features. The user must specify the name of the input clip file (--polygons), wich +/// must be a vector of a Polygon base shape type. The clip file may contain multiple polygon features and polygon hole +/// parts will be respected during clipping, i.e. LiDAR points within polygon holes will be remain in the output LAS +/// file. +/// +/// Use the `ClipLidarToPolygon` tool to perform the complementary operation of clipping (isolating) points from a LAS file +/// that are contained within a set of polygons, while removing points that lie outside the input polygons. +/// +/// # See Also +/// `ClipLidarToPolygon`, `Clip`, `ClipRasterToPolygon` pub struct ErasePolygonFromLidar { name: String, description: String, diff --git a/src/tools/lidar_analysis/lidar_join.rs b/src/tools/lidar_analysis/lidar_join.rs index 2edb3b9d1..f9d7276a7 100644 --- a/src/tools/lidar_analysis/lidar_join.rs +++ b/src/tools/lidar_analysis/lidar_join.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 21, 2017 +Created: 21/06/2017 Last Modified: 29/08/2018 License: MIT */ @@ -13,6 +13,12 @@ use std::env; use std::io::{Error, ErrorKind}; use std::path; +/// This tool can be used to merge multiple LiDAR LAS files into a single output LAS file. Due to their large size, +/// LiDAR data sets are often tiled into smaller, non-overlapping tiles. Sometimes it is more convenient to combine +/// multiple tiles together for data processing and `LidarJoin` can be used for this purpose. +/// +/// # See Also +/// `LidarTile` pub struct LidarJoin { name: String, description: String, diff --git a/src/tools/lidar_analysis/lidar_tile.rs b/src/tools/lidar_analysis/lidar_tile.rs index e81c10b1d..063d31f4d 100644 --- a/src/tools/lidar_analysis/lidar_tile.rs +++ b/src/tools/lidar_analysis/lidar_tile.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 26, 2017 -Last Modified: 29/08/2018 +Created: 26/06/2017 +Last Modified: 05/02/2019 License: MIT */ use crate::lidar::*; @@ -14,7 +14,15 @@ use std::io::{Error, ErrorKind}; use std::path; use std::path::Path; -/// Tiles a LiDAR LAS file into multiple LAS files. +/// This tool can be used to break a LiDAR LAS file into multiple, non-overlapping tiles, each saved as a +/// single LAS file. The user must specify the parameter of the tile grid, including its origin (`--origin_x` and +/// `--origin_y`) and the tile width and height (`--width` and `--height`). Tiles containing fewer points than +/// specified in the `--min_points` parameter will not be output. This can be useful when tiling terrestrial LiDAR +/// datasets because the low point density at the edges of the point cloud (i.e. most distant from the scan +/// station) can result in poorly populated tiles containing relatively few points. +/// +/// # See Also +/// `LidarJoin`, `LidarTileFootprint` pub struct LidarTile { name: String, description: String, @@ -41,8 +49,8 @@ impl LidarTile { }); parameters.push(ToolParameter { - name: "Tile Width in X Dimension".to_owned(), - flags: vec!["--width_x".to_owned()], + name: "Tile Width".to_owned(), + flags: vec!["--width".to_owned()], description: "Width of tiles in the X dimension; default 1000.0.".to_owned(), parameter_type: ParameterType::Float, default_value: Some("1000.0".to_owned()), @@ -50,9 +58,9 @@ impl LidarTile { }); parameters.push(ToolParameter { - name: "Tile Width in Y Dimension".to_owned(), - flags: vec!["--width_y".to_owned()], - description: "Width of tiles in the Y dimension.".to_owned(), + name: "Tile Height".to_owned(), + flags: vec!["--height".to_owned()], + description: "Height of tiles in the Y dimension.".to_owned(), parameter_type: ParameterType::Float, default_value: Some("1000.0".to_owned()), optional: true, @@ -97,7 +105,7 @@ impl LidarTile { if e.contains(".exe") { short_exe += ".exe"; } - let usage = format!(">>.*{0} -r={1} -v -i=*path*to*data*input.las --width_x=1000.0 --width_y=2500.0 -=min_points=100", short_exe, name).replace("*", &sep); + let usage = format!(">>.*{0} -r={1} -v -i=*path*to*data*input.las --width=1000.0 --height=2500.0 -=min_points=100", short_exe, name).replace("*", &sep); LidarTile { name: name, @@ -180,13 +188,13 @@ impl WhiteboxTool for LidarTile { } else { input_file = args[i + 1].to_string(); } - } else if flag_val == "-width_x" { + } else if flag_val == "-width_x" || flag_val == "-width" { if keyval { width_x = vec[1].to_string().parse::().unwrap(); } else { width_x = args[i + 1].to_string().parse::().unwrap(); } - } else if flag_val == "-width_y" { + } else if flag_val == "-width_y" || flag_val == "-height" { if keyval { width_y = vec[1].to_string().parse::().unwrap(); } else { diff --git a/src/tools/lidar_analysis/lidar_tile_footprint.rs b/src/tools/lidar_analysis/lidar_tile_footprint.rs index ec3f42497..17127864d 100644 --- a/src/tools/lidar_analysis/lidar_tile_footprint.rs +++ b/src/tools/lidar_analysis/lidar_tile_footprint.rs @@ -21,8 +21,17 @@ use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; use std::thread; -/// Creates a vector polygon of the convex hull of a LiDAR point cloud. When the input/output parameters -/// are not specified, the tool works with all LAS files contained within the working directory. +/// This tool can be used to create a vector polygon of the convex hull of a LiDAR point cloud (i.e. LAS file). +/// If the user specified an input file (`--input`) and output file (`--output`), the tool will calculate the convex +/// hull, containing all of the data points, and output this feature to a vector polygon file. If the `input` and +/// `output` parameters are left unspecified, the tool will calculate the hulls of every LAS file contained within the +/// working directory and output these features to a single vector polygon file. If this is the desired mode of +/// operation, it is important to specify the working directory (`--wd`) containing the group of LAS files; do not +/// specify the optional `--input` and `--output` parameters in this case. Each polygon in the output vector will contain +/// a `LAS_NM` field, specifying the source LAS file name, and a `NUM_PNTS` field, containing the number of points +/// within the source file. This output can therefore be useful to create an index map of a large tiled LiDAR dataset. +/// +/// `LidarTile`, `LayerFootprint` pub struct LidarTileFootprint { name: String, description: String, diff --git a/src/tools/math_stat_analysis/principal_component_analysis.rs b/src/tools/math_stat_analysis/principal_component_analysis.rs index c5ee2b9c3..182667c5f 100644 --- a/src/tools/math_stat_analysis/principal_component_analysis.rs +++ b/src/tools/math_stat_analysis/principal_component_analysis.rs @@ -2,12 +2,8 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay Created: 15/03/2018 -Last Modified: 13/10/2018 +Last Modified: 05/02/2019 License: MIT - -Note: The current implementation reads every raster into memory at one time. This is -because of the calculation of the co-variances. As such, if the entire image stack can't -fit in memory, the tool will not work. */ use crate::na::DMatrix; @@ -24,6 +20,54 @@ use std::io::{Error, ErrorKind}; use std::path; use std::process::Command; +/// Principal component analysis (PCA) is a common data reduction technique that is used to reduce the dimensionality of +/// multi-dimensional space. In the field of remote sensing, PCA is often used to reduce the number of bands of +/// multi-spectral, or hyper-spectral, imagery. Image correlation analysis often reveals a substantial level of correlation +/// among bands of multi-spectral imagery. This correlation represents data redundancy, i.e. fewer images than the number +/// of bands are required to represent the same information, where the information is related to variation within the imagery. +/// PCA transforms the original data set of *n* bands into *n* 'component' images, where each component image is uncorrelated +/// with all other components. The technique works by transforming the axes of the multi-spectral space such that it coincides +/// with the directions of greatest correlation. Each of these new axes are orthogonal to one another, i.e. they are at right +/// angles. PCA is therefore a type of coordinate system transformation. The PCA component images are arranged such that the +/// greatest amount of variance (or information) within the original data set, is contained within the first component and the +/// amount of variance decreases with each component. It is often the case that the majority of the information contained in a +/// multi-spectral data set can be represented by the first three or four PCA components. The higher-order components are often +/// associated with noise in the original data set. +/// +/// The user must specify the names of the multiple input images (`--inputs`). Additionally, the user must specify whether to +/// perform a standardized PCA (`--standardized`) and the number of output components (`--num_comp`) to generate (all components +/// will be output unless otherwise specified). A standardized PCA is performed using the correlation matrix rather than the +/// variance-covariance matrix. This is appropriate when the variances in the input images differ substantially, such as would be +/// the case if they contained values that were recorded in different units (e.g. feet and meters) or on different scales (e.g. +/// 8-bit vs. 16 bit). +/// +/// Several outputs will be generated when the tool has completed. The PCA report will be embeded within an output (`--output`) +/// HTML file, which should be automatically displayed after the tool has completed. This report contains useful data summarizing +/// the results of the PCA, including the explained variances of each factor, the Eigenvalues and Eigenvectors associated with +/// factors, the factor loadings, and a scree plot. The first table that is in the PCA report lists the amount of explained +/// variance (in non-cumulative and cumulative form), the Eigenvalue, and the Eigenvector for each component. Each of the PCA +/// components refer to the newly created, transformed images that are created by running the tool. The amount of explained +/// variance associated with each component can be thought of as a measure of how much information content within the original +/// multi-spectral data set that a component has. The higher this value is, the more important the component is. This same +/// information is presented in graphical form in the *scree plot*, found at the bottom of the PCA report. The Eigenvalue is +/// another measure of the information content of a component and the eigenvector describes the mathematical transformation +/// (rotation coordinates) that correspond to a particular component image. +/// +/// Factor loadings are also output in a table within the PCA text report (second table). These loading values describe the +/// correlation (i.e. *r* values) between each of the PCA components (columns) and the original images (rows). These values +/// show you how the information contained in an image is spread among the components. An analysis of factor loadings can be +/// reveal useful information about the data set. For example, it can help to identify groups of similar images. +/// +/// PCA is used to reduce the number of band images necessary for classification (i.e. as a data reduction technique), for +/// noise reduction, and for change detection applications. When used as a change detection technique, the major PCA components +/// tend to be associated with stable elements of the data set while variance due to land-cover change tend to manifest in the +/// high-order, 'change components'. When used as a noise reduction technique, an inverse PCA is generally performed, leaving +/// out one or more of the high-order PCA components, which account for noise variance. +/// +/// Note: the current implementation reads every raster into memory at one time. This is because of the calculation of the +/// co-variances. As such, if the entire image stack cannot fit in memory, the tool will likely experience an out-ofs-memory error. +/// This tool should be run using the `--wd` flag to spectify the working directory into which the component images will be +/// written. pub struct PrincipalComponentAnalysis { name: String, description: String, @@ -53,7 +97,7 @@ impl PrincipalComponentAnalysis { parameters.push(ToolParameter { name: "Output HTML Report File".to_owned(), - flags: vec!["--out_html".to_owned()], + flags: vec!["--out_html".to_owned(), "--output".to_owned()], description: "Output HTML report file.".to_owned(), parameter_type: ParameterType::NewFile(ParameterFileType::Html), default_value: None, @@ -89,7 +133,7 @@ impl PrincipalComponentAnalysis { if e.contains(".exe") { short_exe += ".exe"; } - let usage = format!(">>.*{} -r={} -v --wd='*path*to*data*' -i='image1.tif;image2.tif;image3.tif' --out_html=report.html --num_comp=3 --standardized", short_exe, name).replace("*", &sep); + let usage = format!(">>.*{} -r={} -v --wd='*path*to*data*' -i='image1.tif;image2.tif;image3.tif' --output=report.html --num_comp=3 --standardized", short_exe, name).replace("*", &sep); PrincipalComponentAnalysis { name: name, @@ -138,6 +182,7 @@ impl WhiteboxTool for PrincipalComponentAnalysis { let mut input_files_str = String::new(); let mut output_html_file = String::new(); let mut num_comp = 0usize; + let mut num_comp_set = false; let mut standardized = false; if args.len() == 0 { @@ -162,7 +207,7 @@ impl WhiteboxTool for PrincipalComponentAnalysis { } else { args[i + 1].to_string() }; - } else if flag_val == "-out_html" { + } else if flag_val == "-out_html" || flag_val == "-output" { output_html_file = if keyval { vec[1].to_string() } else { @@ -174,6 +219,7 @@ impl WhiteboxTool for PrincipalComponentAnalysis { } else { args[i + 1].to_string().parse::().unwrap() as usize }; + num_comp_set = true; } else if flag_val == "-standardized" { standardized = true; } @@ -219,9 +265,10 @@ impl WhiteboxTool for PrincipalComponentAnalysis { let mut average = vec![0f64; num_files]; let mut num_cells = vec![0f64; num_files]; let mut input_raster: Vec = Vec::with_capacity(num_files); - let mut wd = "".to_string(); let mut file_names = vec![]; - println!("Calculating image means..."); + if verbose { + println!("Calculating image means..."); + } for i in 0..num_files { if !input_files[i].trim().is_empty() { // quality control on the image file name. @@ -244,8 +291,6 @@ impl WhiteboxTool for PrincipalComponentAnalysis { if rows == -1 || columns == -1 { rows = input_raster[i].configs.rows as isize; columns = input_raster[i].configs.columns as isize; - wd = input_file - .replace(&format!("{}.dep", input_raster[i].get_short_filename()), ""); } else { if input_raster[i].configs.rows as isize != rows || input_raster[i].configs.columns as isize != columns @@ -420,9 +465,9 @@ impl WhiteboxTool for PrincipalComponentAnalysis { } writer.write_all("

".as_bytes())?; - ///////////////////////////////////////// + //////////////////////////// // Factor Loadings Report // - ///////////////////////////////////////// + //////////////////////////// writer.write_all("

".as_bytes())?; writer.write_all("".as_bytes())?; writer.write_all("".as_bytes())?; @@ -432,7 +477,7 @@ impl WhiteboxTool for PrincipalComponentAnalysis { writer.write_all("".as_bytes())?; if !standardized { for j in 0..num_files { - let mut s = format!("", (j + 1)); for k in 0..num_files { pc = component_order[k]; let loading = (eigenvectors[pc * num_files + j] * eigenvalues[pc].sqrt()) @@ -507,10 +552,15 @@ impl WhiteboxTool for PrincipalComponentAnalysis { } // Output the component images + if num_comp == 0 && !num_comp_set { + // if it's not set, then output all the components. + num_comp = num_files; + } for a in 0..num_comp { pc = component_order[a]; - let out_file = format!("{}PCA_component{}.dep", wd, (a + 1)); + let out_file = format!("{}PCA_component{}.tif", working_directory, (a + 1)); let mut output = Raster::initialize_using_file(&out_file, &input_raster[0]); + output.configs.data_type = DataType::F32; for row in 0..rows { for col in 0..columns { z1 = input_raster[0].get_value(row, col); diff --git a/src/tools/stream_network_analysis/horton_order.rs b/src/tools/stream_network_analysis/horton_order.rs index ac2352785..fa257c068 100644 --- a/src/tools/stream_network_analysis/horton_order.rs +++ b/src/tools/stream_network_analysis/horton_order.rs @@ -28,7 +28,7 @@ use std::path; /// The user must specify the names of a streams raster image (`--streams`) and D8 pointer image (`--d8_pntr`). Stream cells /// are designated in the streams image as all positive, nonzero values. Thus all non-stream or background grid cells are /// commonly assigned either zeros or NoData values. The pointer image is used to traverse the stream network and should only -/// be created using the D8 algorithm. Background cells will be assigned the NoData value in the output image, unless the +/// be created using the D8 algorithm (`D8Pointer`). Background cells will be assigned the NoData value in the output image, unless the /// `--zero_background` parameter is used, in which case non-stream cells will be assinged zero values in the output. /// /// By default, the pointer raster is assumed to use the clockwise indexing method used by WhiteboxTools. diff --git a/src/tools/stream_network_analysis/long_profile.rs b/src/tools/stream_network_analysis/long_profile.rs index f8e34f440..647195182 100644 --- a/src/tools/stream_network_analysis/long_profile.rs +++ b/src/tools/stream_network_analysis/long_profile.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: February 20, 2018 +Created: 20/02/2018 Last Modified: 12/10/2018 License: MIT */ @@ -24,7 +24,29 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; -/// Plots the stream longitudinal profiles for one or more rivers. +/// This tool can be used to create a [longitudinal profile](http://www.fao.org/docrep/003/X6841E/X6841E02.HTM) plot. +/// A longitudinal stream profile is a plot of elevation against downstream distance. Most long profiles use distance +/// from channel head as the distance measure. This tool, however, uses the distance to the stream network outlet cell, +/// or mouth, as the distance measure. The reason for this difference is that while for any one location within a stream +/// network there is only ever one downstream outlet, there is usually many upstream channel heads. Thus plotted using +/// the traditional downstream-distance method, the same point within a network will plot in many different long profile +/// locations, whereas it will always plot on one unique location in the distance-to-mouth method. One consequence of +/// this difference is that the long profile will be oriented from right-to-left rather than left-to-right, as would +/// traditionally be the case. +/// +/// The tool outputs an interactive SVG line graph embedded in an HTML document (`--output`). The user must specify the +/// names of a D8 pointer (`--d8_pntr`) image (flow direction), a streams raster image +/// (`--streams`), and a digital elevation model (`--dem`). Stream cells are designated in the streams image as all +/// positive, nonzero values. Thus all non-stream or background grid cells are commonly assigned either zeros or NoData +/// values. The pointer image is used to traverse the stream network and should only be created using the D8 algorithm +/// (`D8Pointer`). The streams image should be derived using a flow accumulation based stream network extraction +/// algorithm, also based on the D8 flow algorithm. +/// +/// By default, the pointer raster is assumed to use the clockwise indexing method used by WhiteboxTools. +/// If the pointer file contains ESRI flow direction values instead, the `--esri_pntr` parameter must be specified. +/// +/// # See Also +/// `LongProfileFromPoints`, `Profile`, `D8Pointer` pub struct LongProfile { name: String, description: String, diff --git a/src/tools/stream_network_analysis/long_profile_from_points.rs b/src/tools/stream_network_analysis/long_profile_from_points.rs index 6a5b6317d..1f3803120 100644 --- a/src/tools/stream_network_analysis/long_profile_from_points.rs +++ b/src/tools/stream_network_analysis/long_profile_from_points.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: February 21, 2018 +Created: 21/02/2018 Last Modified: 12/10/2018 License: MIT */ diff --git a/src/tools/stream_network_analysis/raster_streams_to_vector.rs b/src/tools/stream_network_analysis/raster_streams_to_vector.rs index 414d681e4..1808bf1f3 100644 --- a/src/tools/stream_network_analysis/raster_streams_to_vector.rs +++ b/src/tools/stream_network_analysis/raster_streams_to_vector.rs @@ -25,6 +25,9 @@ use std::path; /// link in the stream network. The flow pointer file must be calculated from a DEM with /// all topographic depressions and flat areas removed and must be calculated using the /// D8 flow pointer algorithm. The output vector will contain PolyLine features. +/// +/// # See Also +/// `RasterizeStreams`, `RasterToVectorLines` pub struct RasterStreamsToVector { name: String, description: String, diff --git a/src/tools/stream_network_analysis/rasterize_streams.rs b/src/tools/stream_network_analysis/rasterize_streams.rs index 5eb4db211..3d9bc5dbb 100644 --- a/src/tools/stream_network_analysis/rasterize_streams.rs +++ b/src/tools/stream_network_analysis/rasterize_streams.rs @@ -24,7 +24,7 @@ use std::path; /// 41(5): 658–668. DOI: 10.1002/esp.3888 /// /// # See Also -/// `RasterizeStreams` +/// `RasterStreamsToVector` pub struct RasterizeStreams { name: String, description: String, diff --git a/src/tools/terrain_analysis/circular_variance_of_aspect.rs b/src/tools/terrain_analysis/circular_variance_of_aspect.rs index 68e15a126..52b4372a2 100644 --- a/src/tools/terrain_analysis/circular_variance_of_aspect.rs +++ b/src/tools/terrain_analysis/circular_variance_of_aspect.rs @@ -228,7 +228,9 @@ impl WhiteboxTool for CircularVarianceOfAspect { // Smooth the DEM let mut smoothed_dem = input.get_data_as_array2d(); - println!("Smoothing the input DEM..."); + if verbose { + println!("Smoothing the input DEM..."); + } let sigma = (midpoint as f64 - 0.5) / 3f64; if sigma < 1.8 && filter_size > 3 { let recip_root_2_pi_times_sigma_d = 1.0 / ((2.0 * f64::consts::PI).sqrt() * sigma); @@ -537,7 +539,9 @@ impl WhiteboxTool for CircularVarianceOfAspect { for col in 0..columns { sumx += xc.get_value(row, col); sumy += yc.get_value(row, col); - if smoothed_dem.get_value(row, col) == nodata { + if smoothed_dem.get_value(row, col) == nodata || + (xc.get_value(row, col) == 0f64 && yc.get_value(row, col) == 0f64) { + // it's either nodata or a flag cell in the DEM. i_n.decrement(row, col, 1); } sumn += i_n.get_value(row, col); @@ -553,7 +557,9 @@ impl WhiteboxTool for CircularVarianceOfAspect { xc.increment(row, col, xc.get_value(row, col-1)); yc.increment(row, col, yc.get_value(row, col-1)); i_n.increment(row, col, i_n.get_value(row, col-1)); - if smoothed_dem.get_value(row, col) == nodata { + if smoothed_dem.get_value(row, col) == nodata || + (xc.get_value(row, col) == 0f64 && yc.get_value(row, col) == 0f64) { + // it's either nodata or a flag cell in the DEM. i_n.decrement(row, col, 1); } } diff --git a/src/tools/terrain_analysis/drainage_preserving_smoothing.rs b/src/tools/terrain_analysis/drainage_preserving_smoothing.rs index 21496c53f..acf5b234b 100644 --- a/src/tools/terrain_analysis/drainage_preserving_smoothing.rs +++ b/src/tools/terrain_analysis/drainage_preserving_smoothing.rs @@ -2,7 +2,7 @@ This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay and Anthony Francioni Created: 06/09/2018 -Last Modified: 12/10/2018 +Last Modified: 31/01/2019 License: MIT */ @@ -88,7 +88,7 @@ impl DrainagePreservingSmoothing { flags: vec!["--num_iter".to_owned()], description: "Number of iterations.".to_owned(), parameter_type: ParameterType::Integer, - default_value: Some("10".to_owned()), + default_value: Some("3".to_owned()), optional: true, }); @@ -97,7 +97,7 @@ impl DrainagePreservingSmoothing { flags: vec!["--max_diff".to_owned()], description: "Maximum allowable absolute elevation change (optional).".to_owned(), parameter_type: ParameterType::Float, - default_value: Some("2.0".to_owned()), + default_value: Some("0.5".to_owned()), optional: true, }); @@ -205,7 +205,7 @@ impl WhiteboxTool for DrainagePreservingSmoothing { let mut output_file = String::new(); let mut filter_size = 11usize; let mut max_norm_diff = 8f64; - let mut num_iter = 5; + let mut num_iter = 3; let mut reduction = 80f64; let mut dfm_threshold = 0.15; let mut z_factor = 1f64; @@ -329,7 +329,7 @@ impl WhiteboxTool for DrainagePreservingSmoothing { if verbose { println!("Reading data...") - }; + } let input = Arc::new(Raster::new(&input_file, "r")?); @@ -609,13 +609,15 @@ impl WhiteboxTool for DrainagePreservingSmoothing { } let t1 = Instant::now(); - println!( - "{}", - format!( - "Calculating normal vectors: {}", - get_formatted_elapsed_time(start) - ) - ); + if verbose { + println!( + "{}", + format!( + "Calculating normal vectors: {}", + get_formatted_elapsed_time(start) + ) + ); + } ////////////////////////////////////////////////////////// // Smooth the normal vector field of the fitted planes. // @@ -720,13 +722,15 @@ impl WhiteboxTool for DrainagePreservingSmoothing { } } - println!( - "{}", - format!( - "Smoothing normal vectors: {}", - get_formatted_elapsed_time(t1) - ) - ); + if verbose { + println!( + "{}", + format!( + "Smoothing normal vectors: {}", + get_formatted_elapsed_time(t1) + ) + ); + } /////////////////////////////////////////////////////////////////////////// // Update the elevations of the DEM based on the smoothed normal vectors // @@ -747,9 +751,13 @@ impl WhiteboxTool for DrainagePreservingSmoothing { let mut zn: f64; let mut output = Raster::initialize_using_file(&output_file, &input); output.set_data_from_raster(&input)?; - println!("Updating elevations..."); + if verbose { + println!("Updating elevations..."); + } for loop_num in 0..num_iter { - println!("Iteration {} of {}...", loop_num + 1, num_iter); + if verbose { + println!("Iteration {} of {}...", loop_num + 1, num_iter); + } for row in 0..rows { for col in 0..columns { @@ -830,6 +838,7 @@ impl WhiteboxTool for DrainagePreservingSmoothing { output.add_metadata_entry(format!("Iterations: {}", num_iter)); output.add_metadata_entry(format!("Reduction factor: {}", reduction * 100f64)); output.add_metadata_entry(format!("DFM threhsold: {}", dfm_threshold)); + output.add_metadata_entry(format!("Max. z difference: {}", max_z_diff)); output.add_metadata_entry(format!("Z-factor: {}", z_factor)); output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); diff --git a/src/tools/terrain_analysis/elev_percentile.rs b/src/tools/terrain_analysis/elev_percentile.rs index 253185484..b30bbcad5 100644 --- a/src/tools/terrain_analysis/elev_percentile.rs +++ b/src/tools/terrain_analysis/elev_percentile.rs @@ -50,7 +50,7 @@ use std::thread; /// These dimensions should be odd, positive integer values (e.g. 3, 5, 7, 9, etc.). /// /// # References -/// Newman, D. R., Lindsay, J. B., & Cockburn, J. M. H. (2018). Evaluating metrics of local topographic position +/// Newman, D. R., Lindsay, J. B., and Cockburn, J. M. H. (2018). Evaluating metrics of local topographic position /// for multiscale geomorphometric analysis. Geomorphology, 312, 40-50. /// /// Huang, T., Yang, G.J.T.G.Y. and Tang, G., 1979. A fast two-dimensional median filtering algorithm. IEEE diff --git a/src/tools/terrain_analysis/feature_preserving_denoise.rs b/src/tools/terrain_analysis/feature_preserving_denoise.rs index 91c317ac5..8fc0e80cd 100644 --- a/src/tools/terrain_analysis/feature_preserving_denoise.rs +++ b/src/tools/terrain_analysis/feature_preserving_denoise.rs @@ -1,8 +1,8 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: November 23, 2017 -Last Modified: 12/10/2018 +Created: 23/11/2017 +Last Modified: 31/01/2019 License: MIT */ @@ -104,7 +104,7 @@ impl FeaturePreservingDenoise { flags: vec!["--num_iter".to_owned()], description: "Number of iterations.".to_owned(), parameter_type: ParameterType::Integer, - default_value: Some("10".to_owned()), + default_value: Some("3".to_owned()), optional: true, }); @@ -113,7 +113,7 @@ impl FeaturePreservingDenoise { flags: vec!["--max_diff".to_owned()], description: "Maximum allowable absolute elevation change (optional).".to_owned(), parameter_type: ParameterType::Float, - default_value: Some("2.0".to_owned()), + default_value: Some("0.5".to_owned()), optional: true, }); @@ -199,7 +199,7 @@ impl WhiteboxTool for FeaturePreservingDenoise { let mut output_file = String::new(); let mut filter_size = 11usize; let mut max_norm_diff = 8f64; - let mut num_iter = 5; + let mut num_iter = 3; let mut z_factor = 1f64; let mut max_z_diff = f64::INFINITY; @@ -295,7 +295,7 @@ impl WhiteboxTool for FeaturePreservingDenoise { if verbose { println!("Reading data...") - }; + } let input = Arc::new(Raster::new(&input_file, "r")?); @@ -391,13 +391,15 @@ impl WhiteboxTool for FeaturePreservingDenoise { } let t1 = Instant::now(); - println!( - "{}", - format!( - "Calculating normal vectors: {}", - get_formatted_elapsed_time(start) - ) - ); + if verbose { + println!( + "{}", + format!( + "Calculating normal vectors: {}", + get_formatted_elapsed_time(start) + ) + ); + } ////////////////////////////////////////////////////////// // Smooth the normal vector field of the fitted planes. // @@ -503,13 +505,15 @@ impl WhiteboxTool for FeaturePreservingDenoise { } } - println!( - "{}", - format!( - "Smoothing normal vectors: {}", - get_formatted_elapsed_time(t1) - ) - ); + if verbose { + println!( + "{}", + format!( + "Smoothing normal vectors: {}", + get_formatted_elapsed_time(t1) + ) + ); + } /////////////////////////////////////////////////////////////////////////// // Update the elevations of the DEM based on the smoothed normal vectors // @@ -528,9 +532,13 @@ impl WhiteboxTool for FeaturePreservingDenoise { let mut zn: f64; let mut output = Raster::initialize_using_file(&output_file, &input); output.set_data_from_raster(&input)?; - println!("Updating elevations..."); + if verbose { + println!("Updating elevations..."); + } for loop_num in 0..num_iter { - println!("Iteration {} of {}...", loop_num + 1, num_iter); + if verbose { + println!("Iteration {} of {}...", loop_num + 1, num_iter); + } for row in 0..rows { for col in 0..columns { @@ -598,12 +606,13 @@ impl WhiteboxTool for FeaturePreservingDenoise { output.add_metadata_entry(format!("Filter size: {}", filter_size)); output.add_metadata_entry(format!("Normal difference threshold: {}", max_norm_diff)); output.add_metadata_entry(format!("Iterations: {}", num_iter)); + output.add_metadata_entry(format!("Max. z difference: {}", max_z_diff)); output.add_metadata_entry(format!("Z-factor: {}", z_factor)); output.add_metadata_entry(format!("Elapsed Time (excluding I/O): {}", elapsed_time)); if verbose { println!("Saving data...") - }; + } let _ = match output.write() { Ok(_) => { if verbose { diff --git a/src/tools/terrain_analysis/percent_elev_range.rs b/src/tools/terrain_analysis/percent_elev_range.rs index 7e2dab28f..1446d9c05 100644 --- a/src/tools/terrain_analysis/percent_elev_range.rs +++ b/src/tools/terrain_analysis/percent_elev_range.rs @@ -34,11 +34,11 @@ use std::thread; /// to outliers in neighbouring elevations (e.g. the presence of off-terrain objects in the DEM). /// /// # References -/// Newman, D. R., Lindsay, J. B., & Cockburn, J. M. H. (2018). Evaluating metrics of local topographic position +/// Newman, D. R., Lindsay, J. B., and Cockburn, J. M. H. (2018). Evaluating metrics of local topographic position /// for multiscale geomorphometric analysis. Geomorphology, 312, 40-50. /// /// # See Also -/// `ElevPercentile`, `DevFromMeanElev`, `DiffFromMeanElev` +/// `ElevPercentile`, `DevFromMeanElev`, `DiffFromMeanElev`, `RelativeTopographicPosition` pub struct PercentElevRange { name: String, description: String, diff --git a/src/tools/terrain_analysis/profile.rs b/src/tools/terrain_analysis/profile.rs index 5c66fe835..b5761a492 100644 --- a/src/tools/terrain_analysis/profile.rs +++ b/src/tools/terrain_analysis/profile.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: February 21, 2018 +Created: 21/02/2018 Last Modified: 12/10/2018 License: MIT */ @@ -20,6 +20,17 @@ use std::io::{Error, ErrorKind}; use std::path; use std::process::Command; +/// This tool can be used to plot the data profile, along a set of one or more vector lines (`--lines`), in +/// an input (`--surface`) digital elevation model (DEM), or other surface model. The data profile plots +/// surface height (y-axis) against distance along profile (x-axis). The tool outputs an interactive SVG line +/// graph embedded in an HTML document (`--output`). If the vector lines file contains multiple line features, +/// the output plot will contain each of the input profiles. +/// +/// If you want to extract the [longitudinal profile](http://www.fao.org/docrep/003/X6841E/X6841E02.HTM) of a river, +/// use the `LongProfile` tool instead. +/// +/// # See Also +/// `LongProfile`, `HypsometricAnalysis` pub struct Profile { name: String, description: String, diff --git a/src/tools/terrain_analysis/relative_aspect.rs b/src/tools/terrain_analysis/relative_aspect.rs index 034267921..d306e91f6 100644 --- a/src/tools/terrain_analysis/relative_aspect.rs +++ b/src/tools/terrain_analysis/relative_aspect.rs @@ -17,6 +17,23 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; +/// This tool creates a new raster in which each grid cell is assigned the terrain aspect relative to a user-specified +/// direction (`--azimuth`). Relative terrain aspect is the angular distance (measured in degrees) between the land-surface +/// aspect and the assumed regional wind azimuth (Bohner and Antonic, 2007). It is bound between 0-degrees (windward direction) +/// and 180-degrees (leeward direction). Relative terrain aspect is the simplest of the measures of topographic exposure to +/// wind, taking into account terrain orientation only and neglecting the influences of topographic shadowing by distant +/// landforms and the deflection of wind by topography. +/// +/// The user must specify the name of a digital elevation model (DEM) (`--dem`) and an azimuth (i.e. a wind direction). The +/// Z Conversion Factor (`--zfactor`) is only important when the vertical and horizontal units are not the same in the DEM. +/// When this is the case, the algorithm will multiply each elevation in the DEM by the Z Conversion Factor. +/// +/// # Reference +/// Böhner, J., and Antonić, O. (2009). Land-surface parameters specific to topo-climatology. Developments in Soil +/// Science, 33, 195-226. +/// +/// # See Also +/// `Aspect` pub struct RelativeAspect { name: String, description: String, diff --git a/src/tools/terrain_analysis/relative_topographic_position.rs b/src/tools/terrain_analysis/relative_topographic_position.rs index c85e560ee..f183dff33 100644 --- a/src/tools/terrain_analysis/relative_topographic_position.rs +++ b/src/tools/terrain_analysis/relative_topographic_position.rs @@ -1,7 +1,7 @@ /* This tool is part of the WhiteboxTools geospatial analysis library. Authors: Dr. John Lindsay -Created: June 6, 2017 +Created: 06/06/2017 Last Modified: 12/10/2018 License: MIT */ @@ -18,6 +18,35 @@ use std::sync::mpsc; use std::sync::Arc; use std::thread; +/// Relative topographic position (RTP) is an index of local topographic position (i.e. how +/// elevated or low-lying a site is relative to its surroundings) and is a modification of percent +/// elevation range (PER; `PercentElevRange`) and accounts for the elevation distribution. Rather than +/// positioning the central cell's elevation solely between the filter extrema, RTP is a piece-wise +/// function that positions the central elevation relative to the minimum (zmin), mean (μ), +/// and maximum values (zmax), within a local neighbourhood of a user-specified size (`--filterx`, +/// `--filtery`), such that: +/// +/// > RTP = (z0 − μ) / (μ − zmin), if z0 < μ +/// > +/// > OR +/// > +/// > RTP = (z0 − μ) / (zmax - μ), if z0 >= μ +///  +/// +/// The resulting index is bound by the interval [−1, 1], where the sign indicates if the cell is above or below +/// than the filter mean. Although RTP uses the mean to define two linear functions, the reliance on the filter +/// extrema is expected to result in sensitivity to outliers. Furthermore, the use of the mean implies assumptions +/// of unimodal and symmetrical elevation distribution. +/// +/// In many cases, Elevation Percentile (`ElevPercentile`) and deviation from mean elevation (`DevFromMeanElev`) +/// provide more suitable and robust measures of relative topographic position. +/// +/// # Reference +/// Newman, D. R., Lindsay, J. B., and Cockburn, J. M. H. (2018). Evaluating metrics of local topographic +/// position for multiscale geomorphometric analysis. Geomorphology, 312, 40-50. +/// +/// # See Also +/// `DevFromMeanElev`, `DiffFromMeanElev`, `ElevPercentile`, `PercentElevRange` pub struct RelativeTopographicPosition { name: String, description: String, diff --git a/whitebox_plugin_generator.py b/whitebox_plugin_generator.py index 3b465397c..f3a480e1a 100644 --- a/whitebox_plugin_generator.py +++ b/whitebox_plugin_generator.py @@ -123,8 +123,7 @@ def camel_to_snake(s): # fn_def = fn_def.rstrip(', ') fn_def += "callback=None):" - doc_str += "{}callback -- Custom function for handling tool text outputs.".format( - st_val) + doc_str += " callback -- Custom function for handling tool text outputs." fn = """ {} diff --git a/whitebox_tools.py b/whitebox_tools.py index 540859916..f0a005d93 100644 --- a/whitebox_tools.py +++ b/whitebox_tools.py @@ -366,6 +366,8 @@ def list_tools(self, keywords=[]): + + ############## # Data Tools # ############## @@ -376,7 +378,7 @@ def add_point_coordinates_to_table(self, i, callback=None): Keyword arguments: i -- Input vector Points file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -548,7 +550,7 @@ def print_geo_tiff_tags(self, i, callback=None): Keyword arguments: i -- Input GeoTIFF file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -588,7 +590,7 @@ def reinitialize_attribute_table(self, i, callback=None): Keyword arguments: i -- Input vector file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -966,18 +968,20 @@ def extract_nodes(self, i, output, callback=None): args.append("--output='{}'".format(output)) return self.run_tool('extract_nodes', args, callback) # returns 1 if error - def extract_raster_values_at_points(self, inputs, points, callback=None): + def extract_raster_values_at_points(self, inputs, points, out_text=False, callback=None): """Extracts the values of raster(s) at vector point locations. Keyword arguments: inputs -- Input raster files. points -- Input vector points file. + out_text -- Output point values as text? Otherwise, the only output is to to the points file's attribute table. callback -- Custom function for handling tool text outputs. """ args = [] args.append("--inputs='{}'".format(inputs)) args.append("--points='{}'".format(points)) + if out_text: args.append("--out_text") return self.run_tool('extract_raster_values_at_points', args, callback) # returns 1 if error def find_lowest_or_highest_points(self, i, output, out_type="lowest", callback=None): @@ -1148,7 +1152,7 @@ def polygon_area(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1174,7 +1178,7 @@ def polygon_perimeter(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1860,7 +1864,7 @@ def compactness_ratio(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1888,7 +1892,7 @@ def elongation_ratio(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1914,7 +1918,7 @@ def hole_proportion(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1926,7 +1930,7 @@ def linearity_index(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1938,7 +1942,7 @@ def patch_orientation(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1950,7 +1954,7 @@ def perimeter_area_ratio(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1978,7 +1982,7 @@ def related_circumscribing_circle(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -1990,7 +1994,7 @@ def shape_complexity_index(self, i, callback=None): Keyword arguments: i -- Input vector polygon file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i)) @@ -2104,7 +2108,7 @@ def downslope_index(self, dem, output, drop=2.0, out_type="tangent", callback=No args.append("--out_type={}".format(out_type)) return self.run_tool('downslope_index', args, callback) # returns 1 if error - def drainage_preserving_smoothing(self, dem, output, filter=11, norm_diff=15.0, num_iter=10, max_diff=2.0, reduction=80.0, dfm=0.15, zfactor=1.0, callback=None): + def drainage_preserving_smoothing(self, dem, output, filter=11, norm_diff=15.0, num_iter=3, max_diff=0.5, reduction=80.0, dfm=0.15, zfactor=1.0, callback=None): """Reduces short-scale variation in an input DEM while preserving breaks-in-slope and small drainage features using a modified Sun et al. (2007) algorithm. Keyword arguments: @@ -2216,7 +2220,7 @@ def elev_relative_to_watershed_min_max(self, dem, watersheds, output, callback=N args.append("--output='{}'".format(output)) return self.run_tool('elev_relative_to_watershed_min_max', args, callback) # returns 1 if error - def feature_preserving_denoise(self, dem, output, filter=11, norm_diff=15.0, num_iter=10, max_diff=2.0, zfactor=1.0, callback=None): + def feature_preserving_denoise(self, dem, output, filter=11, norm_diff=15.0, num_iter=3, max_diff=0.5, zfactor=1.0, callback=None): """Reduces short-scale variation in an input DEM using a modified Sun et al. (2007) algorithm. Keyword arguments: @@ -3418,7 +3422,7 @@ def hillslopes(self, d8_pntr, streams, output, esri_pntr=False, callback=None): if esri_pntr: args.append("--esri_pntr") return self.run_tool('hillslopes', args, callback) # returns 1 if error - def impoundment_index(self, dem, output, damlength, out_type="depth", callback=None): + def impoundment_size_index(self, dem, output, damlength, out_type="depth", callback=None): """Calculates the impoundment size resulting from damming a DEM. Keyword arguments: @@ -3434,7 +3438,7 @@ def impoundment_index(self, dem, output, damlength, out_type="depth", callback=N args.append("--output='{}'".format(output)) args.append("--out_type={}".format(out_type)) args.append("--damlength='{}'".format(damlength)) - return self.run_tool('impoundment_index', args, callback) # returns 1 if error + return self.run_tool('impoundment_size_index', args, callback) # returns 1 if error def isobasins(self, dem, output, size, callback=None): """Divides a landscape into nearly equal sized drainage basins (i.e. watersheds). @@ -3969,7 +3973,7 @@ def opening(self, i, output, filterx=11, filtery=11, callback=None): return self.run_tool('opening', args, callback) # returns 1 if error def remove_spurs(self, i, output, iterations=10, callback=None): - """Removes the spurs (pruning operation) from a Boolean line image.; intended to be used on the output of the LineThinning tool. + """Removes the spurs (pruning operation) from a Boolean line image; intended to be used on the output of the LineThinning tool. Keyword arguments: @@ -4753,7 +4757,7 @@ def direct_decorrelation_stretch(self, i, output, k=0.5, clip=1.0, callback=None return self.run_tool('direct_decorrelation_stretch', args, callback) # returns 1 if error def gamma_correction(self, i, output, gamma=0.5, callback=None): - """Performs a sigmoidal contrast stretch on input images. + """Performs a gamma correction on an input images. Keyword arguments: @@ -4876,7 +4880,7 @@ def panchromatic_sharpening(self, pan, output, red=None, green=None, blue=None, args.append("--method={}".format(method)) return self.run_tool('panchromatic_sharpening', args, callback) # returns 1 if error - def percentage_contrast_stretch(self, i, output, clip=0.0, tail="both", num_tones=256, callback=None): + def percentage_contrast_stretch(self, i, output, clip=1.0, tail="both", num_tones=256, callback=None): """Performs a percentage linear contrast stretch on input images. Keyword arguments: @@ -5040,7 +5044,7 @@ def las_to_ascii(self, inputs, callback=None): Keyword arguments: inputs -- Input LiDAR files. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--inputs='{}'".format(inputs)) @@ -5052,7 +5056,7 @@ def las_to_multipoint_shapefile(self, i=None, callback=None): Keyword arguments: i -- Input LiDAR file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] if i is not None: args.append("--input='{}'".format(i)) @@ -5064,7 +5068,7 @@ def las_to_shapefile(self, i=None, callback=None): Keyword arguments: i -- Input LiDAR file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] if i is not None: args.append("--input='{}'".format(i)) @@ -5542,14 +5546,14 @@ def lidar_thin_high_density(self, i, output, density, resolution=1.0, save_filte if save_filtered: args.append("--save_filtered") return self.run_tool('lidar_thin_high_density', args, callback) # returns 1 if error - def lidar_tile(self, i, width_x=1000.0, width_y=1000.0, origin_x=0.0, origin_y=0.0, min_points=2, callback=None): + def lidar_tile(self, i, width=1000.0, height=1000.0, origin_x=0.0, origin_y=0.0, min_points=2, callback=None): """Tiles a LiDAR LAS file into multiple LAS files. Keyword arguments: i -- Input LiDAR file. - width_x -- Width of tiles in the X dimension; default 1000.0. - width_y -- Width of tiles in the Y dimension. + width -- Width of tiles in the X dimension; default 1000.0. + height -- Height of tiles in the Y dimension. origin_x -- Origin point X coordinate for tile grid. origin_y -- Origin point Y coordinate for tile grid. min_points -- Minimum number of points contained in a tile for it to be saved. @@ -5557,8 +5561,8 @@ def lidar_tile(self, i, width_x=1000.0, width_y=1000.0, origin_x=0.0, origin_y=0 """ args = [] args.append("--input='{}'".format(i)) - args.append("--width_x={}".format(width_x)) - args.append("--width_y={}".format(width_y)) + args.append("--width={}".format(width)) + args.append("--height={}".format(height)) args.append("--origin_x={}".format(origin_x)) args.append("--origin_y={}".format(origin_y)) args.append("--min_points={}".format(min_points)) @@ -6438,20 +6442,20 @@ def power(self, input1, input2, output, callback=None): args.append("--output='{}'".format(output)) return self.run_tool('power', args, callback) # returns 1 if error - def principal_component_analysis(self, inputs, out_html, num_comp=None, standardized=False, callback=None): + def principal_component_analysis(self, inputs, output, num_comp=None, standardized=False, callback=None): """Performs a principal component analysis (PCA) on a multi-spectral dataset. Keyword arguments: inputs -- Input raster files. - out_html -- Output HTML report file. + output -- Output HTML report file. num_comp -- Number of component images to output; <= to num. input images. standardized -- Perform standardized PCA?. callback -- Custom function for handling tool text outputs. """ args = [] args.append("--inputs='{}'".format(inputs)) - args.append("--out_html='{}'".format(out_html)) + args.append("--output='{}'".format(output)) if num_comp is not None: args.append("--num_comp='{}'".format(num_comp)) if standardized: args.append("--standardized") return self.run_tool('principal_component_analysis', args, callback) # returns 1 if error @@ -6522,7 +6526,7 @@ def raster_summary_stats(self, i, callback=None): Keyword arguments: i -- Input raster file. -callback -- Custom function for handling tool text outputs. + callback -- Custom function for handling tool text outputs. """ args = [] args.append("--input='{}'".format(i))
Factor Loadings
Image
{}", (j + 1)); + let mut s = format!("{}