| 1 | ############################################################################## |
|---|
| 2 | # Python library to deal with BIL (Band Interleaved by Line) and BSQ (Band Sequential) files |
|---|
| 3 | # |
|---|
| 4 | # Author: Ben Taylor |
|---|
| 5 | # |
|---|
| 6 | # History: |
|---|
| 7 | # 12th Feb. 2009: (benj) Created |
|---|
| 8 | # 9th Jun. 2009: (benj) Added writeData function |
|---|
| 9 | # 20th Aug. 2009: (benj) Added append option to writeDataFile |
|---|
| 10 | # |
|---|
| 11 | # Available functions: |
|---|
| 12 | # readxy: Wrapper function for readBil/readBsq that allows you to omit the number of bands in the file (works it out from the file size) |
|---|
| 13 | # readxb: Wrapper function for readBil/readBsq that allows you to omit the number of lines in the file (works it out from the file size) |
|---|
| 14 | # readyb: Wrapper function for readBil/readBsq that allows you to omit the number of pixels per line (works it out from the file size) |
|---|
| 15 | # readBil: Reads a BIL file and returns a list containing the data from the file |
|---|
| 16 | # readBilLine: Reads a line from an open BIL file and returns a list containing the data that was read |
|---|
| 17 | # readBsq: Reads a BSQ file and returns a list containing the data from the file |
|---|
| 18 | # readBsqBand: Reads a band from an open BSQ file and returns a list containing the data that was read |
|---|
| 19 | # writeData: Writes data to an output file straight from an input list |
|---|
| 20 | # writeDataFile: Writes a BIL or BSQ file from a 1D list containing data already in the right file order |
|---|
| 21 | # write3DData: Writes a BIL or BSQ file from a 3D list with 1st dimension band number, 2nd dimension line number and 3rd dimension pixel number (unfolds to a 1D array then calls writeDataFile) |
|---|
| 22 | # writeHdrFile: Writes an ENVI .hdr file to be associated with a BIL or BSQ file |
|---|
| 23 | # readHdrFile: Reads data from a given ENVI-style header file |
|---|
| 24 | # getEnviType: Gets the ENVI type code equivalent to a particular Python struct format string |
|---|
| 25 | # getStructType: Gets the Python struct format string equivalent to a particular ENVI type code |
|---|
| 26 | # |
|---|
| 27 | # You may use or alter this script as you wish, but no warranty of any kind is offered, nor is it guaranteed |
|---|
| 28 | # not to cause security holes in an unsafe environment. |
|---|
| 29 | ############################################################################## |
|---|
| 30 | |
|---|
| 31 | import os |
|---|
| 32 | import stat |
|---|
| 33 | import struct |
|---|
| 34 | import re |
|---|
| 35 | import sys |
|---|
| 36 | |
|---|
| 37 | defformat = "h" # Default data format (2-byte signed short int) |
|---|
| 38 | |
|---|
| 39 | # Function readxy |
|---|
| 40 | # Wrapper function for readBil/readBsq that allows you to omit the number of bands in the file (works it out from the file size) |
|---|
| 41 | # See readBil/readBsq description for arguments and return value |
|---|
| 42 | # filetype: "bil" or "bsq" appropriately |
|---|
| 43 | def readxy(filename, numlines, pixperline, dataformat=defformat, filetype="bil"): |
|---|
| 44 | fileinfo = os.stat(filename) |
|---|
| 45 | filesize = fileinfo[stat.ST_SIZE] |
|---|
| 46 | |
|---|
| 47 | # Check given format string is valid |
|---|
| 48 | try: |
|---|
| 49 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 50 | except: |
|---|
| 51 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 52 | # end try |
|---|
| 53 | |
|---|
| 54 | numbands = ((filesize / float(numlines)) / float(pixperline)) / float(bytesperpix) |
|---|
| 55 | |
|---|
| 56 | # Should be an integer, if it's not then one of the given attributes is wrong or the file is corrupt |
|---|
| 57 | if (numbands == int(numbands)): |
|---|
| 58 | if (filetype == "bil"): |
|---|
| 59 | return readBil(filename, int(numlines), int(pixperline), int(numbands), dataformat) |
|---|
| 60 | else: |
|---|
| 61 | if (filetype == "bsq"): |
|---|
| 62 | return readBsq(filename, int(numlines), int(pixperline), int(numbands), dataformat) |
|---|
| 63 | else: |
|---|
| 64 | raise ValueError, "File type argument must be either 'bil' or 'bsq', got: " + filetype |
|---|
| 65 | # end if |
|---|
| 66 | # end if |
|---|
| 67 | else: |
|---|
| 68 | raise ValueError, "File size and supplied attributes do not match" |
|---|
| 69 | # end if |
|---|
| 70 | # end function |
|---|
| 71 | |
|---|
| 72 | # Function readxb |
|---|
| 73 | # Wrapper function for readBil/readBsq that allows you to omit the number of lines in the file (works it out from the file size) |
|---|
| 74 | # See readBil/readBsq description for arguments and return value |
|---|
| 75 | # filetype: "bil" or "bsq" appropriately |
|---|
| 76 | def readxb(filename, pixperline, numbands, dataformat=defformat, filetype="bil"): |
|---|
| 77 | fileinfo = os.stat(filename) |
|---|
| 78 | filesize = fileinfo[stat.ST_SIZE] |
|---|
| 79 | |
|---|
| 80 | # Check given format string is valid |
|---|
| 81 | try: |
|---|
| 82 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 83 | except: |
|---|
| 84 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 85 | # end try |
|---|
| 86 | |
|---|
| 87 | numlines = ((filesize / float(numbands)) / float(pixperline)) / float(bytesperpix) |
|---|
| 88 | |
|---|
| 89 | # Should be an integer, if it's not then one of the given attributes is wrong or the file is corrupt |
|---|
| 90 | if (numlines == int(numlines)): |
|---|
| 91 | if (filetype == "bil"): |
|---|
| 92 | return readBil(filename, int(numlines), int(pixperline), int(numbands), dataformat) |
|---|
| 93 | else: |
|---|
| 94 | if (filetype == "bsq"): |
|---|
| 95 | return readBsq(filename, int(numlines), int(pixperline), int(numbands), dataformat) |
|---|
| 96 | else: |
|---|
| 97 | raise ValueError, "File type argument must be either 'bil' or 'bsq', got: " + filetype |
|---|
| 98 | # end if |
|---|
| 99 | # end if |
|---|
| 100 | else: |
|---|
| 101 | raise ValueError, "File size and supplied attributes do not match" |
|---|
| 102 | # end if |
|---|
| 103 | # end function |
|---|
| 104 | |
|---|
| 105 | # Function readyb |
|---|
| 106 | # Wrapper function for readBil/readBsq that allows you to omit the number of pixels per line (works it out from the file size) |
|---|
| 107 | # See readBil/readBsq description for arguments and return value |
|---|
| 108 | # filetype: "bil" or "bsq" appropriately |
|---|
| 109 | def readyb(filename, numlines, numbands, dataformat=defformat, filetype="bil"): |
|---|
| 110 | fileinfo = os.stat(filename) |
|---|
| 111 | filesize = fileinfo[stat.ST_SIZE] |
|---|
| 112 | |
|---|
| 113 | # Check given format string is valid |
|---|
| 114 | try: |
|---|
| 115 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 116 | except: |
|---|
| 117 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 118 | # end try |
|---|
| 119 | |
|---|
| 120 | pixperline = ((filesize / float(numbands)) / float(numlines)) / float(bytesperpix) |
|---|
| 121 | |
|---|
| 122 | # Should be an integer, if it's not then one of the given attributes is wrong or the file is corrupt |
|---|
| 123 | if (numlines == int(numlines)): |
|---|
| 124 | if (filetype == "bil"): |
|---|
| 125 | return readBil(filename, int(numlines), int(pixperline), int(numbands), dataformat) |
|---|
| 126 | else: |
|---|
| 127 | if (filetype == "bsq"): |
|---|
| 128 | return readBsq(filename, int(numlines), int(pixperline), int(numbands), dataformat) |
|---|
| 129 | else: |
|---|
| 130 | raise ValueError, "File type argument must be either 'bil' or 'bsq', got: " + filetype |
|---|
| 131 | # end if |
|---|
| 132 | # end if |
|---|
| 133 | else: |
|---|
| 134 | raise ValueError, "File size and supplied attributes do not match" |
|---|
| 135 | # end if |
|---|
| 136 | # end function |
|---|
| 137 | |
|---|
| 138 | # Function readBil |
|---|
| 139 | # Reads a BIL file and returns a list containing the data from the file |
|---|
| 140 | # |
|---|
| 141 | # Arguments: |
|---|
| 142 | # filename: Name of file to read |
|---|
| 143 | # numlines: Number of lines of data in the file |
|---|
| 144 | # pixperline: Number of pixels on a line |
|---|
| 145 | # numbands: Number of bands in the file |
|---|
| 146 | # dataformat: Format string for data, as Python struct definition |
|---|
| 147 | # |
|---|
| 148 | # Returns: A list containing the data from filename formatted as a list of bands |
|---|
| 149 | # containing a list of lines, each containing a list of pixel values |
|---|
| 150 | def readBil(filename, numlines, pixperline, numbands, dataformat=defformat): |
|---|
| 151 | |
|---|
| 152 | # Check file exists and is a file |
|---|
| 153 | if (not os.path.isfile(filename)): |
|---|
| 154 | raise ValueError, "Supplied filename " + str(filename) + " does not exist" |
|---|
| 155 | # end if |
|---|
| 156 | |
|---|
| 157 | # Check given format string is valid |
|---|
| 158 | try: |
|---|
| 159 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 160 | except: |
|---|
| 161 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 162 | # end try |
|---|
| 163 | |
|---|
| 164 | # Check file size matches with size attributes |
|---|
| 165 | fileinfo = os.stat(filename) |
|---|
| 166 | filesize = fileinfo[stat.ST_SIZE] |
|---|
| 167 | checknum = (((filesize / float(numbands)) / float(numlines)) / float(bytesperpix)) / pixperline |
|---|
| 168 | if (checknum != 1): |
|---|
| 169 | raise ValueError, "File size and supplied attributes do not match" |
|---|
| 170 | # end if |
|---|
| 171 | |
|---|
| 172 | # Open the file for reading in binary mode |
|---|
| 173 | try: |
|---|
| 174 | bilfile = open(filename, "rb") |
|---|
| 175 | except: |
|---|
| 176 | print "Failed to open BIL file " + filename |
|---|
| 177 | raise |
|---|
| 178 | # end try |
|---|
| 179 | |
|---|
| 180 | # Create a list of bands containing an empty list for each band |
|---|
| 181 | bands = [[] for i in range(0, numbands)] |
|---|
| 182 | |
|---|
| 183 | # BIL format so have to cycle through lines at top level rather than bands |
|---|
| 184 | for linenum in range(0, numlines): |
|---|
| 185 | for bandnum in range(0, numbands): |
|---|
| 186 | |
|---|
| 187 | if (linenum == 0): |
|---|
| 188 | # For each band create an empty list of lines in the band, but only the first time |
|---|
| 189 | bands[bandnum] = [[] for i in range(0, numlines)] |
|---|
| 190 | # end if |
|---|
| 191 | |
|---|
| 192 | for pixnum in range(0, pixperline): |
|---|
| 193 | |
|---|
| 194 | # Read one data item (pixel) from the data file. No error checking because we want this to fall over |
|---|
| 195 | # if it fails. |
|---|
| 196 | dataitem = bilfile.read(bytesperpix) |
|---|
| 197 | |
|---|
| 198 | # If we get a blank string then we hit EOF early, raise an error |
|---|
| 199 | if (dataitem == ""): |
|---|
| 200 | raise EOFError, "Ran out of data to read before we should have" |
|---|
| 201 | # end if |
|---|
| 202 | |
|---|
| 203 | # If everything worked, unpack the binary value and store it in the appropriate pixel value |
|---|
| 204 | bands[bandnum][linenum].append(struct.unpack(dataformat, dataitem)[0]) |
|---|
| 205 | # end for |
|---|
| 206 | # end for |
|---|
| 207 | # end for |
|---|
| 208 | bilfile.close() |
|---|
| 209 | |
|---|
| 210 | return bands |
|---|
| 211 | # end function |
|---|
| 212 | |
|---|
| 213 | # Function readBilLine |
|---|
| 214 | # Reads a line of data from an open BIL file |
|---|
| 215 | # |
|---|
| 216 | # Arguments: |
|---|
| 217 | # bilfile: Open BIL file object |
|---|
| 218 | # pixperline: Number of pixels on a line |
|---|
| 219 | # numbands: Number of bands in the file |
|---|
| 220 | # dataformat: Format string for data, as Python struct definition |
|---|
| 221 | # |
|---|
| 222 | # Returns: A 2D list with the band number in the first dimension and the pixel number in the second, containing the data values |
|---|
| 223 | # for the line that was read |
|---|
| 224 | def readBilLine(bilfile, pixperline, numbands, dataformat=defformat): |
|---|
| 225 | line = [] |
|---|
| 226 | |
|---|
| 227 | # Get the size in bytes for the given data format |
|---|
| 228 | itemsize = struct.calcsize(dataformat) |
|---|
| 229 | |
|---|
| 230 | # For each pixel in each band, read a data item, unpack it and store it in the output list |
|---|
| 231 | for bandnum in range(0, numbands): |
|---|
| 232 | line.append([]) |
|---|
| 233 | for pixnum in range(0, pixperline): |
|---|
| 234 | dataitem = bilfile.read(itemsize) |
|---|
| 235 | |
|---|
| 236 | if ((dataitem == "") or (len(dataitem) < itemsize)): |
|---|
| 237 | raise EOFError, "Ran out of data to read before we should have" |
|---|
| 238 | # end if |
|---|
| 239 | |
|---|
| 240 | line[bandnum].append(struct.unpack(dataformat, dataitem)[0]) |
|---|
| 241 | # end for |
|---|
| 242 | # end for |
|---|
| 243 | |
|---|
| 244 | return line |
|---|
| 245 | # end function |
|---|
| 246 | |
|---|
| 247 | # Function readBsq |
|---|
| 248 | # Reads a BSQ file and returns a list containing the data from the file |
|---|
| 249 | # |
|---|
| 250 | # Arguments: |
|---|
| 251 | # filename: Name of file to read |
|---|
| 252 | # numlines: Number of lines of data in the file |
|---|
| 253 | # pixperline: Number of pixels on a line |
|---|
| 254 | # numbands: Number of bands in the file |
|---|
| 255 | # dataformat: Format string for data, as Python struct definition |
|---|
| 256 | # |
|---|
| 257 | # Returns: A list containing the data from filename formatted as a list of bands |
|---|
| 258 | # containing a list of lines, each containing a list of pixel values |
|---|
| 259 | def readBsq(filename, numlines, pixperline, numbands, dataformat=defformat): |
|---|
| 260 | |
|---|
| 261 | # Check file exists and is a file |
|---|
| 262 | if (not os.path.isfile(filename)): |
|---|
| 263 | raise ValueError, "Supplied filename " + str(filename) + " does not exist" |
|---|
| 264 | # end if |
|---|
| 265 | |
|---|
| 266 | # Check given format string is valid |
|---|
| 267 | try: |
|---|
| 268 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 269 | except: |
|---|
| 270 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 271 | # end try |
|---|
| 272 | |
|---|
| 273 | # Check file size matches with size attributes |
|---|
| 274 | fileinfo = os.stat(filename) |
|---|
| 275 | filesize = fileinfo[stat.ST_SIZE] |
|---|
| 276 | checknum = (((filesize / float(numbands)) / float(numlines)) / float(bytesperpix)) / pixperline |
|---|
| 277 | if (checknum != 1): |
|---|
| 278 | raise ValueError, "File size and supplied attributes do not match" |
|---|
| 279 | # end if |
|---|
| 280 | |
|---|
| 281 | # Open the file for reading in binary mode |
|---|
| 282 | try: |
|---|
| 283 | bsqfile = open(filename, "rb") |
|---|
| 284 | except: |
|---|
| 285 | print "Failed to open BSQ file " + filename |
|---|
| 286 | raise |
|---|
| 287 | # end try |
|---|
| 288 | |
|---|
| 289 | # Create a list of bands containing an empty list for each band |
|---|
| 290 | bands = [] |
|---|
| 291 | |
|---|
| 292 | # Read data for each band at a time |
|---|
| 293 | for bandnum in range(0, numbands): |
|---|
| 294 | bands.append([]) |
|---|
| 295 | |
|---|
| 296 | for linenum in range(0, numlines): |
|---|
| 297 | |
|---|
| 298 | bands[bandnum].append([]) |
|---|
| 299 | |
|---|
| 300 | for pixnum in range(0, pixperline): |
|---|
| 301 | |
|---|
| 302 | # Read one data item (pixel) from the data file. No error checking because we want this to fall over |
|---|
| 303 | # if it fails. |
|---|
| 304 | dataitem = bsqfile.read(bytesperpix) |
|---|
| 305 | |
|---|
| 306 | # If we get a blank string then we hit EOF early, raise an error |
|---|
| 307 | if (dataitem == ""): |
|---|
| 308 | raise EOFError, "Ran out of data to read before we should have" |
|---|
| 309 | # end if |
|---|
| 310 | |
|---|
| 311 | # If everything worked, unpack the binary value and store it in the appropriate pixel value |
|---|
| 312 | bands[bandnum][linenum].append(struct.unpack(dataformat, dataitem)[0]) |
|---|
| 313 | # end for |
|---|
| 314 | # end for |
|---|
| 315 | # end for |
|---|
| 316 | |
|---|
| 317 | bsqfile.close() |
|---|
| 318 | |
|---|
| 319 | return bands |
|---|
| 320 | # end function |
|---|
| 321 | |
|---|
| 322 | # Function readBsqBand |
|---|
| 323 | # Reads a band of data from an open BSQ file |
|---|
| 324 | # |
|---|
| 325 | # Arguments: |
|---|
| 326 | # bsqfile: Open BSQ file object |
|---|
| 327 | # pixperline: Number of pixels on a line |
|---|
| 328 | # numlines: Number of lines in the file |
|---|
| 329 | # dataformat: Format string for data, as Python struct definition |
|---|
| 330 | # |
|---|
| 331 | # Returns: A 2D list with the band number in the first dimension and the pixel number in the second, containing the data values |
|---|
| 332 | # for the line that was read |
|---|
| 333 | def readBsqBand(bsqfile, pixperline, numlines, dataformat=defformat): |
|---|
| 334 | band = [] |
|---|
| 335 | |
|---|
| 336 | # Get the size in bytes for the given data format |
|---|
| 337 | itemsize = struct.calcsize(dataformat) |
|---|
| 338 | |
|---|
| 339 | # For each pixel in each band, read a data item, unpack it and store it in the output list |
|---|
| 340 | for linenum in range(0, numlines): |
|---|
| 341 | band.append([]) |
|---|
| 342 | for pixnum in range(0, pixperline): |
|---|
| 343 | dataitem = bsqfile.read(itemsize) |
|---|
| 344 | |
|---|
| 345 | if ((dataitem == "") or (len(dataitem) < itemsize)): |
|---|
| 346 | raise EOFError, "Ran out of data to read before we should have" |
|---|
| 347 | # end if |
|---|
| 348 | |
|---|
| 349 | band[linenum].append(struct.unpack(dataformat, dataitem)[0]) |
|---|
| 350 | # end for |
|---|
| 351 | # end for |
|---|
| 352 | |
|---|
| 353 | return band |
|---|
| 354 | # end function |
|---|
| 355 | |
|---|
| 356 | # Function writeData |
|---|
| 357 | # Writes data to an output file straight from an input list |
|---|
| 358 | # |
|---|
| 359 | # Arguments: |
|---|
| 360 | # data: List containing data to be written |
|---|
| 361 | # datafile: Open (binary) data file to write to |
|---|
| 362 | # dataformat: Format string for data, as Python struct definition |
|---|
| 363 | def writeData(data, datafile, dataformat=defformat): |
|---|
| 364 | |
|---|
| 365 | # Check given format string is valid |
|---|
| 366 | try: |
|---|
| 367 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 368 | except: |
|---|
| 369 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 370 | # end try |
|---|
| 371 | |
|---|
| 372 | # Get the size in bytes for the given data format |
|---|
| 373 | itemsize = struct.calcsize(dataformat) |
|---|
| 374 | |
|---|
| 375 | # Write data to file in order |
|---|
| 376 | for dataitem in data: |
|---|
| 377 | try: |
|---|
| 378 | packeditem = struct.pack(dataformat, dataitem) |
|---|
| 379 | except: |
|---|
| 380 | datafile.close() |
|---|
| 381 | raise IOError, "Could not pack " + str(dataitem) + " into " + str(bytesperpix) + " bytes. Reason: " + str(sys.exc_info()[1]) |
|---|
| 382 | # end try |
|---|
| 383 | |
|---|
| 384 | try: |
|---|
| 385 | datafile.write(packeditem) |
|---|
| 386 | except: |
|---|
| 387 | datafile.close() |
|---|
| 388 | raise IOError, "Failed to write to data file. Reason: " + str(sys.exc_info()[1]) |
|---|
| 389 | # end try |
|---|
| 390 | # end for |
|---|
| 391 | # end function |
|---|
| 392 | |
|---|
| 393 | # Function writeDataFile |
|---|
| 394 | # Writes a data file (BIL or BSQ) from a 1D list containing data already in the right file order |
|---|
| 395 | # (ie data are written to the file in the order that they're in the list) |
|---|
| 396 | # |
|---|
| 397 | # Arguments: |
|---|
| 398 | # data: List containing data to write to the file |
|---|
| 399 | # filename: Name of file to be written to |
|---|
| 400 | # dataformat: Format string for data, as Python struct definition |
|---|
| 401 | # append: If True then appends the data to the end of the file rather than writing a new blank file. Default False |
|---|
| 402 | def writeDataFile(data, filename, dataformat=defformat, append=False): |
|---|
| 403 | |
|---|
| 404 | # Get correct format string to open the file with |
|---|
| 405 | if (append): |
|---|
| 406 | writeformat = "ab" |
|---|
| 407 | else: |
|---|
| 408 | writeformat = "wb" |
|---|
| 409 | # end if |
|---|
| 410 | |
|---|
| 411 | # Open the data file for writing in binary mode |
|---|
| 412 | try: |
|---|
| 413 | datafile = open(filename, writeformat) |
|---|
| 414 | except: |
|---|
| 415 | print "Could not open data file " + str(filename) + " for writing" |
|---|
| 416 | raise |
|---|
| 417 | # end try |
|---|
| 418 | |
|---|
| 419 | # Check given format string is valid |
|---|
| 420 | try: |
|---|
| 421 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 422 | except: |
|---|
| 423 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 424 | # end try |
|---|
| 425 | |
|---|
| 426 | # Pack each data item into binary data and write it to the output file |
|---|
| 427 | for dataitem in data: |
|---|
| 428 | try: |
|---|
| 429 | packeditem = struct.pack(dataformat, dataitem) |
|---|
| 430 | except: |
|---|
| 431 | datafile.close() |
|---|
| 432 | print "Could not pack " + str(dataitem) + " into " + str(bytesperpix) + " bytes" |
|---|
| 433 | raise |
|---|
| 434 | # end try |
|---|
| 435 | |
|---|
| 436 | try: |
|---|
| 437 | datafile.write(packeditem) |
|---|
| 438 | except: |
|---|
| 439 | print "Failed to write to data file. Reason: " + str(sys.exc_info()[1]) |
|---|
| 440 | datafile.close() |
|---|
| 441 | raise |
|---|
| 442 | # end try |
|---|
| 443 | # end for |
|---|
| 444 | |
|---|
| 445 | datafile.close() |
|---|
| 446 | # end function |
|---|
| 447 | |
|---|
| 448 | # Function write3DData |
|---|
| 449 | # Writes a BIL or BSQ file from a 3D list with 1st dimension band number, 2nd dimension line number |
|---|
| 450 | # and 3rd dimension pixel number (unfolds to a 1D array then calls writeDataFile) |
|---|
| 451 | # |
|---|
| 452 | # Arguments: |
|---|
| 453 | # data: List containing data to write to the file |
|---|
| 454 | # filename: Name of file to be written to |
|---|
| 455 | # writehdr: Flag denoting whether to write an ENVI header file (default true) |
|---|
| 456 | # dataformat: Format string for data, as Python struct definition |
|---|
| 457 | # interleave: "bil" or "bsq" appropriately |
|---|
| 458 | def write3DData(data, filename, writehdr=True, dataformat=defformat, interleave="bil"): |
|---|
| 459 | |
|---|
| 460 | # Store numbers of bands, lines and pixels per line for convenience |
|---|
| 461 | numbands = len(data) |
|---|
| 462 | numlines = len(data[0]) |
|---|
| 463 | pixperline = len(data[0][0]) |
|---|
| 464 | |
|---|
| 465 | # Check given format string is valid |
|---|
| 466 | try: |
|---|
| 467 | bytesperpix = struct.calcsize(dataformat) |
|---|
| 468 | except: |
|---|
| 469 | raise ValueError, "Supplied data format " + str(dataformat) + " is invalid" |
|---|
| 470 | # end try |
|---|
| 471 | |
|---|
| 472 | # Create list for unfolding to |
|---|
| 473 | outdata = [0.0 for i in range(0, numbands * numlines * pixperline)] |
|---|
| 474 | |
|---|
| 475 | # Run through the data array and put all the data in the right place in the unfolded list |
|---|
| 476 | for bandnum in range(0, numbands): |
|---|
| 477 | for linenum in range(0, numlines): |
|---|
| 478 | for pixnum in range(0, pixperline): |
|---|
| 479 | # Work out appropriate index within BIL file format for next pixel and store in 1D data array |
|---|
| 480 | if (interleave == "bil"): |
|---|
| 481 | pixindex = (pixperline * numbands * linenum) + (pixperline * bandnum) + pixnum |
|---|
| 482 | else: |
|---|
| 483 | if (interleave == "bsq"): |
|---|
| 484 | pixindex = (pixperline * numbands * linenum) + (pixperline * linenum) + pixnum |
|---|
| 485 | else: |
|---|
| 486 | raise ValueError, "Interleave argument to write3DData must be either 'bil' or 'bsq', got: " + interleave |
|---|
| 487 | # end if |
|---|
| 488 | # end if |
|---|
| 489 | outdata[pixindex] = data[bandnum][linenum][pixnum] |
|---|
| 490 | # end for |
|---|
| 491 | # end for |
|---|
| 492 | # end for |
|---|
| 493 | |
|---|
| 494 | # Write output file (or throw an error if there's no data to write) |
|---|
| 495 | if (len(outdata) > 0): |
|---|
| 496 | writeDataFile(outdata, filename, dataformat) |
|---|
| 497 | else: |
|---|
| 498 | raise ValueError, "One or more dimensions of the data array were 0" |
|---|
| 499 | # end if |
|---|
| 500 | |
|---|
| 501 | # Write header file if requested |
|---|
| 502 | if (writehdr): |
|---|
| 503 | try: |
|---|
| 504 | # Check ENVI data type |
|---|
| 505 | datatype = getEnviType(dataformat) |
|---|
| 506 | except: |
|---|
| 507 | datafile.close() |
|---|
| 508 | print "Unable to generate header for type " + dataformat + ", data type is not valid for ENVI" |
|---|
| 509 | # end try |
|---|
| 510 | |
|---|
| 511 | writeHdrFile(filename + ".hdr", pixperline, numlines, numbands, datatype, interleave) |
|---|
| 512 | # end if |
|---|
| 513 | # end function |
|---|
| 514 | |
|---|
| 515 | # Function writeHdrFile |
|---|
| 516 | # Writes an ENVI .hdr file to be associated with a data file |
|---|
| 517 | # |
|---|
| 518 | # Arguments: |
|---|
| 519 | # filename: Name of .hdr file to be written |
|---|
| 520 | # samples: Number of pixels per line (samples) |
|---|
| 521 | # lines: Number of lines |
|---|
| 522 | # bands: Number of bands |
|---|
| 523 | # datatype: Numeric code for relevant data type |
|---|
| 524 | def writeHdrFile(filename, samples, lines, bands, datatype, interleave="bil"): |
|---|
| 525 | try: |
|---|
| 526 | hdrfile = open(filename, "w") |
|---|
| 527 | except: |
|---|
| 528 | print "Could not open header file " + str(filename) + " for writing" |
|---|
| 529 | raise |
|---|
| 530 | # end try |
|---|
| 531 | |
|---|
| 532 | hdrfile.write("ENVI\n") |
|---|
| 533 | hdrfile.write("description = { Created by bil_handler.py }\n") |
|---|
| 534 | hdrfile.write("samples = " + str(samples) + "\n") |
|---|
| 535 | hdrfile.write("lines = " + str(lines) + "\n") |
|---|
| 536 | hdrfile.write("bands = " + str(bands) + "\n") |
|---|
| 537 | hdrfile.write("header offset = 0\n") |
|---|
| 538 | hdrfile.write("file type = ENVI Standard\n") |
|---|
| 539 | hdrfile.write("data type = " + str(datatype) + "\n") |
|---|
| 540 | hdrfile.write("interleave = " + interleave + "\n") |
|---|
| 541 | hdrfile.write("byte order = 0\n") |
|---|
| 542 | |
|---|
| 543 | hdrfile.flush() |
|---|
| 544 | hdrfile.close() |
|---|
| 545 | # end function |
|---|
| 546 | |
|---|
| 547 | # Function readHdrFile |
|---|
| 548 | # Reads data from a given ENVI-style header file |
|---|
| 549 | # |
|---|
| 550 | # Arguments |
|---|
| 551 | # hdrfilename: Name of header file to be read |
|---|
| 552 | # |
|---|
| 553 | # Returns: Dictionary containing keys/values from header file |
|---|
| 554 | def readHdrFile(hdrfilename): |
|---|
| 555 | output = {} |
|---|
| 556 | inblock = False |
|---|
| 557 | |
|---|
| 558 | try: |
|---|
| 559 | hdrfile = open(hdrfilename, "r") |
|---|
| 560 | except: |
|---|
| 561 | print "Could not open hdr file '" + str(hdrfilename) + "'" |
|---|
| 562 | raise |
|---|
| 563 | # end try |
|---|
| 564 | |
|---|
| 565 | # Read line, split it on equals, strip whitespace from resulting strings and add key/value pair to output |
|---|
| 566 | currentline = hdrfile.readline() |
|---|
| 567 | while (currentline != ""): |
|---|
| 568 | # ENVI headers accept blocks bracketed by curly braces - check for these |
|---|
| 569 | if (not inblock): |
|---|
| 570 | # Split line on first equals sign |
|---|
| 571 | if (re.search("=", currentline) != None): |
|---|
| 572 | linesplit = re.split("=", currentline, 1) |
|---|
| 573 | key = linesplit[0].strip() |
|---|
| 574 | value = linesplit[1].strip() |
|---|
| 575 | |
|---|
| 576 | # If value starts with an open brace, it's the start of a block - strip the brace off and read the rest of the block |
|---|
| 577 | if (re.match("{", value) != None): |
|---|
| 578 | inblock = True |
|---|
| 579 | value = re.sub("^{", "", value, 1) |
|---|
| 580 | |
|---|
| 581 | # If value ends with a close brace it's the end of the block as well - strip the brace off |
|---|
| 582 | if (re.search("}$", value)): |
|---|
| 583 | inblock = False |
|---|
| 584 | value = re.sub("}$", "", value, 1) |
|---|
| 585 | # end if |
|---|
| 586 | # end if |
|---|
| 587 | value = value.strip() |
|---|
| 588 | output[key] = value |
|---|
| 589 | # end if |
|---|
| 590 | else: |
|---|
| 591 | # If we're in a block, just read the line, strip whitespace (and any closing brace ending the block) and add the whole thing |
|---|
| 592 | value = currentline.strip() |
|---|
| 593 | if (re.search("}$", value)): |
|---|
| 594 | inblock = False |
|---|
| 595 | value = re.sub("}$", "", value, 1) |
|---|
| 596 | value = value.strip() |
|---|
| 597 | # end if |
|---|
| 598 | output[key] = output[key] + value |
|---|
| 599 | # end if |
|---|
| 600 | |
|---|
| 601 | currentline = hdrfile.readline() |
|---|
| 602 | # end while |
|---|
| 603 | |
|---|
| 604 | hdrfile.close() |
|---|
| 605 | |
|---|
| 606 | return output |
|---|
| 607 | # end function |
|---|
| 608 | |
|---|
| 609 | # Function getEnviType |
|---|
| 610 | # Gets the ENVI type code equivalent to a particular Python struct format string |
|---|
| 611 | # |
|---|
| 612 | # Arguments |
|---|
| 613 | # formatstr: Struct format string to get ENVI type code for |
|---|
| 614 | # |
|---|
| 615 | # Returns: ENVI numeric type code for supplied format string |
|---|
| 616 | def getEnviType(formatstr): |
|---|
| 617 | |
|---|
| 618 | dtype = -1 |
|---|
| 619 | |
|---|
| 620 | # Check the given format string is valid |
|---|
| 621 | try: |
|---|
| 622 | struct.calcsize(formatstr) |
|---|
| 623 | except: |
|---|
| 624 | raise ValueError, formatstr + " is not a valid format string" |
|---|
| 625 | # end try |
|---|
| 626 | |
|---|
| 627 | # Do the conversion |
|---|
| 628 | if (formatstr == "b"): |
|---|
| 629 | dtype = 1 # Signed (?) byte |
|---|
| 630 | elif (formatstr == "h"): |
|---|
| 631 | dtype = 2 # 2-byte signed short int (ENVI calls it an int) |
|---|
| 632 | elif (formatstr == "H"): |
|---|
| 633 | dtype = 12 # 2-byte unsigned int (ENVI calls it an int) |
|---|
| 634 | elif (formatstr == "i"): |
|---|
| 635 | dtype = 3 # 4-byte signed int (ENVI calls it a Long) |
|---|
| 636 | elif (formatstr == "I"): |
|---|
| 637 | dtype = 13 # 4-byte unsigned int (ENVI calls it a Long) |
|---|
| 638 | elif (formatstr == "f"): |
|---|
| 639 | dtype = 4 # 4-byte float |
|---|
| 640 | elif (formatstr == "d"): |
|---|
| 641 | dtype = 5 # 8-byte double precision |
|---|
| 642 | elif (formatstr == "l"): |
|---|
| 643 | dtype = 14 # 8-byte long int (ENVI 64-bit int) |
|---|
| 644 | elif (formatstr == "L"): |
|---|
| 645 | dtype = 15 # 8-byte unsigned long int (ENVI 64-bit int) |
|---|
| 646 | else: |
|---|
| 647 | # If we get here then the format string is valid for Python but not for ENVI, raise an error |
|---|
| 648 | raise ValueError, formatstr + " is a valid Python format string but does not have an ENVI equivalent" |
|---|
| 649 | # end if |
|---|
| 650 | |
|---|
| 651 | return dtype |
|---|
| 652 | # end function |
|---|
| 653 | |
|---|
| 654 | # Function getStructType |
|---|
| 655 | # Gets the Python struct format string equivalent to a particular ENVI type code |
|---|
| 656 | # |
|---|
| 657 | # Arguments |
|---|
| 658 | # typecode: ENVI type code to get Python format string for |
|---|
| 659 | # |
|---|
| 660 | # Returns: Single-character Python struct format string |
|---|
| 661 | def getStructType(typecode): |
|---|
| 662 | |
|---|
| 663 | try: |
|---|
| 664 | inttype = int(typecode) |
|---|
| 665 | except: |
|---|
| 666 | raise ValueError, str(typecode) + " is not a valid ENVI type for conversion" |
|---|
| 667 | # end try |
|---|
| 668 | |
|---|
| 669 | # Do the conversion |
|---|
| 670 | if (inttype == 1): |
|---|
| 671 | formatstr = "b" # Signed (?) byte |
|---|
| 672 | elif (inttype == 2): |
|---|
| 673 | formatstr = "h" # 2-byte signed short int (ENVI calls it an int) |
|---|
| 674 | elif (inttype == 12): |
|---|
| 675 | formatstr = "H" # 2-byte unsigned int (ENVI calls it an int) |
|---|
| 676 | elif (inttype == 3): |
|---|
| 677 | formatstr = "i" # 4-byte signed int (ENVI calls it a Long) |
|---|
| 678 | elif (inttype == 13): |
|---|
| 679 | formatstr = "I" # 4-byte unsigned int (ENVI calls it a Long) |
|---|
| 680 | elif (inttype == 4): |
|---|
| 681 | formatstr = "f" # 4-byte float |
|---|
| 682 | elif (inttype == 5): |
|---|
| 683 | formatstr = "d" # 8-byte double precision |
|---|
| 684 | elif (inttype == 14): |
|---|
| 685 | formatstr = "l" # 8-byte long int (ENVI 64-bit int) |
|---|
| 686 | elif (inttype == 15): |
|---|
| 687 | formatstr = "L" # 8-byte unsigned long int (ENVI 64-bit int) |
|---|
| 688 | else: |
|---|
| 689 | # If we get here then the type code doesn't have a Python equivalent, raise an error |
|---|
| 690 | raise ValueError, str(typecode) + " does not have an equivalent Python format string" |
|---|
| 691 | # end if |
|---|
| 692 | |
|---|
| 693 | return formatstr |
|---|
| 694 | # end function |
|---|