Skip to content

Commit

Permalink
Imagery: add support of original class value in sig file (OSGeo#2425)
Browse files Browse the repository at this point in the history
* Imagery: use original raster values for classification output in workflow i.gensig -> i.maxlik (fixes bug OSGeo#2413)
  • Loading branch information
marisn authored Jun 18, 2022
1 parent 1957229 commit 79f9500
Show file tree
Hide file tree
Showing 10 changed files with 552 additions and 7 deletions.
3 changes: 3 additions & 0 deletions imagery/i.gensig/get_train.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ int get_training_classes(struct files *files, struct Signature *S)
/* convert this to an array */
Rast_rewind_cell_stats(&cell_stats);
n = 0;
S->have_oclass = 1;
while (Rast_next_cell_stat(&cat, &count, &cell_stats)) {
if (count > 1) {
I_new_signature(S);
Expand All @@ -48,6 +49,8 @@ int get_training_classes(struct files *files, struct Signature *S)
Rast_get_c_cat(&cat, &files->training_labels),
sizeof(S->sig[n].desc)
);
S->sig[n].desc[255] = '\0'; /* desc is limited to 256 */
S->sig[n].oclass = cat;
n++;
}
else
Expand Down
14 changes: 14 additions & 0 deletions imagery/i.gensig/testsuite/data/b1.ascii
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
north: 5
south: 0
east: 5
west: 0
rows: 5
cols: 5
null: n
type: float

3.1 2.9 8.8 8.9 9.0
2.8 2.7 8.9 9.0 9.1
6.5 6.8 8.7 8.8 8.9
6.9 6.7 2.8 2.9 3.1
7.1 6.8 2.7 3.0 3.1
14 changes: 14 additions & 0 deletions imagery/i.gensig/testsuite/data/b2.ascii
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
north: 5
south: 0
east: 5
west: 0
rows: 5
cols: 5
null: n
type: int

8 8 1 1 2
7 7 2 1 2
9 9 2 2 1
8 9 7 7 6
8 8 8 7 8
14 changes: 14 additions & 0 deletions imagery/i.gensig/testsuite/data/train.ascii
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
north: 5
south: 0
east: 5
west: 0
rows: 5
cols: 5
null: n
type: int

1 n n n 6
n 1 6 6 n
9 n n n 6
9 n n n 1
n 9 n 1 n
159 changes: 159 additions & 0 deletions imagery/i.gensig/testsuite/test_i_gensig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
Name: i.gensig general functionality
Purpose: Test ability to generate signature files
Author: Maris Nartiss
Copyright: (C) 2022 by Maris Nartiss and the GRASS Development Team
Licence: This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS
for details.
"""

import os
import stat
import ctypes
import shutil

from grass.pygrass import utils
from grass.pygrass.gis import Mapset
from grass.script.core import tempname

from grass.gunittest.case import TestCase
from grass.gunittest.main import test

from grass.lib.gis import G_mapset_path
from grass.lib.raster import Rast_write_semantic_label
from grass.lib.imagery import (
Signature,
Ref,
I_init_group_ref,
I_add_file_to_group_ref,
I_put_group_ref,
I_put_subgroup_ref,
I_fopen_signature_file_old,
I_read_signatures,
)


class SuccessTest(TestCase):
"""Test successful generation of a sig file"""

@classmethod
def setUpClass(cls):
"""Ensures expected computational region and generated data"""
cls.use_temp_region()
cls.runModule("g.region", n=5, s=0, e=5, w=0, res=1)
cls.data_dir = "data"
cls.mpath = utils.decode(G_mapset_path())
cls.mapset_name = Mapset().name

# Load imagery bands and training set
cls.b1 = tempname(10)
cls.runModule(
"r.in.ascii",
input=os.path.join(cls.data_dir, "b1.ascii"),
output=cls.b1,
)
cls.b2 = tempname(10)
cls.runModule(
"r.in.ascii",
input=os.path.join(cls.data_dir, "b2.ascii"),
output=cls.b2,
)
cls.train = tempname(10)
cls.runModule(
"r.in.ascii",
input=os.path.join(cls.data_dir, "train.ascii"),
output=cls.train,
)
cls.group = tempname(10)
cls.semantic_label1 = "The_Doors"
cls.semantic_label2 = "The_Who"
Rast_write_semantic_label(cls.b1, cls.semantic_label1)
Rast_write_semantic_label(cls.b2, cls.semantic_label2)
Rg = Ref()
I_init_group_ref(ctypes.byref(Rg))
I_add_file_to_group_ref(cls.b1, cls.mapset_name, ctypes.byref(Rg))
I_add_file_to_group_ref(cls.b2, cls.mapset_name, ctypes.byref(Rg))
I_put_group_ref(cls.group, ctypes.byref(Rg))
Rs = Ref()
I_init_group_ref(ctypes.byref(Rs))
I_add_file_to_group_ref(cls.b1, cls.mapset_name, ctypes.byref(Rs))
I_add_file_to_group_ref(cls.b2, cls.mapset_name, ctypes.byref(Rs))
I_put_subgroup_ref(cls.group, cls.group, ctypes.byref(Rs))

# Location of target signature file
cls.sig_name1 = tempname(10)
cls.sig_dir1 = f"{cls.mpath}/signatures/sig/{cls.sig_name1}"

@classmethod
def tearDownClass(cls):
"""Remove the temporary region and generated data"""
cls.del_temp_region()
shutil.rmtree(cls.sig_dir1, ignore_errors=True)
cls.runModule("g.remove", flags="f", type="raster", name=cls.b1, quiet=True)
cls.runModule("g.remove", flags="f", type="raster", name=cls.b2, quiet=True)
cls.runModule("g.remove", flags="f", type="raster", name=cls.train, quiet=True)

def test_creation(self):
"""Test creating a signature"""
self.assertModule(
"i.gensig",
trainingmap=self.train,
group=self.group,
subgroup=self.group,
signaturefile=self.sig_name1,
quiet=True,
)

# File must be present
sig_stat = os.stat(f"{self.sig_dir1}/sig")
self.assertTrue(stat.S_ISREG(sig_stat.st_mode))

# Compare values within sig file
Sn = Signature()
p_old_sigfile = I_fopen_signature_file_old(self.sig_name1)
ret = I_read_signatures(p_old_sigfile, ctypes.byref(Sn))
self.assertEqual(ret, 1)
self.assertEqual(Sn.nbands, 2)
self.assertEqual(Sn.nsigs, 3)
self.assertEqual(Sn.have_oclass, 1)
semantic_label = utils.decode(
ctypes.cast(Sn.semantic_labels[0], ctypes.c_char_p).value
)
self.assertEqual(semantic_label, self.semantic_label1)
semantic_label = utils.decode(
ctypes.cast(Sn.semantic_labels[1], ctypes.c_char_p).value
)
self.assertEqual(semantic_label, self.semantic_label2)
# 1
self.assertEqual(Sn.sig[0].status, 1)
self.assertEqual(Sn.sig[0].have_color, 0)
self.assertEqual(Sn.sig[0].npoints, 4)
self.assertEqual(Sn.sig[0].oclass, 1)
self.assertTrue(abs(Sn.sig[0].mean[0] - 2.9) < 0.1)
self.assertTrue(abs(Sn.sig[0].mean[1] - 7.0) < 0.1)
self.assertTrue(abs(Sn.sig[0].var[0][0] - 0.03) < 0.01)
self.assertTrue(abs(Sn.sig[0].var[1][1] - 0.6) < 0.1)
# 6
self.assertEqual(Sn.sig[1].status, 1)
self.assertEqual(Sn.sig[1].have_color, 0)
self.assertEqual(Sn.sig[1].npoints, 4)
self.assertEqual(Sn.sig[1].oclass, 6)
self.assertTrue(abs(Sn.sig[1].mean[0] - 8.9) < 0.1)
self.assertTrue(abs(Sn.sig[1].mean[1] - 1.5) < 0.1)
self.assertTrue(abs(Sn.sig[1].var[0][0] - 0.003) < 0.001)
self.assertTrue(abs(Sn.sig[1].var[1][1] - 0.3) < 0.1)
# 9
self.assertEqual(Sn.sig[2].status, 1)
self.assertEqual(Sn.sig[2].have_color, 0)
self.assertEqual(Sn.sig[2].npoints, 3)
self.assertEqual(Sn.sig[2].oclass, 9)
self.assertTrue(abs(Sn.sig[2].mean[0] - 6.7) < 0.1)
self.assertTrue(abs(Sn.sig[2].mean[1] - 8.3) < 0.1)
self.assertTrue(abs(Sn.sig[2].var[0][0] - 0.043) < 0.01)
self.assertTrue(abs(Sn.sig[2].var[1][1] - 0.3) < 0.1)


if __name__ == "__main__":
test()
15 changes: 12 additions & 3 deletions imagery/i.maxlik/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ int main(int argc, char *argv[])
Rast_get_d_row(cellfd[band], cell[band], row);

classify(class_cell, reject_cell, ncols);
Rast_put_row(class_fd, class_cell, CELL_TYPE);
if (S.have_oclass) {
for (int col = 0; col < ncols; col++) {
/* Predicted classes start at 1 but signature array is 0 based */
class_cell[col] = S.sig[class_cell[col] - 1].oclass;
}
}
Rast_put_row(class_fd, class_cell, CELL_TYPE);
if (reject_fd > 0)
Rast_put_row(reject_fd, reject_cell, CELL_TYPE);
}
Expand All @@ -133,8 +139,11 @@ int main(int argc, char *argv[])
Rast_init_cats("Maximum Likelihood Classification", &cats);
for (i = 0; i < S.nsigs; i++) {
if (*S.sig[i].desc) {
cat = i + 1;
Rast_set_c_cat(&cat, &cat, S.sig[i].desc, &cats);
if (S.have_oclass)
cat = S.sig[i].oclass;
else
cat = i + 1;
Rast_set_c_cat(&cat, &cat, S.sig[i].desc, &cats);
}
}
Rast_write_cats(class_name, &cats);
Expand Down
Loading

0 comments on commit 79f9500

Please sign in to comment.