7

Dec

2016

Maintaining Backward Compatibility in IDL 8.6 - Keep Calm And Read Your Release Notes

Author: Jim Pendleton

One of the "benefits" of being a Harris Geospatial Solutions insider is access to pre-release candidates of the commercial products developed by our engineering group.

We in Custom Software Solutions are sometimes the canaries in the coal mine, learning on occasion that an "undocumented feature" that we had used benignly or even to our advantage has been removed from the language in a newer release. Generally, these changes are justifiable. 

Where to Find Release Notes

Sometimes it's not enough to look at the documentation center's What's New help page to learn of all these changes. An additional source of information, generally prepared after the "What's New" documentation has gone to press, is located in a place other than your IDL or ENVI installation's documentation subdirectory.

This file includes information about supported platforms and potential backward-compatibility issues.

If you have received a Harris product installation DVD, check the info subdirectory on the DVD itself for the release notes files.

If your installation was downloaded from the Harris Geospatial Solutions Download and License Center, you or your site's designated license administrator will need to retrieve the release notes from a link that is separate from the product installer. 

Downloading Release Notes

After logging in, select the "Harris" link near the bottom of the web page, under "Browse My Software and Documentation".

Select the "IDL" link, that pops up in a new list in the "Product Lines" column.

Select the "IDL" link in the "Current Releases" tab.

On the "Product Download" page, select the appropriate item for the Release Notes document.

An Example Backward Compatibility Note

Recently, I discovered some of my routines were using an admittedly illegal syntax involving the "_REF_EXTRA" keyword passing mechanism.  The code wasn't so much illegal, as it was ignored. And one could argue the compiler should have complained about it from the time that _REF_EXTRA was added to the language. For example,

function MyRoutine, _REF_EXTRA=extra

    MySubroutine, _REF_EXTRA=extra

end

Can you spot the problem?

The _REF_EXTRA keyword is only intended to appear in the declaration of a function or procedure.Within the body of the code, you should always use the _EXTRA keyword when making calls to other routines.

In the form shown above, the code is basically ignored at execution time.  It serves no useful purpose.

Up until IDL 8.6, illegal use of the _REF_EXTRA syntax would simply be skipped by the compiler and interpreter. In the example above, MySubroutine would be called without any keywords, regardless of what was passed to MyRoutine.

In IDL 8.6, the compiler has been beefed up to complain about the invalid syntax. If you have code that fits this pattern your code will not compile. You may want to simply remove the flagged code because it has never been operational. Or you may want to change the syntax.

function MyRoutine, _REF_EXTRA=extra

    MySubroutine, _EXTRA=extra

end

Carefully consider the implications of changing the keyword, however. Modifying the syntax will also alter the behavior. You may end up modifying keywords on output that you hadn't intended to change!


Comments (0) Number of views (351) Article rating: No rating

Categories: IDL Blog | IDL Data Point

Tags:

1

Dec

2016

Optimizing Max Kernel Operation in IDL

Author: Atle Borsholm

I found this optimization question on the comp.lang.idl-pvwave, and decided to give it a try. The question was to implement an algorithm that replaces every element in a 2-D array with its neighborhood maximum value. In this case the neighborhood size was 101x101 (i.e. -50 to +50), and the array size was 3200x3248. The nested FOR loop approach looks like the following code snippet.

  data = randomu(seed,3200,3248)
  dim = size(data,/dimension)
  nx = dim[0]
  ny = dim[1]
 
  t0 = tic('Nested FOR')
  result2 = data
  for i=0,nx-1 do begin
    for j=0,ny-1 do begin
      result2[i,j] = max(data[(i-50)>0:(i+50)<(nx-1),(j-50)>0:(j+50)<(ny-1)])
    endfor
  endfor
  toc,t0
 

My first thought was to use the > operator which returns the maximum of 2 arguments. It operates on arrays, and in conjunction with the SHIFT function it serves to return the larger of 2 neighbors. The other trick here is that since we are looking for a 101x101 neighborhood maximum, we can use a combination of smaller neighborhood maxima as input in a structured way in order to achieve the exact 101x101 neighborhood size. The code that I ended up with after some trial and error was the following.

  t0 = tic('Iterative >')
  ; Using SHIFT and > in an iterative way
  padded = replicate(min(data),size(data,/dimension)+100)
  padded[50,50] = data
  tmp3 = shift(padded,1,0) > padded > shift(padded,-1,0)
  tmp9 = shift(tmp3,3,0) > tmp3 > shift(tmp3,-3,0)
  tmp27 = shift(tmp9,9,0) > tmp9 > shift(tmp9,-9,0)
  tmp81 = shift(tmp27,27,0) > tmp27 > shift(temporary(tmp27),-27,0)
  tmp99 = shift(tmp9,44,0) > temporary(tmp81) > shift(temporary(tmp9),-44,0)
  tmp101 = shift(tmp3,49,0) > temporary(tmp99) > shift(temporary(tmp3),-49,0)
 
  ; Same for Y-dim
  tmp3 = shift(tmp101,0,1) > tmp101 > shift(temporary(tmp101),0,-1)
  tmp9 = shift(tmp3,0,3) > tmp3 > shift(tmp3,0,-3)
  tmp27 = shift(tmp9,0,9) > tmp9 > shift(tmp9,0,-9)
  tmp81 = shift(tmp27,0,27) > tmp27 > shift(temporary(tmp27),0,-27)
  tmp99 = shift(tmp9,0,44) > temporary(tmp81) > shift(temporary(tmp9),0,-44)
  tmp101 = shift(tmp3,0,49) > temporary(tmp99) > shift(temporary(tmp3),0,-49)
 
  result1 = (temporary(tmp101))[50:50+nx-1,50:50+ny-1]
  toc,t0
 

I didn’t say that optimized code always looks pretty, but the goal here is to run fast. Adding in some result comparison checking to make sure the results are equivalent.

 
  print, array_equal(result1,result2) ? 'Results are matching' : 'SOMETHING went wrong'
 

Finally, here are the results, which yielded an impressive amount of speed-up, the execution time went from 189.4 seconds to 1.4 seconds, and the results are identical:

  % Time elapsed Nested FOR: 189.39470 seconds.
  % Time elapsed Iterative >: 1.4241931 seconds.
  Results are matching
 
 
 

Comments (0) Number of views (255) Article rating: No rating

Categories: IDL Blog | IDL Data Point

Tags:

17

Nov

2016

New in ENVI 5.4: ENVITask Returning a Virtual Raster

Author: Brian Griglak

Since the creation of custom ENVITasks in ENVI 5.2 SP1, there has been the requirement that your procedure must commit all output objects to disk.  There was the rule that the procedure wrapped by the task must have an input keyword telling it the filename to use to write the object to.  In the task template, you would have an output parameter that mapped to that keyword, and then during task loading the framework would magically create an input parameter for you, mapped to the same keyword with TYPE set to “ENVIURI”. 

The automatic creation of the input parameter and the internal correlation of the two parameters were done with the best of intentions, to simplify the process of creating custom tasks.  Alas the user feedback on this feature wasn’t always as rosy as we hoped.

So in ENVI 5.4 we’re shaking things up and giving you the task developer more control.  If you still use the “version” property in the task template, and have it set to “5.2.1”, “5.3”, or “5.3.1”, then you’ll get the old behavior.  But if you switch to using “schema” set to the value “envitask_3.0”, then a new set of rules apply to the procedure, and what you can do inside it.  In the new paradigm, your procedure will have separate keywords for the input filename and output object reference.  If you like, you can skip the filename keyword completely and return an object that hasn’t been tethered to disk at all.  This makes life much easier for types like ENVIGCPSet and ENVITiePointSet, but also allows for a procedure that constructs a complex virtual raster chain based on the other input parameters.

You might be asking why you would want a task to return a virtual raster.  With the new Classification Framework that is part of ENVI 5.4, you need to make sure that you prepare the data you want to run through a trained classifier the same way you prepared your training data.  One way to do this is to create a task that returned the preprocessed data in a consistent way.  If you can get away with not having to save that preprocessed attribute raster to disk, why not take advantage of the time and space advantages of using a virtual raster.

The following example is a modified version of the new Custom Task Tutorial in the ENVI 5.4 release.  That task wraps a procedure that uses a number of other tasks internally to perform all the preprocessing.  Here I’ve modified it to use virtual rasters and other ENVI API function calls to avoid ever writing a file to disk.

The code goes through a number of steps, which I will describe after showing it:

pro SentinelVegIndices_blog, INPUT_RASTER_10M=raster10m, $
                             INPUT_RASTER_20M=raster20m, $
                             OUTPUT_RASTER=outputRaster
  compile_opt idl2, hidden
 
  ; Get the spatial reference of the 10-meter raster
  spatialRef = raster10m.SPATIALREF
  coordSys = ENVICoordSys(COORD_SYS_CODE=spatialRef.COORD_SYS_CODE)
 
  ; Create a spatial grid definition
  grid = ENVIGridDefinition(coordSys, $
                            PIXEL_SIZE=spatialRef.PIXEL_SIZE, $
                            NCOLUMNS = raster10m.NCOLUMNS, $
                            NROWS = raster10m.NROWS, $
                            TIE_POINT_MAP=spatialRef.TIE_POINT_MAP, $
                            TIE_POINT_PIXEL = spatialRef.TIE_POINT_PIXEL)
 
  ; Regrid the 20-meter bands to 10 meters
  regriddedRaster = ENVISpatialGridRaster(raster20m, GRID_DEFINITION=grid)
 
  ; Create a layer stack
  layerStack = ENVIMetaspectralRaster([raster10m, regriddedRaster], $
                                      SPATIALREF=spatialRef)
 
  ; Compute image statistics
  stats = ENVIRasterStatistics(layerStack)
 
  ; Perform dark subtraction as an alternative to atmospheric correction
  bandRasters = ObjArr(layerStack.nBands)
  for i = 1, layerStack.nBands do begin
    expression = 'b' + i.ToString() + ' - ' + stats.Min[i-1].ToString()
    bandRasters[i-1] = ENVIPixelwiseBandMathRaster(layerStack, expression)
  endfor
  bandStackRaster = ENVIMetaspectralRaster(bandRasters, $
                                           SPATIALREF=spatialRef)
 
  ; we need to put the wavelengths back into the band stack,
  ; they were removed by the band math
  metadata = ENVIRasterMetadata()
  metadata['wavelength'] = layerStack.Metadata['wavelength']
  metadata['wavelength units'] = layerStack.Metadata['wavelength units']
  correctedRaster = ENVIMetadataOverrideRaster(bandStackRaster, $
                                               METADATA_OVERRIDE=metadata)
 
  ; Scale pixel values from 0 to 1
  gains = MAKE_ARRAY(layerStack.NBANDS, /FLOAT, VALUE=0.0001)
  offsets = FLTARR(layerStack.NBANDS)
  scaledRaster = ENVIGainOffsetRaster(correctedRaster, gains, offsets)
 
 
  ; Create vegetation indices
  indices = [ 'Enhanced Vegetation Index', $
              'Global Environmental Monitoring Index', $
              'Leaf Area Index', $
              'Plant Senescence Reflectance Index', $
              'Red Edge Normalized Difference Vegetation Index' ]
  outputRaster = ENVISpectralIndexRaster(scaledRaster, indices)
end

The first step is to upsample the 20m raster to 10m pixels, which in the tutorial is performed using the ENVIRegridRasterTask.  This can be done with the ENVISpatialGridRaster virtual raster, once we have constructed an ENVIGridDefinition with the appropriate mixing of properties form the 10m and 20m rasters.

Next the tutorial uses the ENVIBuildBandStackTask to build a metaspectral stack of all the 10m bands.  Here we use the ENVIMetaspectralRaster virtual raster, though we have to pass in the original 10m raster’s spatial reference to keep this raster registered on the map.

Next is the dark subtraction.  The tutorial uses the ENVIRasterStatisticsTask, but here we just use the API function ENVIRasterStatistics() to accomplish the same thing.

The band minima values are used to perform dark object subtraction.  The tutorial uses the ENVIDarkSubtractionCorrectionTask, which handles this as a single raster.  Here I had to build separate band math equations for each band, as the ENVIPixelwiseBandMathRaster virtual raster always returns a single band output, so I have to build an array of band math expressions and then metaspectrally stack the results, again passing in the spatial reference.

One pitfall of the band math is that it removes most of the spectral metadata, so I have to put the wavelength metadata back into the raster so the spectral index calculations select the correct bands.  I do this with ENVIMetadataOverrideRaster(), using a copy of the original metaspectral raster’s metadata values.

The raster is then scaled down by a factor of 10000.0 with ENVIGainOffsetRaster, to simulate the atmospheric correction better and produce spectral index values that are more accurate.  Lastly, the scaled raster is passed into ENVISpectralIndexRaster to calculate 5 different spectral indices.

Once this has been written, we can use an almost identical .task file to wrap the procedure.  The main difference is that we no longer need to specify the OUTPUT_URI parameter, and I had a slightly different procedure name.  This task’s output raster is never commited to disk, but it can be used as input to another task by calling ENVIRaster::Dehydrate() on it, which yields a 25KB JSON representation.

Here is the updated .task file:

{
  "name": "SentinelVegIndices_blog",
  "base_class": "ENVITaskFromProcedure",
  "routine": "SentinelVegIndices_blog",
  "display_name": "Compute Sentinel-2A Vegetation Indices",
  "description": "This task regrids the visible and near-infrared bands of a Sentinel-2A Level-1C 20-meter image to 10 meters. It creates a layer stack of all bands. It applies dark-object subtraction as a simple alternative to atmospheric correction. It scales the reflectance pixel values from 0 to 1, then computes a select group of vegetation indices.",
  "schema": "envitask_3.0",
  "parameters": [
    {
    "name": "INPUT_RASTER_10M",
    "display_name": "Select a 10-meter image",
    "type": "ENVIRASTER",
    "direction": "input",
    "required": true,
    "description": "Select a Sentinel Level-1C 10-meter image."
    },
    {
    "name": "INPUT_RASTER_20M",
    "display_name": "Select a 20-meter image",
    "type": "ENVIRASTER",
    "direction": "input",
    "required": true,
    "description": "Select a Sentinel Level-1C 20-meter image."
    },
    {
    "name": "OUTPUT_RASTER",
    "display_name": "Output image",
    "type": "ENVIRASTER",
    "direction": "output",
    "required": true,
    "description": "This is a reference to the output raster."
    }
  ]
}

Comments (0) Number of views (497) Article rating: No rating

Categories: IDL Blog | IDL Data Point

Tags:

28

Oct

2016

ENVI: Adding custom polygons to the display

Author: Daryl Atencio

A recent project I worked on required custom polygons – Controlled by my application – to be added to the ENVI display.  The following code defines an object class that allows the user to place a polygon in the ENVI display using window coordinates.  To run the application:

  1. Save the code that follows to a file named envi_polygon_example.pro
  2. Open and compile the file in the IDLDE
  3. Execute the following at the IDL command prompt

  4. envi_polygon_example

;------------------------------------------------------------------------------

;+

; Lifecycle method for destroying the object

;-

pro my_polygon::Cleanup

  compile_opt idl2, logical_predicate

  self->Destruct

end

 

;------------------------------------------------------------------------------

;+

; Cleans up the member variables of the object class

;-

pro my_polygon::Destruct

  compile_opt idl2, logical_predicate

  self->IDLmiManipGraphicOverlay::Cleanup

  self->IDLmiManipLayer::Cleanup

  self->IDLgrPolygon::Cleanup

end

 

;------------------------------------------------------------------------------

;+

; Lifecycle method for initializing the class

;

; :Returns:

;   1 if the object initializes successfully and 0 otherwise

;  

; :Params:

;   xy: in, optional, type="int"

;     a [2,n] array containing the [x,y] points in window coordinates

;  

; :Keywords:

;   POLYGONS: in, optional, type="int"

;     An integer array of one or more polygon descriptions.  See IDL help for

;     IDLgrPolygon for more information

;   _REF_EXTRA: Used to set properties of the inherited IDLgrPolygon

;-

function my_polygon::Init, xy, $

  POLYGONS=poly, $

  _REF_EXTRA=refExtra

  compile_opt idl2, logical_predicate

  void = self->IDLmiManipGraphicOverlay::Init(_EXTRA=refExtra)

  if ~void then begin

    return, 0

  endif

  self->InitializeDataspace

  self->SetupManipulatorGraphics

  self->InitializeGraphics

  if n_elements(xy) then begin

    self->SetProperty, DATA=xy, POLYGONS=poly

  endif

  if n_elements(refExtra) then begin

    self->SetProperty, _EXTRA=refExtra

  endif

  return, 1

end

 

;------------------------------------------------------------------------------

;+

; This method initializes the data space used by the graphics layer

;-

pro my_polygon::InitializeDataspace

  compile_opt idl2, logical_predicate

  e = envi(/CURRENT)

  eView = e->GetView()

  eView->GetProperty, _COMPONENT=ecfViewGroup

  oDS = ecfViewGroup->GetDescendants(BY_TYPE='DATASPACE', /FIRST_ONLY)

  self._oTargetDS = oDS

end

 

;------------------------------------------------------------------------------

;+

; Initializes the graphics components of the class

;-

pro my_polygon::InitializeGraphics

  compile_opt idl2, logical_predicate

  void = self->IDLgrPolygon::Init(COLOR=[255,0,0], /PRIVATE, THICK=2)

  self._oGrOverlay->IDLmiContainer::Add, self

end

 

;------------------------------------------------------------------------------

;+

; This method is for setting class properties

;-

pro my_polygon::SetProperty, $

  DATA=data, $

  POLYGONS=poly, $

  _REF_EXTRA=refExtra

  compile_opt idl2, logical_predicate

  if n_elements(data) then begin

    self->SetData, data, POLYGONS=poly

  endif

  if n_elements(refExtra) then begin

    self->IDLgrPolygon::SetProperty, _EXTRA=refExtra

    self->IDLmiManipLayer::SetProperty, _EXTRA=refExtra

  endif

end

 

;------------------------------------------------------------------------------

;+

; This method maps the points from window coordinates to map coordinates and

; adds the mapped points to the IDLgrPolygon.

;

; :Params:

;   xy: in, required, type="int"

;     A [2,n] array of points (In window coordinates) to be added to the polygon

;

; :Keywords:

;   POLYGONS: in, optional, type="int"

;     An integer array of one or more polygon descriptions.  See IDL help for

;     IDLgrPolygon for more information

;-

pro my_polygon::SetData, xy, $

  POLYGONS=poly

  compile_opt idl2, logical_predicate

  self._oTargetDS->WindowToVis, reform(xy[0,*]), reform(xy[1,*]), xVis, yVis

  self->IDLgrPolygon::SetProperty, DATA=transpose([[xVis],[yVis]]), $

    POLYGONS=poly

end

 

;------------------------------------------------------------------------------

;+

; Class structure definition

;-

pro my_polygon__define

  compile_opt idl2, logical_predicate

  void = {my_polygon                    $

    , inherits IDLmiManipGraphicOverlay $

    , inherits IDLmiManipLayer          $

    , inherits IDLgrPolygon             $

    }

end

 

;------------------------------------------------------------------------------

;+

;-

pro envi_polygon_example

  compile_opt idl2, logical_predicate

  e = envi(/CURRENT)

  if ~isa(e, 'envi') then begin

    e = envi()

  endif

  file = FILEPATH('qb_boulder_msi', ROOT_DIR=e.ROOT_DIR, SUBDIRECTORY=['data'])

  eRaster = e->OpenRaster(file)

  eView = e->GetView()

  eLayer = eView->CreateLayer(eRaster)

  xy = [[470,140],[560,140],[560,230],[470,230], $

    [750,115],[1200,115],[1200,665],[750,665]]

  conn = [4,0,1,2,3,4,4,5,6,7]

  oPolygon = obj_new('my_polygon', xy, LINESTYLE=5, POLYGONS=conn, STYLE=1)

end

Comments (0) Number of views (767) Article rating: No rating

20

Oct

2016

Continuum Removal

Author: Austin Coates

Recently, I was given the chance to practice some spectroscopy and in preparation for the project, I realized that I did not have a simple way to visualize the variations in different absorption features between very discreet wavelengths. The method that I elected to employ for this task is called continuum removal  (Kokaly, Despain, Clark, & Livo, 2007). This method allows you to compare different spectra and essentially normalize the data so that they can be more easily compared with one another.

To use the algorithm, you first select the region that you are interested in (for me this was between 550 nm and 700 nm -this is the region of my spectra that deals with chlorophyll absorption and pigment). Once the region is selected then a linear model is fit between the two points and this is called the continuum. The continuum is the hypothetical background absorption feature, or artifact or other absorption feature, which acts as the baseline for the target features to be compared against (Clark, 1999). Once the continuum has been set then the continuum removal process can be performed on all spectra in question using the following equation (Kokaly, Despain, Clark, & Livo, 2007).

 RC is the resulting continuum removed spectra, RL is the continuum line and, Ro is the original reflectance value.

Figure 1: Original spectra of two healthy plants. The dotted line denotes the continuum line. The x axis shows wavelengths in nm and the y axis represents reflectance.

Figure 2: The continuum removal for wavelengths 550 nm - 700 nm.

 

The resulting code gives you a tool that will take in two spectral libraries, with one spectra per library, and return two plots similar to what is shown in Figure 1 and Figure 2.

 

pro Continuum_Removal

compile_opt IDL2

 

Spectra_File_1  =

Spectra_File_2 =

 

; Find the bounds for the feature

FB_left = 550

FB_right =700

 

; Open Spectra 1

oSLI1 = ENVISpectralLibrary(Spectra_File_1)

spectra_name = oSLI1.SPECTRA_NAMES

Spectra_Info_1 = oSLI1.GetSpectrum(spectra_name)

 

; Open Spectra 2

oSLI2 = ENVISpectralLibrary(Spectra_File_2)

spectra_name = oSLI2.SPECTRA_NAMES

Spectra_Info_2 = oSLI2.GetSpectrum(spectra_name)

 

; Get the wavelengths

wl = Spectra_Info_1.wavelengths

 

; Create Bad Bands List (this removes some regions of the spectra associated with water vapor absorption)

bb_range = [[926,970],[1350,1432],[1796,1972],[2349,2500]]

bbl = fltarr(n_elements(wl))+1

dims = size(bb_range, /DIMENSIONS)

for i = 0 , dims[1]-1 do begin

  range = bb_range[*,i]

  p1 = where(wl eq range[0])

  p2 = where(wl eq range[1])

  bbl[p1:p2] = !VALUES.F_Nan

endfor

 

;Plot oSLI1 / oSLI2

p = plot(wl, Spectra_Info_1.spectrum*bbl, xrange = [min(wl, /nan),max(wl, /nan)],$

  yrange=[0,max([Spectra_Info_1.spectrum*bbl,Spectra_Info_2.spectrum*bbl], /nan)], thick = 2, color = 'blue')

 

p = plot(wl, Spectra_Info_2.spectrum*bbl, /overplot, thick = 2, color = 'green')

 

; create the linear segment

Spectra_1_y1 = Spectra_Info_1.spectrum[where( wl eq FB_left)]

Spectra_1_y2 = Spectra_Info_1.spectrum[where( wl eq FB_right)]

pl_1 = POLYLINE([FB_left,FB_right], [Spectra_1_y1, Spectra_1_y2], /overplot, /data, thick = 2, LINESTYLE = '--')

Spectra_2_y1 = Spectra_Info_2.spectrum[where( wl eq FB_left)]

Spectra_2_y2 = Spectra_Info_2.spectrum[where( wl eq FB_right)]

pl_2 = POLYLINE([FB_left,FB_right], [Spectra_2_y1, Spectra_2_y2], /overplot, /data, thick = 2, LINESTYLE = '--')

 

; Get the equation of the line

LF_1 = LINFIT([FB_left,FB_right], [Spectra_1_y1, Spectra_1_y2])

LF_2 = LINFIT([FB_left,FB_right], [Spectra_2_y1, Spectra_2_y2])

 

; Get the values between the lower and upper bounds

x_vals = wl [ where(wl eq FB_left) : where(wl eq FB_right)]

 

; Compute the continuum line

RL_1 = LF_1[0] + LF_1[1]* x_vals

RL_2 = LF_2[0] + LF_2[1]* x_vals

 

; Perform Continuum Removal

Ro_1 = Spectra_Info_1.spectrum[ where(wl eq FB_left) : where(wl eq FB_right)]

RC_1 =  Ro_1 / RL_1

Ro_2 = Spectra_Info_2.spectrum[ where(wl eq FB_left) : where(wl eq FB_right)]

RC_2 = Ro_2 / RL_2

 

; Plot the new Continuum Removal Spectra

pl_RC_1 = plot(x_vals, RC_1, color = 'Blue', xrange = [min(x_vals, /NAN), max(x_vals, /NAN)] )

pl_RC_2 = plot(x_vals, RC_2, color = 'Green', /overplot)

 

end

 

Kokaly, R. F., Despain, D. G., Clark, R. N., & Livo, K. E. (2007). Spectral analysis of absorption features for mapping vegetation cover and microbial communities in Yellowstone National Park using AVIRIS data.

Clark, R. N. (1999). Spectroscopy of rocks and minerals, and principles of spectroscopy. Manual of remote sensing3, 3-58. 

Comments (0) Number of views (510) Article rating: No rating

Categories: IDL Blog | IDL Data Point

Tags:

12345678910 Last

MOST POPULAR POSTS

AUTHORS

Authors



© 2016 Exelis Visual Information Solutions, Inc., a subsidiary of Harris Corporation