pygeotools 1.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygeotools/__init__.py +8 -0
- pygeotools/apply_mask.py +116 -0
- pygeotools/clip_raster_by_shp.py +64 -0
- pygeotools/copyproj.py +9 -0
- pygeotools/filter.py +143 -0
- pygeotools/lib/__init__.py +3 -0
- pygeotools/lib/filtlib.py +635 -0
- pygeotools/lib/geolib.py +2251 -0
- pygeotools/lib/iolib.py +624 -0
- pygeotools/lib/malib.py +1992 -0
- pygeotools/lib/timelib.py +651 -0
- pygeotools/lib/warplib.py +561 -0
- pygeotools/make_stack.py +59 -0
- pygeotools/proj_select.py +52 -0
- pygeotools/raster2shp.py +69 -0
- pygeotools/replace_ndv.py +66 -0
- pygeotools/trim_ndv.py +62 -0
- pygeotools/warptool.py +69 -0
- pygeotools-1.1.2.dist-info/METADATA +141 -0
- pygeotools-1.1.2.dist-info/RECORD +23 -0
- pygeotools-1.1.2.dist-info/WHEEL +5 -0
- pygeotools-1.1.2.dist-info/licenses/LICENSE +21 -0
- pygeotools-1.1.2.dist-info/top_level.txt +1 -0
pygeotools/__init__.py
ADDED
pygeotools/apply_mask.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
|
|
3
|
+
#David Shean
|
|
4
|
+
#dshean@gmail.com
|
|
5
|
+
|
|
6
|
+
#Utility to mask input raster using the mask from another raster
|
|
7
|
+
#Mask dataset can be a standard raster with nodata value specified or a binary mask
|
|
8
|
+
#If a binary mask, values should be:
|
|
9
|
+
#True (1) for masked, False (0) for valid - consistent with np.ma
|
|
10
|
+
|
|
11
|
+
import sys, os
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
from osgeo import gdal
|
|
16
|
+
|
|
17
|
+
from pygeotools.lib import iolib
|
|
18
|
+
from pygeotools.lib import warplib
|
|
19
|
+
|
|
20
|
+
def getparser():
|
|
21
|
+
parser = argparse.ArgumentParser(description="Apply existing mask to input raster")
|
|
22
|
+
#Should add support for similar arguments as in warplib - arbitrary extent, res, etc
|
|
23
|
+
parser.add_argument('-extent', type=str, default='raster', \
|
|
24
|
+
choices=['raster','mask','intersection','union'], help='Desired output extent')
|
|
25
|
+
parser.add_argument('-invert', action='store_true', help='Invert input mask')
|
|
26
|
+
parser.add_argument('-mask_val', type=float, default=None, \
|
|
27
|
+
help='If input mask_fn is classified raster, specify value to use as mask')
|
|
28
|
+
parser.add_argument('-out_fn', type=str, default=None, help='Output filename')
|
|
29
|
+
parser.add_argument('src_fn', type=str, help='Input raster filename')
|
|
30
|
+
parser.add_argument('mask_fn', type=str, help='Input mask filename (can be existing raster with ndv, or binary mask)')
|
|
31
|
+
return parser
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
parser = getparser()
|
|
35
|
+
args = parser.parse_args()
|
|
36
|
+
|
|
37
|
+
src_fn = args.src_fn
|
|
38
|
+
if not iolib.fn_check(src_fn):
|
|
39
|
+
sys.exit("Unable to find src_fn: %s" % src_fn)
|
|
40
|
+
|
|
41
|
+
mask_fn = args.mask_fn
|
|
42
|
+
if not iolib.fn_check(mask_fn):
|
|
43
|
+
sys.exit("Unable to find mask_fn: %s" % mask_fn)
|
|
44
|
+
|
|
45
|
+
#Determine output extent, default is input raster extent
|
|
46
|
+
extent = args.extent
|
|
47
|
+
if extent == 'raster':
|
|
48
|
+
extent = src_fn
|
|
49
|
+
elif extent == 'mask':
|
|
50
|
+
extent = mask_fn
|
|
51
|
+
else:
|
|
52
|
+
#This is a hack for intersection computation
|
|
53
|
+
src_ds_list = [gdal.Open(fn, gdal.GA_ReadOnly) for fn in [src_fn, mask_fn]]
|
|
54
|
+
#t_srs = geolib.get_ds_srs(src_ds_list[0])
|
|
55
|
+
extent = warplib.parse_extent(extent, src_ds_list, src_fn)
|
|
56
|
+
|
|
57
|
+
#Set resampling algorithm appropriately
|
|
58
|
+
r='cubic'
|
|
59
|
+
mask_val=args.mask_val
|
|
60
|
+
if mask_val is not None:
|
|
61
|
+
r='near'
|
|
62
|
+
|
|
63
|
+
print("Warping mask_fn")
|
|
64
|
+
mask_ds = warplib.memwarp_multi_fn([mask_fn,], res=src_fn, extent=extent, t_srs=src_fn, r=r)[0]
|
|
65
|
+
|
|
66
|
+
print("Loading mask array")
|
|
67
|
+
mask_ma_full = iolib.ds_getma(mask_ds)
|
|
68
|
+
mask_ds = None
|
|
69
|
+
|
|
70
|
+
print("Extracting mask")
|
|
71
|
+
if mask_val is not None:
|
|
72
|
+
#Use specified value
|
|
73
|
+
mask = ~((mask_ma_full == mask_val).data)
|
|
74
|
+
elif mask_ma_full.std() != 0:
|
|
75
|
+
#Input mask filename is a raster, or other masked array
|
|
76
|
+
#Just need to extract mask
|
|
77
|
+
mask = np.ma.getmaskarray(mask_ma_full)
|
|
78
|
+
else:
|
|
79
|
+
#Input mask filename is a mask, use directly
|
|
80
|
+
#If input mask values are zero, valid values are nonzero
|
|
81
|
+
#Bool True == 1, so need to invert
|
|
82
|
+
if mask_ma_full.fill_value == 0:
|
|
83
|
+
mask = ~((mask_ma_full.data).astype(bool))
|
|
84
|
+
else:
|
|
85
|
+
mask = (mask_ma_full.data).astype(bool)
|
|
86
|
+
|
|
87
|
+
#Free up memory
|
|
88
|
+
mask_ma_full = None
|
|
89
|
+
|
|
90
|
+
#Add dilation step for buffer
|
|
91
|
+
|
|
92
|
+
#newmask = np.logical_or(np.ma.getmaskarray(src_ma_full), mask)
|
|
93
|
+
|
|
94
|
+
if args.invert:
|
|
95
|
+
print("Inverting mask")
|
|
96
|
+
mask = ~(mask)
|
|
97
|
+
|
|
98
|
+
print("Loading src array and applying updated mask")
|
|
99
|
+
if extent == src_fn:
|
|
100
|
+
src_ds = gdal.Open(src_fn)
|
|
101
|
+
else:
|
|
102
|
+
src_ds = warplib.memwarp_multi_fn([src_fn,], res=src_fn, extent=extent, t_srs=src_fn)[0]
|
|
103
|
+
|
|
104
|
+
#Now load source array with new mask
|
|
105
|
+
src_ma_full = np.ma.array(iolib.ds_getma(src_ds), mask=mask)
|
|
106
|
+
mask = None
|
|
107
|
+
|
|
108
|
+
if args.out_fn is not None:
|
|
109
|
+
src_fn_masked = args.out_fn
|
|
110
|
+
else:
|
|
111
|
+
src_fn_masked = os.path.splitext(src_fn)[0]+'_masked.tif'
|
|
112
|
+
print("Writing out masked version of input raster: %s" % src_fn_masked )
|
|
113
|
+
iolib.writeGTiff(src_ma_full, src_fn_masked, src_ds, create=True)
|
|
114
|
+
|
|
115
|
+
if __name__ == '__main__':
|
|
116
|
+
main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
|
|
3
|
+
#David Shean
|
|
4
|
+
#dshean@gmail.com
|
|
5
|
+
|
|
6
|
+
#Clip input raster to polygons in input shapefile
|
|
7
|
+
#Finally ported clip_raster_by_shp.sh to Python
|
|
8
|
+
|
|
9
|
+
#TODO: Handle same arbitrary res/extent/t_srs as in warplib (isolate/generalize those functions)
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
from osgeo import ogr
|
|
17
|
+
|
|
18
|
+
from pygeotools.lib import iolib
|
|
19
|
+
from pygeotools.lib import geolib
|
|
20
|
+
|
|
21
|
+
def getparser():
|
|
22
|
+
parser = argparse.ArgumentParser(description="Clip input raster by input shp polygons")
|
|
23
|
+
#Should add support for similar arguments as in warplib - arbitrary extent, res, etc
|
|
24
|
+
parser.add_argument('-extent', type=str, default='raster', choices=['raster','shp','intersection','union'],
|
|
25
|
+
help='Desired output extent')
|
|
26
|
+
parser.add_argument('-bbox', action='store_true', help='Clip raster to shp bounding box, but dont mask')
|
|
27
|
+
parser.add_argument('-pad', type=float, default=None, help='Padding around shp extent, in raster units')
|
|
28
|
+
parser.add_argument('-invert', action='store_true', help='Invert the input polygons before clipping')
|
|
29
|
+
parser.add_argument('-out_fn', type=str, default=None, help='Output raster filename (default: *_shpclip.tif)')
|
|
30
|
+
parser.add_argument('r_fn', type=str, help='Input raster filename')
|
|
31
|
+
parser.add_argument('shp_fn', type=str, help='Input shp filename')
|
|
32
|
+
return parser
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
parser = getparser()
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
r_fn = args.r_fn
|
|
39
|
+
if not os.path.exists(r_fn):
|
|
40
|
+
sys.exit("Unable to find r_fn: %s" % r_fn)
|
|
41
|
+
|
|
42
|
+
shp_fn = args.shp_fn
|
|
43
|
+
#Convenience shortcut to clip to glacier polygons (global shp)
|
|
44
|
+
#Requires demcoreg package: https://github.com/dshean/demcoreg
|
|
45
|
+
if shp_fn == 'RGI' or shp_fn == 'rgi':
|
|
46
|
+
from demcoreg.dem_mask import get_glacier_poly
|
|
47
|
+
rgi_fn = get_glacier_poly()
|
|
48
|
+
shp_fn = rgi_fn
|
|
49
|
+
|
|
50
|
+
if not os.path.exists(shp_fn):
|
|
51
|
+
sys.exit("Unable to find shp_fn: %s" % shp_fn)
|
|
52
|
+
|
|
53
|
+
#Do the clipping
|
|
54
|
+
r, r_ds = geolib.raster_shpclip(r_fn, shp_fn, extent=args.extent, bbox=args.bbox, pad=args.pad, invert=args.invert)
|
|
55
|
+
|
|
56
|
+
#Write out
|
|
57
|
+
out_fn = args.out_fn
|
|
58
|
+
if out_fn is None:
|
|
59
|
+
out_fn = os.path.splitext(r_fn)[0]+'_shpclip.tif'
|
|
60
|
+
#Note: passing r_fn here as the src_ds
|
|
61
|
+
iolib.writeGTiff(r, out_fn, r_ds)
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
pygeotools/copyproj.py
ADDED
pygeotools/filter.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Command-line wrapper around raster filters in filtlib
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
#Note: currently need to specify fn first, as -param accepts arbitrary number of arguments
|
|
7
|
+
#Need better way to record params than in filename - write history to header?
|
|
8
|
+
#Precision on float
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from pygeotools.lib import iolib
|
|
17
|
+
from pygeotools.lib import malib
|
|
18
|
+
from pygeotools.lib import filtlib
|
|
19
|
+
from pygeotools.lib import warplib
|
|
20
|
+
|
|
21
|
+
def getparser():
|
|
22
|
+
filter_choices = ['range', 'absrange', 'perc', 'gauss', 'med', 'highpass', 'sigma', 'mad', 'dz']
|
|
23
|
+
parser = argparse.ArgumentParser(description='Filter input raster')
|
|
24
|
+
parser.add_argument('fn', help='Input filename (img1.tif)')
|
|
25
|
+
parser.add_argument('--stats', action='store_true', help='Print stats before and after filtering')
|
|
26
|
+
parser.add_argument('-outdir', default=None, help='Output directory')
|
|
27
|
+
#Should implement subparser here to handle different number of args for different filter types
|
|
28
|
+
#https://docs.python.org/2/library/argparse.html#sub-commands
|
|
29
|
+
#Can call functions directly
|
|
30
|
+
#Could specify sequence of filters here
|
|
31
|
+
#Should accept arbitrary number of ordered filter operations as cli argument
|
|
32
|
+
parser.add_argument('-filt', nargs=1, default='gauss', choices=filter_choices, help='Filter type (default: %(default)s)')
|
|
33
|
+
#size is a param
|
|
34
|
+
#parser.add_argument('-size', type=int, default=7, help='Filter size in pixels (default: %(default)s)')
|
|
35
|
+
parser.add_argument('-param', nargs='+', default=None, help='Filter parameter list (e.g., size, min max, ref_fn min max)')
|
|
36
|
+
return parser
|
|
37
|
+
|
|
38
|
+
def main():
|
|
39
|
+
parser = getparser()
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
|
|
42
|
+
fn = args.fn
|
|
43
|
+
if not iolib.fn_check(fn):
|
|
44
|
+
sys.exit("Unable to locate input file: %s" % fn)
|
|
45
|
+
|
|
46
|
+
#Need some checks on these
|
|
47
|
+
param = args.param
|
|
48
|
+
|
|
49
|
+
print("Loading input raster into masked array")
|
|
50
|
+
ds = iolib.fn_getds(fn)
|
|
51
|
+
#Currently supports only single band operations
|
|
52
|
+
r = iolib.ds_getma(ds, 1)
|
|
53
|
+
|
|
54
|
+
#May need to cast input ma as float32 so np.nan filling works
|
|
55
|
+
#r = r.astype(np.float32)
|
|
56
|
+
#Want function that checks and returns float32 if necessary
|
|
57
|
+
#Should filter, then return original dtype
|
|
58
|
+
|
|
59
|
+
r_fltr = r
|
|
60
|
+
|
|
61
|
+
#Loop through all specified input filters
|
|
62
|
+
#for filt in args.filt:
|
|
63
|
+
filt = args.filt[0]
|
|
64
|
+
|
|
65
|
+
if len(param) == 1:
|
|
66
|
+
param = param[0]
|
|
67
|
+
param_str = ''
|
|
68
|
+
|
|
69
|
+
if filt == 'range':
|
|
70
|
+
#Range filter
|
|
71
|
+
param = [float(i) for i in param[1:]]
|
|
72
|
+
r_fltr = filtlib.range_fltr(r_fltr, param)
|
|
73
|
+
param_str = '_{0:0.2f}-{1:0.2f}'.format(*param)
|
|
74
|
+
elif filt == 'absrange':
|
|
75
|
+
#Range filter of absolute values
|
|
76
|
+
param = [float(i) for i in param[1:]]
|
|
77
|
+
r_fltr = filtlib.absrange_fltr(r_fltr, param)
|
|
78
|
+
param_str = '_{0:0.2f}-{1:0.2f}'.format(*param)
|
|
79
|
+
elif filt == 'perc':
|
|
80
|
+
#Percentile filter
|
|
81
|
+
param = [float(i) for i in param[1:]]
|
|
82
|
+
r_fltr = filtlib.perc_fltr(r, perc=param)
|
|
83
|
+
param_str = '_{0:0.2f}-{1:0.2f}'.format(*param)
|
|
84
|
+
elif filt == 'med':
|
|
85
|
+
#Median filter
|
|
86
|
+
param = int(param)
|
|
87
|
+
r_fltr = filtlib.rolling_fltr(r_fltr, f=np.nanmedian, size=param)
|
|
88
|
+
#r_fltr = filtlib.median_fltr(r_fltr, fsize=param, origmask=True)
|
|
89
|
+
#r_fltr = filtlib.median_fltr_skimage(r_fltr, radius=4, origmask=True)
|
|
90
|
+
param_str = '_%ipx' % param
|
|
91
|
+
elif filt == 'gauss':
|
|
92
|
+
#Gaussian filter (default)
|
|
93
|
+
param = int(param)
|
|
94
|
+
r_fltr = filtlib.gauss_fltr_astropy(r_fltr, size=param, origmask=False, fill_interior=False)
|
|
95
|
+
param_str = '_%ipx' % param
|
|
96
|
+
elif filt == 'highpass':
|
|
97
|
+
#High pass filter
|
|
98
|
+
param = int(param)
|
|
99
|
+
r_fltr = filtlib.highpass(r_fltr, size=param)
|
|
100
|
+
param_str = '_%ipx' % param
|
|
101
|
+
elif filt == 'sigma':
|
|
102
|
+
#n*sigma filter, remove outliers
|
|
103
|
+
param = int(param)
|
|
104
|
+
r_fltr = filtlib.sigma_fltr(r_fltr, n=param)
|
|
105
|
+
param_str = '_n%i' % param
|
|
106
|
+
elif filt == 'mad':
|
|
107
|
+
#n*mad filter, remove outliers
|
|
108
|
+
#Maybe better to use a percentile filter
|
|
109
|
+
param = int(param)
|
|
110
|
+
r_fltr = filtlib.mad_fltr(r_fltr, n=param)
|
|
111
|
+
param_str = '_n%i' % param
|
|
112
|
+
elif filt == 'dz':
|
|
113
|
+
#Difference filter, need to specify ref_fn and range
|
|
114
|
+
#Could let the user compute their own dz, then just run a standard range or absrange filter
|
|
115
|
+
ref_fn = param[0]
|
|
116
|
+
ref_ds = warplib.memwarp_multi_fn([ref_fn,], res=ds, extent=ds, t_srs=ds)[0]
|
|
117
|
+
ref = iolib.ds_getma(ref_ds)
|
|
118
|
+
param = [float(i) for i in param[1:]]
|
|
119
|
+
r_fltr = filtlib.dz_fltr_ma(r, ref, rangelim=param)
|
|
120
|
+
#param_str = '_{0:0.2f}-{1:0.2f}'.format(*param)
|
|
121
|
+
param_str = '_{0:0.0f}_{1:0.0f}'.format(*param)
|
|
122
|
+
else:
|
|
123
|
+
sys.exit("No filter type specified")
|
|
124
|
+
|
|
125
|
+
#Compute and print stats before/after
|
|
126
|
+
if args.stats:
|
|
127
|
+
print("Input stats:")
|
|
128
|
+
malib.print_stats(r)
|
|
129
|
+
print("Filtered stats:")
|
|
130
|
+
malib.print_stats(r_fltr)
|
|
131
|
+
|
|
132
|
+
#Write out
|
|
133
|
+
dst_fn = os.path.splitext(fn)[0]+'_%sfilt%s.tif' % (filt, param_str)
|
|
134
|
+
if args.outdir is not None:
|
|
135
|
+
outdir = args.outdir
|
|
136
|
+
if not os.path.exists(outdir):
|
|
137
|
+
os.makedirs(outdir)
|
|
138
|
+
dst_fn = os.path.join(outdir, os.path.split(dst_fn)[-1])
|
|
139
|
+
print("Writing out filtered raster: %s" % dst_fn)
|
|
140
|
+
iolib.writeGTiff(r_fltr, dst_fn, ds)
|
|
141
|
+
|
|
142
|
+
if __name__ == '__main__':
|
|
143
|
+
main()
|