Commit 18368aa95c6e8cd961dd3316cf9eecb14ea45815
1 parent
e0e01dfb
added a new set of python tools for working with ENVI files, as well as basic cl…
…assification and spectral manipulation
Showing
3 changed files
with
338 additions
and
0 deletions
Show diff stats
1 | +# -*- coding: utf-8 -*- | |
2 | +""" | |
3 | +Created on Sun Jul 23 16:04:33 2017 | |
4 | + | |
5 | +@author: david | |
6 | +""" | |
7 | + | |
8 | +import numpy | |
9 | +import colorsys | |
10 | + | |
11 | +#generate a 2D color class map using a stack of binary class images | |
12 | +def classcolor2(C): | |
13 | + | |
14 | + #determine the number of classes | |
15 | + nc = C.shape[-1] | |
16 | + | |
17 | + #generate an RGB image | |
18 | + RGB = numpy.zeros((C.shape[0], C.shape[1], 3), dtype=numpy.ubyte) | |
19 | + | |
20 | + #for each class | |
21 | + for c in range(0, nc): | |
22 | + hsv = (c * 1.0 / nc, 1, 1) | |
23 | + color = numpy.asarray(colorsys.hsv_to_rgb(hsv[0], hsv[1], hsv[2])) * 255 | |
24 | + RGB[C[:, :, c], :] = color | |
25 | + | |
26 | + return RGB | |
0 | 27 | \ No newline at end of file | ... | ... |
1 | +# -*- coding: utf-8 -*- | |
2 | +""" | |
3 | +Created on Fri Jul 21 20:18:01 2017 | |
4 | + | |
5 | +@author: david | |
6 | +""" | |
7 | + | |
8 | +import os | |
9 | +import numpy | |
10 | +import scipy | |
11 | +import matplotlib.pyplot as plt | |
12 | +import progressbar | |
13 | + | |
14 | +class envi_header: | |
15 | + def __init__(self, filename = ""): | |
16 | + if filename != "": | |
17 | + self.load(filename) | |
18 | + else: | |
19 | + self.initialize() | |
20 | + | |
21 | + #initialization function | |
22 | + def initialize(self): | |
23 | + self.samples = int(0) | |
24 | + self.lines = int(0) | |
25 | + self.bands = int(0) | |
26 | + self.header_offset = int(0) | |
27 | + self.data_type = int(4) | |
28 | + self.interleave = "bsq" | |
29 | + self.sensor_type = "" | |
30 | + self.byte_order = int(0) | |
31 | + self.x_start = int(0) | |
32 | + self.y_start = int(0) | |
33 | + self.z_plot_titles = "" | |
34 | + self.pixel_size = [float(0), float(0)] | |
35 | + self.pixel_size_units = "Meters" | |
36 | + self.wavelength_units = "Wavenumber" | |
37 | + self.description = "" | |
38 | + self.band_names = [] | |
39 | + self.wavelength = [] | |
40 | + | |
41 | + #convert an ENVI data_type value to a numpy data type | |
42 | + def get_numpy_type(self, val): | |
43 | + if val == 1: | |
44 | + return numpy.byte | |
45 | + elif val == 2: | |
46 | + return numpy.int16 | |
47 | + elif val == 3: | |
48 | + return numpy.int32 | |
49 | + elif val == 4: | |
50 | + return numpy.float32 | |
51 | + elif val == 5: | |
52 | + return numpy.float64 | |
53 | + elif val == 6: | |
54 | + return numpy.complex64 | |
55 | + elif val == 9: | |
56 | + return numpy.complex128 | |
57 | + elif val == 12: | |
58 | + return numpy.uint16 | |
59 | + elif val == 13: | |
60 | + return numpy.uint32 | |
61 | + elif val == 14: | |
62 | + return numpy.int64 | |
63 | + elif val == 15: | |
64 | + return numpy.uint64 | |
65 | + | |
66 | + def get_envi_type(self, val): | |
67 | + if val == numpy.byte: | |
68 | + return 1 | |
69 | + elif val == numpy.int16: | |
70 | + return 2 | |
71 | + elif val == numpy.int32: | |
72 | + return 3 | |
73 | + elif val == numpy.float32: | |
74 | + return 4 | |
75 | + elif val == numpy.float64: | |
76 | + return 5 | |
77 | + elif val == numpy.complex64: | |
78 | + return 6 | |
79 | + elif val == numpy.complex128: | |
80 | + return 9 | |
81 | + elif val == numpy.uint16: | |
82 | + return 12 | |
83 | + elif val == numpy.uint32: | |
84 | + return 13 | |
85 | + elif val == numpy.int64: | |
86 | + return 14 | |
87 | + elif val == numpy.uint64: | |
88 | + return 15 | |
89 | + | |
90 | + def load(self, fname): | |
91 | + f = open(fname) | |
92 | + l = f.readlines() | |
93 | + if l[0].strip() != "ENVI": | |
94 | + print("ERROR: not an ENVI file") | |
95 | + return | |
96 | + li = 1 | |
97 | + while li < len(l): | |
98 | + #t = l[li].split() #split the line into tokens | |
99 | + #t = map(str.strip, t) #strip all of the tokens in the token list | |
100 | + | |
101 | + #handle the simple conditions | |
102 | + if l[li].startswith("file type"): | |
103 | + if not l[li].strip().endswith("ENVI Standard"): | |
104 | + print("ERROR: unsupported ENVI file format: " + l[li].strip()) | |
105 | + return | |
106 | + elif l[li].startswith("samples"): | |
107 | + self.samples = int(l[li].split()[-1]) | |
108 | + elif l[li].startswith("lines"): | |
109 | + self.lines = int(l[li].split()[-1]) | |
110 | + elif l[li].startswith("bands"): | |
111 | + self.bands = int(l[li].split()[-1]) | |
112 | + elif l[li].startswith("header offset"): | |
113 | + self.header_offset = int(l[li].split()[-1]) | |
114 | + elif l[li].startswith("data type"): | |
115 | + self.data_type = self.get_numpy_type(int(l[li].split()[-1])) | |
116 | + elif l[li].startswith("interleave"): | |
117 | + self.interleave = l[li].split()[-1].strip() | |
118 | + elif l[li].startswith("sensor type"): | |
119 | + self.sensor_type = l[li].split()[-1].strip() | |
120 | + elif l[li].startswith("byte order"): | |
121 | + self.byte_order = int(l[li].split()[-1]) | |
122 | + elif l[li].startswith("x start"): | |
123 | + self.x_start = int(l[li].split()[-1]) | |
124 | + elif l[li].startswith("y start"): | |
125 | + self.y_start = int(l[li].split()[-1]) | |
126 | + elif l[li].startswith("z plot titles"): | |
127 | + i0 = l[li].rindex('{') | |
128 | + i1 = l[li].rindex('}') | |
129 | + self.z_plot_titles = l[li][i0 + 1 : i1] | |
130 | + elif l[li].startswith("pixel size"): | |
131 | + i0 = l[li].rindex('{') | |
132 | + i1 = l[li].rindex('}') | |
133 | + s = l[li][i0 + 1 : i1].split(',') | |
134 | + self.pixel_size = [float(s[0]), float(s[1])] | |
135 | + self.pixel_size_units = s[2][s[2].rindex('=') + 1:].strip() | |
136 | + elif l[li].startswith("wavelength units"): | |
137 | + self.wavelength_units = l[li].split()[-1].strip() | |
138 | + | |
139 | + #handle the complicated conditions | |
140 | + elif l[li].startswith("description"): | |
141 | + desc = [l[li]] | |
142 | + while l[li].strip()[-1] != '}': | |
143 | + li += 1 | |
144 | + desc.append(l[li]) | |
145 | + desc = ''.join(list(map(str.strip, desc))) #strip all white space from the string list | |
146 | + i0 = desc.rindex('{') | |
147 | + i1 = desc.rindex('}') | |
148 | + self.description = desc[i0 + 1 : i1] | |
149 | + | |
150 | + elif l[li].startswith("band names"): | |
151 | + names = [l[li]] | |
152 | + while l[li].strip()[-1] != '}': | |
153 | + li += 1 | |
154 | + names.append(l[li]) | |
155 | + names = ''.join(list(map(str.strip, names))) #strip all white space from the string list | |
156 | + i0 = names.rindex('{') | |
157 | + i1 = names.rindex('}') | |
158 | + names = names[i0 + 1 : i1] | |
159 | + self.band_names = list(map(str.strip, names.split(','))) | |
160 | + elif l[li].startswith("wavelength"): | |
161 | + waves = [l[li]] | |
162 | + while l[li].strip()[-1] != '}': | |
163 | + li += 1 | |
164 | + waves.append(l[li]) | |
165 | + waves = ''.join(list(map(str.strip, waves))) #strip all white space from the string list | |
166 | + i0 = waves.rindex('{') | |
167 | + i1 = waves.rindex('}') | |
168 | + waves = waves[i0 + 1 : i1] | |
169 | + self.wavelength = list(map(float, waves.split(','))) | |
170 | + | |
171 | + li += 1 | |
172 | + | |
173 | + f.close() | |
174 | + | |
175 | +class envi: | |
176 | + def __init__(self, filename, headername = "", maskname = ""): | |
177 | + self.open(filename, headername) | |
178 | + if maskname == "": | |
179 | + self.mask = numpy.ones((self.header.samples, self.header.lines), dtype=numpy.bool) | |
180 | + else: | |
181 | + self.mask = scipy.misc.imread(maskname, flatten=True).astype(numpy.bool) | |
182 | + | |
183 | + | |
184 | + def open(self, filename, headername = ""): | |
185 | + if headername == "": | |
186 | + headername = filename + ".hdr" | |
187 | + | |
188 | + if not os.path.isfile(filename): | |
189 | + print("ERROR: " + filename + " not found") | |
190 | + return | |
191 | + if not os.path.isfile(headername): | |
192 | + print("ERROR: " + headername + " not found") | |
193 | + return | |
194 | + | |
195 | + #open the file | |
196 | + self.header = envi_header(headername) | |
197 | + self.file = open(filename, "rb") | |
198 | + | |
199 | + def loadall(self): | |
200 | + X = self.header.samples | |
201 | + Y = self.header.lines | |
202 | + B = self.header.bands | |
203 | + | |
204 | + #load the data | |
205 | + D = numpy.fromfile(self.file, dtype=self.header.data_type) | |
206 | + | |
207 | + if self.header.interleave == "bsq": | |
208 | + return numpy.reshape(D, (B, Y, X)) | |
209 | + #return numpy.swapaxes(D, 0, 2) | |
210 | + elif self.header.interleave == "bip": | |
211 | + D = numpy.reshape(D, (Y, X, B)) | |
212 | + return numpy.rollaxis(D, 2) | |
213 | + elif self.header.interleave == "bil": | |
214 | + D = numpy.reshape(D, (Y, B, X)) | |
215 | + return numpy.rollaxis(D, 1) | |
216 | + | |
217 | + #loads all of the pixels where mask != 0 and returns them as a matrix | |
218 | + def loadmask(self, mask): | |
219 | + X = self.header.samples | |
220 | + Y = self.header.lines | |
221 | + B = self.header.bands | |
222 | + | |
223 | + P = numpy.count_nonzero(mask) #count the number of zeros in the mask file | |
224 | + M = numpy.zeros((B, P), dtype=self.header.data_type) | |
225 | + type_bytes = numpy.dtype(self.header.data_type).itemsize | |
226 | + | |
227 | + self.file.seek(0) | |
228 | + if self.header.interleave == "bip": | |
229 | + spectrum = numpy.zeros(B, dtype=self.header.data_type) | |
230 | + flatmask = numpy.reshape(mask, (X * Y)) | |
231 | + i = numpy.flatnonzero(flatmask) | |
232 | + bar = progressbar.ProgressBar(max_value = P) | |
233 | + for p in range(0, P): | |
234 | + self.file.seek(i[p] * B * type_bytes) | |
235 | + self.file.readinto(spectrum) | |
236 | + M[:, p] = spectrum | |
237 | + bar.update(p+1) | |
238 | + if self.header.interleave == "bsq": | |
239 | + band = numpy.zeros(mask.shape, dtype=self.header.data_type) | |
240 | + i = numpy.nonzero(mask) | |
241 | + bar = progressbar.ProgressBar(max_value=B) | |
242 | + for b in range(0, B): | |
243 | + self.file.seek(b * X * Y * type_bytes) | |
244 | + self.file.readinto(band) | |
245 | + M[b, :] = band[i] | |
246 | + bar.update(b+1) | |
247 | + if self.header.interleave == "bil": | |
248 | + plane = numpy.zeros((B, X), dtype=self.header.data_type) | |
249 | + p = 0 | |
250 | + bar = progressbar.ProgressBar(max_value=Y) | |
251 | + for l in range(0, Y): | |
252 | + i = numpy.flatnonzero(mask[l, :]) | |
253 | + self.file.readinto(plane) | |
254 | + M[:, p:p+i.shape[0]] = plane[:, i] | |
255 | + p = p + i.shape[0] | |
256 | + bar.update(l+1) | |
257 | + return M | |
258 | + | |
259 | + | |
260 | + | |
261 | + def __del__(self): | |
262 | + self.file.close() | |
0 | 263 | \ No newline at end of file | ... | ... |
1 | +# -*- coding: utf-8 -*- | |
2 | +""" | |
3 | +Created on Sun Jul 23 13:52:22 2017 | |
4 | + | |
5 | +@author: david | |
6 | +""" | |
7 | +import numpy | |
8 | + | |
9 | +#sift a 2D hyperspectral image into a PxB matrix where P is the number of pixels and B is the number of bands | |
10 | +def sift2(I, mask = []): | |
11 | + | |
12 | + #get the shape of the input array | |
13 | + S = I.shape | |
14 | + | |
15 | + #convert that array into a 1D matrix | |
16 | + M = numpy.reshape(I, (S[2], S[0] * S[1])) | |
17 | + | |
18 | + #gif no mask is provided, just return all pixels | |
19 | + if mask == []: | |
20 | + return M | |
21 | + | |
22 | + #if a mask is provided, only return pixels corresponding to that mask | |
23 | + flatmask = numpy.reshape(mask, (S[0] * S[1])) | |
24 | + i = numpy.flatnonzero(flatmask) #get the nonzero indices | |
25 | + return M[:, i] #return pixels corresponding to the masked values | |
26 | + | |
27 | +def unsift2(M, mask): | |
28 | + | |
29 | + #get the size of the input matrix | |
30 | + S = M.shape | |
31 | + | |
32 | + #count the number of nonzero values in the mask | |
33 | + nnz = numpy.count_nonzero(mask) | |
34 | + | |
35 | + #the number of masked values should be the same as the number of pixels in the input matrix | |
36 | + if len(S) == 1: | |
37 | + if not S[0] == nnz: | |
38 | + print("ERROR: expected " + str(nnz) + " pixels based on the mask but there are " + str(S[0]) + " in the matrix.") | |
39 | + elif not S[1] == nnz: | |
40 | + print("ERROR: expected " + str(nnz) + " pixels based on the mask but there are " + str(S[1]) + " in the matrix.") | |
41 | + | |
42 | + | |
43 | + i = numpy.nonzero(mask) | |
44 | + | |
45 | + if len(S) == 1: | |
46 | + I = numpy.zeros((1, mask.shape[0], mask.shape[1]), dtype=M.dtype) | |
47 | + else: | |
48 | + I = numpy.zeros((M.shape[0], mask.shape[0], mask.shape[1]), dtype=M.dtype) | |
49 | + I[:, i[0], i[1]] = M | |
50 | + return I | |
0 | 51 | \ No newline at end of file | ... | ... |