Skip to content

Commit

Permalink
r.confusionmatrix: Support usage of a single class in reference (#409)
Browse files Browse the repository at this point in the history
* add handling for single class
* remove unnecessary line printing
* add test
* change csv.writer linedelimiter to `\n`
  • Loading branch information
griembauer committed Jan 20, 2021
1 parent 3b61e41 commit 076ca29
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 26 deletions.
29 changes: 22 additions & 7 deletions grass7/raster/r.confusionmatrix/r.confusionmatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ def get_r_kappa(classification, reference):
grass.run_command(
'r.kappa', flags='wmh', classification=classification,
reference=reference, output=tmp_csv, quiet=True)

# read csv: error matrix
errorlist = None
with open(tmp_csv) as csvfile:
Expand Down Expand Up @@ -220,9 +219,12 @@ def convert_output(classified_classes, ref_classes, confusionmatrix, overall_acc
line2.extend(confusionmatrix[0,:].tolist()[0])
line2.extend([user_accuracy[classified_classes[0]], commission[classified_classes[0]]])

line3 = [classification, classified_classes[1]]
line3.extend(confusionmatrix[1,:].tolist()[0])
line3.extend([user_accuracy[classified_classes[1]], commission[classified_classes[1]]])
if len(ref_classes) == 1:
line3 = [classification, '', '', '', '']
else:
line3 = [classification, classified_classes[1]]
line3.extend(confusionmatrix[1,:].tolist()[0])
line3.extend([user_accuracy[classified_classes[1]], commission[classified_classes[1]]])

lines = [["", "", "Reference Map", refname], line1, line2, line3]
for i in range(2, confusionmatrix.shape[0]):
Expand All @@ -231,7 +233,7 @@ def convert_output(classified_classes, ref_classes, confusionmatrix, overall_acc
linei.extend([user_accuracy[classified_classes[i]], commission[classified_classes[i]]])
lines.append(linei)
producer_accuracy_list = [producer_accuracy[rc] for rc in ref_classes]
commission_list = [commission[rc] for rc in classified_classes]
# commission_list = [commission[rc] for rc in classified_classes]
lineend1 = ["", "Producer Accuracy"]
lineend1.extend(producer_accuracy_list)
lineend1.extend(['Overall Accuracy', overall_accuracy])
Expand Down Expand Up @@ -339,6 +341,17 @@ def main():
if not flags['m']:
grass.message("\nKappa coefficient: %f" % kappa)

# round values to two digits
for item in user_accuracy.items():
user_accuracy[item[0]] = round(item[1], 2)
for item in producer_accuracy.items():
producer_accuracy[item[0]] = round(item[1], 2)
for item in commission.items():
commission[item[0]] = round(item[1], 2)
for item in omission.items():
omission[item[0]] = round(item[1], 2)
overall_accuracy = round(overall_accuracy, 2)
kappa = round(kappa, 2)
# in matrix style
if flags['m'] or options['csvfile']:
lines = convert_output(
Expand All @@ -349,14 +362,16 @@ def main():
# write csv file
if csv_filename:
with open(csv_filename, 'w') as file:
writer = csv.writer(file)
writer = csv.writer(file, lineterminator='\n')
for line in lines:
writer.writerow(line)
if flags['m']:
for line in lines:
# for stdout using print
print(line)

if len(ref_classes) == 1:
grass.warning(_('Only one class in reference dataset.'))

# cleanup
if options['vector_reference']:
grass.run_command('g.remove', type='raster', name=reference, flags='f', quiet=True)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
,,Reference Map,landclass96
,,developed,agriculture,herbaceous,shrubland,forest,water,sediment,Class_7,Class_8,Class_9,Class_10,Class_11,Class_12,Class_13,Class_14,User Accuracy,Commission Error
Classified Map,High Intensity Developed,34931,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100.0,0.0
landuse96_28m,Low Intensity Developed,38146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Cultivated,0,2137,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Managed Herbaceous Cover,0,0,25488,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Upland Herbaceous,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Riverine/Estuarine Herbaceous,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Evergreen Shrubland,0,0,0,16464,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Deciduous Shrubland,0,0,0,320,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Mixed Shrubland,0,0,0,45,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Mixed Hardwoods,0,0,0,0,7893,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Bottomland Hardwoods/Hardwood Swamps,0,0,0,0,19733,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Southern Yellow Pine,0,0,0,0,64774,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Mixed Hardwoods/Conifers,0,0,0,0,33865,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Water Bodies,0,0,0,0,0,5303,0,0,0,0,0,0,0,0,0,0.0,100.0
,Unconsolidated Sediment,0,0,0,0,0,0,194,0,0,0,0,0,0,0,0,0.0,100.0
,Producer Accuracy,47.800265473404764,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,Overall Accuracy,14.010340000721955
,Omission Error,52.199734526595236,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,Kappa coefficient,0.09464798548745997
,,Reference Map,landclass96
,,developed,agriculture,herbaceous,shrubland,forest,water,sediment,Class_7,Class_8,Class_9,Class_10,Class_11,Class_12,Class_13,Class_14,User Accuracy,Commission Error
Classified Map,High Intensity Developed,34931,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100.0,0.0
landuse96_28m,Low Intensity Developed,38146,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Cultivated,0,2137,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Managed Herbaceous Cover,0,0,25488,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Upland Herbaceous,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Riverine/Estuarine Herbaceous,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Evergreen Shrubland,0,0,0,16464,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Deciduous Shrubland,0,0,0,320,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Mixed Shrubland,0,0,0,45,0,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Mixed Hardwoods,0,0,0,0,7893,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Bottomland Hardwoods/Hardwood Swamps,0,0,0,0,19733,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Southern Yellow Pine,0,0,0,0,64774,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Mixed Hardwoods/Conifers,0,0,0,0,33865,0,0,0,0,0,0,0,0,0,0,0.0,100.0
,Water Bodies,0,0,0,0,0,5303,0,0,0,0,0,0,0,0,0,0.0,100.0
,Unconsolidated Sediment,0,0,0,0,0,0,194,0,0,0,0,0,0,0,0,0.0,100.0
,Producer Accuracy,47.8,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,Overall Accuracy,14.01
,Omission Error,52.2,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,Kappa coefficient,0.09
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
,,Reference Map,landclass96_class1only
,,Class_0,User Accuracy,Commission Error
Classified Map,Class_0,73077,100.0,0.0
landuse96_28m_class1only,,,,
,Producer Accuracy,100.0,Overall Accuracy,100.0
,Omission Error,0.0,Kappa coefficient,nan
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@

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

class Testconfusionmatrix(TestCase):
classification = 'landuse96_28m'
classification_oneclass = 'landuse96_28m_class1only'
raster_ref = 'landclass96'
raster_ref_oneclass = 'landclass96_class1only'
output_csv = 'output_confusionmatrix.csv'

@classmethod
Expand All @@ -25,11 +28,19 @@ def setUpClass(cls):
cls.use_temp_region()
# set region to classification
cls.runModule('g.region', raster=cls.classification)
cls.runModule('r.mapcalc', expression='%s = if(%s==1,1, null())' % (
cls.raster_ref_oneclass, cls.raster_ref))
cls.runModule('r.mapcalc', expression='%s = 1' % (
cls.classification_oneclass))

@classmethod
def tearDownClass(cls):
"""Remove the temporary region and generated data"""
cls.del_temp_region()
cls.runModule('g.remove', type='raster',
name='%s,%s' % (
cls.raster_ref_oneclass, cls.classification_oneclass),
flags='f')

def tearDown(self):
"""Remove the outputs created
Expand All @@ -50,10 +61,29 @@ def test_confusionmatrix_with_raster_reference(self):
self.assertFileExists(self.output_csv,
msg='Output file does not exist')
# check if the output file is equal to the reference file

self.assertFilesEqualMd5(self.output_csv,
'data/confusionmatrix_raster_matrix.csv',
msg='Output file is not equal to reference file')

def test_confusionmatrix_with_singleclass(self):
""" Test confusionmatrix with a single class reference and classification"""
r_confusion = SimpleModule('r.confusionmatrix',
classification=self.classification_oneclass,
raster_reference=self.raster_ref_oneclass,
flags='m',
csvfile=self.output_csv)
self.assertModule(r_confusion)
# check to see if output file exists
self.assertFileExists(self.output_csv,
msg='Output file does not exist')
# check if the output file is equal to the reference file
self.assertFilesEqualMd5(self.output_csv,
'data/confusionmatrix_raster_matrix_oneclass.csv',
msg='Output file is not equal to reference file')
#test that the warning is shown
stderr = r_confusion.outputs.stderr
self.assertIn('Only one class in reference dataset.', stderr)


if __name__ == '__main__':
Expand Down

0 comments on commit 076ca29

Please sign in to comment.