hylite.io
Import and export hyperspectral data. For hyperspectral images this is mostly done using GDAL, while for point clouds and hyperspectral libraries a variety of different methods are included.
1""" 2Import and export hyperspectral data. For hyperspectral images this is mostly done using GDAL, 3while for point clouds and hyperspectral libraries a variety of different methods are included. 4""" 5from .headers import * 6from .images import * 7from .clouds import * 8from .libraries import * 9from .pmaps import * 10from .cameras import saveCameraTXT, loadCameraTXT 11from pathlib import Path 12 13from hylite import HyImage, HyCloud, HyLibrary, HyCollection, HyScene, HyData 14from hylite.project import PMap, Camera, Pushbroom 15from hylite.analyse.mwl import MWL 16from distutils.dir_util import copy_tree 17import os 18 19# check if gdal is installed 20try: 21 from osgeo import gdal 22 usegdal = True 23except ModuleNotFoundError: 24 usegdal = False 25 26def save(path, data, **kwds): 27 """ 28 A generic function for saving HyData instances such as HyImage, HyLibrary and HyCloud. The appropriate file format 29 will be chosen automatically. 30 31 Args: 32 path (str): the path to save the file too. 33 data (HyData or ndarray): the data to save. This must be an instance of HyImage, HyLibrary or HyCloud. 34 **kwds: Keywords can include: 35 36 - vmin = the data value that = 0 when saving RGB images. 37 - vmax = the data value that = 255 when saving RGB images. Must be > vmin. 38 """ 39 40 if isinstance(data, HyImage): 41 42 # special case - save ternary image to png or jpg or bmp 43 ext = os.path.splitext(path)[1].lower() 44 if 'jpg' in ext or 'bmp' in ext or 'png' in ext or 'pdf' in ext: 45 if data.band_count() == 1 or data.band_count() == 3 or data.band_count == 4: 46 rgb = np.transpose( data.data, (1,0,2) ) 47 if not ((data.is_int() and np.max(rgb) <= 255)): # handle normalisation 48 vmin = kwds.get("vmin", np.nanpercentile(rgb, 1 ) ) 49 vmax = kwds.get("vmax", np.nanpercentile(rgb, 99) ) 50 rgb = (rgb - vmin) / (vmax-vmin) 51 rgb = (np.clip(rgb, 0, 1) * 255).astype(np.uint8) # convert to 8 bit image 52 #from matplotlib.pyplot import imsave 53 # imsave( path, rgb ) 54 from skimage import io as skio 55 skio.imsave( path, rgb ) # save the image 56 return 57 elif ((data.band_count() == 1) or (data.band_count() == 3) or (data.band_count() == 4)) and (data.data.dtype == np.uint8): 58 # save 1, 3 and 4 band uint8 arrays as png files 59 # from matplotlib.pyplot import imsave 60 # imsave( os.path.splitext(path)[0]+".png", data.data) # save the image 61 #from skimage import io as skio 62 #skio.imsave(os.path.splitext(path)[0]+".png", np.transpose( data.data, (1,0,2) )) # save the image 63 from PIL import Image 64 if (data.band_count() == 1): 65 img = Image.fromarray(data.data[..., 0].T) # single-band PNG 66 else: 67 img = Image.fromarray(np.transpose(data.data, (1, 0, 2))) # ternary PNG 68 69 img.save(os.path.splitext(path)[0]+".png", "PNG") 70 save( os.path.splitext(path)[0] + ".hdr", data.header ) # save header 71 return 72 else: # save hyperspectral image 73 if usegdal: 74 from osgeo import gdal # is gdal installed? 75 save_func = saveWithGDAL 76 else: # no gdal 77 #save_func = saveWithSPy 78 save_func = saveWithNumpy 79 if 'lib' in ext: # special case - we are actually saving a HyLibrary (as an image) 80 ext = 'lib' 81 else: 82 ext = 'dat' 83 elif isinstance(data, HyHeader): 84 save_func = saveHeader 85 ext = 'hdr' 86 elif isinstance(data, HyCloud): 87 save_func = saveCloudPLY 88 ext = 'ply' 89 elif isinstance(data, HyLibrary): 90 save_func = saveLibraryLIB 91 ext = 'lib' 92 elif isinstance(data, PMap ): 93 save_func = savePMap 94 ext = 'npz' 95 elif isinstance(data, Camera ): 96 save_func = saveCameraTXT 97 ext = 'cam' 98 elif isinstance(data, Pushbroom): 99 save_func = saveCameraTXT 100 ext = 'brm' 101 elif isinstance(data, HyCollection): 102 save_func = _saveCollection 103 ext = data.ext[1:] 104 outdir = str( Path(data.root) / os.path.splitext(data.name)[0]) 105 os.makedirs( os.path.splitext(path)[0] +"."+ ext, exist_ok=True ) # make output directory (even if empty) 106 if os.path.splitext(path)[0] != outdir: 107 if os.path.exists( outdir+"."+ext): # if it exists... 108 #if sys.version_info[1] >= 8: # python 3.8 or greater 109 # shutil.copytree( outdir+"."+ext, os.path.splitext(path)[0]+"."+ext, dirs_exist_ok=True) 110 #else: 111 # shutil.copytree( outdir+"."+ext, os.path.splitext(path)[0]+"."+ext ) # will fail if directory already exists unfortunately. 112 copy_tree(outdir+"."+ext, os.path.splitext(path)[0]+"."+ext) 113 114 115 elif isinstance(data, np.ndarray) or isinstance(data, list): 116 save_func = np.save 117 ext = 'npy' 118 elif isinstance( data, dict ): 119 # try serialising dicts as json files 120 def save_json( path, data ): 121 import json 122 with open(path,'w') as f: 123 json.dump( data, f, **kwds) 124 save_func = save_json 125 ext = 'json' 126 else: 127 assert False, "Error - data type %s is unsupported by hylite.io.save." % type(data) 128 129 # check path file extension 130 if 'hdr' in os.path.splitext(path)[1]: # auto strip .hdr extensions if provided 131 path = os.path.splitext(path)[0] 132 if ext not in os.path.splitext(path)[1]: # add type-specific extension if needed 133 path += '.%s'%ext 134 135 # save! 136 os.makedirs( os.path.dirname(path), exist_ok=True) # make output directory 137 save_func( path, data ) 138 139def load(path): 140 """ 141 A generic function for loading hyperspectral images, point clouds and libraries. The appropriate load function 142 will be chosen based on the file extension. 143 144 Args: 145 path (str): the path of the file to load. 146 147 Returns: 148 The loaded data. 149 """ 150 151 assert os.path.exists( path ), "Error: file %s does not exist." % path 152 153 # load file formats with no associated header 154 if 'npz' in os.path.splitext( path )[1].lower(): 155 return loadPMap(path) 156 elif 'npy' in os.path.splitext( path )[1].lower(): 157 return np.load( path ) # load numpy 158 elif 'json' in os.path.splitext( path )[1].lower(): 159 import json 160 out = {} 161 with open(path,'r') as f: 162 out = json.load( f ) 163 return out 164 165 # file (should/could) have header - look for it 166 header, data = matchHeader( path ) 167 assert os.path.exists(str(data)), "Error - data file %s does not exist." % path 168 ext = os.path.splitext(data)[1].lower() 169 if ext == '': 170 assert os.path.isfile(data), "Error - %s is a directory not a file." % data 171 172 # load other file types 173 if 'ply' in ext: # point or hypercloud 174 out = loadCloudPLY(path) # load dataset 175 elif 'las' in ext: # point or hypercloud 176 out = loadCloudLAS(path) 177 elif 'csv' in ext: # (flat) spectral library 178 out = loadLibraryCSV(path) 179 elif 'txt' in ext: # (flat) spectral library 180 out = loadLibraryTXT(path) 181 elif 'sed' in ext: # (flat) spectral library 182 out = loadLibrarySED(path) 183 elif 'tsg' in ext: # (flat) spectral library 184 out = loadLibraryTSG(path) 185 elif 'hyc' in ext or 'hys' in ext or 'mwl' in ext: # load hylite collection, hyscene or mwl map 186 out = _loadCollection(path) 187 elif 'cam' in ext or 'brm' in ext: # load pushbroom and normal cameras 188 out = loadCameraTXT(path) 189 else: # image 190 # load conventional images with PIL 191 if 'png' in ext or 'jpg' in ext or 'bmp' in ext: 192 # load image with matplotlib 193 #from matplotlib.pyplot import imread 194 #im = imread(path) 195 from skimage import io as skio 196 im = skio.imread(data) 197 if len(im.shape) == 2: 198 im = im[:,:,None] # add last dimension if greyscale image is loaded 199 out = HyImage(np.transpose(im, (1, 0, 2))) 200 if header is not None: 201 out.header = loadHeader(header) 202 else: 203 if usegdal: 204 from osgeo import gdal # is gdal installed? 205 out = loadWithGDAL(path) 206 else: # no gdal 207 #out = loadWithSPy(path) 208 out = loadWithNumpy(path) 209 # special case - loading spectral library; convert image to HyData 210 if 'lib' in ext: 211 out = HyLibrary(out.data, header=out.header) 212 return out # return dataset 213 214############################################## 215## save and load data collections 216############################################## 217# save collection 218def _saveCollection(path, collection): 219 # generate file paths 220 dirmap = collection.get_file_dictionary(root=os.path.dirname(path), 221 name=os.path.splitext(os.path.basename(path))[0]) 222 # print(dirmap) 223 for p, o in dirmap.items(): 224 os.makedirs(os.path.dirname(p), exist_ok=True) # make output directory if needed 225 save(p, o) # save each path and item [ n.b. this includes the header file! :-) ] 226 227def _loadCollection(path): 228 # load header and find directory path 229 header, directory = matchHeader(path) 230 231 # parse name and root 232 root = os.path.dirname(directory) 233 name = os.path.basename(os.path.splitext(directory)[0]) 234 235 if 'hyc' in os.path.splitext(directory)[1]: 236 C = HyCollection(name, root, header=loadHeader(header)) 237 elif 'hys' in os.path.splitext(directory)[1]: 238 C = HyScene(name, root, header=loadHeader(header)) 239 elif 'mwl' in os.path.splitext(directory)[1]: 240 C = MWL(name, root, header=loadHeader(header)) 241 else: 242 # print(header, directory ) 243 assert False, "Error - %s is an invalid collection." % directory 244 return C
def
save(path, data, **kwds):
28def save(path, data, **kwds): 29 """ 30 A generic function for saving HyData instances such as HyImage, HyLibrary and HyCloud. The appropriate file format 31 will be chosen automatically. 32 33 Args: 34 path (str): the path to save the file too. 35 data (HyData or ndarray): the data to save. This must be an instance of HyImage, HyLibrary or HyCloud. 36 **kwds: Keywords can include: 37 38 - vmin = the data value that = 0 when saving RGB images. 39 - vmax = the data value that = 255 when saving RGB images. Must be > vmin. 40 """ 41 42 if isinstance(data, HyImage): 43 44 # special case - save ternary image to png or jpg or bmp 45 ext = os.path.splitext(path)[1].lower() 46 if 'jpg' in ext or 'bmp' in ext or 'png' in ext or 'pdf' in ext: 47 if data.band_count() == 1 or data.band_count() == 3 or data.band_count == 4: 48 rgb = np.transpose( data.data, (1,0,2) ) 49 if not ((data.is_int() and np.max(rgb) <= 255)): # handle normalisation 50 vmin = kwds.get("vmin", np.nanpercentile(rgb, 1 ) ) 51 vmax = kwds.get("vmax", np.nanpercentile(rgb, 99) ) 52 rgb = (rgb - vmin) / (vmax-vmin) 53 rgb = (np.clip(rgb, 0, 1) * 255).astype(np.uint8) # convert to 8 bit image 54 #from matplotlib.pyplot import imsave 55 # imsave( path, rgb ) 56 from skimage import io as skio 57 skio.imsave( path, rgb ) # save the image 58 return 59 elif ((data.band_count() == 1) or (data.band_count() == 3) or (data.band_count() == 4)) and (data.data.dtype == np.uint8): 60 # save 1, 3 and 4 band uint8 arrays as png files 61 # from matplotlib.pyplot import imsave 62 # imsave( os.path.splitext(path)[0]+".png", data.data) # save the image 63 #from skimage import io as skio 64 #skio.imsave(os.path.splitext(path)[0]+".png", np.transpose( data.data, (1,0,2) )) # save the image 65 from PIL import Image 66 if (data.band_count() == 1): 67 img = Image.fromarray(data.data[..., 0].T) # single-band PNG 68 else: 69 img = Image.fromarray(np.transpose(data.data, (1, 0, 2))) # ternary PNG 70 71 img.save(os.path.splitext(path)[0]+".png", "PNG") 72 save( os.path.splitext(path)[0] + ".hdr", data.header ) # save header 73 return 74 else: # save hyperspectral image 75 if usegdal: 76 from osgeo import gdal # is gdal installed? 77 save_func = saveWithGDAL 78 else: # no gdal 79 #save_func = saveWithSPy 80 save_func = saveWithNumpy 81 if 'lib' in ext: # special case - we are actually saving a HyLibrary (as an image) 82 ext = 'lib' 83 else: 84 ext = 'dat' 85 elif isinstance(data, HyHeader): 86 save_func = saveHeader 87 ext = 'hdr' 88 elif isinstance(data, HyCloud): 89 save_func = saveCloudPLY 90 ext = 'ply' 91 elif isinstance(data, HyLibrary): 92 save_func = saveLibraryLIB 93 ext = 'lib' 94 elif isinstance(data, PMap ): 95 save_func = savePMap 96 ext = 'npz' 97 elif isinstance(data, Camera ): 98 save_func = saveCameraTXT 99 ext = 'cam' 100 elif isinstance(data, Pushbroom): 101 save_func = saveCameraTXT 102 ext = 'brm' 103 elif isinstance(data, HyCollection): 104 save_func = _saveCollection 105 ext = data.ext[1:] 106 outdir = str( Path(data.root) / os.path.splitext(data.name)[0]) 107 os.makedirs( os.path.splitext(path)[0] +"."+ ext, exist_ok=True ) # make output directory (even if empty) 108 if os.path.splitext(path)[0] != outdir: 109 if os.path.exists( outdir+"."+ext): # if it exists... 110 #if sys.version_info[1] >= 8: # python 3.8 or greater 111 # shutil.copytree( outdir+"."+ext, os.path.splitext(path)[0]+"."+ext, dirs_exist_ok=True) 112 #else: 113 # shutil.copytree( outdir+"."+ext, os.path.splitext(path)[0]+"."+ext ) # will fail if directory already exists unfortunately. 114 copy_tree(outdir+"."+ext, os.path.splitext(path)[0]+"."+ext) 115 116 117 elif isinstance(data, np.ndarray) or isinstance(data, list): 118 save_func = np.save 119 ext = 'npy' 120 elif isinstance( data, dict ): 121 # try serialising dicts as json files 122 def save_json( path, data ): 123 import json 124 with open(path,'w') as f: 125 json.dump( data, f, **kwds) 126 save_func = save_json 127 ext = 'json' 128 else: 129 assert False, "Error - data type %s is unsupported by hylite.io.save." % type(data) 130 131 # check path file extension 132 if 'hdr' in os.path.splitext(path)[1]: # auto strip .hdr extensions if provided 133 path = os.path.splitext(path)[0] 134 if ext not in os.path.splitext(path)[1]: # add type-specific extension if needed 135 path += '.%s'%ext 136 137 # save! 138 os.makedirs( os.path.dirname(path), exist_ok=True) # make output directory 139 save_func( path, data )
A generic function for saving HyData instances such as HyImage, HyLibrary and HyCloud. The appropriate file format will be chosen automatically.
Arguments:
- path (str): the path to save the file too.
- data (HyData or ndarray): the data to save. This must be an instance of HyImage, HyLibrary or HyCloud.
**kwds: Keywords can include:
- vmin = the data value that = 0 when saving RGB images.
- vmax = the data value that = 255 when saving RGB images. Must be > vmin.
def
load(path):
141def load(path): 142 """ 143 A generic function for loading hyperspectral images, point clouds and libraries. The appropriate load function 144 will be chosen based on the file extension. 145 146 Args: 147 path (str): the path of the file to load. 148 149 Returns: 150 The loaded data. 151 """ 152 153 assert os.path.exists( path ), "Error: file %s does not exist." % path 154 155 # load file formats with no associated header 156 if 'npz' in os.path.splitext( path )[1].lower(): 157 return loadPMap(path) 158 elif 'npy' in os.path.splitext( path )[1].lower(): 159 return np.load( path ) # load numpy 160 elif 'json' in os.path.splitext( path )[1].lower(): 161 import json 162 out = {} 163 with open(path,'r') as f: 164 out = json.load( f ) 165 return out 166 167 # file (should/could) have header - look for it 168 header, data = matchHeader( path ) 169 assert os.path.exists(str(data)), "Error - data file %s does not exist." % path 170 ext = os.path.splitext(data)[1].lower() 171 if ext == '': 172 assert os.path.isfile(data), "Error - %s is a directory not a file." % data 173 174 # load other file types 175 if 'ply' in ext: # point or hypercloud 176 out = loadCloudPLY(path) # load dataset 177 elif 'las' in ext: # point or hypercloud 178 out = loadCloudLAS(path) 179 elif 'csv' in ext: # (flat) spectral library 180 out = loadLibraryCSV(path) 181 elif 'txt' in ext: # (flat) spectral library 182 out = loadLibraryTXT(path) 183 elif 'sed' in ext: # (flat) spectral library 184 out = loadLibrarySED(path) 185 elif 'tsg' in ext: # (flat) spectral library 186 out = loadLibraryTSG(path) 187 elif 'hyc' in ext or 'hys' in ext or 'mwl' in ext: # load hylite collection, hyscene or mwl map 188 out = _loadCollection(path) 189 elif 'cam' in ext or 'brm' in ext: # load pushbroom and normal cameras 190 out = loadCameraTXT(path) 191 else: # image 192 # load conventional images with PIL 193 if 'png' in ext or 'jpg' in ext or 'bmp' in ext: 194 # load image with matplotlib 195 #from matplotlib.pyplot import imread 196 #im = imread(path) 197 from skimage import io as skio 198 im = skio.imread(data) 199 if len(im.shape) == 2: 200 im = im[:,:,None] # add last dimension if greyscale image is loaded 201 out = HyImage(np.transpose(im, (1, 0, 2))) 202 if header is not None: 203 out.header = loadHeader(header) 204 else: 205 if usegdal: 206 from osgeo import gdal # is gdal installed? 207 out = loadWithGDAL(path) 208 else: # no gdal 209 #out = loadWithSPy(path) 210 out = loadWithNumpy(path) 211 # special case - loading spectral library; convert image to HyData 212 if 'lib' in ext: 213 out = HyLibrary(out.data, header=out.header) 214 return out # return dataset
A generic function for loading hyperspectral images, point clouds and libraries. The appropriate load function will be chosen based on the file extension.
Arguments:
- path (str): the path of the file to load.
Returns:
The loaded data.