Repository: rasterio/rasterio

Issue:
Expose GDALChecksumImage
http://www.gdal.org/gdal__alg_8h.html#abe2846ac3a350c24f07dc9ceac8f4d14

I propose to expose this not as a dataset method but as a standalone function and
- add a `--checksums` option to rio-info that will print the checksum for a specified band
- add a "checksums" item to the JSON output in `--verbose` mode containing a list of checksums for all bands



Discussion:
@sgillies sounds like a good plan.


Resolving patch:
diff --git a/rasterio/rio/info.py b/rasterio/rio/info.py
--- a/rasterio/rio/info.py
+++ b/rasterio/rio/info.py
@@ -257,6 +257,9 @@ def env(ctx, key):
 @click.option('--stats', 'meta_member', flag_value='stats',
               help="Print statistics (min, max, mean) of a single band "
                    "(use --bidx).")
+@click.option('--checksum', 'meta_member', flag_value='checksum',
+              help="Print integer checksum of a single band "
+                   "(use --bidx).")
 @click.option('-v', '--tell-me-more', '--verbose', is_flag=True,
               help="Output extra information.")
 @options.bidx_opt
@@ -292,6 +295,7 @@ def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx,
                               'mean': float(b.mean())
                               } for b in src.read(masked=masked)]
                     info['stats'] = stats
+                    info['checksum'] = [src.checksum(i) for i in src.indexes]
                 if aspect == 'meta':
                     if meta_member == 'stats':
                         band = src.read(bidx, masked=masked)
@@ -299,6 +303,8 @@ def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx,
                             float(band.min()),
                             float(band.max()),
                             float(band.mean())))
+                    elif meta_member == 'checksum':
+                        click.echo(str(src.checksum(bidx)))
                     elif meta_member:
                         if isinstance(info[meta_member], (list, tuple)):
                             click.echo(" ".join(map(str, info[meta_member])))


Test patch:
diff --git a/tests/test_checksum.py b/tests/test_checksum.py
new file mode 100644
--- /dev/null
+++ b/tests/test_checksum.py
@@ -0,0 +1,45 @@
+import rasterio
+
+
+def test_checksum_band():
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        checksums = [src.checksum(i) for i in src.indexes]
+        assert checksums == [25420, 29131, 37860]
+
+
+def test_checksum_band_window():
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        window = ((0, src.height), (0, src.width))
+        checksums = [src.checksum(i, window=window) for i in src.indexes]
+        assert checksums == [25420, 29131, 37860]
+
+
+def test_checksum_band_window_min():
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        window = ((0, 1), (0, 1))
+        checksums = [src.checksum(i, window=window) for i in src.indexes]
+        assert checksums == [0, 0, 0]
+
+
+def test_checksum_band_window_quarter():
+    """A quarter window's checksum is different from the full image's"""
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        window = ((0, src.height//2), (0, src.width//2))
+        checksums = [src.checksum(i, window=window) for i in src.indexes]
+        assert checksums != [25420, 29131, 37860]
+
+
+def test_checksum_band_window_too_tall():
+    """Windows get truncated to maximum extent"""
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        window = ((0, 10000), (0, src.width))
+        checksums = [src.checksum(i, window=window) for i in src.indexes]
+        assert checksums == [25420, 29131, 37860]
+
+
+def test_checksum_band_window_too_short():
+    """Negative values in windows get evaluated properly"""
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        window = ((0, -1), (0, src.width))
+        checksums = [src.checksum(i, window=window) for i in src.indexes]
+        assert checksums == [25420, 29131, 37860]
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
--- a/tests/test_rio_info.py
+++ b/tests/test_rio_info.py
@@ -641,3 +641,19 @@ def test_insp_err():
         'tests'
     ])
     assert result.exit_code == 1
+
+
+def test_info_checksums():
+    runner = CliRunner()
+    result = runner.invoke(
+        info.info, ['tests/data/RGB.byte.tif', '--tell-me-more'])
+    assert result.exit_code == 0
+    assert '"checksum": [25420, 29131, 37860]' in result.output
+
+
+def test_info_checksums_only():
+    runner = CliRunner()
+    result = runner.invoke(
+        info.info, ['tests/data/RGB.byte.tif', '--checksum', '--bidx', '2'])
+    assert result.exit_code == 0
+    assert result.output.strip() == '29131'