├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── build_uav_toolkit.pro ├── custom_code ├── README.md ├── bandAlignment │ ├── bandalignment_applyreferencetiepointstogroups.pro │ ├── bandalignment_applyreferencetiepointswithenvi.pro │ ├── bandalignment_applyreferencetiepointswithidl.pro │ ├── bandalignment_find_good_image_group.pro │ ├── bandalignment_generatereferencetiepoints.pro │ ├── bandalignment_get_image_groups.pro │ ├── bandalignment_group_to_virtualraster.pro │ ├── bandalignment_processsensor_getreferencetiepoints.pro │ ├── bandalignment_savereferencetiepoints.pro │ ├── bandalignment_set_task_parameters.pro │ ├── bandalignment_setupsensorforprocessing.pro │ ├── bandalignment_simple_kappa_filter.pro │ ├── bandalignmenttiepoints__define.pro │ ├── uavbandalignment.pro │ ├── uavbandalignment.task │ └── uavbandalignmentbasic.task ├── batchTasks │ ├── uavbatchrededge.pro │ └── uavbatchrededge.task └── filterRaster │ └── filter_raster.pro ├── docs ├── .nojekyll ├── CHANGELOG.html ├── CONTRIBUTING.html ├── LICENSE.html ├── css │ ├── anchor-fix.css │ ├── bootstrap.min.css │ ├── callouts.css │ ├── copy-button.css │ ├── cosmos.bootstrap.min.css │ ├── figures-and-tables.css │ ├── general-styles.css │ ├── haroopad-markdown.css │ ├── idl-styles.css │ ├── search-results.css │ └── sidebar.css ├── custom_code │ ├── README.html │ ├── bandAlignment │ │ └── uavbandalignment.html │ └── batchTasks │ │ └── uavbatchrededge.html ├── images │ ├── L3Harrislogowhite.svg │ ├── baseline_code_white_48dp.png │ ├── baseline_home_white_48dp.png │ ├── baseline_menu_white_48dp.png │ └── baseline_search_white_48dp.png ├── index.html ├── index │ ├── index.html │ ├── indexSearch.js │ └── lunrIndex.js ├── js │ ├── LICENSE.clipboard.txt │ ├── LICENSE.lunr.txt │ ├── bootstrap.min.js │ ├── clipboard.min.js │ ├── copy-button.js │ ├── highlight │ │ ├── LICENSE │ │ ├── highlight.pack.js │ │ └── styles │ │ │ ├── agate.css │ │ │ ├── atom-one-dark-reasonable.css │ │ │ ├── atom-one-dark.css │ │ │ ├── atom-one-light.css │ │ │ ├── github-gist.css │ │ │ ├── github.css │ │ │ ├── monokai-sublime.css │ │ │ ├── railscasts.css │ │ │ ├── rainbow.css │ │ │ └── solarized-dark.css │ ├── jquery.min.js │ └── lunr.min.js ├── src │ ├── generate_cam_file.html │ ├── generate_gps_file.html │ ├── join_rededge_datadirs.html │ ├── rededge_to_radiance.html │ └── unjoin_rededge_datadir.html └── uav_toolkit.html ├── idl.lint.json ├── idl.package.json ├── idl.package.lock.json ├── idl.test.json ├── idl_packages ├── github-bridgeit │ ├── LICENSE.txt │ ├── README.md │ ├── bridge_it__define.pro │ └── bridge_it_example.pro └── github-se-awesomeenvitoolkit │ ├── .gitignore │ ├── LICENSE.txt │ ├── README.md │ ├── idl.lint.json │ ├── idl.package.json │ ├── idl.package.lock.json │ ├── idl.test.json │ └── src │ ├── awesomeENVIProgress │ ├── README.md │ ├── awesomeenviprogress__define.pro │ └── awesomeenviprogress__define.spec.pro │ ├── awesomeENVITask │ ├── awesomeenvitask.pro │ ├── awesomeenvitask.spec.pro │ └── classify_raster.task │ ├── awesomeGetENVI │ ├── awesomegetenvi.pro │ └── awesomegetenvi.spec.pro │ ├── awesomeSelectTaskParameters │ └── awesomeselecttaskparameters.pro │ ├── awesomeTileIterator │ ├── README.md │ ├── createawesomeoptimizedtileiterator.pro │ ├── createawesometileiterator.pro │ └── createawesometileiterator.spec.pro │ ├── awesomecatchblock.pro │ ├── awesomeenviareextensionsinitialized.pro │ ├── awesomepixelsize.pro │ ├── awesomexmlparse.pro │ ├── bak │ └── awesomeenvirastergetdata.pro.bak │ └── enviraster_methods │ ├── enviraster__awesomeexport.pro │ └── enviraster__awesomeexport.spec.pro ├── src ├── generate_cam_file.pro ├── generate_gps_file.pro ├── get_co_calibration.pro ├── get_histograms.pro ├── get_reflectance_panels.pro ├── image_groups_to_virtual_rasters.pro ├── image_info__define.pro ├── join_rededge_datadirs.pro ├── rededge_to_radiance.pro ├── uav_toolkit_calibrate_data.pro └── unjoin_rededge_datadir.pro ├── tests ├── uav_toolkit_test_calibration.spec.pro ├── uav_toolkit_test_generic.spec.pro ├── uav_toolkit_test_generic_multipage.spec.pro ├── uav_toolkit_test_rededge.spec.pro ├── uav_toolkit_test_rededge_panel_extraction.spec.pro ├── uav_toolkit_test_rededge_radiance.spec.pro └── uav_toolkit_test_sequoia.spec.pro └── uav_toolkit.pro /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore file initialized by the IDL Developer Tools 2 | /dist 3 | *spec.pro.log 4 | *idl.test.log 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file summarizes the overall changes for the UAV toolkit. 4 | 5 | ## Version 2.3.5 6 | 7 | Updated copyright statements 8 | 9 | Added updated example to README for UAVBatchRededge 10 | 11 | Rebuilt docs 12 | 13 | Some minor updates to the idl.package.json file 14 | 15 | ## Version 2.3.1 16 | 17 | Comment updates to add tooltips 18 | 19 | Documentation updated 20 | 21 | LICENSE.txt updated to add "Copyright" 22 | 23 | idl.package.json updated for hooks into ENVI Tasks and Extensions 24 | 25 | ## Version 2.3 26 | 27 | Updated the reflectance panel extractor to work better when the background is also bright. As long as the reflectance panels are greater than the mean reflectance of the image, they should be extractable. The new algorithm finds a measure of "squareness" and uses an optmization function to extract the most "square" clump of pixels in the scene. 28 | 29 | Documentation has been updated. 30 | 31 | 32 | ## Version 2.2 33 | 34 | Updated some of the progress messages when running ENVI headlessly 35 | 36 | Added support for data that is already a multi-page TIFF in a single file, created a test for it, and updated the README with an example. 37 | 38 | 39 | ## Version 2.1 40 | 41 | Added buttons to the uav_toolkit procedure so that you can have a UI for the tools and updated the readme. With this change, the progress information has also been updated. 42 | 43 | Separated code that belogs o other repositories which is now included in the `idl_pacakges` folder. 44 | 45 | Fixed a bug with Parrot Sequoia metadata have a different tag name for ISOSpeed. 46 | 47 | Updated some routine names, task display names, and reorganized task parameters. This should not affect any code as the proper entry point is through the tasks. 48 | 49 | Added a new Basic version of the UAV Toolkit Band Alignment task that has about 1/3 of the parameters to make it easier to use in the ENVI UI. 50 | 51 | 52 | ## Version 2.0.2 53 | 54 | When generating the tie points for the bands, we algorithm now only uses ENVI's advanced algorithm for tie point generation. This means that the tie point generation process may take 10-15 minutes, depending on your data. This change removed the `RIGOROUS_ALIGNMENT` task parameter of the `UAVBandAlignment` task. 55 | 56 | 57 | ## Version 2.0.1 58 | 59 | There have been many overall updates to the UAV Toolkit since it's initial release which consists of: 60 | 61 | - Complete automation of the process for generating and applying reference tie points to imagery. 62 | 63 | There is a new routine that works behind the scenes to find an image group that the reference tie points can be taken from and it has worked very well on the test datasets that I have used. 64 | 65 | - Refactoring and optimization of much of the code 66 | 67 | Behind the scenes a new suite of routines is used for performing the band-band registration that can be used with any type of imagery or raster. 68 | 69 | - Generic support for any multispectral sensor that produces separate images for each band. 70 | 71 | If there are other sensors feel free to make a request for adding them, provided sample data can be shared. 72 | 73 | - Refocused overall tools for band-band alignment 74 | 75 | - **This means that some routines have been removed completely from this version of the UAV Toolkit. If there are missing functions/procedures that you want which are missing, then file a bug report via the main GitHub repository and I will add them back in.** 76 | 77 | - Packaged the tools to use the ENVI Task framework -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | See the [ENVI contributing guide](https://github.com/envi-idl/ContribGuide) and the [IDL contributing guide](https://github.com/interactive-data-language/ContribGuide) for code formatting information. 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Harris Geospatial Solutions, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.TRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /build_uav_toolkit.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | 8 | ;+ 9 | ; 10 | ; :Private: 11 | ; 12 | ; IDL batch file that will compile the code present into a single IDL 13 | ; SAVE file called the UAV Toolkit. This will also place the task files next to 14 | ; the compiled source code. 15 | ; 16 | ; This is only necessary if you are trying to use the UAV Toolkit without IDL 17 | ; or with the enterprise version of ENVI/IDL. 18 | ; 19 | ; Just press compile + run in the IDL workbench to build the IDL SAVE file. 20 | ; 21 | ; The output directory will be called "UAVToolkit-build" in your system temporary 22 | ; folder. This helps avoid any conflicts on IDL's search path. 23 | ; 24 | ; 25 | ; :Author: Zachary Norman - GitLab: [znorman-harris](https://github.com/znorman-harris) 26 | ;- 27 | 28 | 29 | ;get this source directory 30 | thisDir = file_dirname(routine_filepath()) 31 | 32 | ;set up our output location 33 | outDir = filepath('', /TMP) + 'UAVToolkit-build' 34 | if ~file_test(outDir, /DIRECTORY) then file_mkdir, outDir 35 | 36 | ;search for PRO files 37 | files = file_search(thisDir, '*.pro', COUNT = nPro) 38 | 39 | ;validate 40 | if (nPro eq 0) then begin 41 | message, 'No PRO files found in this source directory, where did they go?' 42 | endif 43 | 44 | ;array of files to exclude form the build 45 | exclude = ['build_uav_toolkit.pro'] 46 | 47 | ;start child process 48 | bdg = idl_idlbridge() 49 | 50 | ;compile each file 51 | foreach file, files do begin 52 | ;skip condition 53 | if (total(strlowcase(file_basename(file)) eq exclude) gt 0) then continue 54 | bdg.execute, '.compile "' + file + '"' 55 | endforeach 56 | 57 | ;resolve dependencies 58 | bdg.execute, 'resolve_routine, "readexif__define"' 59 | bdg.execute, 'resolve_routine, "exifmetadata__define"' 60 | bdg.execute, 'resolve_all, /CONTINUE_ON_ERROR, SKIP_ROUTINES = ["envi", "envi_doit"]' 61 | 62 | ;save routines and clean up 63 | bdg.execute, 'save, /ROUTINES, FILENAME = "' + outDir + path_sep() + 'uav_toolkit.sav"' 64 | obj_destroy, bdg 65 | 66 | ;copy task files 67 | taskFiles = file_search(thisDir, '*.task', COUNT = nTask) 68 | if (nTask gt 0) then file_copy, taskFiles, outDir, /OVERWRITE 69 | 70 | ;alert user of output location 71 | print, 'Build located at : ' + outDir 72 | end 73 | -------------------------------------------------------------------------------- /custom_code/README.md: -------------------------------------------------------------------------------- 1 | # UAVToolkit Directory custom_code 2 | 3 | This folder contains the source code used for the different tasks in the UAV Toolkit. They are separated by the overall tasks that they belong to. The task files are also going to be located in these subfolders. -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_applyreferencetiepointswithenvi.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; TODO: save grid definition for regridding iamges so that it can be used elsewhere 14 | ; 15 | ; :Description: 16 | ; Procedure that uses built-in ENVI tasks to perform the image-image 17 | ; registration for band alignment. Instead of doing things in memory, this 18 | ; routine creates files on disk based on the `INPUT_REGISTRATION_TASK` that 19 | ; is specified by the user. 20 | ; 21 | ; Once finished, this task then tiles over the stacked layers and turns off 22 | ; pixels that do not have valid data values for each band. This helps remove 23 | ; potential artifacts around the edges of images that might appear. 24 | ; 25 | ; Fot the stacking this task uses virtual rasters behind the scene which 26 | ; will help make this process more efficient. 27 | ; 28 | ; 29 | ; :Keywords: 30 | ; INPUT_RASTER: in, required, type=ENVIRaster 31 | ; Specify the raster that you want to apply the `INPUT_TIEPOINTS` to. 32 | ; INPUT_TIEPOINTS: in, required, type=BandAlignmentTiePoints 33 | ; This should be set to a pointer array that contains the file coordinates 34 | ; for the tiepoints that will be applied to the images. 35 | ; IMAGE_REGISTRATION_TASK: in, requried, type=ENVITask, taskname=ImageToImageRegistration 36 | ; Provide the task that you want to be used for image-image registration when 37 | ; lining the bands up to one another. 38 | ; OUTPUT_RASTER_URI: in, optional, type=string 39 | ; Specifyt he fully-qualified output file that will be written to disk with the 40 | ; bands registered to one another. 41 | ; 42 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 43 | ;- 44 | pro BandAlignment_ApplyReferenceTiePointsWithENVI,$ 45 | INPUT_RASTER = input_raster,$ 46 | INPUT_BANDALIGNMENTTIEPOINTS = input_BandAlignmentTiePoints,$ 47 | IMAGE_REGISTRATION_TASK = image_registration_task,$ 48 | OUTPUT_RASTER_URI = output_raster_uri 49 | compile_opt idl2 50 | 51 | e = envi(/current) 52 | if (e eq !NULL) then begin 53 | e = envi(/headless) 54 | endif 55 | 56 | ;get info on our raster 57 | nBands = input_raster.NBANDS 58 | 59 | ;make sure that our tie points are valid 60 | if ~isa(input_BandAlignmentTiePoints, 'BandAlignmentTiePoints') then begin 61 | message, 'INPUT_BANDALIGNMENTTIEPOINTS is not a valid BandAlignmentTiePoints object, requried!' 62 | endif 63 | 64 | ;extract the tie points from our object 65 | use_tiepoints = input_BandAlignmentTiePoints.TIE_POINTS 66 | 67 | ;set our reference to the first invalid tiepoint set 68 | idxRef = where(~ptr_valid(use_tiepoints), countRef) 69 | if (countRef eq 0) then begin 70 | message, 'No reference band found within the INPUT_TIEPOINTS and all pointers are valid. Were they generated correctly?' 71 | endif 72 | reference_band = idxRef[0] 73 | 74 | ;create a lost of rasters to clean up 75 | clean = list() 76 | 77 | ;get base band 78 | baseRaster = ENVISubsetRaster(input_raster, BANDS = reference_band) 79 | clean.add, baseRaster 80 | 81 | ;preallocate an array to hold our rasters 82 | outputRasters = objarr(input_raster.NBANDS) 83 | 84 | ;save our base raster 85 | outputRasters[reference_band] = baseRaster 86 | 87 | ;iterate over each tiepoint task 88 | for i=0,nBands-1 do begin 89 | ;skip our base band 90 | if (i eq reference_band) then continue 91 | 92 | ;subset our raster 93 | bandRaster = ENVISubsetRaster(input_raster, BANDS = i) 94 | 95 | ;make a tiepointset 96 | tiePoints = ENVITiePointSet(TIEPOINTS = *use_tiepoints[i], $ 97 | INPUT_RASTER1 = baseRaster, INPUT_RASTER2 = bandRaster) 98 | 99 | ;duplicate our registration task 100 | regTask = ENVITask('ImageToImageRegistration') 101 | regTask.INPUT_TIEPOINTS = tiePoints 102 | regTask.WARPING = image_registration_task.WARPING 103 | regTask.POLYNOMIAL_DEGREE = image_registration_task.POLYNOMIAL_DEGREE 104 | regTask.RESAMPLING = image_registration_task.RESAMPLING 105 | regTask.BACKGROUND = image_registration_task. BACKGROUND 106 | regTask.FULL_EXTENT = image_registration_task.FULL_EXTENT 107 | regTask.OUTPUT_PIXEL_SIZE = image_registration_task.OUTPUT_PIXEL_SIZE 108 | 109 | ;run the task 110 | regTask.execute 111 | 112 | ;save output raster and add to data collection 113 | outputRasters[i] = regTask.OUTPUT_RASTER 114 | clean.add, regTask.OUTPUT_RASTER 115 | 116 | ;close our band raster if not our reference band 117 | bandRaster.close 118 | endfor 119 | 120 | ;find the grid that covers the intersection of all of our data 121 | foreach raster, outputRasters, i do begin 122 | ;skip reference 123 | if (i eq reference_band) then continue 124 | 125 | ; Create a grid definition for the first raster 126 | Raster1CoordSys = ENVICoordSys(COORD_SYS_STR = baseRaster.SPATIALREF.COORD_SYS_STR) 127 | Raster1Grid = ENVIGridDefinition(Raster1CoordSys, $ 128 | PIXEL_SIZE=baseRaster.SPATIALREF.PIXEL_SIZE, $ 129 | NROWS=baseRaster.NROWS, $ 130 | NCOLUMNS=baseRaster.NCOLUMNS, $ 131 | TIE_POINT_MAP=baseRaster.SPATIALREF.TIE_POINT_MAP, $ 132 | TIE_POINT_PIXEL=baseRaster.SPATIALREF.TIE_POINT_PIXEL) 133 | 134 | ; Create a grid definition for the second raster 135 | Raster2CoordSys = ENVICoordSys(COORD_SYS_STR = raster.SPATIALREF.COORD_SYS_STR) 136 | Raster2Grid = ENVIGridDefinition(Raster2CoordSys, $ 137 | PIXEL_SIZE=raster.SPATIALREF.PIXEL_SIZE, $ 138 | NROWS=raster.NROWS, $ 139 | NCOLUMNS=raster.NCOLUMNS, $ 140 | TIE_POINT_MAP=raster.SPATIALREF.TIE_POINT_MAP, $ 141 | TIE_POINT_PIXEL=raster.SPATIALREF.TIE_POINT_PIXEL) 142 | 143 | ; Get the intersection of the two rasters 144 | grid_definition = ENVIGridDefinition(Raster1CoordSys, $ 145 | EXTENT = Raster1Grid.Intersection(Raster2Grid), $ 146 | PIXEL_SIZE=baseRaster.SPATIALREF.PIXEL_SIZE < raster.SPATIALREF.PIXEL_SIZE) 147 | 148 | ;regrid our reference 149 | baseRaster = ENVISpatialgridRaster(baseRaster, GRID_DEFINITION = grid_definition) 150 | clean.add, baseRaster 151 | endforeach 152 | 153 | ;regrid all of our rasters 154 | foreach raster, outputRasters, i do $ 155 | outputRasters[i] = ENVISpatialgridRaster(raster, GRID_DEFINITION = grid_definition) 156 | clean.add, outputRasters, /EXTRACT 157 | 158 | ;stack together 159 | stackedRaster = ENVIMetaspectralRaster(outputRasters, $ 160 | SPATIALREF = outputRasters[reference_band].SPATIALREF) 161 | clean.add, stackedRaster 162 | 163 | ;check if we want to write to disk 164 | if keyword_set(output_raster_uri) then begin 165 | ;init raster after updating metadata to have data ignore value 166 | meta = stackedRaster.METADATA 167 | meta['data ignore value'] = 0 168 | output_raster = ENVIRaster(INHERITS_FROM = stackedRaster, URI = output_raster_uri, METADAT = meta) 169 | 170 | ;create tile iterator 171 | createAwesomeTileIterator,$ 172 | INPUT_RASTER = stackedRaster,$ 173 | OUTPUT_SUB_RECTS = sub_rects 174 | 175 | ;tile over our raster 176 | foreach sub, sub_rects do begin 177 | ;read raster data 178 | dat = stackedRaster.getData(SUB_RECT = sub, PIXEL_STATE = ps, INTERLEAVE = 'BSQ') 179 | 180 | ;collapse pixel state 181 | ps = total(ps, 3, /INTEGER) 182 | 183 | ;find pixels to turn off 184 | idxOff = where(ps, countOff) 185 | if (countOff gt 0) then begin 186 | for z=0, nBands-1 do begin 187 | bandDat = dat[*,*,z] 188 | bandDat[idxOff] = 0 189 | dat[*,*,z] = temporary(bandDat) 190 | endfor 191 | endif 192 | 193 | ;write our data 194 | output_raster.setData, dat, SUB_RECT = sub 195 | endforeach 196 | 197 | ;finalize raster 198 | output_raster.save 199 | clean.add, output_raster 200 | endif 201 | 202 | ;clean up 203 | foreach r, clean do if isa(r, 'ENVIRaster') then r.close 204 | end 205 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_applyreferencetiepointswithidl.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Procedure that performs the image-image registration in memory using 15 | ; IDL and an RST warping method. It returns the output spatial reference 16 | ; and a pointer contianing the data which can be used to create an output 17 | ; raster. 18 | ; 19 | ; For example, after running the procedure you can create a raster like this: 20 | ; 21 | ; ;register our bands using IDL 22 | ; BandAlignment_ApplyReferenceTiePointsWithIDL,$ 23 | ; INPUT_RASTER = input_raster,$ 24 | ; INPUT_BANDALIGNMENTTIEPOINTS = filtered_tiepoints,$ 25 | ; REFERENCE_BAND = reference_band,$ 26 | ; OUTPUT_SPATIALREF = output_spatialref,$ 27 | ; OUTPUT_DATA_POINTER = output_data_pointer 28 | ; 29 | ; ;save as an ENVI raster 30 | ; newRaster = ENVIRaster(*output_data_pointer, SPATIALREF = output_spatialref) 31 | ; newRaster.save 32 | ; 33 | ; The purpose of providing a pointer is to reduce memory costs with larger scenes and to give 34 | ; users the options to save the data as any desired format such as TIFF, JPEG, PNG, or ENVI. 35 | ; 36 | ; 37 | ; :Keywords: 38 | ; INPUT_RASTER: in, required, type=ENVIRaster 39 | ; Specify the input raster that you want to perform band-band alignment on. 40 | ; INPUT_BANDALIGNMENTTIEPOINTS: in, required, type=BandAlignmentTiePoints 41 | ; Specify the BandAlignmentTiePoints object that contains the tie points generated from 42 | ; the procedure ```idl BandAlignment_GenerateReferenceTiePoints```. 43 | ; OUTPUT_SPATIALREF, out, required, type=ENVISpatialReference 44 | ; Contains the output spatial reference for the band-band aligned datasets. 45 | ; OUTPUT_DATA_POINTER: out, requried, type=pointer 46 | ; This will contain a pointer that references the data that is ready to 47 | ; be written to disk. The pointer can be accessed with the asterisk to 48 | ; extract the data. i.e.: 49 | ; 50 | ; dat = *output_data_pointer 51 | ; 52 | ; This pointer is used to minimize the amount of memory that is consumed. 53 | ; 54 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 55 | ;- 56 | pro BandAlignment_ApplyReferenceTiePointsWithIDL,$ 57 | BAND_DATA_POINTERS = band_data_pointers,$ 58 | INPUT_RASTER = input_raster,$ 59 | INPUT_BANDALIGNMENTTIEPOINTS = input_BandAlignmentTiePoints,$ 60 | OUTPUT_SPATIALREF = output_spatialref,$ 61 | OUTPUT_DATA_POINTER = output_data_pointer 62 | compile_opt idl2, hidden 63 | 64 | ;validate our input 65 | if ~isa(input_BandAlignmentTiePoints, 'BandAlignmentTiePoints') then begin 66 | message, 'INPUT_BANDALIGNMENTTIEPOINTS is not a valid BandAlignmentTiePoints object!' 67 | endif 68 | if ~isa(input_raster, 'ENVIRASTER') then begin 69 | message, 'INPUT_RASTER is not a valid ENVIRASTER object!' 70 | endif 71 | 72 | ;get number of bands 73 | nBands = input_raster.NBANDS 74 | nPtrs = n_elements(band_data_pointers) 75 | 76 | ;make sure that we have RST, otherwise calculate it 77 | if ~input_BandAlignmentTiePoints.RST_CALCULATED then begin 78 | input_BandAlignmentTiePoints.GenerateRSTTransform, input_raster 79 | endif 80 | 81 | ;extract information from our tie points 82 | dims = input_BandAlignmentTiePoints.RST_OUTPUT_DIMENSIONS 83 | rst = input_BandAlignmentTiePoints.RST_TRANSFORMS 84 | subs = input_BandAlignmentTiePoints.RST_SUB_RECT 85 | 86 | ;check if we were already provided with our data 87 | if (nPtrs eq nBands) then begin 88 | ;preallocate an array to hold our data for warping 89 | ;get the typecode from our input raster by reading a single pixel 90 | write_data = make_array(dims[0], dims[1], nbands, $ 91 | TYPE = ((*band_data_pointers[0])[0]).TYPECODE, /NOZERO) 92 | endif else begin 93 | ;preallocate an array to hold our data for warping 94 | ;get the typecode from our input raster by reading a single pixel 95 | write_data = make_array(dims[0], dims[1], nbands, $ 96 | TYPE = (input_raster.GetData(BANDS = 0, SUB_RECT = [0,0,1,1])).TYPECODE, /NOZERO) 97 | endelse 98 | 99 | ;register all our bands! 100 | for i=0,nbands-1 do begin 101 | ;get the RST transform 102 | xy1 = *rst[i] 103 | 104 | ;fill our data array with values 105 | ;check if we were already provided with our data 106 | if (nPtrs eq nBands) then begin 107 | write_data[*,*,i] = interpolate(*band_data_pointers[i], xy1[0,*], xy1[1,*]) 108 | endif else begin 109 | write_data[*,*,i] = interpolate(input_raster.GetData(BANDS = i), xy1[0,*], xy1[1,*]) 110 | endelse 111 | endfor 112 | 113 | ;create a pointer to our image data 114 | output_data_pointer = ptr_new(write_data, /NO_COPY) 115 | 116 | ;make a spatial reference for our data - this get's within 1/5 of a pixel and 117 | ;is good enough 118 | subset = ENVISubsetRaster(input_raster, SUB_RECT = [subs[0],subs[2],subs[1], subs[3]]) 119 | 120 | ;get our spatialreference use ENVIHydrate so that we have an 121 | ;object that does not below to the virtual raster 122 | ;this keeps it alive when we close the virtual raster 123 | output_spatialref = ENVIHydrate(subset.SPATIALREF.dehydrate()) 124 | 125 | ;close our subset 126 | subset.close 127 | end 128 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_find_good_image_group.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Procedure that will automatically find the best image group to 15 | ; use for the band-band registration for a given set of data. This 16 | ; routine is data-set independent and just relies on the `GROUPS` 17 | ; keyword below. 18 | ; 19 | ; 20 | ; 21 | ; :Keywords: 22 | ; GROUPS: input, required, type=hash/orderedhash 23 | ; Set this input keyword to an ordered hash where the key/value 24 | ; pairs correspond to the folder and base name of the image groups and an array of 25 | ; the fully-qualified paths to the iamges for that group. For example, here is 26 | ; an image group for the MicaSense RedEdge: 27 | ; 28 | ; "C:\\Users\\Traininglead\\Desktop\\test\\north_block_east_west\\000\\IMG_0289":[ 29 | ; "C:\\Users\\Traininglead\\Desktop\\test\\north_block_east_west\\000\\IMG_0289_1.tif", 30 | ; "C:\\Users\\Traininglead\\Desktop\\test\\north_block_east_west\\000\\IMG_0289_2.tif", 31 | ; "C:\\Users\\Traininglead\\Desktop\\test\\north_block_east_west\\000\\IMG_0289_3.tif", 32 | ; "C:\\Users\\Traininglead\\Desktop\\test\\north_block_east_west\\000\\IMG_0289_4.tif", 33 | ; "C:\\Users\\Traininglead\\Desktop\\test\\north_block_east_west\\000\\IMG_0289_5.tif" 34 | ; ] 35 | ; INPUTDIR: in, optional, type=string 36 | ; This optional keyword can be set to the directory that wants to be searched for 37 | ; image groups. You can use this if you don't pass in the `GROUPS` keyword. This 38 | ; assumes that there are only MicaSense RedEdge images present. 39 | ; OUTPUT_IMAGE_GROUP: out, requried, type=string 40 | ; The key name for the image group that is the most likely candidate for providing 41 | ; good band-band registration results. 42 | ; KAPPA_FILTER: in, optional, type=boolean 43 | ; This optional keyword will filter out scenes that may be associated with fixed-wing 44 | ; drones turning around. You don't want to use these images for generating tiepoints 45 | ; because they do not have the same viewing geometry as the nadir images. 46 | ; BASEBAND: in, optional, type=int, default=0 47 | ; This optional keyword specifies the zero-based band number that you want to use for 48 | ; evaluating the quality of the band for reference tiepoints. 49 | ; 50 | ; :Author: Zachary Norman - znorman@harris.com 51 | ;- 52 | pro bandalignment_find_good_image_group, $ 53 | GROUPS = groups, $ 54 | OUTPUT_IMAGE_GROUP = output_image_group,$ 55 | KAPPA_FILTER = kappa_filter,$ 56 | BASEBAND = baseband 57 | compile_opt idl2, hidden 58 | 59 | ;check to see if ENVI is running 60 | e = awesomeGetENVI() 61 | 62 | ;check what the baseband is 63 | ;default is first 64 | if (baseband eq !NULL) then begin 65 | baseband = 0 66 | endif 67 | 68 | ;make sure we have groups 69 | if ~n_elements(groups) then begin 70 | message, 'No groups passed in for processing, required!', LEVEL = -1 71 | endif 72 | 73 | ;initialize progress 74 | prog = awesomeENVIProgress('Finding Ideal Image Group', /PRINT) 75 | prog.setProgress, 'Processing...', 0, /PRINT 76 | 77 | ;get the group names that fall within approximate straight lines if asked for 78 | if keyword_set(kappa_filter) then begin 79 | ;filter groups - error thrown internally if no groups exist after filtering 80 | groupNames = bandalignment_simple_kappa_filter(groups) 81 | endif else begin 82 | groupNames = (groups.keys()).toArray() 83 | endelse 84 | 85 | ;get the number of groups 86 | nGroups = n_elements(groupNames) 87 | 88 | ;determine how often to print 89 | nPrint = (nGroups lt 100) ? 1 : floor(nGroups/100.0) 90 | 91 | ;preallocate an array to hold our metric for the best image to register 92 | edges = dblarr(nGroups) 93 | 94 | ;alert user 95 | prog.setProgress, 'Processing', 0, /PRINT 96 | 97 | ;iterate over each image group 98 | foreach groupName, groupNames, i do begin 99 | ;get the file names for each group 100 | images = groups[groupName] 101 | 102 | ;open raster and get data from first band 103 | ;TODO: maybe improve this logic if bands are in one file? 104 | rasters = e.openRaster(images[baseband]) 105 | dat = rasters[0].getdata(BANDS = [0]) 106 | foreach r, rasters do r.close 107 | 108 | ;check if we were cancelled 109 | prog.abortRequested 110 | 111 | ;find the total number of edges in the image 112 | edges[i] = mean(sobel(temporary(dat))) 113 | 114 | ;update user 115 | if ~(i mod nPrint) then prog.setProgress, 'Processing...', 100*float(i+1)/nGroups, /PRINT, /TIME 116 | endforeach 117 | 118 | ;get the min/max values 119 | maxEdge = max(edges, idx_max) 120 | minEdge = min(edges, idx_min) 121 | 122 | ;return the iamge with the largest mean value of edges 123 | output_image_group = groupNames[idx_max] 124 | 125 | ;finish our progress 126 | prog.finish, /PRINT 127 | end 128 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_get_image_groups.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; This function searches a directory for files and groups multiple files together 15 | ; based on unique identifiers. Essentially, this function is useful for grouping 16 | ; images together that represent different bands for a dataset if each band is in 17 | ; a separate file. 18 | ; 19 | ; This routine is also helpful to re-order the bands by increasing/decreasing wavelengths 20 | ; as is demonstrated in the example below. 21 | ; 22 | ; :Example: 23 | ; To search for MicaSense RedEdge files you can simply use the following (5 comes before 4): 24 | ; 25 | ; dir = 'C:\some\directory\000' 26 | ; ids = ['_1.tif', '_2.tif', '_3.tif', '_5.tif', '_4.tif'] 27 | ; groups = BandAlignment_Get_Image_Groups(dir, ids) 28 | ; 29 | ; This same routine can be used to search for other data such as Parrot Sequoia: 30 | ; 31 | ; dir = 'C:\some\directory\data' 32 | ; ids = ['_GRE.TIF', '_RED.TIF', '_NIR.TIF', '_REG.TIF'] 33 | ; groups = BandAlignment_Get_Image_Groups(dir, ids) 34 | ; 35 | ; :Params: 36 | ; directory: in, required, type=string 37 | ; Specify the directory that you want to search for imagery 38 | ; uniqueIdentifiers: in, requried, type=stringarr 39 | ; Specify the unique parts of each filename that correspond to the identifier 40 | ; for that file. This should be something like the end of the file. See the 41 | ; examples above for a reference. 42 | ; 43 | ; 44 | ; 45 | ; :Author: Zachary Norman - Github: [znorman-harris](https://github.com/znorman-harris) 46 | ;- 47 | function BandAlignment_Get_Image_Groups, directory, uniqueIdentifiers 48 | compile_opt idl2, hidden 49 | 50 | ;get the number of bands 51 | nBands = n_elements(uniqueIdentifiers) 52 | 53 | ;save search results 54 | files = orderedhash() 55 | 56 | ;init data structure to store the group information 57 | groups = orderedhash() 58 | 59 | ;find files in the directory 60 | cd, directory, CURRENT = first_dir 61 | dirFiles = file_search(COUNT = nFiles) 62 | cd, first_dir 63 | 64 | ;return empty orderedhash 65 | if (nFiles eq 0) then return, groups 66 | 67 | ;search for files 68 | foreach id, uniqueIdentifiers, i do begin 69 | idxMatch = where(strpos(strlowcase(dirFiles), strlowcase(id)) ne -1, countMatch) 70 | 71 | ;make sure we found results 72 | if (countMatch eq 0) then return, groups 73 | 74 | ;save complete files 75 | files[id] = directory + path_sep() + dirFiles[idxMatch] 76 | 77 | ;save base names of first matching ID (any works for finding all other groups) 78 | if (i eq 0) then baseNames = file_basename(strlowcase(dirFiles[idxMatch]), strlowcase(id)) 79 | endforeach 80 | 81 | ;find the matches for each base name 82 | foreach base, baseNames do begin 83 | ;init list to store matches 84 | matches = list() 85 | 86 | ;loop over each list of files 87 | foreach fileArr, files do begin 88 | idx = where(strpos(strlowcase(fileArr), base) ne -1, countIdx) 89 | if (countIdx eq 1) then begin 90 | matches.Add, fileArr[idx[0]] 91 | endif 92 | endforeach 93 | 94 | ;validate that we have anough matches 95 | if (n_elements(matches) eq nBands) then begin 96 | groups[base] = matches.toArray() 97 | endif else begin 98 | print, ' incomplete group "' + base + '", skipping...' 99 | endelse 100 | endforeach 101 | 102 | ;return group information 103 | return, groups 104 | end 105 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_group_to_virtualraster.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Function that creates a virtual raster from the input image groups. The 15 | ; raster has a dummy spatial reference applied so that the image can be 16 | ; processed by image-registration routines. 17 | ; 18 | ; Note that each file must have the same dimensions. 19 | ; 20 | ; :Returns: 21 | ; A virtual raster where each band represents one of the images in `groupFiles`. 22 | ; 23 | ; :Params: 24 | ; groupFiles: in, required, type=string[*] 25 | ; Specify the files that represent an image group. 26 | ; 27 | ; 28 | ; 29 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 30 | ;- 31 | function bandAlignment_group_to_virtualRaster, groupFiles, RASTERS = rasters 32 | compile_opt idl2, hidden 33 | 34 | e = envi(/current) 35 | 36 | ;open as rasters 37 | rasters = list() 38 | foreach file, groupFiles do rasters.Add, e.openRaster(file), /EXTRACT 39 | 40 | ;check for spatialref and make dummy one if not present 41 | if (rasters[0]).SPATIALREF eq !NULL then begin 42 | spatialref = ENVIStandardRasterSpatialRef($ 43 | coord_sys_code = 4326, $ 44 | /GEOGCS,$ 45 | pixel_size = [0.00001d, 0.00001d], $ 46 | tie_point_pixel = [0, 0], $ 47 | tie_point_map = [0, 0]) 48 | endif else begin 49 | spatialref = (rasters[0]).SPATIALREF 50 | endelse 51 | 52 | ;make a metaspectral raster 53 | return, ENVIMetaspectralRaster(rasters.toArray(), SPATIALREF = spatialref) 54 | end 55 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_processsensor_getreferencetiepoints.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Simple procedure that will generate tie points base don the input group. 15 | ; 16 | ; :Params: 17 | ; group: in, requried, type=string[*] 18 | ; Array of strigns that represents the files to process. 19 | ; parameters: in, required, type=dictionary 20 | ; The dictionary parameters used for all the band alignment processing. 21 | ; 22 | ; :Keywords: 23 | ; RASTER: in, optional, type=ENVIRASTER 24 | ; If set, then we wont stack the images into a raster. Can save a small amount of time. 25 | ; NO_OUTPUT: in, optional, type=boolean, default=false 26 | ; If set, then no output files will be created. Otherwise a .dat file and tie points 27 | ; will be saved to disk. Used for secondary image registration. 28 | ; FILTERED_TIEPOINTS: out, optional, type=bandAlignmentTiePoints 29 | ; Returns a direct reference to the tie points that are generated by this procedure. 30 | ; 31 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 32 | ;- 33 | pro BandAlignment_ProcessSensor_GetReferenceTiePoints, group, parameters, $ 34 | RASTER = raster, $ 35 | NO_OUTPUT = no_output, $ 36 | FILTERED_TIEPOINTS = filtered_tiepoints 37 | compile_opt idl2, hidden 38 | e = envi(/CURRENT) 39 | 40 | ;initialize progress 41 | prog = awesomeENVIProgress('Generating Reference Tie Points', /PRINT) 42 | prog.setProgress, 'Initializing', 0, /PRINT 43 | 44 | ;check if we were provided a raster or not 45 | if ~isa(raster, 'ENVIRASTER') then begin 46 | cleanup = 1 47 | raster = bandAlignment_group_to_virtualRaster(group, RASTERS = sourceRasters) 48 | endif 49 | 50 | ;set task parameters - from banadalignment_set_task_parameters which ALWAYS gets called 51 | ;first so we should have the function defined 52 | 53 | ;set custom task params based on sensor 54 | case (parameters.SENSOR) of 55 | 'rededge':bandalignment_set_rededge_task_parameters, parameters.CORRELATION_TASK, parameters, group 56 | else:;do nothing 57 | endcase 58 | 59 | ;generate reference tiepoints 60 | BandAlignment_GenerateReferenceTiepoints,$ 61 | INPUT_RASTER = raster,$ 62 | PROGRESS = prog,$ 63 | TIEPOINT_GENERATION_TASK = ~(parameters.RIGOROUS_ALIGNMENT) ? parameters.CORRELATION_TASK : parameters.MUTUAL_TASK,$ 64 | TIEPOINT_FILTERING_TASK = parameters.FILTER_TASK,$ 65 | REFERENCE_BAND = parameters.BASE_BAND,$ 66 | MINIMUM_FILTERED_TIEPOINTS = parameters.MINIMUM_FILTERED_TIEPOINTS,$ 67 | OUTPUT_BANDALIGNMENTTIEPOINTS = filtered_tiepoints 68 | 69 | ;create output if not told to skip 70 | if ~keyword_set(no_output) then begin 71 | prog.setProgress, 'Generating output', 99, /PRINT 72 | 73 | ;save the tie points 74 | BandAlignment_SaveReferenceTiepoints,$ 75 | INPUT_BANDALIGNMENTTIEPOINTS = filtered_tiepoints,$ 76 | OUTPUT_TIEPOINTS_SAVEFILE_URI = parameters.POINTS_FILE 77 | 78 | ;create image that we can look at 79 | BandAlignment_ApplyReferenceTiePointsWithIDL,$ 80 | INPUT_RASTER = raster,$ 81 | INPUT_BANDALIGNMENTTIEPOINTS = filtered_tiepoints,$ 82 | OUTPUT_DATA_POINTER = datPtr,$ 83 | OUTPUT_SPATIALREF = outSref 84 | 85 | ;save to disk 86 | if ~file_test(parameters.RIGOROUS_DIR + '_out') then file_mkdir, parameters.RIGOROUS_DIR + '_out' 87 | uri = parameters.RIGOROUS_DIR + '_out' + path_sep() + file_basename(group[0], '.tif') + '.dat' 88 | if file_test(uri) then file_delete, uri, /QUIET 89 | if ~file_test(uri) then begin 90 | if parameters.hasKey('METADATA') then begin 91 | newRaster = ENVIRaster(*datPtr, SPATIALREF = outSref, URI = uri, METADATA = parameters.METADATA) 92 | endif else begin 93 | newRaster = ENVIRaster(*datPtr, SPATIALREF = outSref, URI = uri) 94 | endelse 95 | newRaster.save 96 | newRaster.close 97 | endif else begin 98 | print, 'Unable to create preview with alignment using reference tiepoints, file locked. File:' 99 | print, ' ' + uri 100 | endelse 101 | 102 | ;clean up so we dont have locks on any files 103 | raster.close 104 | foreach file, group do begin 105 | rasters = e.openRaster(file) 106 | foreach r, rasters do if isa(r, 'enviraster') then r.close 107 | endforeach 108 | endif 109 | 110 | ;check if we need to clean up our source rasters 111 | if keyword_set(cleanup) then begin 112 | if isa(raster, 'ENVIRaster') then raster.close 113 | foreach r, sourceRasters do if isa(r, 'ENVIRaster') then r.close 114 | endif 115 | 116 | ;finish our progress 117 | prog.finish, /PRINT 118 | end 119 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_savereferencetiepoints.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Simple wrapper procedure that validates and saves our tie points to an 15 | ; IDL save file for later use. The tie points that are saved represent 16 | ; an array of pointers that, if valid pointers, contain the file coordinates 17 | ; of two rasters for image registration. 18 | ; 19 | ; To use the tie points later you will need to do the following: 20 | ; 21 | ; file = 'C:\someeDir\points.sav' 22 | ; tiePoints = BandAlignmentTiePoints(SAVE_FILE = file) 23 | ; 24 | ; 25 | ; 26 | ; :Keywords: 27 | ; INPUT_BANDALIGNMENTTIEPOINTS: in, required, type=BandAlignmentTiePoints 28 | ; Specify the BandAlignmentTiePoints object that contains the file coordinates for 29 | ; our ENVI tie points. 30 | ; OUTPUT_TIEPOINTS_SAVEFILE_URI: in, optional, type=string 31 | ; Specify the fully-qualified path to an IDL SAVE file that will contain the 32 | ; tie points. If not specified, then this keyword will be populated with 33 | ; a filename that was picked by ENVI. 34 | ; 35 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 36 | ;- 37 | pro BandAlignment_SaveReferenceTiepoints,$ 38 | INPUT_BANDALIGNMENTTIEPOINTS = input_BandAlignmentTiePoints,$ 39 | OUTPUT_TIEPOINTS_SAVEFILE_URI = output_tiepoints_savefile_uri 40 | compile_opt idl2, hidden 41 | 42 | ;get current ENVI 43 | e = envi(/CURRENT) 44 | 45 | ;make sure that our outputs are specified 46 | if ~keyword_set(output_tiepoints_savefile_uri) then begin 47 | output_tiepoints_savefile_uri = e.getTemporaryFilename('sav') 48 | endif 49 | 50 | ;make sure we have valid tiepoints 51 | if ~isa(input_BandAlignmentTiePoints, 'BandAlignmentTiePoints') then begin 52 | message, 'input_tiepoints are not a valid BandAlignmentTiePoints object, required!' 53 | endif 54 | 55 | ;duplicate variable name for consistent naming 56 | bandAlignmentTiePointsObject = input_BandAlignmentTiePoints 57 | 58 | ;save to disk 59 | save, bandAlignmentTiePointsObject, FILENAME = output_tiepoints_savefile_uri 60 | end 61 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_set_task_parameters.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; Simple routine that sets task parameters from the input parameters. Just convenience 14 | ; in case we need to set parameters in multiple places. 15 | ; 16 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 17 | ;- 18 | pro bandalignment_set_rededge_task_parameters, task, parameters, group 19 | compile_opt idl2, hidden 20 | 21 | ;check if we want a dynamic search window 22 | if parameters.SEARCH_WINDOW_FROM_HEIGHT then begin 23 | ;extract image properties 24 | oImageInfo = obj_new('image_info', group[0], /PRINT_GPS) 25 | height = oImageInfo.Get('HEIGHT_ABOVE_GROUND') 26 | 27 | ;update the task 28 | task.SEARCH_WINDOW = 2*(((height>0)/3 < 100) + 5) 29 | endif else begin 30 | ;update the task 31 | task.SEARCH_WINDOW = parameters.SEARCH_WINDOW 32 | endelse 33 | 34 | ;set matching window 35 | task.MATCHING_WINDOW = (task.SEARCH_WINDOW/2) > 21 36 | end 37 | 38 | 39 | 40 | pro bandalignment_set_task_parameters, parameters 41 | compile_opt idl2, hidden 42 | 43 | ;set cross correlation parameters 44 | parameters.CORRELATION_TASK.MINIMUM_MATCHING_SCORE = parameters.CORRELATION_MATCHING_SCORE 45 | parameters.CORRELATION_TASK.REQUESTED_NUMBER_OF_TIEPOINTS = parameters.REQUESTED_NUMBER_OF_TIEPOINTS 46 | parameters.CORRELATION_TASK.SEARCH_WINDOW = parameters.CORRELATION_SEARCH_WINDOW 47 | parameters.CORRELATION_TASK.MATCHING_WINDOW = (parameters.CORRELATION_TASK.SEARCH_WINDOW/2) > 21 48 | 49 | ;set mutual information parameters 50 | parameters.MUTUAL_TASK.MINIMUM_MATCHING_SCORE = parameters.MUTUAL_MATCHING_SCORE 51 | parameters.MUTUAL_TASK.REQUESTED_NUMBER_OF_TIEPOINTS = parameters.REQUESTED_NUMBER_OF_TIEPOINTS 52 | parameters.MUTUAL_TASK.SEARCH_WINDOW = parameters.MUTUAL_SEARCH_WINDOW 53 | parameters.MUTUAL_TASK.MATCHING_WINDOW = (parameters.MUTUAL_TASK.SEARCH_WINDOW/2) > 21 54 | 55 | end 56 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_setupsensorforprocessing.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Main orchestrator for processing data for band-band alignment. This will 15 | ; set up tasks, task parameters, and call the right routines for generating 16 | ; and applying reference tie points. 17 | ; 18 | ; There is a case statement at the beginning of the procedure that contians the 19 | ; defaults for each sensor type. 20 | ; 21 | ; 22 | ; 23 | ; :Keywords: 24 | ; PARAMETERS: in, required, type=dictionary 25 | ; Pass in the parameters needed for image-iamge registration. A 26 | ; dictionary is used for simplicity and convenience to be able to 27 | ; easily add new parameters for use in different routines. 28 | ; 29 | ; :Author: Zachary Norman - GitLab: [znorman-harris](https://github.com/znorman-harris) 30 | ;- 31 | pro BandAlignment_SetUpSensorForProcessing, PARAMETERS = parameters 32 | compile_opt idl2, hidden 33 | 34 | ;get current ENVI 35 | e = awesomeGetENVI() 36 | 37 | ;search for image groups 38 | groups = bandalignment_get_image_groups(parameters.INPUTDIR, parameters.FILE_IDENTIFIERS) 39 | if (n_elements(groups) eq 0) then begin 40 | message, 'No image groups found for processing in directory "' + parameters.INPUTDIR + '"' 41 | endif 42 | 43 | ;initialize our tasks for image-image registration 44 | ;generic 45 | correlationTask = ENVITask('GenerateTiePointsByCrossCorrelation') 46 | correlationTask.MINIMUM_MATCHING_SCORE = parameters.CORRELATION_MATCHING_SCORE 47 | correlationTask.REQUESTED_NUMBER_OF_TIEPOINTS = parameters.REQUESTED_NUMBER_OF_TIEPOINTS 48 | parameters['CORRELATION_TASK'] = correlationTask 49 | 50 | ;rigorous 51 | mutualTask = ENVITask('GenerateTiePointsByMutualInformation') 52 | mutualTask.MINIMUM_MATCHING_SCORE = parameters.MUTUAL_MATCHING_SCORE 53 | mutualTask.REQUESTED_NUMBER_OF_TIEPOINTS = parameters.REQUESTED_NUMBER_OF_TIEPOINTS 54 | parameters['MUTUAL_TASK'] = mutualTask 55 | 56 | ;update overall task parameters based on our input parameters 57 | bandalignment_set_task_parameters, parameters 58 | 59 | ;tie point filtering task 60 | filterTask = ENVITask('FilterTiePointsByGlobalTransform') 61 | parameters['FILTER_TASK'] = filterTask 62 | 63 | ;check what we need to do for our processing 64 | if keyword_set(parameters.GENERATE_REFERENCE_TIEPOINTS) then begin 65 | ;lets try and find a good image group to use for generating reference tie points 66 | bandalignment_find_good_image_group, $ 67 | GROUPS = groups, $ 68 | OUTPUT_IMAGE_GROUP = reference_image_group,$ 69 | KAPPA_FILTER = parameters.KAPPA_FILTER,$ 70 | BASEBAND = parameters.BASE_BAND 71 | 72 | ;because we have a good group, write to folder called rigorous 73 | rigDir = file_dirname(parameters.INPUTDIR) + path_sep() + 'rigorous' 74 | parameters['RIGOROUS_DIR'] = rigDir 75 | if file_test(rigDir) then begin 76 | file_delete, rigDir, /RECURSIVE, /QUIET 77 | wait, 0.01 ;permissions issues sometimes if we dont wait, not sure why 78 | endif 79 | 80 | ;copy new files to the right directory so that users can see 81 | ;which images are picked 82 | if ~file_test(rigDir) then file_mkdir, rigDir 83 | file_copy, groups[reference_image_group], rigDir, /OVERWRITE 84 | 85 | ;generate the reference tie points 86 | BandAlignment_ProcessSensor_GetReferenceTiePoints,$ 87 | groups[reference_image_group], parameters 88 | endif 89 | 90 | ;check if we want to apply our reference tiepoints 91 | if keyword_set(parameters.APPLY_REFERENCE_TIEPOINTS) then begin 92 | ;make sure that we have reference tie points to use 93 | if ~file_test(parameters.POINTS_FILE) then begin 94 | message, 'Requested to apply reference tiepoints, but no reference tie points found! Please generate first.' 95 | endif 96 | 97 | ;check if we need to perform localized histogram color balancing 98 | if keyword_set(parameters.CO_CALIBRATION) then begin 99 | ;check if we have reference information about reflectance panels 100 | ;if so, get the means and camera exposure settings 101 | if file_test(parameters.PANELDIR, /DIRECTORY) then begin 102 | ;search for image group sin our panel directory 103 | panelGroups = bandalignment_get_image_groups(parameters.PANELDIR, parameters.FILE_IDENTIFIERS) 104 | 105 | ;only process if we find images 106 | if (n_elements(panelGroups) gt 0) then begin 107 | ;get the keys for our groups 108 | keys = panelGroups.keys() 109 | 110 | ;check if we have max valules to set 111 | maxFlag = parameters.hasKey('MAX_PIXEL_VALUE') AND parameters.hasKey('MAX_VALUE_DIVISOR') 112 | 113 | ;attempt to extract the reflectance panels 114 | panel_info = get_reflectance_panels(panelGroups[keys[0]], parameters.SENSOR,$ 115 | MAX_PIXEL_VALUE = maxFlag ? parameters.MAX_PIXEL_VALUE : !NULL,$ 116 | MAX_VALUE_DIVISOR = maxFlag ? parameters.MAX_VALUE_DIVISOR : !NULL,$ 117 | PANEL_REFLECTANCE = parameters.PANEL_REFLECTANCE) 118 | 119 | ;save the panel information 120 | save, panel_info, FILENAME = parameters.PANELDIR + path_sep() + 'panel_info.sav' 121 | endif 122 | endif else begin 123 | panel_info = dictionary() 124 | endelse 125 | 126 | ;extract scale factors for each group 127 | get_co_calibration, $ 128 | GROUPS = groups,$ 129 | PANEL_INFO = panel_info 130 | endif 131 | 132 | ;apply our reference tiepoints to our image groups 133 | BandAlignment_ApplyReferenceTiePointsToGroups, groups, parameters 134 | endif 135 | end 136 | -------------------------------------------------------------------------------- /custom_code/bandAlignment/bandalignment_simple_kappa_filter.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ;- 11 | 12 | ;+ 13 | ; :Description: 14 | ; Routine that returns key names for the image groups that likely fall 15 | ; within a straight line. This can remove images from the candidate pool 16 | ; from points where a fixed wing UAV is turning. Helps ensure better quality 17 | ; image groups are found for generating reference tiepoints. 18 | ; 19 | ; :Params: 20 | ; groups: in, required, type=hash/orderedhash 21 | ; The image groups that you want to apply kappa filtering 22 | ; for. 23 | ; 24 | ; 25 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 26 | ;- 27 | function bandalignment_simple_kappa_filter, groups, DEBUG = debug 28 | compile_opt idl2 29 | if ~keyword_set(debug) then on_error, 2 30 | 31 | ;======================================= 32 | ;hard coded parameters 33 | ;number of left-right images to check 34 | ncheck = 2 35 | 36 | ;threshold for maximum allowable degree separation for straight lines 37 | theta_diff = 5 ;degrees 38 | theta_diff *= ncheck 39 | ;======================================= 40 | 41 | ;get the names of our groups 42 | groupNames = groups.keys() 43 | ngroups = n_elements(groups) 44 | 45 | ;preallocate an array to hold positions 46 | locations = dblarr(2,ngroups) ;[lon, lat] 47 | 48 | ;get all iamge locations 49 | i=0 50 | foreach images, groups, group do begin 51 | oInfo = obj_new('image_info', images[0], /NO_SPATIALREF, /NO_PRINT, /ERROR_GPS) 52 | locations[*,i] = oInfo.Get('LON_LAT') 53 | i++ 54 | endforeach 55 | 56 | ;preallocate ana rray to hold our kappa angles 57 | kappas = dblarr(ngroups-ncheck) 58 | 59 | ;iterate over each group for kappa 60 | for i=ncheck, ngroups-1-ncheck do begin 61 | ;preallocate arrays 62 | inFront = 0.0 63 | inBack = 0.0 64 | 65 | ;find all angle differences 66 | for j=1,ncheck do begin 67 | inFront += atan((locations[1,i+j] - locations[1,i]), (locations[0,i+j] - locations[0,i]))*(1/(!DTOR)) 68 | inBack += atan((locations[1,i] - locations[1,i-j]), (locations[0,i] - locations[0,i-j]))*(1/(!DTOR)) 69 | endfor 70 | 71 | ;save value 72 | kappas[i] = inFront - inBack 73 | endfor 74 | 75 | ;get stats 76 | meanval = mean(kappas) 77 | stddev = stddev(kappas) 78 | 79 | ;find which groups we can use 80 | available = where((abs(kappas) lt theta_diff) AND ([0:ngroups-1-ncheck] gt (ncheck-1)), count_valid) 81 | 82 | ;make sure that we found some results 83 | if (count_valid eq 0) then begin 84 | message, 'Flight lines are not straight enough to filter out turning points in the flight lines!' + $ 85 | string(10b) + string(9b) + ' No valid images found to check for automating tie point generation process.', LEVEL = -1 86 | endif 87 | 88 | ;debug plots to show before/after filtering 89 | if keyword_set(debug) then begin 90 | sp1 = scatterplot(locations[0,*],locations[1,*]) 91 | sp2 = scatterplot(locations[0,available],locations[1,available], $ 92 | SYM_COLOR = 'red', /OVERPLOT, current = SP1) 93 | end 94 | 95 | ;return our best group 96 | return, groupNames[available] 97 | end 98 | -------------------------------------------------------------------------------- /custom_code/batchTasks/uavbatchrededge.task: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UAVBatchRedEdge", 3 | "version": "5.3", 4 | "baseClass": "ENVITaskFromProcedure", 5 | "routine": "uavbatchrededge", 6 | "displayName": "UAV Batch RedEdge", 7 | "description": "Task that batch processes a folder of MicaSense RedEdge imagery.", 8 | "parameters": [ 9 | { 10 | "name": "BAND_ALIGNMENT_TASK", 11 | "displayName": "Band Alignment Task", 12 | "description": "Specify a UAVBandAlignment task that you want to use for processing the RedEdge data. A default task will be used if one is not provided.", 13 | "direction": "input", 14 | "parameterType": "optional", 15 | "dataType": "ENVITASK" 16 | }, 17 | { 18 | "name": "FLIGHTDIR", 19 | "displayName": "Flightdir", 20 | "description": "Specify the folder that contains directories of RedEdge data that will be batch processed. All flights must have the same height above the ground for the alignment to be valid.", 21 | "direction": "input", 22 | "parameterType": "required", 23 | "dataType": "string" 24 | }, 25 | { 26 | "name": "PANEL_REFLECTANCE", 27 | "displayName": "Panel Reflectance", 28 | "description": "This represents the percent reflectance (0 to 100) of reflectance panel images that are provided in PANELDIR. Specify a single value or an array of values that represents the percent reflectance of each band of the reflectance panel. The value should be between 0 and 100. If a scalar is provided, then it is assumed to be a constant value for each band. The order of this array should match the FILE_IDENTIFIERS. If you are using Sequioa or RedEdge data, then it is from shortest to longest wavelength.", 29 | "direction": "input", 30 | "parameterType": "required", 31 | "dataType": "float[*]", 32 | "min": 0, 33 | "max": 100, 34 | "defaultValue":[70] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /custom_code/filterRaster/filter_raster.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; Routine to apply sobel filter to raster to try and improve band-band registration 10 | ; 11 | ; 12 | ; :Keywords: 13 | ; INPUT_RASTER 14 | ; FILTER 15 | ; OUTPUT_RASTER_URI 16 | ; 17 | ; :Author: znorman 18 | ;- 19 | pro filter_raster,$ 20 | INPUT_RASTER = input_raster,$ 21 | FILTER = filter,$ 22 | OUTPUT_RASTER_URI = output_raster_uri 23 | compile_opt idl2 24 | 25 | e = envi(/current) 26 | if (e eq !NULL) then begin 27 | e = envi(/headless) 28 | endif 29 | 30 | if (filter eq !NULL) then begin 31 | filter = 'roberts' 32 | endif 33 | 34 | ;make sure the output filename is specified 35 | if (output_raster_uri eq !NULL) then begin 36 | output_raster_uri = e.GettemporaryFilename() 37 | endif 38 | 39 | ;get info on our raster 40 | dims = [input_raster.NCOLUMNS, input_raster.NROWS] 41 | nbands = input_raster.NBANDS 42 | 43 | ;initialize our tile iterator 44 | ;add a buffer of 3 to each tile since our kernels are mostly 3 x 3 (i think) 45 | createAwesomeTileIterator,$ 46 | INPUT_RASTER = input_raster,$ 47 | TILE_BUFFER = 3, $ 48 | TILE_SIZE = [20480, 20480],$ 49 | NCOLUMNS = ncolumns,$ 50 | NROWS = nrows,$ 51 | OUTPUT_SUB_RECTS = output_sub_rects,$ 52 | OUTPUT_RASTER_TILE_LOCATIONS = output_raster_tile_locations,$ 53 | OUTPUT_TILE_SUB_RECTS = output_tile_sub_rects 54 | 55 | 56 | ;get original raster metadata 57 | orig_meta = input_raster.meta.dehydrate() 58 | 59 | ;set up our output raster 60 | meta = ENVIRasterMetadata() 61 | meta.AddItem, 'band names', filter.CapWords() + ' Filter: ' + orig_meta['BAND NAMES'] 62 | 63 | ;tile! 64 | foreach sub_rect, output_sub_rects, i do begin 65 | ;get the data 66 | tile_data = input_raster.GetData(SUB_RECT = sub_rect, INTERLEAVE = 'BSQ', PIXEL_STATE = ps) 67 | 68 | ;update any nans 69 | idxBad = where(~finite(tile_data), countBad, COMPLEMENT = idxGood, NCOMPLEMENT = countGood) 70 | if (countBad gt 0) then begin 71 | if (countGood eq 0) then begin 72 | message, 'No valid data to process....' 73 | endif 74 | 75 | stop 76 | 77 | ;get tile size 78 | tDims = size(tile_data, /DIMENSIONS) 79 | 80 | ; ps[idxBad]++ 81 | ; tile_data[idxBad] = fix(interpolate(tile_data[idxGood], idxGood mod tDims[0], idxGood / tDims[0], $ 82 | ; idxBad mod tDims[0], idxBad / tDims[1]), TYPE=tile_data.TYPECODE) 83 | endif 84 | 85 | ;do edge detection for every band 86 | ;ensure that the type of the data matches the original 87 | ;this should be true for the filter functions, but it is imporant just to make sure 88 | for j=0, nbands-1 do begin 89 | case strlowcase(filter) of 90 | 'sobel' : edgeBand = sobel(tile_data[*,*,j]) 91 | 'prewitt' : edgeBand = prewitt(tile_data[*,*,j]) 92 | 'roberts' : edgeBand = roberts(tile_data[*,*,j]) 93 | 'emboss' : edgeBand = emboss(tile_data[*,*,j]) 94 | 'edge_dog' : edgeBand = edge_dog(tile_data[*,*,j]) 95 | 'shift_diff' : edgeBand = shift_diff(tile_data[*,*,j]) 96 | 'laplacian' : edgeBand = laplacian(tile_data[*,*,j]) 97 | endcase 98 | 99 | if (j eq 0) then begin 100 | ;preallocate an array to hold our edge data 101 | tile_edge_data = make_array(sub_rect[2] - sub_rect[0] + 1, sub_rect[3] - sub_rect[1] + 1, nbands, TYPE = edgeBand.typecode, /NOZERO) 102 | endif 103 | 104 | tile_edge_data[0,0,j] = edgeBand 105 | endfor 106 | 107 | ;if first sub rect, get the typecode and initialize our raster 108 | if (i eq 0) then begin 109 | typecode = tile_edge_data.typecode 110 | output_raster = ENVIRaster($ 111 | DATA_TYPE = typecode,$ 112 | NBANDS = nbands,$ 113 | NCOLUMNS = dims[0],$ 114 | NROWS = dims[1],$ 115 | SPATIALREF = input_raster.SPATIALREF,$ 116 | METADATA = meta,$ 117 | URI = output_raster_uri) 118 | endif 119 | 120 | ;check pixel state 121 | idxOff = where(ps, countOff) 122 | if (countOff gt 0) then begin 123 | tile_data[idxOff] = -1 124 | endif 125 | 126 | ;get the output index locations 127 | idx_tile = output_tile_sub_rects[i] 128 | 129 | ;set the data 130 | sub = tile_edge_data[idx_tile[0]:idx_tile[2],idx_tile[1]:idx_tile[3],0:nbands-1] 131 | output_raster.SetData, sub, SUB_RECT = output_raster_tile_locations[i] 132 | endforeach 133 | 134 | ;save our output raster 135 | output_raster.save 136 | output_raster.close 137 | end 138 | 139 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 40 | 41 | 42 | 43 | 91 |
92 |
93 | 94 | 99 |
100 |
101 | 102 |
103 |

Contributing

104 |
105 |

See the ENVI contributing guide and the IDL contributing guide for code formatting information.

106 |
107 |

Copyright © 2019 Harris Geospatial Solutions, Inc.

108 |

Licensed under MIT. See LICENSE.txt for additional details and information.

109 |
110 |
111 |
112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 40 | 41 | 42 | 43 | 91 |
92 |
93 | 94 | 99 |
100 |
101 | 102 |
103 |

LICENSE.txt

104 |
105 |

The MIT License (MIT)

106 |

Copyright (c) 2018 Harris Geospatial Solutions, Inc.

107 |

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

108 |

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

109 |

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.TRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

110 |
111 |

Copyright © 2019 Harris Geospatial Solutions, Inc.

112 |

Licensed under MIT. See LICENSE.txt for additional details and information.

113 |
114 |
115 |
116 | 117 | 118 | -------------------------------------------------------------------------------- /docs/css/anchor-fix.css: -------------------------------------------------------------------------------- 1 | .anchor_sub::before { 2 | content:""; 3 | display:block; 4 | height:51px; /* fixed header height*/ 5 | margin:-51px 0 0; /* negative fixed header height */ 6 | } 7 | -------------------------------------------------------------------------------- /docs/css/callouts.css: -------------------------------------------------------------------------------- 1 | .callout { 2 | padding: 20px; 3 | margin: 20px 0; 4 | border: 2px solid #eee; 5 | border-left-width: 5px; 6 | border-radius: 7px; 7 | margin-top: 0; 8 | margin-bottom: 5px; 9 | border-left-color: #777; 10 | } 11 | 12 | .callout-success { 13 | border-left-color: #5cb85c; 14 | } 15 | 16 | .callout-danger { 17 | border-left-color: #d9534f; 18 | } 19 | 20 | .callout-warning { 21 | border-left-color: #f0ad4e; 22 | } 23 | 24 | .callout-info { 25 | border-left-color: #5bc0de; 26 | } 27 | -------------------------------------------------------------------------------- /docs/css/copy-button.css: -------------------------------------------------------------------------------- 1 | .copy-button { 2 | float:right; 3 | z-index: 10; 4 | display: block; 5 | padding: .25rem .5rem; 6 | /* font-size: 75%; */ 7 | color: #fff; 8 | cursor: pointer; 9 | background-color: #121212; 10 | /* border-radius: 7px; */ 11 | } 12 | .copy-button:hover { 13 | color: #fff; 14 | background-color: #C8102E; 15 | } -------------------------------------------------------------------------------- /docs/css/figures-and-tables.css: -------------------------------------------------------------------------------- 1 | /* add white space to figures*/ 2 | figure { 3 | margin-top: 30px; 4 | margin-bottom: 30px; 5 | } 6 | 7 | /* class to resize our images by default*/ 8 | img.scalable_image { 9 | max-width:100%; 10 | height:auto; 11 | width:auto; 12 | } 13 | 14 | /* class to resize our images by default*/ 15 | img.inline_image { 16 | max-height:20px; 17 | height:auto; 18 | width:auto; 19 | } 20 | 21 | /* lines in tables*/ 22 | table { 23 | border-collapse: collapse; 24 | border-top: 1px solid #ccc; 25 | border-bottom: 1px solid #ccc; 26 | margin-top: 30px; 27 | margin-bottom: 30px; 28 | margin-left: auto; 29 | margin-right: auto; 30 | } 31 | 32 | /* border after header if present*/ 33 | thead { 34 | border-bottom: 1px solid #ccc; 35 | } 36 | 37 | /* border in the table header*/ 38 | td { 39 | padding-bottom: 1.0em; 40 | } 41 | 42 | /*make our tables fancy*/ 43 | tr.odd { 44 | background-color: #f2f2f2; 45 | } 46 | tr.even:hover { 47 | background-color: #ddd; 48 | } 49 | tr.odd:hover { 50 | background-color: #ddd; 51 | } 52 | 53 | /* color and style of the captions for tables*/ 54 | .table_caption { 55 | font-weight:bold; 56 | color:black; 57 | } -------------------------------------------------------------------------------- /docs/css/general-styles.css: -------------------------------------------------------------------------------- 1 | 2 | /* change the default color and test color when text is highlighted*/ 3 | ::-moz-selection { /* Code for Firefox */ 4 | color: white; 5 | background: #C8102E; 6 | } 7 | 8 | ::selection { 9 | color: white; 10 | background: #C8102E; 11 | } 12 | 13 | .course-title { 14 | /* border-left-color: white; 15 | border-left-width: 1px; 16 | border-left-style: solid; 17 | padding-left: 20px; */ 18 | } 19 | 20 | /* header bar images */ 21 | img.header-image { 22 | height: 25px; 23 | } 24 | 25 | /* buffer to make some of the ehaders more readable 26 | h4 { 27 | padding-top: 20px; 28 | } 29 | */ -------------------------------------------------------------------------------- /docs/css/idl-styles.css: -------------------------------------------------------------------------------- 1 | /* CSS file that specifies the different syntax highlighting for each part of IDL code. */ 2 | 3 | /* class for idl links to the documentation */ 4 | .idl_docs_link { 5 | text-decoration: none; 6 | } 7 | 8 | /* 9 | class for block of IDL code which exists as a pre 10 | when we set visible to auto, we get scroll bars, but we 11 | have tooltips go outside of the bounds of the div they come 12 | from 13 | */ 14 | pre.idl_code_block { 15 | color: black; 16 | padding-left: 15px; 17 | background-color: #f5f5f5; 18 | border-style:solid; 19 | border-width:1px; 20 | border-color:#cccccc; 21 | overflow: visible; 22 | /* word break, not working right 23 | overflow-y: visible; 24 | overflow-x: scroll; 25 | white-space:pre-wrap; 26 | word-break: break-word; 27 | */ 28 | } 29 | 30 | /* class for IDL code that is embedded in a line */ 31 | code.idl_line { 32 | color:black; 33 | background:#f5f5f5; 34 | border-style:solid; 35 | border-width:1px; 36 | border-color:#cccccc; 37 | border-radius: 5px; 38 | } 39 | 40 | /* tooltips class in IDL*/ 41 | .idl_tt{ 42 | display: inline; 43 | position: relative; 44 | } 45 | 46 | /* makes the content for the tooltip*/ 47 | .idl_tt:hover:after{ 48 | background: #333; 49 | background: rgba(0,0,0,.8); 50 | border-radius: 5px; 51 | bottom: 26px; 52 | color: #fff; 53 | /* the idl_tt class elements also need an idl_tt attribute with the tooltip*/ 54 | content: attr(idl_tt); 55 | left: 20%; 56 | padding: 5px 15px; 57 | position: absolute; 58 | z-index:97; 59 | /*word wrap*/ 60 | white-space:pre-line; 61 | width:250px; 62 | word-break: break-word; 63 | } 64 | 65 | /* makes the arrow for the tooltip */ 66 | .idl_tt:hover:before{ 67 | border: solid; 68 | border-color: #333 transparent; 69 | border-width: 6px 6px 0 6px; 70 | bottom: 20px; 71 | content: ""; 72 | left: 50%; 73 | position: absolute; 74 | z-index:98; 75 | } 76 | 77 | /* allow overflow so that the tooltips dont disappear out of their elements*/ 78 | body { 79 | overflow: visible; 80 | } 81 | /* Makes text underlined and makes cursor turn into a question mark*/ 82 | .tt_text { 83 | border-bottom-style: inset; 84 | cursor: help; 85 | } 86 | 87 | 88 | /* IDL comments*/ 89 | .idl_comment { 90 | color:#29A3A3; 91 | } 92 | 93 | /* IDL strings*/ 94 | .idl_str { 95 | color:#FF0000; 96 | } 97 | 98 | /* IDL numbers*/ 99 | .idl_num { 100 | color:#808000; 101 | font-weight:bold; 102 | } 103 | 104 | /* IDL control statements*/ 105 | .idl_control { 106 | color:#7F0000; 107 | font-weight:bold; 108 | } 109 | 110 | /* IDL PRO code functions*/ 111 | .idl_lib_func { 112 | color:#00CCCC; 113 | font-weight:bold; 114 | } 115 | 116 | /* IDL PRO code procedures*/ 117 | .idl_lib_pro { 118 | color:#006666; 119 | font-weight:bold; 120 | } 121 | 122 | /* IDL internal functions*/ 123 | .idl_sys_func { 124 | color:#0000FF; 125 | font-weight:bold; 126 | } 127 | 128 | /* IDL internal procedures*/ 129 | .idl_sys_pro { 130 | color:#00007F; 131 | font-weight:bold; 132 | } 133 | 134 | /* Bold things. "IDL>" OR "ENVI>"*/ 135 | .idl_bold { 136 | font-weight:bold; 137 | } 138 | 139 | /*gray lines*/ 140 | .idl_gray { 141 | color:#A9A9A9; 142 | } 143 | 144 | /* IDL system variables "!NULL"*/ 145 | .idl_sysv { 146 | color:black; 147 | font-weight:bold; 148 | } 149 | 150 | /* IDL properties "something.property"*/ 151 | .idl_prop { 152 | font-style:italic; 153 | } 154 | 155 | /* IDL executive commands ".edit"*/ 156 | .idl_exec { 157 | font-style:italic; 158 | } 159 | 160 | /* structure definition tags "something:"*/ 161 | .idl_struct_tag { 162 | font-style:italic; 163 | } -------------------------------------------------------------------------------- /docs/css/search-results.css: -------------------------------------------------------------------------------- 1 | ul.search { 2 | list-style-type: none; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | li.search { 8 | font: 200 20px/1.5 Helvetica, Verdana, sans-serif; 9 | border-bottom: 1px solid #ccc; 10 | } 11 | 12 | li.search:last-child { 13 | border: none; 14 | } 15 | 16 | li.search a { 17 | text-decoration: none; 18 | color: #000; 19 | -webkit-transition: font-size 0.3s ease, background-color 0.3s ease; 20 | -moz-transition: font-size 0.3s ease, background-color 0.3s ease; 21 | -o-transition: font-size 0.3s ease, background-color 0.3s ease; 22 | -ms-transition: font-size 0.3s ease, background-color 0.3s ease; 23 | transition: font-size 0.3s ease, background-color 0.3s ease; 24 | display: block; 25 | } 26 | 27 | li.search a:hover { 28 | font-size: 30px; 29 | background: #f6f6f6; 30 | } 31 | 32 | li.searchMatch { 33 | font-size: 30px; 34 | border-bottom: 1px solid #ccc; 35 | } 36 | 37 | p.location { 38 | font-size:15px; 39 | border-bottom: 1px solid #ccc; 40 | } 41 | 42 | a.searchLink { 43 | font-size:25px; 44 | } 45 | 46 | li.searchItem { 47 | font-size:30px; 48 | } -------------------------------------------------------------------------------- /docs/css/sidebar.css: -------------------------------------------------------------------------------- 1 | /*text overflow*/ 2 | .sidebar-overflow { 3 | overflow-x: auto; 4 | height: 75vh; 5 | /* transform:rotateX(180deg); 6 | -ms-transform:rotateX(180deg); 7 | -webkit-transform:rotateX(180deg); */ 8 | } 9 | 10 | /* .sidebar-overflow-content{ 11 | transform:rotateX(-180deg); 12 | -ms-transform:rotateX(-180deg); 13 | -webkit-transform:rotateX(-180deg); 14 | } */ 15 | 16 | 17 | .fixed { 18 | position: fixed; 19 | } 20 | /* sidebar */ 21 | .bs-docs-sidebar { 22 | padding-left: 20px; 23 | margin-top: 20px; 24 | margin-bottom: 20px; 25 | } 26 | /* all links */ 27 | .bs-docs-sidebar .nav>li>a { 28 | color: #999; 29 | border-left: 2px solid transparent; 30 | padding: 4px 20px; 31 | font-size: 13px; 32 | font-weight: 400; 33 | max-width: 270px; 34 | } 35 | 36 | /*dropdown menu for chapter selection with limited size and scrollbar*/ 37 | ul.dropdown-menu { 38 | max-height:200px; 39 | overflow-y:auto; 40 | } 41 | 42 | @media (max-width: 1200px) and (min-width: 992px) { 43 | /* all links */ 44 | .bs-docs-sidebar .nav>li>a { 45 | color: #999; 46 | border-left: 2px solid transparent; 47 | padding: 4px 20px; 48 | font-size: 13px; 49 | font-weight: 400; 50 | max-width: 225px; 51 | } 52 | } 53 | 54 | 55 | @media (max-width: 991px) and (min-width: 768px) { 56 | /* all links */ 57 | .bs-docs-sidebar .nav>li>a { 58 | color: #999; 59 | border-left: 2px solid transparent; 60 | padding: 4px 20px; 61 | font-size: 13px; 62 | font-weight: 400; 63 | max-width: 185px; 64 | } 65 | } 66 | 67 | @media (max-width: 767px) { 68 | /* all links */ 69 | .bs-docs-sidebar .nav>li>a { 70 | display: none; 71 | color: #999; 72 | border-left: 2px solid transparent; 73 | padding: 4px 20px; 74 | font-size: 13px; 75 | font-weight: 400; 76 | max-width: 185px; 77 | } 78 | } 79 | 80 | /* 81 | @media (max-width: 800px) { 82 | display: none; 83 | }*/ 84 | 85 | 86 | /* nested links */ 87 | .bs-docs-sidebar .nav .nav>li>a { 88 | padding-top: 1px; 89 | padding-bottom: 1px; 90 | padding-left: 30px; 91 | font-size: 12px; 92 | } 93 | /* active & hover links */ 94 | .bs-docs-sidebar .nav>.active>a, 95 | .bs-docs-sidebar .nav>li>a:hover, 96 | .bs-docs-sidebar .nav>li>a:focus { 97 | color: #C8102E; 98 | text-decoration: none; 99 | background-color: transparent; 100 | border-left-color: #C8102E; 101 | } 102 | /* all active links */ 103 | .bs-docs-sidebar .nav>.active>a, 104 | .bs-docs-sidebar .nav>.active:hover>a, 105 | .bs-docs-sidebar .nav>.active:focus>a { 106 | font-weight: 700; 107 | } 108 | /* nested active links */ 109 | .bs-docs-sidebar .nav .nav>.active>a, 110 | .bs-docs-sidebar .nav .nav>.active:hover>a, 111 | .bs-docs-sidebar .nav .nav>.active:focus>a { 112 | font-weight: 500; 113 | } 114 | /* hide inactive nested list when commented out*/ 115 | .bs-docs-sidebar .nav ul.nav { 116 | display: none; 117 | } 118 | 119 | /* show active nested list */ 120 | .bs-docs-sidebar .nav>.active>ul.nav { 121 | display: block; 122 | } -------------------------------------------------------------------------------- /docs/custom_code/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 40 | 41 | 42 | 43 | 91 |
92 |
93 | 94 | 99 |
100 |
101 | 102 |
103 |

UAVToolkit Directory custom_code

104 |
105 |

This folder contains the source code used for the different tasks in the UAV Toolkit. They are separated by the overall tasks that they belong to. The task files are also going to be located in these subfolders.

106 |
107 |

Copyright © 2019 Harris Geospatial Solutions, Inc.

108 |

Licensed under MIT. See LICENSE.txt for additional details and information.

109 |
110 |
111 |
112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/images/L3Harrislogowhite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 24 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/images/baseline_code_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envi-idl/UAVToolkit/95545c6794d40c56fa08d954b0238a6b485816a2/docs/images/baseline_code_white_48dp.png -------------------------------------------------------------------------------- /docs/images/baseline_home_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envi-idl/UAVToolkit/95545c6794d40c56fa08d954b0238a6b485816a2/docs/images/baseline_home_white_48dp.png -------------------------------------------------------------------------------- /docs/images/baseline_menu_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envi-idl/UAVToolkit/95545c6794d40c56fa08d954b0238a6b485816a2/docs/images/baseline_menu_white_48dp.png -------------------------------------------------------------------------------- /docs/images/baseline_search_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/envi-idl/UAVToolkit/95545c6794d40c56fa08d954b0238a6b485816a2/docs/images/baseline_search_white_48dp.png -------------------------------------------------------------------------------- /docs/index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 88 |
89 | 90 |
91 |

Search

92 |
93 | 94 |
95 |
96 |
97 |

Results

98 | 99 |
100 |
101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/index/indexSearch.js: -------------------------------------------------------------------------------- 1 | //h+ 2 | // Copyright (c) 2018 Harris Geospatial Solutions, Inc. 3 | // 4 | // Licensed under MIT. See LICENSE.txt for additional details and information. 5 | //h- 6 | 7 | //search function 8 | $(document).ready(function () { 9 | //get search box 10 | var searchBox = $("#searchInput") 11 | 12 | // get div for search results 13 | var resultdiv = $("#searchResults"); 14 | 15 | //set search input as selected then loading page 16 | searchBox.focus(); 17 | 18 | // Set up search 19 | var index, store; 20 | 21 | // Create index 22 | index = lunr.Index.load(indexJSON.index); 23 | 24 | //validate the index 25 | //this is for the purpose of debugging and it will be output to 26 | //the console if there are any problems 27 | Object.keys(index.documentStore.store) 28 | .forEach(function (documentId) { 29 | var documentTokens = index.documentStore.store[documentId]; 30 | 31 | documentTokens.forEach(function (token) { 32 | if (index.corpusTokens.indexOf(token) == -1) { 33 | console.log('not in corpus tokens', documentId, token); 34 | } 35 | 36 | if (!index.tokenStore.has(token)) { 37 | console.log('not in token store', documentId, token); 38 | } 39 | }); 40 | }); 41 | 42 | // Create store 43 | //store contains: title, body, and section 44 | store = indexJSON.store; 45 | 46 | //setup before functions 47 | var typingTimer; //timer identifier 48 | var doneTypingInterval = 250; //time in ms, 5 second for example 49 | 50 | //on keyup, start the countdown or check for enter 51 | searchBox.on('keyup', function (event) { 52 | //pressed the enter key, otherwise use a timer 53 | if(event.keyCode == 13){ 54 | doneTyping (event) 55 | } else { 56 | clearTimeout(typingTimer); 57 | typingTimer = setTimeout(function() {doneTyping(event);}, doneTypingInterval); 58 | } 59 | }); 60 | 61 | //on keydown, clear the countdown 62 | searchBox.on('keydown', function () { 63 | clearTimeout(typingTimer); 64 | }); 65 | 66 | //user is "finished typing," do something 67 | function doneTyping (event) { 68 | // Get query without white spaces 69 | var query = event.target.value.trim(); 70 | 71 | // clear previous results 72 | resultdiv.hide(); 73 | resultdiv.empty(); 74 | 75 | //chekc for valid query 76 | if (query != "" && query.length > 1){ 77 | //set result dir value for search item 78 | resultdiv.append('
  • Matches for "' + query + '"
  • '); 79 | 80 | // Search for it 81 | var result = index.search(query); 82 | 83 | // check if we found matches 84 | if (result.length === 0) { 85 | // nothing found 86 | resultdiv.append('
  • No results found
  • '); 87 | } else { 88 | // display what we found 89 | for (var item in result) { 90 | var href = result[item].ref; 91 | var searchitem = '
  • ' + store[href].title + '

    ' + store[href].section + '

  • '; 92 | resultdiv.append(searchitem); 93 | } 94 | } 95 | }; 96 | 97 | //show results 98 | resultdiv.show(); 99 | } 100 | }); 101 | -------------------------------------------------------------------------------- /docs/js/LICENSE.clipboard.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2018 Zeno Rocha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/js/LICENSE.lunr.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 by Oliver Nightingale 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /docs/js/copy-button.js: -------------------------------------------------------------------------------- 1 | //h+ 2 | // Copyright (c) 2018 Harris Geospatial Solutions, Inc. 3 | // 4 | // Licensed under MIT. See LICENSE.txt for additional details and information. 5 | //h- 6 | 7 | $(document).ready(function () { 8 | var pre = document.getElementsByTagName('pre'); 9 | 10 | // add buttons to every pre element 11 | for (var i = 0; i < pre.length; i++) { 12 | //make sure that we want a copy button 13 | if (pre[i].className !== 'no-copy-button'){ 14 | var p = document.createElement('p') 15 | pre[i].prepend(p); 16 | var p = document.createElement('p') 17 | pre[i].append(p); 18 | var span = document.createElement('span'); 19 | span.className = 'copy-button'; 20 | span.textContent = 'COPY'; 21 | //add button to the code block 22 | pre[i].prepend(span); 23 | } 24 | }; 25 | 26 | //set tooltip parameters 27 | $('.copy-button').tooltip({ 28 | trigger: 'click', 29 | placement: 'bottom', 30 | hideDelay: 200 31 | }); 32 | 33 | //show the tooltip 34 | function setTooltip(btn, message) { 35 | $(btn).attr('data-original-title', message) 36 | .tooltip('show'); 37 | } 38 | 39 | //hide tooltip after a certain amount of time 40 | function hideTooltip(btn) { 41 | setTimeout(function() { 42 | $(btn).tooltip('hide'); 43 | }, 750); 44 | } 45 | 46 | //manage events for clipboard 47 | var clipboard = new Clipboard('.copy-button', { 48 | target: function(trigger) { 49 | //get rid of text present so it isn't copied too 50 | //do this because all elements are together instead 51 | //of separate 52 | trigger.textContent = '' 53 | return trigger.parentNode; 54 | } 55 | }); 56 | 57 | //follow up on our event if we are successful or fail 58 | clipboard.on('success', function(e) { 59 | //clear selection because we don't want to see that crap 60 | e.clearSelection(); 61 | //reset original trigger button to normal text 62 | e.trigger.textContent = 'Copy' 63 | //let user know we copied code 64 | setTooltip(e.trigger, 'Copied!'); 65 | hideTooltip(e.trigger); 66 | }); 67 | 68 | //show errors in the console 69 | clipboard.on('error', function(e) { 70 | console.error('Action:', e.action); 71 | console.error('Trigger:', e.trigger); 72 | //reset original trigger button to normal text 73 | e.trigger.textContent = 'Copy' 74 | }); 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /docs/js/highlight/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Ivan Sagalaev 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of highlight.js nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/agate.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Agate by Taufik Nurrohman 3 | * ---------------------------------------------------- 4 | * 5 | * #ade5fc 6 | * #a2fca2 7 | * #c6b4f0 8 | * #d36363 9 | * #fcc28c 10 | * #fc9b9b 11 | * #ffa 12 | * #fff 13 | * #333 14 | * #62c8f3 15 | * #888 16 | * 17 | */ 18 | 19 | .hljs { 20 | display: block; 21 | overflow-x: auto; 22 | padding: 0.5em; 23 | background: #333; 24 | color: white; 25 | } 26 | 27 | .hljs-name, 28 | .hljs-strong { 29 | font-weight: bold; 30 | } 31 | 32 | .hljs-code, 33 | .hljs-emphasis { 34 | font-style: italic; 35 | } 36 | 37 | .hljs-tag { 38 | color: #62c8f3; 39 | } 40 | 41 | .hljs-variable, 42 | .hljs-template-variable, 43 | .hljs-selector-id, 44 | .hljs-selector-class { 45 | color: #ade5fc; 46 | } 47 | 48 | .hljs-string, 49 | .hljs-bullet { 50 | color: #a2fca2; 51 | } 52 | 53 | .hljs-type, 54 | .hljs-title, 55 | .hljs-section, 56 | .hljs-attribute, 57 | .hljs-quote, 58 | .hljs-built_in, 59 | .hljs-builtin-name { 60 | color: #ffa; 61 | } 62 | 63 | .hljs-number, 64 | .hljs-symbol, 65 | .hljs-bullet { 66 | color: #d36363; 67 | } 68 | 69 | .hljs-keyword, 70 | .hljs-selector-tag, 71 | .hljs-literal { 72 | color: #fcc28c; 73 | } 74 | 75 | .hljs-comment, 76 | .hljs-deletion, 77 | .hljs-code { 78 | color: #888; 79 | } 80 | 81 | .hljs-regexp, 82 | .hljs-link { 83 | color: #c6b4f0; 84 | } 85 | 86 | .hljs-meta { 87 | color: #fc9b9b; 88 | } 89 | 90 | .hljs-deletion { 91 | background-color: #fc9b9b; 92 | color: #333; 93 | } 94 | 95 | .hljs-addition { 96 | background-color: #a2fca2; 97 | color: #333; 98 | } 99 | 100 | .hljs a { 101 | color: inherit; 102 | } 103 | 104 | .hljs a:focus, 105 | .hljs a:hover { 106 | color: inherit; 107 | text-decoration: underline; 108 | } 109 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/atom-one-dark-reasonable.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Dark With support for ReasonML by Gidi Morris, based off work by Daniel Gamage 4 | 5 | Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax 6 | 7 | */ 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | line-height: 1.3em; 13 | color: #abb2bf; 14 | background: #282c34; 15 | border-radius: 5px; 16 | } 17 | .hljs-keyword, .hljs-operator { 18 | color: #F92672; 19 | } 20 | .hljs-pattern-match { 21 | color: #F92672; 22 | } 23 | .hljs-pattern-match .hljs-constructor { 24 | color: #61aeee; 25 | } 26 | .hljs-function { 27 | color: #61aeee; 28 | } 29 | .hljs-function .hljs-params { 30 | color: #A6E22E; 31 | } 32 | .hljs-function .hljs-params .hljs-typing { 33 | color: #FD971F; 34 | } 35 | .hljs-module-access .hljs-module { 36 | color: #7e57c2; 37 | } 38 | .hljs-constructor { 39 | color: #e2b93d; 40 | } 41 | .hljs-constructor .hljs-string { 42 | color: #9CCC65; 43 | } 44 | .hljs-comment, .hljs-quote { 45 | color: #b18eb1; 46 | font-style: italic; 47 | } 48 | .hljs-doctag, .hljs-formula { 49 | color: #c678dd; 50 | } 51 | .hljs-section, .hljs-name, .hljs-selector-tag, .hljs-deletion, .hljs-subst { 52 | color: #e06c75; 53 | } 54 | .hljs-literal { 55 | color: #56b6c2; 56 | } 57 | .hljs-string, .hljs-regexp, .hljs-addition, .hljs-attribute, .hljs-meta-string { 58 | color: #98c379; 59 | } 60 | .hljs-built_in, .hljs-class .hljs-title { 61 | color: #e6c07b; 62 | } 63 | .hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-type, .hljs-selector-class, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-number { 64 | color: #d19a66; 65 | } 66 | .hljs-symbol, .hljs-bullet, .hljs-link, .hljs-meta, .hljs-selector-id, .hljs-title { 67 | color: #61aeee; 68 | } 69 | .hljs-emphasis { 70 | font-style: italic; 71 | } 72 | .hljs-strong { 73 | font-weight: bold; 74 | } 75 | .hljs-link { 76 | text-decoration: underline; 77 | } 78 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/atom-one-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Dark by Daniel Gamage 4 | Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax 5 | 6 | base: #282c34 7 | mono-1: #abb2bf 8 | mono-2: #818896 9 | mono-3: #5c6370 10 | hue-1: #56b6c2 11 | hue-2: #61aeee 12 | hue-3: #c678dd 13 | hue-4: #98c379 14 | hue-5: #e06c75 15 | hue-5-2: #be5046 16 | hue-6: #d19a66 17 | hue-6-2: #e6c07b 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #abb2bf; 26 | background: #282c34; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #5c6370; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #c678dd; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e06c75; 47 | } 48 | 49 | .hljs-literal { 50 | color: #56b6c2; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #98c379; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #e6c07b; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #d19a66; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #61aeee; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/atom-one-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Atom One Light by Daniel Gamage 4 | Original One Light Syntax theme from https://github.com/atom/one-light-syntax 5 | 6 | base: #fafafa 7 | mono-1: #383a42 8 | mono-2: #686b77 9 | mono-3: #a0a1a7 10 | hue-1: #0184bb 11 | hue-2: #4078f2 12 | hue-3: #a626a4 13 | hue-4: #50a14f 14 | hue-5: #e45649 15 | hue-5-2: #c91243 16 | hue-6: #986801 17 | hue-6-2: #c18401 18 | 19 | */ 20 | 21 | .hljs { 22 | display: block; 23 | overflow-x: auto; 24 | padding: 0.5em; 25 | color: #383a42; 26 | background: #fafafa; 27 | } 28 | 29 | .hljs-comment, 30 | .hljs-quote { 31 | color: #a0a1a7; 32 | font-style: italic; 33 | } 34 | 35 | .hljs-doctag, 36 | .hljs-keyword, 37 | .hljs-formula { 38 | color: #a626a4; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name, 43 | .hljs-selector-tag, 44 | .hljs-deletion, 45 | .hljs-subst { 46 | color: #e45649; 47 | } 48 | 49 | .hljs-literal { 50 | color: #0184bb; 51 | } 52 | 53 | .hljs-string, 54 | .hljs-regexp, 55 | .hljs-addition, 56 | .hljs-attribute, 57 | .hljs-meta-string { 58 | color: #50a14f; 59 | } 60 | 61 | .hljs-built_in, 62 | .hljs-class .hljs-title { 63 | color: #c18401; 64 | } 65 | 66 | .hljs-attr, 67 | .hljs-variable, 68 | .hljs-template-variable, 69 | .hljs-type, 70 | .hljs-selector-class, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-number { 74 | color: #986801; 75 | } 76 | 77 | .hljs-symbol, 78 | .hljs-bullet, 79 | .hljs-link, 80 | .hljs-meta, 81 | .hljs-selector-id, 82 | .hljs-title { 83 | color: #4078f2; 84 | } 85 | 86 | .hljs-emphasis { 87 | font-style: italic; 88 | } 89 | 90 | .hljs-strong { 91 | font-weight: bold; 92 | } 93 | 94 | .hljs-link { 95 | text-decoration: underline; 96 | } 97 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/github-gist.css: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Gist Theme 3 | * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro 4 | */ 5 | 6 | .hljs { 7 | display: block; 8 | background: white; 9 | padding: 0.5em; 10 | color: #333333; 11 | overflow-x: auto; 12 | } 13 | 14 | .hljs-comment, 15 | .hljs-meta { 16 | color: #969896; 17 | } 18 | 19 | .hljs-string, 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-strong, 23 | .hljs-emphasis, 24 | .hljs-quote { 25 | color: #df5000; 26 | } 27 | 28 | .hljs-keyword, 29 | .hljs-selector-tag, 30 | .hljs-type { 31 | color: #a71d5d; 32 | } 33 | 34 | .hljs-literal, 35 | .hljs-symbol, 36 | .hljs-bullet, 37 | .hljs-attribute { 38 | color: #0086b3; 39 | } 40 | 41 | .hljs-section, 42 | .hljs-name { 43 | color: #63a35c; 44 | } 45 | 46 | .hljs-tag { 47 | color: #333333; 48 | } 49 | 50 | .hljs-title, 51 | .hljs-attr, 52 | .hljs-selector-id, 53 | .hljs-selector-class, 54 | .hljs-selector-attr, 55 | .hljs-selector-pseudo { 56 | color: #795da3; 57 | } 58 | 59 | .hljs-addition { 60 | color: #55a532; 61 | background-color: #eaffea; 62 | } 63 | 64 | .hljs-deletion { 65 | color: #bd2c00; 66 | background-color: #ffecec; 67 | } 68 | 69 | .hljs-link { 70 | text-decoration: underline; 71 | } 72 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/monokai-sublime.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #23241f; 12 | } 13 | 14 | .hljs, 15 | .hljs-tag, 16 | .hljs-subst { 17 | color: #f8f8f2; 18 | } 19 | 20 | .hljs-strong, 21 | .hljs-emphasis { 22 | color: #a8a8a2; 23 | } 24 | 25 | .hljs-bullet, 26 | .hljs-quote, 27 | .hljs-number, 28 | .hljs-regexp, 29 | .hljs-literal, 30 | .hljs-link { 31 | color: #ae81ff; 32 | } 33 | 34 | .hljs-code, 35 | .hljs-title, 36 | .hljs-section, 37 | .hljs-selector-class { 38 | color: #a6e22e; 39 | } 40 | 41 | .hljs-strong { 42 | font-weight: bold; 43 | } 44 | 45 | .hljs-emphasis { 46 | font-style: italic; 47 | } 48 | 49 | .hljs-keyword, 50 | .hljs-selector-tag, 51 | .hljs-name, 52 | .hljs-attr { 53 | color: #f92672; 54 | } 55 | 56 | .hljs-symbol, 57 | .hljs-attribute { 58 | color: #66d9ef; 59 | } 60 | 61 | .hljs-params, 62 | .hljs-class .hljs-title { 63 | color: #f8f8f2; 64 | } 65 | 66 | .hljs-string, 67 | .hljs-type, 68 | .hljs-built_in, 69 | .hljs-builtin-name, 70 | .hljs-selector-id, 71 | .hljs-selector-attr, 72 | .hljs-selector-pseudo, 73 | .hljs-addition, 74 | .hljs-variable, 75 | .hljs-template-variable { 76 | color: #e6db74; 77 | } 78 | 79 | .hljs-comment, 80 | .hljs-deletion, 81 | .hljs-meta { 82 | color: #75715e; 83 | } 84 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/railscasts.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Railscasts-like style (c) Visoft, Inc. (Damien White) 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #232323; 12 | color: #e6e1dc; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #bc9458; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag { 23 | color: #c26230; 24 | } 25 | 26 | .hljs-string, 27 | .hljs-number, 28 | .hljs-regexp, 29 | .hljs-variable, 30 | .hljs-template-variable { 31 | color: #a5c261; 32 | } 33 | 34 | .hljs-subst { 35 | color: #519f50; 36 | } 37 | 38 | .hljs-tag, 39 | .hljs-name { 40 | color: #e8bf6a; 41 | } 42 | 43 | .hljs-type { 44 | color: #da4939; 45 | } 46 | 47 | 48 | .hljs-symbol, 49 | .hljs-bullet, 50 | .hljs-built_in, 51 | .hljs-builtin-name, 52 | .hljs-attr, 53 | .hljs-link { 54 | color: #6d9cbe; 55 | } 56 | 57 | .hljs-params { 58 | color: #d0d0ff; 59 | } 60 | 61 | .hljs-attribute { 62 | color: #cda869; 63 | } 64 | 65 | .hljs-meta { 66 | color: #9b859d; 67 | } 68 | 69 | .hljs-title, 70 | .hljs-section { 71 | color: #ffc66d; 72 | } 73 | 74 | .hljs-addition { 75 | background-color: #144212; 76 | color: #e6e1dc; 77 | display: inline-block; 78 | width: 100%; 79 | } 80 | 81 | .hljs-deletion { 82 | background-color: #600; 83 | color: #e6e1dc; 84 | display: inline-block; 85 | width: 100%; 86 | } 87 | 88 | .hljs-selector-class { 89 | color: #9b703f; 90 | } 91 | 92 | .hljs-selector-id { 93 | color: #8b98ab; 94 | } 95 | 96 | .hljs-emphasis { 97 | font-style: italic; 98 | } 99 | 100 | .hljs-strong { 101 | font-weight: bold; 102 | } 103 | 104 | .hljs-link { 105 | text-decoration: underline; 106 | } 107 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/rainbow.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Style with support for rainbow parens 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #474949; 12 | color: #d1d9e1; 13 | } 14 | 15 | 16 | .hljs-comment, 17 | .hljs-quote { 18 | color: #969896; 19 | font-style: italic; 20 | } 21 | 22 | .hljs-keyword, 23 | .hljs-selector-tag, 24 | .hljs-literal, 25 | .hljs-type, 26 | .hljs-addition { 27 | color: #cc99cc; 28 | } 29 | 30 | .hljs-number, 31 | .hljs-selector-attr, 32 | .hljs-selector-pseudo { 33 | color: #f99157; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag, 38 | .hljs-regexp { 39 | color: #8abeb7; 40 | } 41 | 42 | .hljs-title, 43 | .hljs-name, 44 | .hljs-section, 45 | .hljs-built_in { 46 | color: #b5bd68; 47 | } 48 | 49 | .hljs-variable, 50 | .hljs-template-variable, 51 | .hljs-selector-id, 52 | .hljs-class .hljs-title { 53 | color: #ffcc66; 54 | } 55 | 56 | .hljs-section, 57 | .hljs-name, 58 | .hljs-strong { 59 | font-weight: bold; 60 | } 61 | 62 | .hljs-symbol, 63 | .hljs-bullet, 64 | .hljs-subst, 65 | .hljs-meta, 66 | .hljs-link { 67 | color: #f99157; 68 | } 69 | 70 | .hljs-deletion { 71 | color: #dc322f; 72 | } 73 | 74 | .hljs-formula { 75 | background: #eee8d5; 76 | } 77 | 78 | .hljs-attr, 79 | .hljs-attribute { 80 | color: #81a2be; 81 | } 82 | 83 | .hljs-emphasis { 84 | font-style: italic; 85 | } 86 | -------------------------------------------------------------------------------- /docs/js/highlight/styles/solarized-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #002b36; 12 | color: #839496; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #586e75; 18 | } 19 | 20 | /* Solarized Green */ 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-addition { 24 | color: #859900; 25 | } 26 | 27 | /* Solarized Cyan */ 28 | .hljs-number, 29 | .hljs-string, 30 | .hljs-meta .hljs-meta-string, 31 | .hljs-literal, 32 | .hljs-doctag, 33 | .hljs-regexp { 34 | color: #2aa198; 35 | } 36 | 37 | /* Solarized Blue */ 38 | .hljs-title, 39 | .hljs-section, 40 | .hljs-name, 41 | .hljs-selector-id, 42 | .hljs-selector-class { 43 | color: #268bd2; 44 | } 45 | 46 | /* Solarized Yellow */ 47 | .hljs-attribute, 48 | .hljs-attr, 49 | .hljs-variable, 50 | .hljs-template-variable, 51 | .hljs-class .hljs-title, 52 | .hljs-type { 53 | color: #b58900; 54 | } 55 | 56 | /* Solarized Orange */ 57 | .hljs-symbol, 58 | .hljs-bullet, 59 | .hljs-subst, 60 | .hljs-meta, 61 | .hljs-meta .hljs-keyword, 62 | .hljs-selector-attr, 63 | .hljs-selector-pseudo, 64 | .hljs-link { 65 | color: #cb4b16; 66 | } 67 | 68 | /* Solarized Red */ 69 | .hljs-built_in, 70 | .hljs-deletion { 71 | color: #dc322f; 72 | } 73 | 74 | .hljs-formula { 75 | background: #073642; 76 | } 77 | 78 | .hljs-emphasis { 79 | font-style: italic; 80 | } 81 | 82 | .hljs-strong { 83 | font-weight: bold; 84 | } 85 | -------------------------------------------------------------------------------- /idl.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "syntax": { 4 | "indent": [ 5 | true, 6 | 2 7 | ], 8 | "whitespace": [ 9 | true, 10 | "remove-trailing", 11 | "empty-new-lines" 12 | ], 13 | "quote": [ 14 | true, 15 | "single" 16 | ], 17 | "code-blocks-required": [ 18 | true, 19 | "if", 20 | "for", 21 | "foreach", 22 | "while", 23 | "repeat" 24 | ], 25 | "max-line-length": [ 26 | false, 27 | 140 28 | ] 29 | }, 30 | "naming": { 31 | "routine-prefix": "", 32 | "underscore": [ 33 | true, 34 | "only-start", 35 | "routines", 36 | "methods" 37 | ], 38 | "keywords": [ 39 | true, 40 | "upper-case-left" 41 | ] 42 | }, 43 | "comments": { 44 | "routine-comments-required": false, 45 | "file-comments-required": false, 46 | "whitespace-after-comment": true, 47 | "header-content": "" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /idl.package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UAVToolkit", 3 | "version": "2.3.5", 4 | "license": "MIT", 5 | "author": "Zachary Norman", 6 | "description": "A tool used to preprocess multispectral, multi-lens imagery.", 7 | "entry_point": "uav_toolkit", 8 | "build_types": [ 9 | "idl" 10 | ], 11 | "ignore": [ 12 | "dist", 13 | "docs" 14 | ], 15 | "scripts": { 16 | "add-headers": [ 17 | [ 18 | "self.addFileHeaders, /OVERWRITE, ['Copyright (c) 2019 Harris Geospatial Solutions, Inc.',", 19 | "'',", 20 | "'Licensed under MIT. See LICENSE.txt for additional details and information.']" 21 | ] 22 | ], 23 | "doc": [ 24 | [ 25 | "self.document, /CLEAR_PREVIOUS, /NO_AUTO_OPEN, ", 26 | "FOOTER=['Copyright © 2019 Harris Geospatial Solutions, Inc.',", 27 | "'', 'Licensed under MIT. See ![FILE: LICENSE.txt] for additional details and information.']" 28 | ] 29 | ], 30 | "doc-dev": [ 31 | [ 32 | "self.document, /CLEAR_PREVIOUS, ", 33 | "FOOTER=['Copyright © 2019 Harris Geospatial Solutions, Inc.',", 34 | "'', 'Licensed under MIT. See ![FILE: LICENSE.txt] for additional details and information.']" 35 | ] 36 | ] 37 | }, 38 | "dependencies": { 39 | "github-se-awesomeenvitoolkit": { 40 | "version": "^v0.0.2", 41 | "url": "hgsolutions/SE-AwesomeENVIToolkit" 42 | }, 43 | "github-bridgeit": { 44 | "version": "^v1.0.0", 45 | "url": "interactive-data-language/BridgeIt" 46 | } 47 | }, 48 | "can_install": "verifyVersion, '8.6'", 49 | "add_envi_extensions": "uav_toolkit_extensions_init", 50 | "add_envi_tasks": "uav_toolkit" 51 | } 52 | -------------------------------------------------------------------------------- /idl.package.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /idl.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "sol": { 3 | "bridge_debug_log": "sol.log", 4 | "quiet": 0, 5 | "verbose": 0, 6 | "debug": false, 7 | "search_pattern": ".spec.pro", 8 | "auto_generate_luna_reports": true, 9 | "auto_discover_source": false 10 | }, 11 | "luna": { 12 | "bridge_debug_log": "luna.log", 13 | "quiet": 0, 14 | "verbose": 0, 15 | "debug": false, 16 | "profile": false, 17 | "no_log": false, 18 | "no_routines": false, 19 | "auto_discover_source": false, 20 | "routines": [ 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /idl_packages/github-bridgeit/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017, Exelis Visual Information Solutions, Inc. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /idl_packages/github-bridgeit/bridge_it_example.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; 3 | ; Example for how to use the bridge_it object. This object allows for a user to easily 4 | ; distribute processing between IDL_IDLBridge objects without needing to do anything to 5 | ; set up the bridges, which can be hard at times. The examples below cover the syntax 6 | ; for most of the methods that you can use with the bridge_it object. 7 | ; 8 | ; :Author: Zachary Norman 9 | ;- 10 | 11 | 12 | 13 | pro bridge_it_example 14 | compile_opt idl2 15 | 16 | ;catch statement is necessary to properly clean up the bridge processes so we 17 | ;don't have zombie child processes running around 18 | ;following this catch block will also keep IDL from freezing when you try to reset 19 | ;while a child process is running. 20 | catch, err 21 | if (err ne 0) then begin 22 | catch, /CANCEL 23 | bdg_it.cleanup 24 | help, /LAST_MESSAGE, output = msg 25 | print, msg 26 | retall 27 | endif 28 | 29 | ;number of bridge processes to create 30 | nbridges = 1 31 | thisdir = FILE_DIRNAME(((SCOPE_TRACEBACK(/STRUCT))[-1]).FILENAME) 32 | 33 | ;optional init keyword to set preferences in the child process 34 | init = $ 35 | ;add this dir to path 36 | ['PREF_SET, "IDL_PATH", !PATH + path_sep(/SEARCH_PATH) + "' + thisdir + '",/COMMIT'] 37 | 38 | ;create the child processes and specify the log directory which wil have all_logs.txt for the 39 | ;output/print statements from all child processes 40 | ;this will also greatly help with debugging child processes executing procedures/functions 41 | bdg_it = bridge_it(nbridges, INIT = init, LOGDIR = thisdir, NREFRESH = 10) 42 | 43 | ;================================================================================= 44 | ;do some processing 45 | ;create 1 plot with function graphics 46 | ;use the time keyword to display the time is takes to complete the bridge process 47 | void = bdg_it.run('plot',ARG1 = findgen(10), ARG2 = 2*findgen(10), _KW_COLOR='Red', /TIME) 48 | 49 | ;create 1 plot with direct graphics 50 | bdg_it.run, 'plot', ARG1=findgen(10), ARG2 = 2*findgen(10) 51 | 52 | ;run a function and get the function result back 53 | void = bdg_it.run('findgen', ARG1=3, ARG2 = 3, /CALLBACK, /TIME) 54 | 55 | ;run a procedure and get the results back from some one keyword 56 | bdg_it.run, 'triangulate', ARG1=randomu(!NULL, 25), ARG2 = randomu(!NULL, 25), ARG3 = 1, _KW_CONNECTIVITY = 1, ARGUMENTS_OUT = 'ARG3', KEYWORDS_OUT = 'CONNECTIVITY', /TIME 57 | 58 | ;run a function and get back a positional keyword and a positional argument 59 | void = bdg_it.run('min', ARG1=findgen(10), ARG2 = 1, ARGUMENTS_OUT = 'ARG2', KEYWORDS_OUT = 'MAX', /CALLBACK, /TIME) 60 | 61 | ;================================================================================= 62 | 63 | ;wait for all bridges to be done before proceeding 64 | bdg_it.wait 65 | print 66 | 67 | ;get the results from the bridges 68 | results = bdg_it.getResults() 69 | print, 'Bridge results: ' 70 | print, results, /IMPLIED_PRINT 71 | print 72 | 73 | ;get the number of runs for each bridge 74 | nruns = bdg_it.getNruns() 75 | print, 'Information for bridge runs:' 76 | print, nruns, /IMPLIED_PRINT 77 | 78 | ;get the actual references to the IDL_IDLBridge objects 79 | bridges = bdg_it.getBridges() 80 | 81 | ;stop here so the graphics don't totally disappear 82 | ;they go away when the process that created them dies 83 | stop 84 | 85 | ;clean up the bridges 86 | bdg_it.cleanup 87 | end 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore file initialized by the IDL Developer Tools 2 | /dist 3 | /idl_packages 4 | *spec.pro.log 5 | *idl.test.log 6 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017, Exelis Visual Information Solutions, Inc. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/README.md: -------------------------------------------------------------------------------- 1 | # SE-AwesomeENVIToolkit 2 | 3 | A collection of tools to help make programming with the ENVI API easier than ever and streamline the process to create new analytics/routines. Below you will find a short description of each of the routines that are included. 4 | 5 | ## AwesomeENVITask 6 | 7 | This function has an alternative approach to instantiating ENVI tasks that does the same thing as the `IDLTask` function. If the task you want to create does not exist in ENVI's task catalog, then it will search IDL's path and load the task from disk. This is very useful for programmers and those that do not want to install ENVI tasks in the `custom_code` directory. Here is an example: 8 | 9 | ```idl 10 | ; open a task not in ENVI's custom_code folder 11 | task = awesomeENVITask('MySuperTask') 12 | 13 | ; open a task that is in ENVI's task catalog 14 | task = awesomeENVITask('ISODATAClassification') 15 | ``` 16 | 17 | ## AwesomeENVIProgress 18 | 19 | Helpful tool that simplies sending progress messages to the ENVI interface with custom routines and includes features for printing messages to the IDL console, timing processes, and checking for users cancelling progress. 20 | 21 | Here is an example that uses most of the keywords for processing. 22 | 23 | ```idl 24 | ; start ENVI 25 | e = envi() 26 | 27 | ; create our progress object 28 | prog = awesomeENVIProgress('My Super Progress Message', /PRINT) 29 | 30 | ; send some progress messages 31 | for i=0, 9 do begin 32 | ; check for cancellation 33 | prog.abortRequested 34 | 35 | ; send progress messages 36 | prog.setProgress, 'Processing...', 100*((i+1)/10.0), /PRINT, /TIME 37 | 38 | wait, 1 39 | endfor 40 | 41 | ; close progress message 42 | prog.finish, /PRINT 43 | ``` 44 | 45 | The results of the above code should look something like this: 46 | 47 | ``` 48 | My Super Progress Message 49 | Processing..., Progress=10 [0.1 (s) left] 50 | Processing..., Progress=20 [4.1 (s) left] 51 | Processing..., Progress=30 [4.7 (s) left] 52 | Processing..., Progress=40 [4.5 (s) left] 53 | Processing..., Progress=50 [4.0 (s) left] 54 | Processing..., Progress=60 [3.3 (s) left] 55 | Processing..., Progress=70 [2.6 (s) left] 56 | Processing..., Progress=80 [1.7 (s) left] 57 | Processing..., Progress=90 [0.9 (s) left] 58 | Processing..., Progress=100 [0.0 (s) left] 59 | Finished! 60 | ``` 61 | 62 | In addition to the above example, you **must** properly handle errors when using progress messages or, if you have the ENVI interface open, you will see old progress dialogs. Here is how you can do that upon object creation: 63 | 64 | ```idl 65 | ;handle errors to properly clean up our progress 66 | catch, err 67 | if (err ne 0) then begin 68 | catch, /CANCEL 69 | prog.finish, /PRINT, /ERROR 70 | message, /REISSUES_LAST 71 | endif 72 | 73 | ; create our progress object 74 | prog = awesomeENVIProgress('My Super Progress Message', /PRINT) 75 | ``` 76 | 77 | ### Note for Developers 78 | 79 | There is some basic logic within the awesome progress that checks to see if a "parent" progress dialog/object has already been created. When this is the case, then all "child" progress messages do not get printed to the IDL Console to make things much cleaner especially for enterprise deployments. If you get into a bad state and messages are no longer printing, then you just need to run the following line to fix the problem: 80 | 81 | ```idl 82 | awesomeENVIProgress, /CLEAR_PROGRESS 83 | ``` 84 | 85 | ## AwesomeCatchBlock 86 | 87 | The `awesomeCatchBlock` is a simple catch block that helps simplify and standardize error catching from within different routines. The contents of the catch block are as follows: 88 | 89 | ```idl 90 | if ~keyword_set(debug) then begin 91 | on_error, 2 92 | catch, err 93 | if (err ne 0) then begin 94 | catch, /CANCEL 95 | help, /LAST_MESSAGE 96 | message, /REISSUE_LAST 97 | endif 98 | endif 99 | ``` 100 | 101 | The catch block should be used at the beginning of routines like this: 102 | 103 | ```idl 104 | pro someProcedure, DEBUG = debug 105 | compile_opt idl2, hidden 106 | @awesomeCatchBlock 107 | 108 | ... 109 | 110 | end 111 | ``` 112 | 113 | Note that, if you add another catch block after the `awesomeCatchBlock` then you should use `catch, /CANCEL` like this, before 114 | 115 | ```idl 116 | pro someProcedure, DEBUG = debug 117 | compile_opt idl2, hidden 118 | @awesomeCatchBlock 119 | 120 | ... 121 | 122 | ; handle errors and clean up 123 | catch, /CANCEL 124 | catch, err 125 | if (err ne 0) then begin 126 | catch, /CANCEL 127 | prog.finish, /ERROR 128 | message, /REISSUE_LAST 129 | endif 130 | 131 | ... 132 | 133 | end 134 | ``` 135 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/idl.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "syntax": { 4 | "indent": [ 5 | true, 6 | 2 7 | ], 8 | "whitespace": [ 9 | true, 10 | "remove-trailing", 11 | "empty-new-lines" 12 | ], 13 | "quote": [ 14 | true, 15 | "single" 16 | ], 17 | "code-blocks-required": [ 18 | true, 19 | "if", 20 | "for", 21 | "foreach", 22 | "while", 23 | "repeat" 24 | ], 25 | "max-line-length": [ 26 | false, 27 | 140 28 | ] 29 | }, 30 | "naming": { 31 | "routine-prefix": "", 32 | "underscore": [ 33 | true, 34 | "only-start", 35 | "routines", 36 | "methods" 37 | ], 38 | "keywords": [ 39 | true, 40 | "upper-case-left" 41 | ] 42 | }, 43 | "comments": { 44 | "routine-comments-required": false, 45 | "file-comments-required": false, 46 | "whitespace-after-comment": true, 47 | "header-content": "" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/idl.package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CustomIDLPackage", 3 | "version": "1.0", 4 | "license": "", 5 | "author": "IDL Package Creator", 6 | "description": "An IDL package created by the IDL Package Creator", 7 | "entry_point": "", 8 | "build_types": [ 9 | "idl" 10 | ], 11 | "build_ignore": [ 12 | "dist", 13 | "docs" 14 | ], 15 | "dependencies": { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/idl.package.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/idl.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "sol": { 3 | "bridge_debug_log": "sol.log", 4 | "quiet": 0, 5 | "verbose": 0, 6 | "debug": false, 7 | "search_pattern": ".spec.pro", 8 | "auto_generate_luna_reports": true, 9 | "auto_discover_source": false 10 | }, 11 | "luna": { 12 | "bridge_debug_log": "luna.log", 13 | "quiet": 0, 14 | "verbose": 0, 15 | "debug": false, 16 | "profile": false, 17 | "no_log": false, 18 | "no_routines": false, 19 | "auto_discover_source": false, 20 | "routines": [ 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeENVIProgress/README.md: -------------------------------------------------------------------------------- 1 | # Better Progress Message 2 | 3 | An improved API for setting ENVI progress messages build on what is provided in ENVI. Simply wraps the existing objects that are used to create progress dialogs in ENVI. 4 | 5 | 6 | ## Requirements 7 | 8 | ENVI 5.4 or later. Should work on ENVI 5.3 as well (untested). 9 | 10 | 11 | ## Usage 12 | 13 | Once you have added the object to IDL's search path, you can simply use the folowing example for how to initialize, update progress, and check to see if a user has pressed the cancel button: 14 | 15 | ``` 16 | ;start ENVI 17 | e = envi() 18 | 19 | ;initialize the progress message 20 | prog = awesomeENVIProgress('Progress title') 21 | 22 | ;set our first progress update 23 | prog.SetProgress, 'Processing...', 50 24 | 25 | ;check to see if a user has tried to cancel processing 26 | print, prog.AbortRequested() 27 | 28 | ;clean up properly 29 | prog.finish 30 | ``` 31 | 32 | 33 | ## Notes on Proper Usage 34 | 35 | When using progress messages in ENVI, you need to make sure that you are properly handling errors so that the progress messages are closed correctly. Otherwise they can be left in a bad state and new ones will no longer pop up. To do this, simply use a catch statement after you intialize the progress message: 36 | 37 | ``` 38 | ;start ENVI 39 | e = envi() 40 | 41 | ;initialize the progress message 42 | prog = awesomeENVIProgress('Progress title') 43 | 44 | ;properly hadle errors 45 | catch, err 46 | if (err ne 0) then begin 47 | catch, /CANCEL 48 | prog.finish 49 | message, /REISSUE_LAST 50 | endif 51 | 52 | ;set our first progress update 53 | prog.SetProgress, 'Processing...', 50 54 | 55 | ;check to see if a user has tried to cancel processing 56 | print, prog.AbortRequested() 57 | 58 | ;clean up properly 59 | prog.finish 60 | ``` 61 | 62 | 63 | ## Licensing 64 | 65 | Licensed under MIT. Full details in LICENSE.txt. -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeENVIProgress/awesomeenviprogress__define.spec.pro: -------------------------------------------------------------------------------- 1 | ; create our tester object 2 | l = luna(CONFIG_FILE='./../../idl.test.json') 3 | 4 | ; start envi headlessly 5 | e = envi(/HEADLESS) 6 | 7 | ;create a suite 8 | s = l.suite('Test suite that') 9 | 10 | ; create a test 11 | it = s.test('validates our definition and other routines') 12 | 13 | (it.expects('awesomeENVIProgress')).toRunProcedure, /CLEAR_PROGRESS 14 | (it.expects('awesomeENVIProgress__define')).toRunProcedure 15 | 16 | 17 | ; create a test 18 | it = s.test('validates our progress message as an object') 19 | 20 | ; create our progress object 21 | prog = awesomeENVIProgress('My Super Progress Message', /PRINT) 22 | 23 | ;creat an expectation to run against 24 | e = it.expects(prog) 25 | 26 | ; make sure it is a valid object 27 | e.toBeAValidObject 28 | 29 | ; send some progress messages 30 | for i=0, 3 do begin 31 | ; check for cancellation both ways 32 | e.toRunProcedureMethod, 'abortRequested' 33 | e.toRunFunctionMethod, 'abortRequested' 34 | 35 | ; send progress messages 36 | if (i eq 0) then begin 37 | prog.setProgress, 'Processing...', 100*((i+1)/3), APPEND = 'something', /NO_TAB, /PRINT, /TIME 38 | endif 39 | e.toRunProcedureMethod, 'setProgress', 'Processing...', 100*((i+1)/3) 40 | 41 | wait, 1 42 | endfor 43 | 44 | ; close progress message 45 | e.toRunProcedureMethod, 'finish', /PRINT 46 | 47 | l.generateTestSummary 48 | end 49 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeENVITask/awesomeenvitask.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Procedure that adds a nice, basic level of logic 4 | ; to creating ENVI task objects that will check to see 5 | ; if the task is in ENVI's task catalog and, if not, 6 | ; will search IDL's path for a corresponding task file 7 | ; for processing. 8 | ; 9 | ; For example, if you have a task named `MySuperTask` this 10 | ; routine will check ENVI's task catalog to see if it is 11 | ; present and, if not, it will then search IDL's path for 12 | ; a file called `mysupertask.task` and attempt to open that 13 | ; as a task. 14 | ; 15 | ; :Params: 16 | ; taskName: in, required, type=string 17 | ; Specify the name of a task that you want to request from 18 | ; ENVI. 19 | ; 20 | ; :Keywords: 21 | ; DEBUG: in, optional, type=boolean 22 | ; If set, errors will be stopped on. 23 | ; IGNORE_CASE: in, optional, type=boolean 24 | ; When set, it will check the system path for a file 25 | ; exactly matching the case of the `taskName` argument. By 26 | ; default the task name is converted to lower case as it is 27 | ; the standard for IDL programs and ENVI task file naming. This 28 | ; is more of a routine that may be needed on Linux/Mac systems 29 | ; as it will not affect the behavior of windows systems. 30 | ; PATH: in, optional, type=boolean 31 | ; Setting this keyword will force the routine to use the 32 | ; search path instead of ENVI's task catalog. This is useful 33 | ; if you are developing a task and want to use the newest 34 | ; content on disk. 35 | ; 36 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 37 | ;- 38 | function awesomeENVITask, taskName,$ 39 | DEBUG = debug,$ 40 | IGNORE_CASE = ignore_case,$ 41 | PATH = path 42 | compile_opt idl2, hidden 43 | if ~keyword_set(debug) then on_error, 2 44 | 45 | ;validate our input 46 | inputValidator, hash('taskName', ['required', 'string']) 47 | 48 | ;make sure that ENVI has started 49 | e = awesomeGetENVI() 50 | 51 | ;try to get the task if we are not using the path 52 | if ~keyword_set(path) then task = ENVITask(taskName, ERROR = err) 53 | 54 | ;check if there was an error or if we are using the path 55 | if keyword_set(err) OR keyword_set(path) then begin 56 | ;specify task file 57 | findFile = (keyword_set(ignore_case) ? taskName : strlowcase(taskName)) + '.task' 58 | 59 | ;check path 60 | taskFile = file_which(findFile) 61 | 62 | ;make sure we found it 63 | if ~keyword_set(taskFile) then begin 64 | message, 'No task file was found matching "' + findFile + '"', LEVEL = -1 65 | endif 66 | 67 | ;attempt to open the taskfile as a task 68 | task = ENVITask(taskFile) 69 | endif 70 | 71 | ;return our task 72 | return, task 73 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeENVITask/awesomeenvitask.spec.pro: -------------------------------------------------------------------------------- 1 | ; create our tester 2 | l = luna(CONFIG_FILE='./../../idl.test.json') 3 | 4 | ; start envi headlessly 5 | e = envi(/HEADLESS) 6 | 7 | ;create a suite 8 | s = l.suite('Test suite that') 9 | 10 | ; create a test 11 | it = s.test('makes sure our function works') 12 | 13 | ; add an expectation for our test with existing task 14 | (it.expects('awesomeENVITask')).toRunFunction, 'ISODataClassification' 15 | 16 | ; add an expectation for our test with task on the path 17 | (it.expects('awesomeENVITask')).toRunFunction, 'classify_raster' 18 | 19 | l.generateTestSummary 20 | end 21 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeENVITask/classify_raster.task: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ClassifyRaster", 3 | "schema": "envitask_3.2", 4 | "base_class": "ENVITaskFromProcedure", 5 | "routine": "classify_raster", 6 | "display_name": "Classify Raster", 7 | "description": "This is an example of a custom task that performs classification and classification cleanup.", 8 | "parameters": [ 9 | { 10 | "name": "INPUT_RASTER", 11 | "display_name": "Input Raster", 12 | "description": "Specify the raster to perform classification on.", 13 | "direction": "input", 14 | "required": true, 15 | "type": "ENVIRaster" 16 | }, 17 | { 18 | "name": "OUTPUT_RASTER_URI", 19 | "display_name": "Output Raster URI", 20 | "description": "Specify a string with the fully-qualified path and filename for OUTPUT_RASTER.", 21 | "direction": "input", 22 | "required": false, 23 | "type": "ENVIURI", 24 | "auto_extension": ".dat" 25 | }, 26 | { 27 | "name": "OUTPUT_RASTER", 28 | "display_name": "Output Raster", 29 | "description": "This is a reference to an ENVIRaster object.", 30 | "direction": "output", 31 | "required": true, 32 | "type": "ENVIRaster", 33 | "uri_param": "OUTPUT_RASTER_URI" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeGetENVI/awesomegetenvi.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Simple function that gets the current ENVI session and 4 | ; validates that, if it is supposed to be open, the ENVI 5 | ; UI is valid. This works around a bug where you can have a bad 6 | ; state with the ENVI UI being open, stop is pressed in 7 | ; the IDL workbench, the UI closes, but ENVI remains 8 | ; open. In this situation you cannot use ENVI object 9 | ; methods as they will fail. 10 | ; 11 | ; 12 | ; :Keywords: 13 | ; UI: in, optional, tpye=boolean 14 | ; Set this keyword to make sure that ENVI is not in 15 | ; headless mode. 16 | ; 17 | ; 18 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 19 | ;- 20 | function awesomeGetENVI, UI = ui 21 | compile_opt idl2, hidden 22 | on_error, 2 23 | 24 | ;get current ENVI 25 | e = envi(/CURRENT) 26 | if (e eq !NULL) then begin 27 | message, 'ENVI has not started yet, required!', LEVEL = -1 28 | endif else begin 29 | ;check if we have a widget ID and, if so, make sure it is valid 30 | if (e.widget_id gt 0) then begin 31 | if ~widget_info(e.widget_id, /VALID_ID) then begin 32 | message, 'ENVI has started and the UI should be present but it is missing.' + $ 33 | ' Please reset your IDL session (or restart IDL) and try again.', LEVEL = -1 34 | endif 35 | endif 36 | 37 | ;validate object 38 | if ~obj_valid(e) then begin 39 | message, 'ENVI has started, but is not a valid IDL object.' + $ 40 | ' Please reset your IDL session (or restart IDL/ENVI) and try again.', LEVEL = -1 41 | endif 42 | 43 | ;make sure the UI is up and running 44 | if keyword_set(ui) then begin 45 | if (e.widget_id eq 0) then begin 46 | message, 'ENVI has started, but is in headless mode.', LEVEL = -1 47 | endif 48 | endif 49 | endelse 50 | 51 | ;return ENVI 52 | return, e 53 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeGetENVI/awesomegetenvi.spec.pro: -------------------------------------------------------------------------------- 1 | ; create our tester object 2 | l = luna(CONFIG_FILE='./../../idl.test.json') 3 | 4 | ;create a suite 5 | s = l.suite('Test suite that') 6 | 7 | ; create a test 8 | it = s.test('fails to get the current ENVI session because it has not started') 9 | 10 | ; check if ENVI is open and close if it is 11 | e = envi(/CURRENT) 12 | if (e ne !NULL) then begin 13 | e.close 14 | endif 15 | 16 | ; add an expectation for our test 17 | (it.expects('awesomeGetENVI'))._not_.toRunFunction 18 | 19 | ; create a test 20 | it = s.test('gets the current ENVI session') 21 | 22 | ; start envi headlessly 23 | e = envi(/HEADLESS) 24 | 25 | ; add an expectation for our test 26 | (it.expects('awesomeGetENVI')).toRunFunction 27 | 28 | ; create a test 29 | it = s.test('fails when the UI is not open and the UI is requested') 30 | 31 | ; add an expectation for our test 32 | (it.expects('awesomeGetENVI'))._not_.toRunFunction, /UI 33 | 34 | ; create a test 35 | it = s.test('passes when the UI is open and the UI is requested') 36 | 37 | ; close envi and start with UI 38 | e.close 39 | e = envi() 40 | 41 | ; add an expectation for our test 42 | (it.expects('awesomeGetENVI')).toRunFunction, /UI 43 | 44 | ; clean up 45 | e.close 46 | 47 | l.generateTestSummary 48 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeSelectTaskParameters/awesomeselecttaskparameters.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Procedure that wraps some fancy ENVI-code for 4 | ; making a non-blocking widget to select and validate 5 | ; task parameters. 6 | ; 7 | ; :Params: 8 | ; taskName: in, required, type=string 9 | ; 10 | ; :Keywords: 11 | ; SHOW_DISPLAY_OPTION: in, optional, type=boolean 12 | ; Set this keyword to have options in the UI for 13 | ; being able to display the result. 14 | ; SHOW_PREVIEW: in, optional, type=boolean 15 | ; Set this keyword to allow users to preview what 16 | ; the results will look like. 17 | ; TASK: in, optional, type=ENVITask 18 | ; Optionally specify an ENVI task that has been created 19 | ; to generate a UI for. 20 | ; 21 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 22 | ;- 23 | pro awesomeSelectTaskParameters, taskName, $ 24 | SHOW_DISPLAY_OPTION = show_display_option,$ 25 | SHOW_PREVIEW = show_preview,$ 26 | TASK = task 27 | compile_opt idl2, hidden 28 | on_error, 2 29 | 30 | ;validate our inputs 31 | if ~isa(task, 'ENVITask') then begin 32 | if (taskName eq !NULL) then begin 33 | message, 'taskName required, but not specified!', LEVEL = -1 34 | endif 35 | endif 36 | 37 | ;catch error and report to level above us 38 | catch, err 39 | if (err ne 0) then begin 40 | catch, /CANCEL 41 | message, /REISSUE_LAST, LEVEL = -1 42 | endif 43 | 44 | ;get current ENVI and make sure the UI is open 45 | e = awesomeGetENVI(/UI) 46 | 47 | ;get our task 48 | if ~isa(task, 'ENVITask') then begin 49 | task = ENVITask(taskName, ERROR = error) 50 | if keyword_set(error) then begin 51 | message, error 52 | endif 53 | endif 54 | 55 | ;create UI 56 | IDLcf$Get, TOOL=oTool 57 | oUIsvc = oTool.Get(IDENTIFIER='UI/Services/ENVITaskParameters') 58 | !null = oUIsvc.DoUIService(task, SHOW_DISPLAY_OPTION = show_display_option, SHOW_PREVIEW = show_preview) 59 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeTileIterator/createawesomeoptimizedtileiterator.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Tile iterator that generates tiles that are more optimized 4 | ; for different image interleaves. Essentially it just ensures 5 | ; that the sub_rects generated contain a full "column" of data. 6 | ; 7 | ; Ensuring that our tiles occupy full columns of information helps 8 | ; reduce the number of jumps that happen when reading and writing 9 | ; data. This can drastically improve read/write speeds (depending on 10 | ; hardware and raster) up to about 15x at extremes. Speed improvements 11 | ; are not guaranteed for all algorithms and do depend on what chunks 12 | ; of your raster data you are extracting. 13 | ; 14 | ; The "column" of data is based on the interelave. For BIL and BSQ 15 | ; the columns represent whole rows of pixels (and all bands) while 16 | ; the columns for BIP represent all the pixels for a given sample 17 | ; and line. 18 | ; 19 | ; The metric for generating tiles in this routine is the number of 20 | ; pixels that you want per tile which takes into account the number 21 | ; of bands in your raster. 22 | ; 23 | ; The sub-rects are generated with the intent of reading/writing all 24 | ; bands of data at once. Performance will likely be affected for 25 | ; BIL and BIP interleaves if this is not followed. 26 | ; 27 | ; Note that this tiler does not support tile buffers like the 28 | ; ```idl createAwesomeTileIterator``` procedure does. 29 | ; 30 | ; 31 | ; 32 | ; :Keywords: 33 | ; INPUT_RASTER: in, required, type=ENVIRaster 34 | ; Specify the raster that you want to generate tiles for. 35 | ; PIXELS_PER_TILE: in, optional, default = 1024*1024 36 | ; Optionally specify how many pixels you want a tile to approximately 37 | ; have. Tiles are rounded up for some interleaves to ensure faster 38 | ; processing times. 39 | ; OUTPUT_SUB_RECTS: out, requried, type=list 40 | ; List that contains the sub rects of data to read/write to a raster. 41 | ; OUTPUT_TILE_SUB_RECTS: out, required, type=list, private 42 | ; List that specifies the [xmin, ymin, xmax, ymax] of valid pixel 43 | ; coordinates for the data. Not meant for general use. 44 | ; 45 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 46 | ;- 47 | pro createAwesomeOptimizedTileIterator,$ 48 | INPUT_RASTER = input_raster,$ 49 | PIXELS_PER_TILE = pixels_per_tile,$ 50 | OUTPUT_SUB_RECTS = output_sub_rects,$ 51 | OUTPUT_TILE_SUB_RECTS = output_tile_sub_rects 52 | compile_opt idl2 53 | 54 | ;get current ENVI session 55 | e = awesomegetenvi() 56 | 57 | ;validate input 58 | if (input_raster eq !NULL) then begin 59 | message, 'INPUT_RASTER not specified, required!' 60 | endif 61 | 62 | ;check how many pixels per tile we want 63 | if (pixels_per_tile eq !NULL) then begin 64 | pixels_per_tile = 1024*ulong(1024) 65 | endif 66 | 67 | ;calculate the number of columns of pixel data that we want to extract 68 | nCols = float(pixels_per_tile)/input_raster.NBANDS 69 | 70 | ;get the number of rows 71 | nRows = nCols / input_raster.NCOLUMNS 72 | 73 | ;round up and cap if we have a float 74 | nCols = ceil(nCols) < input_raster.NCOLUMNS 75 | 76 | ;check if we have bsq or bil data - need at least one full row of data 77 | ;which means we need as many columns as our raster has - doesn't matter for bip 78 | if (input_raster.INTERLEAVE eq 'bsq') OR (input_raster.INTERLEAVE eq 'bil') then begin 79 | nCols >= input_raster.NCOLUMNS 80 | endif 81 | 82 | ;roun up nrows 83 | nRows = ceil(nRows) < input_raster.NROWS 84 | 85 | ;get our sub-rects 86 | createAwesomeTileIterator,$ 87 | INPUT_RASTER = input_raster,$ 88 | TILE_SIZE = [nCols, nRows],$ 89 | OUTPUT_SUB_RECTS = output_sub_rects,$ 90 | OUTPUT_TILE_SUB_RECTS = output_tile_sub_rects 91 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeTileIterator/createawesometileiterator.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; (c) 2017 Exelis Visual Information Solutions, Inc., a subsidiary of Harris Corporation. 3 | ; 4 | ; :Description: 5 | ; Procedure to create a much better tile iterator which includes a buffer to 6 | ; remove tiling effects from some processing. If you specify that you want a 7 | ; buffer then the OUTPUT_TILE_SUB_RECTS will contain a list of the regions of 8 | ; each tile that represents the actualy data in the raster. 9 | ; 10 | ; 11 | ; 12 | ; :Keywords: 13 | ; INPUT_RASTER: in, required, tpye=ENVIRaster 14 | ; Specify the ENVIRaster that you want to create a nice tile iterator for 15 | ; TILE_BUFFER: in, optional, type=long 16 | ; Set this keyword to the buffer that you want to add to each tile to help 17 | ; remove edge effects from processing. This is added to the sides of each tile 18 | ; when added. See `OUTPUT_TILE_SUB_RECTS` for how to get the actual data out of the 19 | ; tiles ignoring the buffer. 20 | ; TILE_SIZE: in, optional, type=longarr, default=[1024,1024] 21 | ; Specify the custom tile size that you want to use when determining the sub rects 22 | ; to split up the INPUT_RASTER. If the raster is smaller than the requested tile 23 | ; size then the sub rects will be snapped to the size of the raster. 24 | ; OUTPUT_SUB_RECTS: out, required, type=list 25 | ; This output keyword contains the sub rects for the `INPUT_RASTER`. 26 | ; OUTPUT_RASTER_TILE_LOCATIONS: out, required, type=list 27 | ; This outward parameters contains the sub rects in the original raster that the 28 | ; `OUTPUT_TILE_SUB_RECTS` will correspond to. This is added so you can easily map 29 | ; the tiles back to the original raster. This is only needed if you specify the 30 | ; `TILE_BUFFER` keyword. 31 | ; OUTPUT_TILE_MAP: out, optional, type=arr 32 | ; A 2D array that contains the index of each tile in the `OUTPUT_SUB_RECTS` list so that 33 | ; a user can easily understand the spatial content between them all. 34 | ; OUTPUT_TILE_SUB_RECTS: out, required, type=list 35 | ; This keyword contains a list where each element represents the sub rect of each tile that 36 | ; will exclude the buffer. This is used so that you can remove the buffer from each tile and, 37 | ; in conjunction with the `OUTPUT_RASTER_TILE_LOCATIONS`, can be used to place the data where 38 | ; it belongs in an output raster. This output keyword is only needed when `TILE_BUFFER` is set. 39 | ; 40 | ; 41 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 42 | ;- 43 | pro createAwesomeTileIterator,$ 44 | DEBUG = debug,$ 45 | INPUT_RASTER = input_raster,$ 46 | TILE_BUFFER = tile_buffer, $ 47 | TILE_SIZE = tile_Size,$ 48 | NCOLUMNS = ncolumns,$ 49 | NROWS = nrows,$ 50 | OUTPUT_SUB_RECTS = output_sub_rects,$ 51 | OUTPUT_RASTER_TILE_LOCATIONS = output_raster_tile_locations,$ 52 | OUTPUT_TILE_MAP = output_tile_map,$ 53 | OUTPUT_TILE_SUB_RECTS = output_tile_sub_rects 54 | compile_opt idl2, hidden 55 | if ~keyword_set(debug) then on_error, 2 56 | 57 | if keyword_set(input_raster) then begin 58 | ;get some information from the INPUT_RASTER 59 | ncolumns = input_raster.NCOLUMNS 60 | nrows = input_Raster.NROWS 61 | endif else begin 62 | if ~keyword_set(ncolumns) then begin 63 | message, 'NCOLUMNS was not specified when INPUT_RASTER was set, required!' 64 | endif 65 | if ~keyword_set(nrows) then begin 66 | message, 'NROWS was not specified when INPUT_RASTER was set, required!' 67 | endif 68 | endelse 69 | 70 | ;default tile buffer 71 | if ~keyword_set(tile_buffer) then begin 72 | tile_buffer = 0 73 | endif 74 | 75 | ;default tile size 76 | if ~keyword_set(tile_size) then begin 77 | tile_Size = [1024, 1024] 78 | endif 79 | 80 | ;determine how many tiles we need 81 | if (ncolumns le tile_size[0]) then begin 82 | nx = 1 83 | endif else begin 84 | nx = ceil(float(ncolumns)/tile_size[0]) 85 | endelse 86 | 87 | if (nrows le tile_size[1]) then begin 88 | ny = 1 89 | endif else begin 90 | ny = ceil(float(nrows)/tile_size[1]) 91 | endelse 92 | 93 | ;hold a reference to all the output tiles in a map 94 | output_tile_map = ulonarr(nx, ny) 95 | 96 | ;determine our sub rects without a buffer that correspond to where the real data 97 | ;in each tile will exist for our output raster 98 | output_raster_tile_locations = list() 99 | for j = 0, ny - 1 do begin 100 | for i = 0, nx - 1 do begin 101 | left = (i*tile_size[0]) > 0 102 | right = (((i+1)*tile_size[0]-1) < (ncolumns-1)) > 0 103 | top = (j*tile_size[1]) > 0 104 | bottom = (((j+1)*tile_size[1]-1) < (nrows-1)) > 0 105 | output_raster_tile_locations.add, [left, top, right, bottom] 106 | endfor 107 | endfor 108 | 109 | ;determine our sub rects with a buffer 110 | output_sub_rects = list() 111 | for j = 0, ny - 1 do begin 112 | for i = 0, nx - 1 do begin 113 | left = (i*tile_size[0]-tile_buffer) > 0 114 | right = (((i+1)*tile_size[0]-1+tile_buffer) < (ncolumns-1)) > 0 115 | top = (j*tile_size[1]-tile_buffer) > 0 116 | bottom = (((j+1)*tile_size[1]-1+tile_buffer) < (nrows-1)) > 0 117 | output_tile_map[i,j] = n_elements(output_sub_rects) 118 | output_sub_rects.add, [left, top, right, bottom] 119 | endfor 120 | endfor 121 | 122 | ; get the regions in our sub_rects that represent real data 123 | ; this only matters i buffer is set which means that each sub rect we calculated above 124 | ; actually represents data that is duplicated between each tile to help remove edge effects for 125 | ; some processing. the 126 | output_tile_sub_rects = list() 127 | foreach sub_rect, output_sub_rects, i do begin 128 | out_loc = output_raster_tile_locations[i] 129 | 130 | case sub_rect[0] of 131 | 0: minx = 0 132 | else: minx = tile_buffer 133 | endcase 134 | 135 | case sub_rect[1] of 136 | 0: miny = 0 137 | else: miny = tile_buffer 138 | endcase 139 | 140 | case sub_rect[2] of 141 | (ncolumns-1): maxx = sub_rect[2] - sub_rect[0] 142 | else: maxx = sub_rect[2] - sub_rect[0] - tile_buffer 143 | endcase 144 | 145 | case 1 of 146 | (sub_rect[3] eq (nrows-1)): maxy = sub_rect[3] - sub_rect[1] 147 | else: maxy = sub_rect[3] - sub_rect[1] - tile_buffer 148 | endcase 149 | 150 | ;correct for tile buffers where the buffer is less than the extra space for 151 | ;the tile. i.e. we have a 1015 by 1015 array with 1000 by 1000 tiles and 152 | ;a 16 pixel buffer 153 | if (tile_buffer gt 0) then begin 154 | diffx = sub_rect[2] - out_loc[2] 155 | if (diffx gt 0) and (diffx lt tile_buffer) then begin 156 | maxx -= diffx 157 | endif 158 | 159 | diffy = sub_rect[3] - out_loc[3] 160 | if (diffy gt 0) and (diffy lt tile_buffer) then begin 161 | maxy -= diffy 162 | endif 163 | endif 164 | 165 | output_tile_sub_rects.add, [minx, miny, maxx, maxy] > 0 166 | endforeach 167 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeTileIterator/createawesometileiterator.spec.pro: -------------------------------------------------------------------------------- 1 | ;create a tester 2 | l = luna(CONFIG_FILE='./../../idl.test.json', /DEBUG) 3 | 4 | ; start envi headlessly 5 | e = envi(/HEADLESS) 6 | 7 | ; Open an input file 8 | File = Filepath('qb_boulder_msi', Subdir=['data'], $ 9 | Root_Dir=e.Root_Dir) 10 | Raster = e.OpenRaster(File) 11 | 12 | ;create a suite 13 | s = l.suite('Test suite that') 14 | 15 | ; create a test 16 | it = s.test('makes sure we can tile over a raster and that our results match expected') 17 | 18 | ; add an expectation for our test 19 | (it.expects('createAwesomeTileIterator')).toRunProcedure,$ 20 | INPUT_RASTER = raster,$ 21 | OUTPUT_SUB_RECTS = sub_rects 22 | 23 | ;make sure our results are equal 24 | (it.expects(sub_rects)).toEqual, list([0, 0, 1023, 1023]) 25 | 26 | ; create a test 27 | it = s.test('makes sure we can tile over a raster with a buffer and that our results match expected') 28 | 29 | ; add an expectation for our test 30 | (it.expects('createAwesomeTileIterator')).toRunProcedure,$ 31 | TILE_SIZE = [512, 512],$ 32 | TILE_BUFFER = 2,$ 33 | INPUT_RASTER = raster,$ 34 | OUTPUT_SUB_RECTS = sub_rects,$ 35 | OUTPUT_RASTER_TILE_LOCATIONS = raster_tile_locations,$ 36 | OUTPUT_TILE_SUB_RECTS = tile_sub_rects 37 | 38 | ; specify what our results should be 39 | expected_subs = list([0,0,513,513], [510,0,1023,513], [0,510,513,1023], [510,510,1023,1023]) 40 | expected_locs = list([0,0,511,511], [511,0,1023,511], [0,511,511,1023], [511,511,1023,1023]) 41 | expected_tile_subs = list([0,0,511,511], [2,0,513,511], [0,2,511,513], [2,2,513,513]) 42 | 43 | ;make sure our results are equal 44 | (it.expects(sub_rects)).toEqual, expected_subs 45 | (it.expects(raster_tile_locations)).toEqual, expected_locs 46 | (it.expects(expected_tile_subs)).toEqual, tile_sub_rects 47 | 48 | ; create a test 49 | it = s.test('makes sure we can tile over dimensions with a buffer and that our results match expected') 50 | 51 | ; add an expectation for our test 52 | (it.expects('createAwesomeTileIterator')).toRunProcedure,$ 53 | TILE_SIZE = [512, 512],$ 54 | TILE_BUFFER = 2,$ 55 | NCOLUMNS = 1024,$ 56 | NROWS = 1024,$ 57 | OUTPUT_SUB_RECTS = sub_rects,$ 58 | OUTPUT_RASTER_TILE_LOCATIONS = raster_tile_locations,$ 59 | OUTPUT_TILE_SUB_RECTS = tile_sub_rects 60 | 61 | ; specify what our results should be 62 | expected_subs = list([0,0,513,513], [510,0,1023,513], [0,510,513,1023], [510,510,1023,1023]) 63 | expected_locs = list([0,0,511,511], [511,0,1023,511], [0,511,511,1023], [511,511,1023,1023]) 64 | expected_tile_subs = list([0,0,511,511], [2,0,513,511], [0,2,511,513], [2,2,513,513]) 65 | 66 | ;make sure our results are equal 67 | (it.expects(sub_rects)).toEqual, expected_subs 68 | (it.expects(raster_tile_locations)).toEqual, expected_locs 69 | (it.expects(expected_tile_subs)).toEqual, tile_sub_rects 70 | 71 | l.generateTestSummary 72 | end 73 | -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomecatchblock.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; Catch block that can be inserted into any IDL program with 3 | ; the syntax "@awesomecatchblock" near the top of your program. 4 | ; 5 | ; This catch block also sets `on_error` to not enter the routine 6 | ; if an error is caught. The catch block and the `on_error` state 7 | ; are both skipped if there is a vaiable defined as `debug`. This 8 | ; can commonly be a keyword for the routine you are processing and 9 | ; help for production/development use. 10 | ; 11 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 12 | ;- 13 | if ~keyword_set(debug) then begin 14 | on_error, 2 15 | catch, err 16 | if (err ne 0) then begin 17 | catch, /CANCEL 18 | help, /LAST_MESSAGE 19 | message, /REISSUE_LAST 20 | endif 21 | endif -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomeenviareextensionsinitialized.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Function that is intended for use in code blocks before 4 | ; adding extensions to ENVI. The reason for this routine is 5 | ; that, for newer versions of ENVI, you can dynamically add 6 | ; extensions to the toolbox. The only downside to this is that 7 | ; you can keep adding the same tools to the toolbox. Here is an 8 | ; example of how you might use this procedure. 9 | ; 10 | ; ```idl 11 | ; pro routine_extensions_init 12 | ; compile_opt idl2, hidden 13 | ; e = envi(/CURRENT) 14 | ; 15 | ; if ~awesomeENVIAreExtensionsInitialized('routine') then begin 16 | ; e.AddExtension, 'My Awesome Extension, 'run_routine' 17 | ; endif 18 | ; 19 | ; end 20 | ; ``` 21 | ; 22 | ; With the example above, you could call the `routine_extensions_init` 23 | ; multiple times and only have a single button added in the toolbox. 24 | ; 25 | ; 26 | ; :Params: 27 | ; name: in, required, type=string 28 | ; Specify a unique identifier associated with the extensions 29 | ; that you are checking. 30 | ; 31 | ; 32 | ; 33 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 34 | ;- 35 | function awesomeENVIAreExtensionsInitialized, name, DEBUG = debug, RESET = reset 36 | compile_opt idl2, hidden 37 | if ~keyword_set(debug) then on_error,2 38 | 39 | ;validate our input 40 | inputValidator, hash('name', ['string', 'required']) 41 | 42 | ;check if our system variable exists or not 43 | defsysv, '!initializedENVIextensions', EXISTS = exists 44 | if ~exists then begin 45 | defsysv, '!initializedENVIextensions', hash(/FOLD_CASE) 46 | endif else begin 47 | if keyword_set(reset) then begin 48 | !initializedENVIextensions = hash(/FOLD_CASE) 49 | endif 50 | endelse 51 | 52 | ;check if we have our key added to our hash 53 | flag = !initializedENVIextensions.hasKey(name) 54 | 55 | ;add if not present 56 | if ~flag then begin 57 | ;have to make variable - IDL parsing is invalid if we try to set directly 58 | ;not an issue though since we have an objref 59 | xt = !initializedENVIextensions 60 | xt[name] = !TRUE 61 | endif 62 | 63 | ;return the flag 64 | return, flag 65 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/awesomepixelsize.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Function that returns the pixel size of an ENVIRaster in meters. This 4 | ; function returns a 2 element array for X and Y pixel sizes. 5 | ; 6 | ; :Params: 7 | ; raster: in, required, type=ENVIRaster 8 | ; 9 | ; :Example: 10 | ; 11 | ; raster = e.openraster('C:\Program Files\Harris\ENVI54\data\qb_boulder_msi') 12 | ; pixSize = raster_pixel_size(raster) 13 | ; 14 | ; :Author: Zachary Norman 15 | ;- 16 | function awesomePixelSize, raster 17 | compile_opt idl2 18 | on_error, 2 19 | 20 | ;make sure ENVI has started 21 | e = envi(/current) 22 | if (e eq !NULL) then begin 23 | message, 'ENVI has not started yet, requried!' 24 | endif 25 | 26 | ;make sure we passed in a spatial reference 27 | if (raster eq !NULL) then begin 28 | message, '"raster" argument not set, required!' 29 | endif 30 | 31 | ;get raster center latitude for rasters, leaving code here for reference 32 | raster._Component->GetProperty, CENTER_LATITUDE = refLat 33 | 34 | ;get the raster pixel size 35 | pixelSize = raster.SpatialRef._Component.Pixel_Size 36 | 37 | ;get the units for the spatial reference 38 | units = raster.SpatialRef._Component.Units 39 | 40 | ;convert the pixel size to meters 41 | pixelSizeMeters = IDLcfProjUnitsConvertValue(pixelSize, units, IDLcfProjUnitsTranslate('Meters'), REFERENCE_LATITUDE = refLat) 42 | 43 | ;return values 44 | return, pixelSizeMeters 45 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/bak/awesomeenvirastergetdata.pro.bak: -------------------------------------------------------------------------------- 1 | function awesomeENVIRasterGetData, raster,$ 2 | BANDS = bands,$ 3 | COMPLEX_FUNCTION = complex_function,$ 4 | ERROR = error,$ 5 | INTERLEAVE = interleave,$ 6 | INTERPOLATION = interpolation,$ 7 | PIXEL_STATE = pixel_state,$ 8 | ROI = roi,$ 9 | SUB_RECT = sub_rect,$ 10 | XFACTOR = xfactor,$ 11 | YFACTOR = yfactor 12 | compile_opt idl2 13 | ; on_error, 2 14 | 15 | ;get current ENVI session (should always be defined if we are here) 16 | e = envi(/CURRENT) 17 | 18 | ;validate input 19 | if (raster eq !NULL) then begin 20 | message, 'raster not specified, required!' 21 | endif 22 | 23 | ;get the raster data 24 | data = raster.getData(BANDS = bands,$ 25 | COMPLEX_FUNCTION = complex_function,$ 26 | ERROR = error,$ 27 | INTERPOLATION = interpolation,$ 28 | PIXEL_STATE = pixel_state,$ 29 | ROI = roi,$ 30 | SUB_RECT = sub_rect,$ 31 | XFACTOR = xfactor,$ 32 | YFACTOR = yfactor) 33 | 34 | ;check if we specified interleave 35 | if keyword_set(interleave) AND ~keyword_set(roi) AND (raster.NBANDS gt 1) then begin 36 | useInterleave = strlowcase(strtrim(interleave)) 37 | 38 | ;check if we have a different interleave 39 | if (useInterleave ne raster.INTERLEAVE) then begin 40 | case (1) of 41 | (useInterleave eq 'bil') AND (raster.INTERLEAVE eq 'bsq'): newIdx = [0, 2, 1] 42 | (useInterleave eq 'bip') AND (raster.INTERLEAVE eq 'bsq'): newIdx = [2, 0, 1] 43 | (useInterleave eq 'bip') AND (raster.INTERLEAVE eq 'bil'): newIdx = [1, 0, 2] 44 | (useInterleave eq 'bsq') AND (raster.INTERLEAVE eq 'bil'): newIdx = [0, 2, 1] 45 | (useInterleave eq 'bsq') AND (raster.INTERLEAVE eq 'bip'): newIdx = [1, 2, 0] 46 | (useInterleave eq 'bil') AND (raster.INTERLEAVE eq 'bip'): newIdx = [1, 0, 2] 47 | else: newIdx = [0,1,2] 48 | endcase 49 | 50 | ;transpose the data 51 | data = transpose(data, newIdx) 52 | 53 | ;check if we have a pixel state to transpose as well 54 | if arg_present(pixel_state) then begin 55 | pixel_state = transpose(pixel_state, newIdx) 56 | endif 57 | endif 58 | endif 59 | 60 | return, data 61 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/enviraster_methods/enviraster__awesomeexport.pro: -------------------------------------------------------------------------------- 1 | ;+ 2 | ; :Description: 3 | ; Procedure method that, when used, will provide feedback to the 4 | ; user on the progress for exporting a raster to disk. As is, you 5 | ; have no indicator for how long it will take to export the raster 6 | ; to disk and is a challenge when dealing with larger rasters. 7 | ; 8 | ; :Params: 9 | ; output_raster_uri: in, required, type=string 10 | ; Specify the fully-qualified filepath to a location 11 | ; on disk where you want the source raster to be saved to. 12 | ; 13 | ; :Keywords: 14 | ; DATA_IGNORE_VALUE: in, optional, type=number 15 | ; Optionally specify a data ignore value for the output raster. 16 | ; If the input raster is a virtual raster then this should be 17 | ; speicified so that the information will persist. 18 | ; DEBUG: in, optional, type=boolean 19 | ; If set, errors will be stopped on. 20 | ; 21 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 22 | ;- 23 | pro ENVIRaster::AwesomeExport, output_raster_uri, DATA_IGNORE_VALUE = data_ignore_value, DEBUG = debug 24 | compile_opt idl2, hidden 25 | if ~keyword_set(debug) then on_error, 2 26 | 27 | ;hard coded parameters 28 | nPixMax = 2048*2048l ;max number of pixels to export 29 | 30 | ;validate inputs 31 | inputValidator, hash('output_raster_uri', ['required', 'string'], 'data_ignore_value', ['number']) 32 | 33 | ;make sure our output file does not exist already 34 | if file_test(output_raster_uri) then begin 35 | message, 'output_raster_uri specified, but file exists already!', LEVEL = -1 36 | endif 37 | 38 | ;check if we need metadata for our output raster 39 | if keyword_set(data_ignore_value) then begin 40 | meta = ENVIRasterMetadata() 41 | meta['data ignore value'] = data_ignore_value 42 | endif 43 | 44 | ;initialize our output raster 45 | outRaster = ENVIRaster(INHERITS_FROM = self, METADATA = meta, URI = output_raster_uri) 46 | 47 | ;get our tiles for iterating 48 | createAwesomeOptimizedTileIterator,$ 49 | PIXELS_PER_TILE = nPixMax,$ 50 | INPUT_RASTER = self,$ 51 | OUTPUT_SUB_RECTS = sub_rects 52 | 53 | ;get the number of tiles 54 | nTiles = n_elements(sub_rects) 55 | 56 | ;get how often to print 57 | nPrint = nTiles lt 100 ? 1 : ceil(float(nTiles)/100) 58 | 59 | ;catch errors 60 | catch, err 61 | if (err ne 0) then begin 62 | catch, /CANCEL 63 | prog.finish, /PRINT, /ERROR 64 | message, /REISSUE_LAST 65 | endif 66 | 67 | ;initialize our progress 68 | prog = awesomeENVIProgress('Awesome Export Raster', /PRINT) 69 | prog.setProgress, 'Processing...', 0, /PRINT 70 | 71 | ;loop over each tile 72 | foreach sub, sub_rects, i do begin 73 | ;get data from our raster 74 | dat = self.GetData(SUB_RECT = sub, PIXEL_STATE = ps) 75 | 76 | ;check if we have a data ignore value to preserve 77 | if keyword_set(data_ignore_value) then begin 78 | idxOff = where(ps, countOff) 79 | if (countOff gt 0) then dat[idxOff] = 0 80 | endif 81 | 82 | ;save data to disk 83 | outRaster.SetData, dat, SUB_RECT = sub 84 | 85 | ;check for abort 86 | prog.abortRequested 87 | 88 | ;update progress 89 | if ~(i mod nPrint) then prog.setProgress, 'Processing...', 100.0*(i+1.0)/nTiles, /PRINT, /TIME 90 | endforeach 91 | 92 | ;save the raster 93 | outRaster.save 94 | 95 | ;close progress 96 | prog.finish, /PRINT 97 | end -------------------------------------------------------------------------------- /idl_packages/github-se-awesomeenvitoolkit/src/enviraster_methods/enviraster__awesomeexport.spec.pro: -------------------------------------------------------------------------------- 1 | ;create a tester 2 | l = luna(CONFIG_FILE='./../../idl.test.json', /DEBUG) 3 | 4 | ; start envi headlessly 5 | e = envi(/HEADLESS) 6 | 7 | ; Open an input file 8 | File = Filepath('qb_boulder_msi', Subdir=['data'], $ 9 | Root_Dir=e.Root_Dir) 10 | Raster = e.OpenRaster(File) 11 | 12 | ;create a suite 13 | s = l.suite('Test suite that') 14 | 15 | ; create a test 16 | it = s.test('makes sure we can export a raster') 17 | 18 | ; add an expectation for our test 19 | (it.expects(raster)).toRunProcedureMethod, 'awesomeexport', e.GetTemporaryFilename(), DATA_IGNORE_VALUE = 0 20 | 21 | l.generateTestSummary 22 | end 23 | -------------------------------------------------------------------------------- /src/generate_cam_file.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; 9 | ; 10 | ; Procedure to generate a CAM formatted file for a directory of images. For the input directory, 11 | ; a random sample of all the images will be selected to check for consistent values for the 12 | ; focal length of the sensor. 13 | ; 14 | ; If the camera make and model are present, then the output CAM file will be named as 15 | ; model.cam and will either be found in the directory specified by `INPUTDIR` or, if `OUTPUTDIR` is 16 | ; set, then it will be found there. 17 | ; 18 | ; ### Example One 19 | ; 20 | ; This first example will search inputdir for files and the CAM file will be generated in outputdir: 21 | ; 22 | ; inputdir = 'C:\Users\username\images' 23 | ; outputdir = 'C:\Users\username\images_out' 24 | ; generate_cam_file, INPUTDIR = inputdir, OUTPUTDIR = outputdir 25 | ; 26 | ; 27 | ; ### Example Two 28 | ; 29 | ; This second example illustrates how to create a CAM file and have it be generated in inputdir: 30 | ; 31 | ; inputdir = 'C:\Users\username\images' 32 | ; generate_cam_file, INPUTDIR = inputdir 33 | ; 34 | ;- 35 | 36 | ;+ 37 | ; :Keywords: 38 | ; INPUTDIR: in, required, type=string 39 | ; Directory to be searched for images to get sensor information from. 40 | ; EXTENSION: in, optional, type=string, default='tif' 41 | ; File extension that will match the files found in `INPUTDIR`. The default value is 'tif' and 42 | ; should be specified if another file extension is used. 43 | ; OUTPUTDIR: in, optional, type=string, default=`INPUTDIR` 44 | ; Set this keyword to the location where you would like the output CAM file to be written to. 45 | ; If you set this keyword and the directory does not exist, then it will be created. 46 | ; 47 | ; :Tooltip: 48 | ; Generates a ".cam" formatted file from reading image metadata 49 | ; 50 | ; :Author: Zachary Norman 51 | ;- 52 | pro generate_cam_file, INPUTDIR = inputdir, EXTENSION = extension, OUTPUTDIR = outputdir 53 | compile_opt idl2, hidden 54 | 55 | if ~file_test(inputdir) then message, 'Input directory does not exist!' 56 | 57 | ;check we want out cam file to go 58 | if keyword_set(outputdir) then begin 59 | if ~file_test(outputdir) then file_mkdir, outputdir 60 | camfiledir = outputdir 61 | endif else begin 62 | camfiledir = inputdir 63 | endelse 64 | 65 | ;default file extension 66 | if ~keyword_set(extension) then extension = 'tif' 67 | 68 | cd, inputdir, CURRENT = first_dir 69 | files = file_search('*.' + extension, COUNT = nfiles) 70 | cd, first_dir 71 | if (nfiles eq 0) then message, 'No files found in "' + inputdir + '"' 72 | 73 | ;build full path 74 | files = inputdir + path_sep() + temporary(files) 75 | 76 | ;get sensor information, assume all the same 77 | oImageInfo = obj_new('image_info', files[0]) 78 | result = oImageInfo.Get(['FOCAL_LENGTH_MM', 'MODEL', 'SENSOR_PIXEL_SIZE_MM']) 79 | 80 | ;validate information 81 | if (result.focal_length_mm eq -!DPI) then begin 82 | print, ' No metadata to calculate focal length, unable to create CAM file, returning' 83 | return 84 | endif 85 | if (result.SENSOR_PIXEL_SIZE_MM eq -!DPI) then begin 86 | print, ' No metadata to calculate focal length, unable to create CAM file, returning' 87 | return 88 | endif 89 | 90 | ;initialize a list to hold the CAM file info 91 | cam_file = list() 92 | 93 | ;add focal length 94 | cam_file.add, '#Focal Length (mm)' 95 | cam_file.add, 'FOCAL ' + strtrim(result.focal_length_mm,2) + 'e+000' 96 | 97 | ;add the pixel size 98 | cam_file.add, '' 99 | cam_file.add, '#PixelSize ' + strtrim(result.SENSOR_PIXEL_SIZE_MM*1000d,2) + 'e-003' 100 | 101 | ;convert our list to an array 102 | cam_file = cam_file.toarray() 103 | 104 | ;check if we have sensor model for CAM file name 105 | if (result.MODEL ne !NULL) then begin 106 | outname = strlowcase(result.MODEL) + '.cam' 107 | endif else begin 108 | outname = 'camera_info.cam' 109 | endelse 110 | outfile = camfiledir + path_sep() + outname 111 | 112 | ;write to disk 113 | openw, lun, outfile, /GET_LUN 114 | printf, lun, cam_file, /IMPLIED_PRINT 115 | free_lun, lun 116 | 117 | ;alert user to location 118 | print, 'Camera file : ' + outfile 119 | print, '' 120 | end 121 | -------------------------------------------------------------------------------- /src/get_co_calibration.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; Internal routine used in the bandalignmenttask.pro file. Docs here may not be up to date. 11 | ; 12 | ; Generates .sav files containing three variables for each image: 13 | ; cocalibration_constant, reference_means, reference_stddevs 14 | ; 15 | ; If the first is present, then the images are scaled in value to account for differences 16 | ; in exposure and ISO speed. The second variable is used to convert the images to reflectance 17 | ; after the image registration has happened and it will be scaled from 0 to 10000. The last 18 | ; variable is not used, but passed in case used in the future. 19 | ; 20 | ; 21 | ; 22 | ;- 23 | 24 | 25 | ;+ 26 | ; 27 | ; :Keywords: 28 | ; REFERENCE_IES : in, optional, type=floatarr 29 | ; Set this keyword to the reference IE (ISO*exposure) values for the reflectance panels. 30 | ; REFERENCES_MEANS : in, optional, type=floatarr 31 | ; Set this keyword to the reference pixel values for the reflectance panels. If present, then 32 | ; the IE's generated for each image will contain the corresponding mean so that they can be converted to 33 | ; reflectance after the image registration process. 34 | ; REFERENCE_STDDEVS : in, optional, type=floatarr 35 | ; Set this keyword to the standard deviations of the pixel values for the reflectance panels. These 36 | ; values are not currently used, just passed in for future use if needed. 37 | ; 38 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 39 | ;- 40 | pro get_co_calibration, $ 41 | DEBUG = debug,$ 42 | GROUPS = groups,$ 43 | PANEL_INFO = panel_info 44 | compile_opt idl2, hidden 45 | if ~keyword_set(debug) then on_error, 2 46 | 47 | ;add some progress information 48 | prog = awesomeENVIProgress('Generating Calibration Information', /PRINT) 49 | 50 | ;extract information from our panel_info 51 | wasCalibrated = panel_info.hasKey('WAS_CALIBRATED') ? panel_info.WAS_CALIBRATED : 0 52 | 53 | ;determine what our progress tring should be 54 | if (wasCalibrated) then begin 55 | progStr = 'Calibrating ' + strtrim(n_elements(groups),2) + ' images...' 56 | endif else begin 57 | progStr = 'Calibrating ' + strtrim(n_elements(groups),2) + ' images to reference...' 58 | endelse 59 | 60 | ; alert user 61 | prog.setProgress, progStr, 0, /PRINT 62 | 63 | ;get info on how many files we need to process 64 | gNames = groups.keys() 65 | nPrint = floor(n_elements(gnames)/20) > 10 66 | nBands = n_elements(groups[gNames[0]]) 67 | 68 | ;extract information from our panel info 69 | reference_means = panel_info.hasKey('PANEL_MEANS') ? panel_info.PANEL_MEANS : fltarr(nBands) + 1 70 | reference_stddevs = panel_info.hasKey('PANEL_STDDEVS') ? panel_info.PANEL_STDDEVS : fltarr(nBands) 71 | 72 | ;check to see if we have a reference group or not 73 | ;if not pick the first image that we found 74 | if ~keyword_set(reference_group_name) then begin 75 | reference_group_name = gNames[0] 76 | endif 77 | 78 | ;generate relative reference intensity and exposure values if we have not been provided reference information 79 | if ~panel_info.hasKey('PANEL_IES') then begin 80 | ;get the reference exposure and IS) settings 81 | ;the ISO is the same for all images captured at the same time, but 82 | ;exposure seems to change from band to band 83 | ; ref_ie = reference iso and exposure 84 | ref_imgs = groups[reference_group_name] 85 | ref_ie = make_array(nbands, TYPE=5, /NOZERO) 86 | 87 | ;loop over each band 88 | for i=0, nbands-1 do begin 89 | ;extract information 90 | oImageinfo = obj_new('image_info', ref_imgs[i], /NO_SPATIALREF) 91 | res = oImageInfo.Get(['EXPOSURETIME', 'ISO']) 92 | 93 | ;check to make sure that we have information for this image 94 | if ((res.EXPOSURETIME eq -!DPI) OR (res.ISO eq -!DPI)) then begin 95 | return 96 | endif 97 | 98 | ;save value 99 | ref_ie[i] = res.EXPOSURETIME*res.ISO 100 | endfor 101 | endif else begin 102 | ref_ie = panel_info.PANEL_IES 103 | endelse 104 | 105 | ;get the reference ISO and exposure time so that we can correctly calibrate 106 | ;the images to a reference group 107 | 108 | ;calculate transformation function for each image 109 | gcount = 0 110 | foreach group_name, gNames do begin 111 | ;init hash to store data 112 | cocalibration = hash() 113 | 114 | ;get the image group 115 | images = groups[group_name] 116 | 117 | ;loop over each image 118 | for i=0, nBands-1 do begin 119 | if (wasCalibrated) then begin 120 | cocalibration_constant = 1.0 121 | endif else begin 122 | ;extract information from our images 123 | oImageinfo = obj_new('image_info', images[i], /NO_SPATIALREF) 124 | res = oImageInfo.Get(['EXPOSURETIME', 'ISO']) 125 | 126 | ;validate that we have the right metadata 127 | if ((res.EXPOSURETIME eq -!DPI) OR (res.ISO eq -!DPI)) then begin 128 | print, 'Found image without exposure and ISO information so we cannot complete for rest of scene, removing generated files and returning...' 129 | cd, inputdir, CURRENT = first_dir 130 | files = file_search('*.sav') 131 | if (files[0] ne '') then file_delete, files 132 | cd, first_dir 133 | return 134 | endif 135 | 136 | ;calcualte the cocalibration constant 137 | cocalibration_constant = ref_ie[i]/(res.EXPOSURETIME*res.ISO) 138 | endelse 139 | 140 | ;save to disk 141 | outfile = strmid(images[i], 0, strpos(images[i], '.', /REVERSE_SEARCH)) + '_co_calibration.sav' 142 | save, cocalibration_constant, reference_means, reference_stddevs, FILENAME = outfile 143 | endfor 144 | 145 | ;increment counter and check if we want to print to the screen 146 | gcount++ 147 | if ~(gcount mod nprint) then begin 148 | prog.setProgress, progStr, 100*float(gCount)/n_elements(gnames), /PRINT, /TIME 149 | endif 150 | 151 | ; check if someone cancelled 152 | if prog.abortRequested() then begin 153 | message, 'Process stopped by user', LEVEL = -1 154 | endif 155 | endforeach 156 | 157 | ;alert user 158 | prog.finish, /PRINT 159 | end 160 | -------------------------------------------------------------------------------- /src/image_groups_to_virtual_rasters.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; not currently used. 10 | ; 11 | ; :Description: 12 | ; Converts image groups to metaspectral (virtual) rasters. Needed for the updated 13 | ; routines that perform band-band registration. This makes the separate files 14 | ; on disk behave in the same manner as a single raster on disk. It simplifies the 15 | ; processing significantly. 16 | ; 17 | ; :Returns: 18 | ; 19 | ; An ENVIRaster object of the group that was passed in. 20 | ; 21 | ; :Params: 22 | ; image_groups: in, requried, type=hash/orderedhash 23 | ; Key-value pair of folder and group name with a value of all 24 | ; the fully-qualified filepaths for the images that belong to the 25 | ; image group. 26 | ; 27 | ; :Keywords: 28 | ; BANDORDER: in, optional, default=indgen(nbands) 29 | ; The optional order for the bands in the virtual raster group. If specified 30 | ; then the bands will be re-ordered based on the value of this array. For 31 | ; example, MicaSense RedEdge used BANDORDER = [0, 1, 2, 4, 3] 32 | ; GET_SPATIALREF: in, optional, type=boolean 33 | ; If specified, a spatial reference will be built from the metadata associated 34 | ; with the scenes, otherwise no spatialref will be used. 35 | ; SPATIALREF: in, optional, type=ENVIStandardrasterSpatialref 36 | ; If you know the spatial reference for your iamge group this can be passed 37 | ; in and will be applied to the metaspectral rasters that are generated. 38 | ; 39 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 40 | ;- 41 | function image_groups_to_virtual_rasters, image_groups,$ 42 | BANDORDER = bandorder,$ 43 | GET_SPATIALREF = get_spatialref,$ 44 | SPATIALREF = spatialref 45 | compile_opt idl2 46 | 47 | ;check for ENVI 48 | e = envi(/current) 49 | if (e eq !NULL) then begin 50 | e = envi(/headless) 51 | endif 52 | 53 | ;create an orderedhash 54 | rasterized_groups = orderedhash() 55 | 56 | ;get all the image groups that we are going to work with 57 | keys = image_groups.keys() 58 | 59 | nImages = n_elements(image_groups[keys[0]]) 60 | 61 | ;loop over each image group 62 | foreach key, keys, kIdx do begin 63 | ;check if we need to get our spatialref 64 | images = image_groups[key] 65 | 66 | if keyword_set(get_spatialref) AND ~keyword_set(spatialref) then begin 67 | oinfo = obj_new('image_info', images[0], /NO_PRINT) 68 | spatialref = oinfo.Get('SPATIALREF') 69 | endif 70 | 71 | ;preallocate an array to hold our rasters 72 | rasters = list() 73 | 74 | ;open each image as a raster 75 | for i=0,n_elements(images)-1 do begin 76 | case (1) of 77 | keyword_set(get_spatialref): rasters.add, e.openRaster(images[i], SPATIALREF_OVERRIDE = spatialref), /EXTRACT 78 | keyword_set(spatialref): rasters.add, e.openRaster(images[i], SPATIALREF_OVERRIDE = spatialref), /EXTRACT 79 | else: rasters.add, e.openRaster(images[i]), /EXTRACT 80 | endcase 81 | endfor 82 | 83 | ;check if we don't pass in the band order 84 | if (kIdx eq 0) then begin 85 | ;get the actual number of images 86 | nImages = n_elements(rasters) 87 | 88 | if ~keyword_set(bandorder) then begin 89 | bandorder = indgen(nImages) 90 | endif 91 | endif 92 | 93 | ;build our metaspectral raster 94 | case (1) of 95 | keyword_set(get_spatialref): metaraster = ENVIMetaspectralRaster(rasters[bandorder], SPATIALREF = spatialref) 96 | keyword_set(spatialref): metaraster = ENVIMetaspectralRaster(rasters[bandorder], SPATIALREF = spatialref) 97 | else: metaraster = ENVIMetaspectralRaster(rasters[bandorder], SPATIALREF = (rasters[0]).SPATIALREF) 98 | endcase 99 | 100 | ;save the stacked raster 101 | rasterized_groups[key] = metaraster 102 | endforeach 103 | 104 | ;return our ordered hash 105 | return, rasterized_groups 106 | end 107 | -------------------------------------------------------------------------------- /src/join_rededge_datadirs.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; Procedure to combine MicaSense data from multiple directories. Running this procedure will rename and copy 9 | ; images from the directories specified by `dirs` and place them in the first directory in `dirs`.The purpose of 10 | ; this procedure is to avoid the naming conflict that can be created by combining folders with 1000+ image groups. An image 11 | ; group for MicaSense data is this: IMG_000_1.tif, IMG_000_2.tif, IMG_000_3.tif, IMG_000_4.tif, IMG_000_5.tif. 12 | ; 13 | ; See the example below for more information regarding the renaming mechanism. 14 | ; 15 | ; If you want to reverse this procedure, see `unjoin_rededge_datadir`. 16 | ; 17 | ; ## Example 18 | ; 19 | ; Here is an example illustrating how to use this procedure: 20 | ; 21 | ; dirs = ['C:\path\to\some\images', 'C:\path\to\other\images'] 22 | ; join_rededge_datadirs, dirs 23 | ; 24 | ; In this case, all of the files in 'C:\path\to\other\images' will be copied into the folder 'C:\path\to\some\images' 25 | ; and renamed such that an image in 'C:\path\to\some\images' will go from "IMG_000_2.tif" to "IMG_001_1_2.tif" where the additional 26 | ; "1" comes from the directory index location in the dirs array. 27 | ;- 28 | 29 | 30 | ;+ 31 | ; 32 | ; 33 | ; :Params: 34 | ; dirs: in, required, type=stringarr 35 | ; This input in required and must be a string array. The first directory in the string array will be where renamed copies 36 | ; of the data in the other directories can be found. 37 | ; 38 | ; :Tooltip: 39 | ; Combines together multiple directories of MicaSense RedEdge data 40 | ; 41 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 42 | ; 43 | ;- 44 | pro join_rededge_datadirs, dirs 45 | compile_opt idl2, hidden 46 | 47 | ;do some error checking 48 | ;check to make sure that the directories exists already 49 | foreach dir, dirs do begin 50 | if ~file_test(dir) then message, 'Directory "' + dir + '" does not exist!' 51 | endforeach 52 | 53 | ;return if only one directory is passed in 54 | if (n_elements(dirs) lt 2) then begin 55 | return 56 | endif 57 | fileshash = orderedhash() 58 | 59 | ;search each directory for files ending with '*.tif' 60 | foreach dir, dirs do begin 61 | cd, dir, current = first_dir 62 | files = file_search('*.tif') 63 | fileshash[dir] = files 64 | cd, first_dir 65 | endforeach 66 | 67 | ;now check for possible naming conflicts before copying files 68 | basefiles = fileshash[dirs[0]] 69 | for i=1,n_elements(dirs)-1 do begin 70 | movefiles = fileshash[dirs[i]] 71 | for j=0, n_elements(movefiles)-1 do begin 72 | newname = strsplit(movefiles[j],'_', /EXTRACT) 73 | newname[1] += '_' + strtrim(i,2) 74 | newname = strjoin(newname,'_') 75 | file_copy, dirs[i] + path_sep() + movefiles[j], dirs[0] + path_sep() + newname, /OVERWRITE 76 | endfor 77 | endfor 78 | end 79 | 80 | 81 | ;cd, 'C:\Users\norm3596\Desktop\Greenhouse', current = first_dir 82 | ;dirs = file_search() 83 | ;cd, first_dir 84 | ; 85 | ;join_rededge_datadirs, 'C:\Users\norm3596\Desktop\Greenhouse' + path_sep() + dirs 86 | ; 87 | ;end 88 | -------------------------------------------------------------------------------- /src/unjoin_rededge_datadir.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; Procedure used to remove any copied files to the directory and reverse the procedure 9 | ; `join_rededge_datadirs`. This can be useful if you want to revert your folders to their original state after 10 | ; combining multiple directories. 11 | ; 12 | ; Note that files added to `dir` with `join_rededge_datadirs` will be permanently deleted and will not be 13 | ; found in your recycle bin. That is why `join_rededge_datadirs` copies original files and is meant to be 14 | ; used with this procedure. 15 | ; 16 | ; ## Example 17 | ; 18 | ; Here is how you can call this procedure:: 19 | ; 20 | ; dir = 'C:\Users\norm3596\Desktop\extra' 21 | ; unjoin_rededge_datadir, dir 22 | ; 23 | ;- 24 | 25 | ;+ 26 | ; :Params: 27 | ; dir: in, required, type=string 28 | ; This input is the fully qualified path to a directory that contains MicaSense data. An error will 29 | ; be thrown if the directory does not exist. 30 | ; 31 | ; :Tooltip: 32 | ; Removes files that were copies to join together RedEdge folders 33 | ; 34 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 35 | ;- 36 | pro unjoin_rededge_datadir, dir 37 | compile_opt idl2, hidden 38 | 39 | ;check to make sure that the directory exists already 40 | if ~file_test(dir) then message, 'Directory "' + dir + '" does not exist!' 41 | 42 | cd, dir, current = first_dir 43 | files = file_search('*.tif') 44 | cd, first_dir 45 | 46 | ;now check for possible naming conflicts before copying files 47 | for i=0,n_elements(files)-1 do begin 48 | splitname = strsplit(files[i],'_', /extract) 49 | if (n_elements(splitname) eq 4) then FILE_DELETE, dir + path_sep() + files[i] 50 | endfor 51 | end 52 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_calibration.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for generic band alignment. This uses 4 of the 5 RedEdge bands to test, 12 | ; so you must specify a folder of RedEdge data. 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; 18 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 19 | ;- 20 | pro uav_toolkit_test_calibration, flightDir 21 | compile_opt idl2 22 | on_error, 2 23 | 24 | ;start ENVI headlessly 25 | e = envi(/HEADLESS) 26 | 27 | ;this folder should be the one that contains the unzipped contents of the sample data. 28 | if (flightdir eq !NULL) then begin 29 | flightDir = dialog_pickfile(/DIRECTORY) 30 | endif 31 | 32 | ;check that flightDir was specified 33 | if ~keyword_set(flightDir) then begin 34 | message, 'flightDir not specified, required!' 35 | endif 36 | 37 | if ~file_test(flightDir, /DIRECTORY) then begin 38 | message, 'flightDir specified, but does not exist! 39 | endif 40 | 41 | ;get original path and reset 42 | pathOrig = !PATH 43 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 44 | 45 | ;update path to this parent directory which will mimick a "fresh" installation 46 | thisDir = file_dirname(routine_filepath()) 47 | parentDir = file_dirname(thisDir) 48 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 49 | pref_set, 'IDL_PATH', newPath, /COMMIT 50 | 51 | ;reset search path 52 | catch, err 53 | if (err ne 0) then begin 54 | catch, /CANCEL 55 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 56 | message, /REISSUE_LAST 57 | endif 58 | 59 | ;initialize the uav_toolkit 60 | uav_toolkit 61 | 62 | ;set up task 63 | alignTask = ENVITask('UAVBandAlignment') 64 | alignTask.SENSOR = 'rededge' 65 | alignTask.INPUTDIR = flightDir 66 | alignTask.APPLY_REFERENCE_TIEPOINTS = 1 67 | alignTask.MULTI_CHANNEL = 1 68 | alignTask.PANEL_REFLECTANCE = [67, 69, 68, 67, 61] ;must be 0 to 100, NOT 0 to 1.0 69 | alignTask.execute 70 | 71 | ;succeeded so reset our path back to the original 72 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 73 | end 74 | 75 | ;uav_toolkit_test_calibration, 'C:\Users\Traininglead\Downloads\rededgeTest\calibration-micasense\000' 76 | uav_toolkit_test_calibration, 'C:\Users\Traininglead\Downloads\rededgeTest\calibration\no_panels' 77 | ;uav_toolkit_test_calibration, 'C:\Users\Traininglead\Downloads\rededgeTest\with_panels' 78 | end 79 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_generic.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for generic band alignment. This uses 4 of the 5 RedEdge bands to test, 12 | ; so you must specify a folder of RedEdge data. 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; 18 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 19 | ;- 20 | pro uav_toolkit_test_generic, flightDir 21 | compile_opt idl2 22 | on_error, 2 23 | 24 | ;start ENVI headlessly 25 | e = envi(/HEADLESS) 26 | 27 | ;this folder should be the one that contains the unzipped contents of the sample data. 28 | if (flightdir eq !NULL) then begin 29 | flightDir = dialog_pickfile(/DIRECTORY) 30 | endif 31 | 32 | ;check that flightDir was specified 33 | if ~keyword_set(flightDir) then begin 34 | message, 'flightDir not specified, required!' 35 | endif 36 | 37 | if ~file_test(flightDir, /DIRECTORY) then begin 38 | message, 'flightDir specified, but does not exist! 39 | endif 40 | 41 | ;get original path and reset 42 | pathOrig = !PATH 43 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 44 | 45 | ;update path to this parent directory which will mimick a "fresh" installation 46 | thisDir = file_dirname(routine_filepath()) 47 | parentDir = file_dirname(thisDir) 48 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 49 | pref_set, 'IDL_PATH', newPath, /COMMIT 50 | 51 | ;reset search path 52 | catch, err 53 | if (err ne 0) then begin 54 | catch, /CANCEL 55 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 56 | message, /REISSUE_LAST 57 | endif 58 | 59 | ;initialize the uav_toolkit 60 | uav_toolkit 61 | 62 | ;set up task 63 | alignTask = ENVITask('UAVBandAlignment') 64 | alignTask.SENSOR = 'generic' 65 | alignTask.FILE_IDENTIFIERS = ['_1.tif', '_2.tif', '_3.tif', '_4.tif'] 66 | alignTask.INPUTDIR = flightDir 67 | alignTask.GENERATE_REFERENCE_TIEPOINTS = 1 68 | alignTask.APPLY_REFERENCE_TIEPOINTS = 1 69 | alignTask.CORRELATION_SEARCH_WINDOW = 40 70 | alignTask.execute 71 | 72 | ;succeeded so reset our path back to the original 73 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 74 | end 75 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_generic_multipage.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for generic band alignment. This uses 4 of the 5 RedEdge bands to test, 12 | ; so you must specify a folder of RedEdge data. 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; 18 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 19 | ;- 20 | pro uav_toolkit_test_generic_multipage, flightDir 21 | compile_opt idl2 22 | on_error, 2 23 | 24 | ;start ENVI headlessly 25 | e = envi(/HEADLESS) 26 | 27 | ;this folder should be the one that contains the unzipped contents of the sample data. 28 | if (flightdir eq !NULL) then begin 29 | flightDir = dialog_pickfile(/DIRECTORY) 30 | endif 31 | 32 | ;check that flightDir was specified 33 | if ~keyword_set(flightDir) then begin 34 | message, 'flightDir not specified, required!' 35 | endif 36 | 37 | if ~file_test(flightDir, /DIRECTORY) then begin 38 | message, 'flightDir specified, but does not exist! 39 | endif 40 | 41 | ;get original path and reset 42 | pathOrig = !PATH 43 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 44 | 45 | ;update path to this parent directory which will mimick a "fresh" installation 46 | thisDir = file_dirname(routine_filepath()) 47 | parentDir = file_dirname(thisDir) 48 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 49 | pref_set, 'IDL_PATH', newPath, /COMMIT 50 | 51 | ;reset search path 52 | catch, err 53 | if (err ne 0) then begin 54 | catch, /CANCEL 55 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 56 | message, /REISSUE_LAST 57 | endif 58 | 59 | ;initialize the uav_toolkit 60 | uav_toolkit 61 | 62 | ;set up task 63 | alignTask = ENVITask('UAVBandAlignment') 64 | alignTask.SENSOR = 'generic' 65 | alignTask.RIGOROUS_ALIGNMENT = !FALSE 66 | alignTask.FILE_IDENTIFIERS = ['.tif'] 67 | alignTask.INPUTDIR = flightDir 68 | alignTask.GENERATE_REFERENCE_TIEPOINTS = 1 69 | alignTask.APPLY_REFERENCE_TIEPOINTS = 1 70 | alignTask.execute 71 | 72 | ;succeeded so reset our path back to the original 73 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 74 | end 75 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_rededge.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for sequoia Rededge alignment. 12 | ; 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 18 | ;- 19 | pro uav_toolkit_test_rededge, flightdir 20 | compile_opt idl2 21 | on_error, 2 22 | 23 | ;start ENVI headlessly 24 | e = envi(/HEADLESS) 25 | 26 | ;this folder should be the one that contains the unzipped contents of the sample data. 27 | if (flightdir eq !NULL) then begin 28 | flightDir = dialog_pickfile(/DIRECTORY) 29 | endif 30 | 31 | ;check that flightDir was specified 32 | if ~keyword_set(flightDir) then begin 33 | message, 'flightDir not specified, required!' 34 | endif 35 | 36 | if ~file_test(flightDir, /DIRECTORY) then begin 37 | message, 'flightDir specified, but does not exist! 38 | endif 39 | 40 | ;get original path and reset 41 | pathOrig = !PATH 42 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 43 | 44 | ;update path to this parent directory which will mimick a "fresh" installation 45 | thisDir = file_dirname(routine_filepath()) 46 | parentDir = file_dirname(thisDir) 47 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 48 | pref_set, 'IDL_PATH', newPath, /COMMIT 49 | 50 | ;reset search path to original if error happened 51 | catch, err 52 | if (err ne 0) then begin 53 | catch, /CANCEL 54 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 55 | message, /REISSUE_LAST 56 | endif 57 | 58 | ;initialize the uav_toolkit 59 | uav_toolkit 60 | 61 | ;create our batch task 62 | rededgeTask = ENVITask('UAVBatchRedEdge') 63 | rededgeTask.FLIGHTDIR = flightdir 64 | rededgetask.PANEL_REFLECTANCE = [67, 69, 68, 67, 61] 65 | rededgeTask.execute 66 | 67 | ;succeeded so reset our path back to the original 68 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 69 | end 70 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_rededge_panel_extraction.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for extracting reflectance panels from RedEdge data. 12 | ; 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 18 | ;- 19 | pro uav_toolkit_test_rededge_panel_extraction, panelDir 20 | compile_opt idl2 21 | on_error, 2 22 | 23 | ;start ENVI headlessly 24 | e = envi(/HEADLESS) 25 | 26 | ;this folder should be the one that contains the unzipped contents of the sample data. 27 | if (panelDir eq !NULL) then begin 28 | panelDir = dialog_pickfile(/DIRECTORY) 29 | endif 30 | 31 | ;check that flightDir was specified 32 | if ~keyword_set(panelDir) then begin 33 | message, 'panelDir not specified, required!' 34 | endif 35 | 36 | if ~file_test(panelDir, /DIRECTORY) then begin 37 | message, 'panelDir specified, but does not exist! 38 | endif 39 | 40 | ;get original path and reset 41 | pathOrig = !PATH 42 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 43 | 44 | ;update path to this parent directory which will mimick a "fresh" installation 45 | thisDir = file_dirname(routine_filepath()) 46 | parentDir = file_dirname(thisDir) 47 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 48 | pref_set, 'IDL_PATH', newPath, /COMMIT 49 | 50 | ;reset search path 51 | catch, err 52 | if (err ne 0) then begin 53 | catch, /CANCEL 54 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 55 | message, /REISSUE_LAST 56 | endif 57 | 58 | ;initialize the uav_toolkit 59 | uav_toolkit 60 | 61 | ;search our folder for image groupd 62 | groups = bandalignment_get_image_groups(panelDir, ['_1.tif', '_2.tif', '_3.tif', '_5.tif', '_4.tif']) 63 | nGroups = n_elements(groups) 64 | 65 | ;skip directory if there are no TIF files 66 | if (nGroups eq 0) then begin 67 | message, 'No image groups found in panelDir, required for the test!' 68 | endif 69 | 70 | ;extract panels from each group 71 | foreach group, groups, i do begin 72 | ;attempt to extract the reflectance panels 73 | panel_info = get_reflectance_panels(group, 'rededge',$ 74 | PANEL_REFLECTANCE = 70) 75 | print, 'Processed image group ' + strtrim(i + 1,2) + ' of ' + strtrim(nGroups,2) 76 | endforeach 77 | 78 | ;succeeded so reset our path back to the original 79 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 80 | end 81 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_rededge_radiance.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for generic band alignment. This uses 4 of the 5 RedEdge bands to test, 12 | ; so you must specify a folder of RedEdge data. 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; 18 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 19 | ;- 20 | pro uav_toolkit_test_rededge_radiance, file 21 | compile_opt idl2 22 | ; on_error, 2 23 | ireset, /NO_PROMPT 24 | 25 | ;start ENVI headlessly 26 | e = envi(/HEADLESS) 27 | 28 | ;check that flightDir was specified 29 | if ~keyword_set(file) then begin 30 | message, 'file not specified, required!' 31 | endif 32 | 33 | if ~file_test(file) then begin 34 | message, 'file specified, but does not exist! 35 | endif 36 | 37 | ;get original path and reset 38 | pathOrig = !PATH 39 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 40 | 41 | ;update path to this parent directory which will mimick a "fresh" installation 42 | thisDir = file_dirname(routine_filepath()) 43 | parentDir = file_dirname(thisDir) 44 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 45 | pref_set, 'IDL_PATH', newPath, /COMMIT 46 | path_cache, /REBUILD 47 | 48 | ;reset search path 49 | catch, err 50 | if (err ne 0) then begin 51 | catch, /CANCEL 52 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 53 | message, /REISSUE_LAST 54 | endif 55 | 56 | ;initialize the uav_toolkit 57 | uav_toolkit 58 | 59 | ;resolve our necessary routine which has a sub routine that we use for finding the panel 60 | resolve_routine, 'get_reflectance_panels', /EITHER, /COMPILE_FULL_FILE 61 | 62 | ;convert the data to radiance 63 | rad = rededge_to_radiance(file, /VERBOSE) 64 | 65 | ;define the image subset for the panel pixels 66 | sub = [660, 490, 840, 670] 67 | 68 | ;define the percent reflectance that our data is 69 | panelReflectance = 0.61 70 | 71 | ;extract panel data 72 | panelDat = rad[sub[0]:sub[2], sub[1]:sub[3]] 73 | 74 | ;make image to show the panel 75 | panelDisplay = 0*rad 76 | panelDisplay[sub[0]:sub[2], sub[1]:sub[3]] = panelDat 77 | 78 | ;convert our panel to reflectance 79 | radianceToReflectance = panelReflectance / panelDat.mean() 80 | refl = rad*(panelReflectance/panelDat.mean()) 81 | panelRefl = panelDat*radianceToReflectance 82 | 83 | ;auomatically extract panel pixels 84 | panel_pixels = get_refelctance_panels_extract_panel(rad) 85 | if (panel_pixels eq !NULL) then begin 86 | message, 'No reflectance panel pixels found in the scene, required!' 87 | endif 88 | 89 | ;create separate variable for displaying the panel information 90 | AutoPanelDisplay = 0*rad 91 | AutoPanelDisplay[panel_pixels] = rad[panel_pixels] 92 | 93 | ;print information for the panel 94 | print 95 | print, 'Min radiance : ', strtrim(panelDat.min(),2) 96 | print, 'Max radiance : ', strtrim(panelDat.max(),2) 97 | print, 'Mean radiance : ', strtrim(panelDat.mean(),2) 98 | print, 'Radiance std. dev. : ', strtrim(stddev(panelDat),2) 99 | print 100 | print, 'Radiance to refl. : ', strtrim(radianceToReflectance, 2) 101 | print 102 | print, 'Min reflectance : ', strtrim(panelRefl.min(),2) 103 | print, 'Max reflectance : ', strtrim(panelRefl.max(),2) 104 | print, 'Mean reflectance : ', strtrim(panelRefl.mean(),2) 105 | print, 'Reflectance std. dev. : ', strtrim(stddev(panelRefl),2) 106 | 107 | ;create window for displaying graphics 108 | w = window(DIMENSIONS = [1200, 900]) 109 | im1 = image(rad, LAYOUT = [2,2,1], CURRENT = w, TITLE = 'Radiance Image', /ORDER) 110 | im2 = image(panelDisplay, LAYOUT = [2,2,2], CURRENT = w, TITLE = 'Reflectance Panel Pixels (manual)', /ORDER) 111 | im3 = image(smooth(panelRefl, 5, /EDGE_MIRROR), LAYOUT = [2,2,3], CURRENT = w, TITLE = 'Reflectance Panel as % reflectance', /ORDER) 112 | im2 = image(AutoPanelDisplay, LAYOUT = [2,2,4], CURRENT = w, TITLE = 'Reflectance Panel Pixels (automated)', /ORDER) 113 | 114 | ;succeeded so reset our path back to the original 115 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 116 | end 117 | 118 | 119 | uav_toolkit_test_rededge_radiance, 'C:\Users\Traininglead\Downloads\rededgeTest\calibration-micasense\000\reflectance_panels\IMG_0000_4.tif' 120 | end 121 | -------------------------------------------------------------------------------- /tests/uav_toolkit_test_sequoia.spec.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | ;+ 8 | ; :Private: 9 | ; 10 | ; :Description: 11 | ; Test for sequoia band alingment. 12 | ; 13 | ; 14 | ; 15 | ; 16 | ; 17 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 18 | ;- 19 | pro uav_toolkit_test_sequoia, flightDir 20 | compile_opt idl2 21 | on_error, 2 22 | 23 | ;start ENVI headlessly 24 | e = envi(/HEADLESS) 25 | 26 | ;this folder should be the one that contains the unzipped contents of the sample data. 27 | if (flightdir eq !NULL) then begin 28 | flightDir = dialog_pickfile(/DIRECTORY) 29 | endif 30 | 31 | ;check that flightDir was specified 32 | if ~keyword_set(flightDir) then begin 33 | message, 'flightDir not specified, required!' 34 | endif 35 | 36 | if ~file_test(flightDir, /DIRECTORY) then begin 37 | message, 'flightDir specified, but does not exist! 38 | endif 39 | 40 | ;get original path and reset 41 | pathOrig = !PATH 42 | pref_set, 'IDL_PATH', /DEFAULT, /COMMIT 43 | 44 | ;update path to this parent directory which will mimick a "fresh" installation 45 | thisDir = file_dirname(routine_filepath()) 46 | parentDir = file_dirname(thisDir) 47 | newPath = !PATH + path_sep(/SEARCH_PATH) + '+' + parentDir 48 | pref_set, 'IDL_PATH', newPath, /COMMIT 49 | 50 | ;reset search path 51 | catch, err 52 | if (err ne 0) then begin 53 | catch, /CANCEL 54 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 55 | message, /REISSUE_LAST 56 | endif 57 | 58 | ;initialize the uav_toolkit 59 | uav_toolkit 60 | 61 | ;set up task 62 | alignTask = ENVITask('UAVBandAlignment') 63 | alignTask.SENSOR = 'sequoia' 64 | alignTask.INPUTDIR = flightDir 65 | alignTask.GENERATE_REFERENCE_TIEPOINTS = 0 66 | alignTask.APPLY_REFERENCE_TIEPOINTS = 1 67 | alignTask.execute 68 | 69 | ;succeeded so reset our path back to the original 70 | pref_set, 'IDL_PATH', pathOrig, /COMMIT 71 | end 72 | -------------------------------------------------------------------------------- /uav_toolkit.pro: -------------------------------------------------------------------------------- 1 | ;h+ 2 | ; Copyright (c) 2019 Harris Geospatial Solutions, Inc. 3 | ; 4 | ; Licensed under MIT. See LICENSE.txt for additional details and information. 5 | ;h- 6 | 7 | 8 | ;+ 9 | ; :Private: 10 | ; 11 | ; :Description: 12 | ; Procedure that adds the buttons to ENVI's toolbox. 13 | ; 14 | ; 15 | ; 16 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 17 | ;- 18 | pro uav_toolkit_extensions_init 19 | compile_opt idl2, hidden 20 | e = envi(/CURRENT) 21 | uav_toolkit 22 | e.AddExtension, 'Band Alignment (Basic)', 'uav_toolkit_extension',$ 23 | UVALUE = 'UAVBandAlignmentBasic', PATH='/UAV Toolkit/' 24 | e.AddExtension, 'Band Alignment (Advanced)', 'uav_toolkit_extension',$ 25 | UVALUE = 'UAVBandAlignment', PATH='/UAV Toolkit/' 26 | e.AddExtension, 'Batch RedEdge', 'uav_toolkit_extension',$ 27 | UVALUE = 'UAVBatchRedEdge', PATH='/UAV Toolkit/' 28 | end 29 | 30 | ;+ 31 | ; :Private: 32 | ; 33 | ; :Description: 34 | ; Displays the task UI for the button we click. 35 | ; 36 | ; :Params: 37 | ; event: in, required, type=structure 38 | ; The event that is passed on when a user clicks 39 | ; on the button in the ENVI toolbox. 40 | ; 41 | ; 42 | ; 43 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 44 | ;- 45 | pro uav_toolkit_extension, event 46 | compile_opt idl2, hidden 47 | 48 | ;error catching 49 | catch, err 50 | if (err ne 0) then begin 51 | catch, /CANCEL 52 | message, /RESET 53 | void = dialog_message('An error occurred while running task:' + $ 54 | !ERROR_STATE.msg, /ERROR) 55 | return 56 | endif 57 | 58 | ;get current session of ENVI 59 | e = awesomeGetENVI(/UI) 60 | 61 | ;return if called as a procedure instead of a button click 62 | if (event eq !NULL) then begin 63 | return 64 | endif 65 | 66 | ;get the name of the task that we want to run 67 | widget_control, event.ID, GET_UVALUE = taskName 68 | 69 | ;create the UI 70 | awesomeSelectTaskParameters, taskName 71 | end 72 | 73 | ;+ 74 | ; The purpose of this procedure is to to used to restore the UAV Toolkit as a .sav file 75 | ; into IDL's session and add to let ENVI know about the tasks that are a part of the 76 | ; UAV Toolkit (even if we are just using PRO code). 77 | ; 78 | ; It also allows for a simple interface to open the help contents regarding the UAV Toolkit. 79 | ; 80 | ; 81 | ; ### Example One 82 | ; 83 | ; Instead of needing to restore the UAV Toolkit .sav file every time in your code, you can just add 84 | ; the source to IDL's search path and do the following in your IDL code: 85 | ; 86 | ; uav_toolkit 87 | ; 88 | ; ### Example Two 89 | ; 90 | ; To open the help using this routine, just do the following: 91 | ; 92 | ; uav_toolkit, /HELP 93 | ; 94 | ; This functionality is tested on windows. 95 | ; 96 | ; 97 | ; :Keywords: 98 | ; HELP: in, optional, type=boolean, default=false 99 | ; Set this keyword to open up the help contents for the UAV Toolkit. Note that if you move the .sav file 100 | ; then IDL may not be able to open up the help contents. 101 | ; 102 | ; :Tooltip: 103 | ; Initializes the UAV Toolkit for use with ENVI 104 | ; 105 | ; :Author: Zachary Norman - GitHub: [znorman-harris](https://github.com/znorman-harris) 106 | ;- 107 | pro uav_toolkit, HELP = help 108 | compile_opt idl2, hidden 109 | on_error, 2 110 | 111 | ;get current directory 112 | thisfilename = routine_filepath() 113 | thisdir = file_dirname(thisfilename) 114 | 115 | ;load help and return 116 | if keyword_set(help) then begin 117 | docsFile = thisdir + path_sep() + 'docs' + path_sep() + 'index.html' 118 | 119 | ;check if the file exists 120 | if file_test(docsFile) then begin 121 | spawn, 'start "" "'+ docsFile + '"', stdout, stderr, /NOWAIT, /HIDE 122 | endif else begin 123 | message, 'Docs files not found next to uav_toolkit source!' 124 | endelse 125 | return 126 | endif 127 | 128 | ;get the current session of ENVI 129 | e = awesomeGetENVI() 130 | 131 | ;if we aren't looking for help, then let's find all of the task files 132 | taskFiles = file_search(thisdir, '*.task') 133 | foreach taskFile, taskFiles do begin 134 | ;skip any errors 135 | catch, err 136 | if (err ne 0) then begin 137 | help, /LAST_MESSAGE, OUTPUT = o 138 | print, o 139 | catch, /CANCEL 140 | continue 141 | endif 142 | 143 | ;try to create a task so ENVI knows about the tools 144 | task = ENVITask(taskFile) 145 | endforeach 146 | catch, /CANCEL 147 | end 148 | --------------------------------------------------------------------------------