Skip to content

Conversation

@YannChemin
Copy link
Contributor

i.spec.convexhull

This module processes all raster bands in a GRASS imagery group, performing continuum removal via convex hull analysis on the spectral profile of each pixel. The result is a new group of raster maps, each corresponding to an input band, with the continuum-removed spectra.

This functionality is very commonly used in mineral mapping in specific parts of hyperspectral SWIR ranges.

Original Signatures
Figure_original

Convexhull applied to signatures
Figure_convexhull

@neteler neteler added the new addon PR contains a new addon or issue proposes one label Jun 30, 2025
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

clang-format

[clang-format] reported by reviewdog 🐶

create_group(output_group->answer);


[clang-format] reported by reviewdog 🐶

add_file_to_group(output_group->answer, outname[i], G_mapset());


[clang-format] reported by reviewdog 🐶

if (infd[i] >= 0) Rast_close(infd[i]);
if (outfd[i] >= 0) Rast_close(outfd[i]);

double y;
} Point;


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change



// Function to calculate the orientation of three points (p, q, r)
double orientation(Point p, Point q, Point r) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
double orientation(Point p, Point q, Point r) {
double orientation(Point p, Point q, Point r)
{

}

// Function to find the convex hull of a set of points
void convexHull(Point *points, int n, Point *hull, int *hullSize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
void convexHull(Point *points, int n, Point *hull, int *hullSize) {
void convexHull(Point *points, int n, Point *hull, int *hullSize)
{

// Storage for the convex hull
*hullSize = 0;

// Start from the leftmost point and move clockwise until the point with maximum x-coordinate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
// Start from the leftmost point and move clockwise until the point with maximum x-coordinate
// Start from the leftmost point and move clockwise until the point with
// maximum x-coordinate

// Add the current point to the result
hull[(*hullSize)++] = points[p];

// Search for a point 'q' such that orientation(p, q, x) is clockwise for all points 'x'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
// Search for a point 'q' such that orientation(p, q, x) is clockwise for all points 'x'
// Search for a point 'q' such that orientation(p, q, x) is clockwise
// for all points 'x'

// Load nrows and ncols
nrows = Rast_window_rows();
ncols = Rast_window_cols();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change

Rast_get_row(infd[i], inbuf[i], row, DCELL_TYPE);
}
// Process each column (pixel spectrum reconstitution)
//#pragma omp parallel for private(i, din, dout)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
//#pragma omp parallel for private(i, din, dout)
// #pragma omp parallel for private(i, din, dout)

din[i] = ((double *)inbuf[i])[col];
}
// Process convex hull per spectrum
convexhull(din, dout, ref.nfiles);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
convexhull(din, dout, ref.nfiles);
convexhull(din, dout, ref.nfiles);

// Clean the output_group from any pre-existing list
if (I_find_group(output_group->answer)) {
if (!G_check_overwrite(argc, argv)) {
G_fatal_error(_("Group <%s> exists. Use --overwrite to replace."), output_group->answer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
G_fatal_error(_("Group <%s> exists. Use --overwrite to replace."), output_group->answer);
G_fatal_error(_("Group <%s> exists. Use --overwrite to replace."),
output_group->answer);

Comment on lines +144 to +145
create_group(output_group->answer);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
create_group(output_group->answer);
} else {
create_group(output_group->answer);
}
else {

Comment on lines +17 to +57
#%module
#% description: Export moving window samples from an imagery group and two rasters.
#% keyword: imagery
#% keyword: ANN
#% keyword: training
#% keyword: export
#%end
#%option G_OPT_I_GROUP
#% description: Imagery group name
#% required: yes
#%end
#%option G_OPT_R_INPUT
#% key: raster1
#% description: First raster input (clump)
#% required: yes
#%end
#%option G_OPT_R_INPUT
#% key: raster2
#% description: Second raster input (value)
#% required: yes
#%end
#%option
#% key: output_directory
#% type: string
#% description: Output directory
#% required: yes
#%end
#%option
#% key: window_width
#% type: integer
#% description: Moving window width in cells
#% required: yes
#% answer: 128
#%end
#%option
#% key: window_height
#% type: integer
#% description: Moving window height in cells
#% required: yes
#% answer: 128
#%end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
#%module
#% description: Export moving window samples from an imagery group and two rasters.
#% keyword: imagery
#% keyword: ANN
#% keyword: training
#% keyword: export
#%end
#%option G_OPT_I_GROUP
#% description: Imagery group name
#% required: yes
#%end
#%option G_OPT_R_INPUT
#% key: raster1
#% description: First raster input (clump)
#% required: yes
#%end
#%option G_OPT_R_INPUT
#% key: raster2
#% description: Second raster input (value)
#% required: yes
#%end
#%option
#% key: output_directory
#% type: string
#% description: Output directory
#% required: yes
#%end
#%option
#% key: window_width
#% type: integer
#% description: Moving window width in cells
#% required: yes
#% answer: 128
#%end
#%option
#% key: window_height
#% type: integer
#% description: Moving window height in cells
#% required: yes
#% answer: 128
#%end
# %module
# % description: Export moving window samples from an imagery group and two rasters.
# % keyword: imagery
# % keyword: ANN
# % keyword: training
# % keyword: export
# %end
# %option G_OPT_I_GROUP
# % description: Imagery group name
# % required: yes
# %end
# %option G_OPT_R_INPUT
# % key: raster1
# % description: First raster input (clump)
# % required: yes
# %end
# %option G_OPT_R_INPUT
# % key: raster2
# % description: Second raster input (value)
# % required: yes
# %end
# %option
# % key: output_directory
# % type: string
# % description: Output directory
# % required: yes
# %end
# %option
# % key: window_width
# % type: integer
# % description: Moving window width in cells
# % required: yes
# % answer: 128
# %end
# %option
# % key: window_height
# % type: integer
# % description: Moving window height in cells
# % required: yes
# % answer: 128
# %end

import grass.script as gs
from grass.script import array as garray

def read_group_bands(group_name):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
def read_group_bands(group_name):
def read_group_bands(group_name):

from grass.script import array as garray

def read_group_bands(group_name):
bands = gs.read_command('i.group', group=group_name, format='shell').splitlines()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
bands = gs.read_command('i.group', group=group_name, format='shell').splitlines()
bands = gs.read_command("i.group", group=group_name, format="shell").splitlines()

bands = gs.read_command('i.group', group=group_name, format='shell').splitlines()
return bands

def extract_window(array, row_off, col_off, height, width):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
def extract_window(array, row_off, col_off, height, width):
def extract_window(array, row_off, col_off, height, width):

return bands

def extract_window(array, row_off, col_off, height, width):
return array[row_off:row_off+height, col_off:col_off+width]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
return array[row_off:row_off+height, col_off:col_off+width]
return array[row_off : row_off + height, col_off : col_off + width]

Comment on lines +150 to +151
xres = (region['e'] - region['w']) / ncols
yres = (region['n'] - region['s']) / nrows
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
xres = (region['e'] - region['w']) / ncols
yres = (region['n'] - region['s']) / nrows
xres = (region["e"] - region["w"]) / ncols
yres = (region["n"] - region["s"]) / nrows

Comment on lines +153 to +154
transform = from_origin(region['w'], region['n'], xres, yres)
wkt = gs.read_command('g.proj', flags='w').strip()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
transform = from_origin(region['w'], region['n'], xres, yres)
wkt = gs.read_command('g.proj', flags='w').strip()
transform = from_origin(region["w"], region["n"], xres, yres)
wkt = gs.read_command("g.proj", flags="w").strip()

idx = 0
for row in range(0, nrows - window_height + 1, window_height):
for col in range(0, ncols - window_width + 1, window_width):
window_bands = [extract_window(b, row, col, window_height, window_width) for b in band_arrays]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
window_bands = [extract_window(b, row, col, window_height, window_width) for b in band_arrays]
window_bands = [
extract_window(b, row, col, window_height, window_width)
for b in band_arrays
]

for clump_id in clump_ids:
if clump_id == 0:
continue
mask = (win1 == clump_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
mask = (win1 == clump_id)
mask = win1 == clump_id

Comment on lines +177 to +179
out_group = os.path.join(out_subdir, f"group_{clump_id}_{corresponding_value}.tif")
export_multiband_geotiff([b * mask for b in window_bands], out_group, transform, crs)
out_r = os.path.join(out_subdir, f"{raster1}_clump{clump_id}_val{corresponding_value}.tif")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ruff] reported by reviewdog 🐶

Suggested change
out_group = os.path.join(out_subdir, f"group_{clump_id}_{corresponding_value}.tif")
export_multiband_geotiff([b * mask for b in window_bands], out_group, transform, crs)
out_r = os.path.join(out_subdir, f"{raster1}_clump{clump_id}_val{corresponding_value}.tif")
out_group = os.path.join(
out_subdir, f"group_{clump_id}_{corresponding_value}.tif"
)
export_multiband_geotiff(
[b * mask for b in window_bands], out_group, transform, crs
)
out_r = os.path.join(
out_subdir,
f"{raster1}_clump{clump_id}_val{corresponding_value}.tif",
)

create_group(output_group->answer);
} else {
// Proceed to create
create_group(output_group->answer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
create_group(output_group->answer);
create_group(output_group->answer);


// Close files and add to group
for (i = 0; i < ref.nfiles; i++) {
add_file_to_group(output_group->answer, outname[i], G_mapset());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
add_file_to_group(output_group->answer, outname[i], G_mapset());
add_file_to_group(output_group->answer, outname[i], G_mapset());

Comment on lines +155 to +156
if (infd[i] >= 0) Rast_close(infd[i]);
if (outfd[i] >= 0) Rast_close(outfd[i]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[clang-format] reported by reviewdog 🐶

Suggested change
if (infd[i] >= 0) Rast_close(infd[i]);
if (outfd[i] >= 0) Rast_close(outfd[i]);
if (infd[i] >= 0)
Rast_close(infd[i]);
if (outfd[i] >= 0)
Rast_close(outfd[i]);

@@ -0,0 +1,7 @@
MODULE_TOPDIR = ../..

PGM = i.ann.mktrainset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @YannChemin Please consider to submit the new i.ann.mktrainset addon as a separate pull request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He is submitting PRs with the default branch, so that's what is happening. There isn't a branch for each of the PRs (and we get notified of his CI failures as that branch is involved in a PR we are involved in)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new addon PR contains a new addon or issue proposes one

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants