18

Aug

2016

Minimum Area Bounding Box

Author: Austin Coates

I find myself drawing bounding boxes around things a lot. I don’t know why I do it so much, but for whatever reason I do, and as of late I wanted to up my bounding box game. In the past, I have simply used the global min and max in both the x and y directions to get the coordinates to form the bounding box; however, this is not always the most elegant solution. For example, when my data follows somewhat of a linear trend, I am left with ample white space not filled by any valuable information

Figure 1: Simple Bounding Box

Figure 2: Minimum Area Bounding Box

This got me thinking, why am I not simply drawing a bounding box around only the data? Sounds great, right? The only problem was I had no idea how to do this. Luckily, there is this thing called the internet and it has vast stores of information and ideas to pull from. I found a very elegant solution by Jesse Buesking on stackoverflow.com which was posted on November 9, 2015. The solution was written in Python which I converted to IDL. My goal in posting this is to show an awesome way to draw a bounding box and also an example of translating between IDL and Python.

function bounding_box, pts = pts, plot_results = plot_results

 compile_opt IDL2

;Get the x and y coordinates

xs = pts[0,*]

ys = pts[1,*]

 

;Find the bounding points

Triangulate, xs, ys, triangles, hull, CONNECTIVITY=CONNECTIVITY



;order hull points in a [2,n] array   

 hull_points = [[xs[hull]]##1,[ys[hull]]##1]

;calculate edge angles

edges = hull_points[*,1:-1] - hull_points[*,0:-2]

angles = atan(edges[1,*], edges[0,*])

pi2 = !DPI/2.

 

angles = abs(angles - floor(angles / pi2) * pi2)

angles = angles[sort(angles)]

angles = angles[UNIQ(angles)]



;find rotation matrices 

rotations = transpose([[cos(angles)],[cos(angles-pi2)],[cos(angles+pi2)],[cos(angles)]])

rotations = REFORM(rotations, [2,2,n_elements(angles)])

 

;apply rotations to the hull 

rot_points = fltarr( n_elements(hull_points)/2, 2, n_elements(angles))

size_rot = size(rotations)

for group = 0 , size_rot[3]-1 do begin   

for row = 0 , size_rot[2]-1 do begin

rot_points[*,row,group] = TRANSPOSE(rotations[*,row,group]) # hull_points

endfor

endfor

;find the bounding points

min_x min(rot_points[*,0,*],DIMENSION=1, /NAN)

max_x max(rot_points[*,0,*],DIMENSION=1, /NAN)

min_y min(rot_points[*,1,*],DIMENSION=1, /NAN)

max_y max(rot_points[*,1,*],DIMENSION=1, /NAN)

;find the box with the best area

areas = (max_x - min_x) * (max_y - min_y)

min_val = min(areas, best_idx)

;return the best box

x1 = max_x[best_idx]

x2 = min_x[best_idx]

y1 = max_y[best_idx]

y2 = min_y[best_idx]

r = rotations[*,*,best_idx]

rval = fltarr(2,4)

rval[*,0] = TRANSPOSE(TRANSPOSE([x1, y2]) # transpose(r))

rval[*,1] = TRANSPOSE(TRANSPOSE([x2, y2]) # transpose(r))

rval[*,2] = TRANSPOSE(TRANSPOSE([x2, y1]) # transpose(r))

rval[*,3] = TRANSPOSE(TRANSPOSE([x1, y1]) # transpose(r))

 

;display results 

if KEYWORD_SET(plot_results) then begin

p = SCATTERPLOT(xs,ys, SYM_COLOR='Red', SYM_FILL_COLOR='Red', SYM_FILLED=1,$

XRANGE=[min(rval[0,*])-1,max(rval[0,*])+1], YRANGE=[min(rval[1,*])-1,max(rval[1,*])+1])

p = POLYGON(rval, /FILL_BACKGROUND, $

FILL_COLOR="light steel blue", PATTERN_ORIENTATION=45, $

PATTERN_SPACING=4, /DATA)

endif



return, rval



end

Source of original Python code : http://stackoverflow.com/questions/13542855/python-help-to-implement-an-algorithm-to-find-the-minimum-area-rectangle-for-gi/33619018#33619018 

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

Categories: IDL Blog | IDL Data Point

Tags:

12

Aug

2016

Compressing hyperspectral data using Motion JPEG2000

Author: Brian Griglak

As I’ve blogged about before, you have to pay attention to a raster’s interleave so that you can iterate over the data efficiently.  That post was ENVI specific, this time I’m here to present a way to store a data cube for efficient BIL mode access that is pure IDL.

As more hyperspectral (HSI) sensors are produced, data cubes with hundreds or even thousands of bands will become more common.  These sensors are usually pushbroom scanners, with a fixed number of samples and bands and a highly variable number of lines collected.  The collection modality lends itself to the BIP and BIL storage interleaves, so each scanline of pixels can be written to disk without having to use large amounts of memory to hold many lines for BSQ or tiled output.  If you are using HSI data, you are probably also interested in most if not all of the bands for your analysis, so BIP and BIL interleaves are more efficient for those data access patterns.

The challenge of HSI data is that it can be large, due to the high number of bands.  Some form of data compression would be useful, as long as it is lossless – why spend the money collecting all those bands if you are going to lose the details with compression or interpolation artifacts?

There a few options for compressing a datacube in IDL:

The zip option is not attractive in that you have to unzip the entire file to use it, so there isn’t any space savings, in fact it’s worse than using the original ENVI file.  The IDL support for the TIFF format allows for 4 different compression modes:
  • None
  • LZW
  • PackBits
  • JPEG

The None option is pointless, since it doesn’t perform any compression.  The JPEG option is lossy and would introduce artifacts, but it can’t be used for this data in the first place.  Firstly, it only supports 8-bit values, so any other datatypes will fail.  Secondly, if you send more than 3 bands of data into the method, you get back the curious error message “Bogus input colorspace”.  This message actually comes from the libTIFF library, trying to tell you it can’t JPEG compress more than 3 band data.

The IDLffMJPEG2000 object is IDL’s interface to the Motion JPEG2000 compression format.  The Motion JPEG2000 standard is an extension to JPEG2000, the wavelet based successor to the venerable JPEG format.  The JPEG2000 format supports both lossy and lossless compression, and we’ll use the latter here to avoid introducing any artifacts to the data.  Unlike make video formats, Motion JPEG2000 only performs intraframe compression, no interframe compression.  While this likely results in lower compression ratios than those other formats, it also means that decompressing a single frame of the animation is faster because it doesn’t need to load other frames for the interframe interpolation.  There are a few degrees of freedom available when creating Motion JPEG2000 files – BIT_RATE, N_LAYERS, N_LEVELS, PROGRESSION, and TILE_DIMENSIONS.  I did not experiment with how changing these values effects the compression ratio or time needed to decompress, so playing with these one your own may be worthwhile.

First I’ll present the code I used to perform the creation of the different compressed formats , and then show the compression ratios and time to compress for a couple different datacubes.

pro compress_to_zip, raster, file   compile_opt idl2
 
  FILE_GZIP, raster.URI, file
end
 
pro compress_to_tiff, raster, file
  compile_opt idl2
 
  dat = raster.GetData()
  WRITE_TIFF, file, dat, /SIGNED, /SHORT, COMPRESSION=1
end
 
pro compress_to_mj2k, raster, file
  compile_opt idl2
 
  dat = raster.GetData()
  signed = !True
  if (raster.Data_Type eq 'byte') then begin
    depth = 8
  endif else if (raster.Data_Type eq 'int') then begin
    depth = 16
  endif else if (raster.Data_Type eq 'long') then begin
    depth = 24
  endif else if (raster.Data_Type eq 'uint') then begin
    depth = 16
    signed = !False
  endif else if (raster.Data_Type eq 'ulong') then begin
    depth = 24
    signed = !False
  endif else begin
    Message, 'Unsupported pixel datatype : ' + raster.Data_Type
  endelse
  mj2k = IDLffMJPEG2000(file, /WRITE, /REVERSIBLE, $
                        BIT_DEPTH=depth, SIGNED=signed, $
                        DIMENSIONS=[raster.nColumns, raster.nBands])
  for row = 0, raster.nRows-1 do begin
    !null = mj2k.SetData(Reform(dat[*, *, row]))
  endfor
  print, mj2k.Commit(30000)
end

 

The IDLffMJPEG2000 class requires you to add data one frame at a time (or subset of a frame), hence the for loop over the number of rows in the raster.  I did have to stick a Reform() call in there, to collapse the 3D slice down to 2D, since the last dimension is always 1.  This object is actually threaded, so the SetData() calls don’t block, they queue up the data to compressed into the output file, so the Commit() function has an optional timeout argument, which is how many milliseconds to wait before returning.  In my testing it doesn’t take much time at all to do the commit, the processor can keep up with the compression requests.

Now the interesting part, compression performance:


Sensor Rows Size GZIP ZIP TIFF LZW TIFF PackBits MJP2
Hyperion
254 cols
242 bands
1310
161046160
96668737
60.0%
7.2 sec
96668847
60.0%
10.6 sec
118720382
73.7 %
4.8 sec
133119274
82.7 %
2.8 sec
64289348
39.9 %
13.2 sec
AVIRIS
614 cols
224 bands
1024
281674956
213266080
75.7 %
15.2 sec
213266228
75.7 %
24.0 sec
271876484
96.5 %
7.8 sec
277500090
98.5 %
3.8 %
129922225
60.9 %
Sec
AVIRIS
952 cols
224 bands
3830
1633479680
784610303
48.0 %
60.5 sec
784610437
48.0 %
92.3 sec
1015098818
62.1 %
44.8 sec
1076409756
65.9 %
25.4 sec
486091874
29.8 %
109.1 sec

 

It isn’t surprising that the GZIP and ZIP results are almost identical, though GZIP is way faster.  It also isn’t surprising that the LZW mode for TIFF is more efficient than the PackBits mode, though slower.  It is somewhat surprising that the LZW compression was so much work than GZIP and ZIP, and that Motion JPEG2000 is a bit better than GZIP and ZIP.

Now let’s say you wanted to read one of the Motion JPEG2000 compressed files back, but only need a spatial and/or spectral subset of it.  Here is a function that will do just that:

function read_mj2k, file, SUB_RECT=subRect, BANDS=bands
  compile_opt idl2
 
  mj2k = IDLffMJPEG2000(file)
 
  mj2k.GetProperty, DIMENSIONS=dims, N_FRAMES=nFrames, $
                    BIT_DEPTH=depth, SIGNED=signed
 
  if (~ISA(subRect)) then begin
    subRect = [0, 0, dims[0]-1, nFrames-1]
  endif
  if (~ISA(bands)) then begin
    bands = INDGEN(dims[1])
  endif
 
  startFrame = subRect[1]
  endFrame = subRect[3]
  leftCol = subRect[0]
  rightCol = subRect[2]
  topRow = MIN(bands)
  bottomRow = MAX(bands)
 
  if (depth le 8) then begin
    type = 1 ; Byte
  endif else if (depth le 16) then begin
    type = signed ? 2 : 12 ; Int or UInt
  endif else begin
    type = signed ? 3 : 13 ; Long or ULong
  endelse
 
  outData = MAKE_ARRAY(rightCol-leftCol+1, N_ELEMENTS(bands), $
                       endFrame-startFrame+1, TYPE=type)
  region = [leftCol, 0, rightCol-leftCol+1, dims[1]]
 
  for frame = 0, endFrame-startFrame do begin
    dat = mj2k.GetData(frame+startFrame, REGION=region)
    outData[*, *, frame] = dat[*, bands]
  endfor
 
  return, outData
end

 

I used the same keyword interpretation as ENVIRaster::GetData():

  • SUB_RECT = [x1, y1, x2, y2]
  • BANDS – any array of nonrepeating band indices

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

Categories: IDL Blog | IDL Data Point

Tags:

4

Aug

2016

Beware of the Diamond Relationship: The danger of multiple class inheritances

Author: Benjamin Foreback

When developing object-oriented IDL programs, a commonly used class is IDL_Object. This convenient superclass is used to provide the subclass that inherits it with IDL's _overload functionality. However, when multiple classes inherit IDL_Object, you can run into a problem if you try to write a class that inherits more than one superclasses that inherits IDL_Object.

Here are some class definition routines that illustrate the problem:

pro Superclass_A__define

  !null =
{Superclass_A, $
    inherits IDL_Object}
  
end

pro Superclass_B__define
  
  !null =
{Superclass_B, $
    inherits IDL_Object}

end

pro Subclass_S__Define
  
  !null =
{Subclass_S, $
    inherits Superclass_A, $
    inherits Superclass_B}
  
end

This can be called a diamond relationship because of the diamond that it makes in a class diagram.

Unfortunately, attempting to run this code would result in an error.

% Conflicting or duplicate structure tag definition: IDL_OBJECT_TOP.
% Execution halted at: SUBCLASS_A__DEFINE

Since there is more than one way to write code, there are a couple of alternatives to avoid this unwanted behavior resulting from the diamond relationship.

Only inherit base level classes (horizontal relationships)

If you want to avoid confusion as to what is inherited by inherited classes, then you could structure the classes so that you only inherit base classes, which are classes that don't inherit anything else. In this model, Superclass_A and Superclass_B would be the base classes. You wouldn't instantiate or call methods on them directly, but instead you would write new classes, Class_A and Class_B that would inherit both the base class and IDL_Object (also a base class). These new subclasses would then be the ones you would interact with.

 

Now Subclass_S would simply inherit the two base classes plus IDL_Object.

 

Use a linear inheritance structure (vertical relationships)

Another option is to use in inheritance structure that is completely linear. This means that each class inherits one and only one superclass.

This structure is simple and very safe in regards to avoiding conflicting superclasses. The drawback is that when you define Superclass_B to inherit Superclass_A, you are stating that B can be identified as A. In other words, Obj_Isa(B, 'Superclass_A') would return true. You may not necessarily want to associate the two together, in which case this would not be a suitable way to define your classes.

Other diamond relationships

In addition to IDL_Object, the diamond relationship can cause problems in many other cases. For instance, when the superclass at the top of the diamond contains a state or member variable, there are possible conflicts when the intermediate classes try to access or modify the single superclasse. In general, it's a relationship that you want to avoid.

These are not the only two solutions but just a couple of suggestions for how to build class relationships that avoid conflicts. If all else fails, draw a class diagram. After all, a picture is worth a thousand lines of code, right?

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

Categories: IDL Blog | IDL Data Point

Tags:

28

Jul

2016

Hough Transform Demo GUI

Author: Daryl Atencio

Hough Transform Demo GUI

The following code demonstrates the Hough transform.  It provides one interactive display for moving points in an image domain and a second display which displays the resulting Hough transform.  To run the application:

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

hough_demo

The optional keyword NUM_POINTS may be used to set the number of points in the image-domain display.  This number must be in the range [2,100].

A brief description of the graphical user interface (Launched using the command hough_demo, NUM_POINTS=10).

The points in the image-domain (left) can be moved by clicking and dragging.  The image in the Hough domain (right) will update as a point moves.  When moving the mouse over the Hough domain a representative line for that point will appear in the image domain.  For instance, hovering over the point in the Hough domain where many of the waves intersect will draw a line that crosses several points in the image domain (Sorry you can’t see the cursor I the image)

Additionally, two line equations are displayed:

  1. Line Equation – The equation of the line between the first two points (red) in order from left to right in the image domain.
  2. Hough Line Equation – The equation of the line defined by a point in the Hough domain.  This will update as the cursor moves the mouse cursor in the Hough-domain window.

The source code:

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

;+

; Main event handler

;

; :Params:

;   sEvent: in, required, type="structure"

;     An IDL widget event structure

;

;-

pro hough_demo_event, sEvent

  compile_opt idl2, logical_predicate

  widget_control, sEvent.top, GET_UVALUE=oHoughDemo

  oHoughDemo->Event, sEvent

end

 

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

;+

; This method is called when the Hough demo GUI has been realized

;

; :Params:

;   tlb: in, required, type="long"

;     The widget ID of the top-level-base

;-

pro hough_demo_realize, tlb

  compile_opt idl2, logical_predicate

  widget_control, tlb, GET_UVALUE=oHoughDemo

  oHoughDemo->NotifyRealize

end

 

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

;+

; Lifecycle method called when the object is destroyed vis OBJ_DESTROY.

;

;-

pro hough_demo::Cleanup

  compile_opt idl2, logical_predicate

  self->Destruct

end

 

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

;+

; Constructs the GUI for the demo application

;-

pro hough_demo::ConstructGUI

  compile_opt idl2, logical_predicate

  self.tlb = widget_base(EVENT_PRO='hough_demo_event', MAP=0, $

    NOTIFY_REALIZE='hough_demo_realize', /ROW, TITLE='Hough Demo', $

    /TLB_KILL_REQUEST_EVENTS)

  wBase = widget_base(self.tlb, /COLUMN)

  wDraw = widget_draw(wBase, /BUTTON_EVENTS, GRAPHICS_LEVEL=2, /MOTION_EVENTS, $

    UNAME='draw_points', XSIZE=300, YSIZE=300)

  wLabel = widget_label(wBase, VALUE='Line Equation')

  wLabel = widget_label(wBase, /DYNAMIC_RESIZE, UNAME='label_line', VALUE='')

  wLabel = widget_label(wBase, VALUE='Hough Line Equation')

  wLabel = widget_label(wBase, /DYNAMIC_RESIZE, UNAME='label_hough', VALUE='')

  wBase = widget_base(self.tlb, /COLUMN)

  wDraw = widget_draw(wBase, GRAPHICS_LEVEL=2, /MOTION_EVENTS, $

    /TRACKING_EVENTS, UNAME='draw_hough', XSIZE=500, YSIZE=400)

  widget_control, self.tlb, /REALIZE, SET_UVALUE=self

end

 

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

;+

; Cleans up any heap member variables

;-

pro hough_demo::Destruct

  compile_opt idl2, logical_predicate

  if widget_info(self.tlb, /VALID_ID) then begin

    void = self->GetObject('model/points', WINDOW=oWindow)

    obj_destroy, oWindow

    void = self->GetObject('model/image', /HOUGH, WINDOW=oWindow)

    obj_destroy, oWindow

    widget_control, self.tlb, /DESTROY

  endif

  ptr_free, [self.pRho, self.pTheta]

end

 

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

;+

; Main entry point for widget events

;

; :Params:

;   sEvent: in, required, type="structure"

;     An IDL widget event structure

;-

pro hough_demo::Event, sEvent

  compile_opt idl2, logical_predicate

  case tag_names(sEvent, /STRUCTURE_NAME) of

    'WIDGET_DRAW': self->EventDraw, sEvent

    'WIDGET_KILL_REQUEST': self->Destruct

    'WIDGET_SLIDER': self->EventSlider, sEvent

    'WIDGET_TRACKING': self->EventTracking, sEvent

    else: help, sEvent

  endcase

end

 

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

;+

; This method handles events from draw widgets.  It simply passes the event

; to the event handler for the specific draw widget.

;

; :Params:

;   sEvent: in, required, type="structure"

;     An IDL {WIDGET_DRAW} structure

;-

pro hough_demo::EventDraw, sEvent

  compile_opt idl2, logical_predicate

  case widget_info(sEvent.id, /UNAME) of

    'draw_hough': self->EventDrawHough, sEvent

    'draw_points': self->EventDrawPoints, sEvent

    else:

  endcase

end

 

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

;+

; This method handles events from the draw widget displaying the Hough domain.

;

; :Params:

;   sEvent: in, required, type="structure"

;     An IDL {WIDGET_DRAW} structure

;-

pro hough_demo::EventDrawHough, sEvent

  compile_opt idl2, logical_predicate

  case sEvent.type of

    2: self->UpdateLinePlot, [sEvent.x,sEvent.y]

    else:

  endcase

end

 

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

;+

; This method handles events from the draw widget displaying the image-domain

; points.

;

; :Params:

;   sEvent: in, required, type="structure"

;     An IDL {WIDGET_DRAW} structure

;-

pro hough_demo::EventDrawPoints, sEvent

  compile_opt idl2, logical_predicate

  widget_control, sEvent.id, GET_VALUE=oWindow

  case sEvent.type of

    0: begin

    ; Press event

      oWindow->GetProperty, GRAPHICS_TREE=oView

      oSelect = oWindow->Select(oVIew, [sEvent.x,sEvent.y])

      if obj_valid(oSelect[0]) then begin

        self.oSelect = oSelect[0]

        self.oSelect->GetProperty, DATA=pts

        void = min(abs(sEvent.x-pts[0,*]) + abs(sEvent.y-pts[1,*]), index)

        self.oSelect->SetProperty, UVALUE=index

      endif

    end

    1: begin

    ; Release event

      self.oSelect = obj_new()

      return

    end

    2: begin

    ; Motion event

      if obj_valid(self.oSelect) then begin

        widget_control, sEvent.id, GET_VALUE=oWindow

        oWindow->GetProperty, DIMENSIONS=dims

        if (sEvent.x LT 0) || (sEvent.y LT 0) $

        || (sEvent.x GT dims[0]) || (sEvent.y GT dims[1]) then begin

          return

        endif

        self.oSelect->GetProperty, DATA=pts, UVALUE=index

        pts[*,index] = [sEvent.x,sEvent.y]

        self.oSelect->SetProperty, DATA=pts

        self->UpdateHough

      endif

    end

    else: return

  endcase

  oWindow->Draw

end

 

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

;+

; This method handles traking events

;

; :Params:

;   sEvent: in, required, type="structure"

;     An IDL {WIDGET_TRACKING} structure

;-

pro hough_demo::EventTracking, sEvent

  compile_opt idl2, logical_predicate

  case widget_info(sEvent.id, /UNAME) of

    'draw_hough': begin

      if ~sEvent.enter then begin

        oPlot = self->GetObject('model/plot', WINDOW=oWindow)

        oPlot->SetProperty, HIDE=1

        oWindow->Draw

        widget_control, self->GetWID('label_hough'), SET_VALUE=''

      endif

    end

    else:

  endcase

end

 

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

;+

; Calculates the default MINX and MINY values used by the HOUGH algorithm

;

; :Keywords:

;   X: out, optional, type="float"

;     Set this keyword to a named variable to retrieve the default MINX value

;     used by HOUGH

;   Y: out, optional, type="float"

;     Set this keyword to a named variable to retrieve the default MINY value

;     used by HOUGH

;-

pro hough_demo::GetMinXY, $

  X=minX, Y=minY

  compile_opt idl2, logical_predicate

  void = self->GetObject('model/points', WINDOW=oWindow)

  oWindow->GetProperty, DIMENSIONS=dims

  minX = -(dims[0]-1)/2

  minY = -(dims[1]-1)/2

end

 

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

;+

; This method is for accessing objects in the object-graphics tree

;

; :Returns:

;   A reference to the object with the spacified name.  If no object contains

;   a match a null object will be returned.

;

; :Params:

;   name: in, required, type="string"

;     The name of the object to be retrieved.

;    

; :Keywords:

;   HOUGH: in, optional, type="boolean"

;     Set this keyword to have the object retrieved from the Hough-domain

;     graphics window.  By default, the object will be retrieved from the

;     image-domian graphics window.

;   VIEW: out, optional, type="objref"

;     Set this keyword to a named variable to retrieve a reference to the

;     IDLGRVIEW object from the graphics window.

;   WINDOW: out, optional, type="objref"

;     Set this keyword to a named variable to retrieve a reference to the

;     IDLGRWINDOW object

;-

function hough_demo::GetObject, name, $

  HOUGH=hough, $

  VIEW=oView, $

  WINDOW=oWindow

  uName = keyword_set(hough) ? 'draw_hough' : 'draw_points'

  widget_control, self->GetWID(uName), GET_VALUE=oWindow

  oWindow->GetProperty, GRAPHICS_TREE=oView

  return, oView->GetByName(name)

end

 

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

;+

; Calculates the slope and y-intercept of a line in either of the graphics

; windows

;

; :Returns:

;   A tow element, floating-point vector containing, in order, the slope and

;   y-intercept of the line

;  

; :Params:

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

;     Set this to the (x,y)-postition of the cursor in the HOUGH window.  This

;     information is only used when the HOUGH keyword is set.

;    

; :Keywords:

;   HOUGH: in, optional, type="boolean"

;     Set this keyword to have the slope and intercept calculated from a point

;     in the Hough domain.  By default, the first two points in the image-

;     domain are used.

;-

function hough_demo::GetSlopeIntercept, xy, $

  HOUGH=hough

  mb = fltarr(2)

  if keyword_set(hough) then begin

    rho = (*self.pRho)[xy[1]]

    theta = (*self.pTheta)[xy[0]]

    self->GetMinXY, X=xMin, Y=yMin

    mb[0] = -1.0/tan(theta)

    mb[1] = (rho - xMin*cos(theta) - yMin*sin(theta)) / sin(theta)

  endif else begin

    oPoints = self->GetObject('model/points')

    oPoints->GetProperty, DATA=pts

    mb[0] = (pts[1,1]-pts[1,0])/(pts[0,1]-pts[0,0])

    mb[1] = pts[1,0] - mb[0]*pts[0,0]

  endelse

  return, mb

end

 

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

;+

; This method the ID of the widget that uses the specified name as its UNAME.

;

; :Returns:

;   The ID of the widget using the specified UNAME.  If no widget is using the

;   UNAME then 0 is returned

;

; :Params:

;   name: in, required, type="string"

;       The UNAME of the widget whose ID is to be returned

;

; :Keywords:

;   PARENT: in, optional, type="integer"

;       The widget ID of the parent widget.  If not set, self.tlb will be used.

;-

function hough_demo::GetWID, name, $

  PARENT=wParent

  compile_opt idl2, logical_predicate

  if ~n_elements(wParent) then wParent = self.tlb

  if ~widget_info(wParent, /VALID_ID) then return, 0

  return, widget_info(wParent, FIND_BY_UNAME=name)

end

 

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

;+

; Lifecycle method for initializing an instance of the object via OBJ_NEW()

;

; :Returns:

;   1 if the object is successfully initialized

;  

; :Params:

;   NUM_POINTS: in, optional, type="integer"

;     Sets the numner of points to be displayed in the image-domain display.

;     The default is 2.  If more than 2 points are displayed the first two will

;     be red and the rest will be blue.  This is because the slope/intercept

;     calculation for this window is done using the first two points.

;-

function hough_demo::Init, $

  NUM_POINTS=nPoints

  compile_opt idl2, logical_predicate

  self.pRho = ptr_new(/ALLOCATE_HEAP)

  self.pTheta = ptr_new(/ALLOCATE_HEAP)

  self.nPoints = n_elements(nPoints) ? nPoints : 2

  return, 1

end

 

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

;+

; This method initializes the object graphics used by the Hough demo

;-

pro hough_demo::InitializeGraphics

  compile_opt idl2, logical_predicate

; Image domain (Points)

  widget_control, self->GetWID('draw_points'), GET_VALUE=oWindowPoint

  oWindowPoint->GetProperty, DIMENSIONS=dims

  oSymbol = obj_new('IDLgrSymbol', 1, THICK=2, SIZE=5)

  x = fix(randomu(s,self.nPoints)*dims[0])

  y = fix(randomu(s,self.nPoints)*dims[1])

  colors = bytarr(3,self.nPoints)

  colors[0,0:1] = 255

  if (self.nPoints GT 2) then colors[2,2:self.nPoints-1] = 255

  oPoints = obj_new('IDLgrPolyline', x, y, LINESTYLE=6, NAME='points', $

    SYMBOL=oSymbol, VERT_COLORS=colors)

  oPlot = obj_new('IDLgrPlot', HIDE=1, NAME='plot')

  oModelPoints = obj_new('IDLgrModel', NAME='model')

  oModelPoints->Add, [oPoints, oPlot]

  oViewPoints = obj_new('IDLgrView', COLOR=[255,255,255], VIEWPLANE_RECT=[0,0,dims])

  oViewPoints->Add, oModelPoints

  oWindowPoint->Setproperty, GRAPHICS_TREE=oViewPoints

  oWindowPoint->Draw

; Hough domain

  widget_control, self->GetWID('draw_hough'), GET_VALUE=oWindowHough

  oImage = obj_new('IDLgrImage', NAME='image')

  oModelHough = obj_new('IDLgrModel', NAME='model')

  oModelHough->Add, oImage

  oViewHough = obj_new('IDLgrView', COLOR=[255,255,255])

  oViewHough->Add, oModelHough

  oWindowHough->SetProperty, GRAPHICS_TREE=oViewHough

  oWindowHough->Draw

end

 

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

;+

; This method is called when the GUI is realized.  It finishes up some

; initialization and starts XMANAGER.

;-

pro hough_demo::NotifyRealize

  compile_opt idl2, logical_predicate

  ss = get_screen_size()

  self->InitializeGraphics

  self->UpdateHough, /RESIZE_WINDOW

  wGeom = widget_info(self.tlb, /GEOMETRY)

  widget_control, self.tlb, XOFFSET=(ss[0]-wGeom.scr_xSize)/2, YOFFSET=(ss[1]-wGeom.scr_ySize)/2

  widget_control, self.tlb, MAP=1

  xmanager, 'hough_demo', self.tlb, /NO_BLOCK

end

 

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

;+

; This method updates the image in the hough domain according to the points

; in the image doimain.

;

; :Keywords:

;   RESIZE_WINDOW: in, optional, type="boolean"

;     Set this keyword to have the Hough-domain display resized to the size

;     of the output from HOUGH.  This is only called the first time a Hough

;     image is calculated.

;-

pro hough_demo::UpdateHough, $

  RESIZE_WINDOW=resize

  compile_opt idl2, logical_predicate

  oPoints = self->GetObject('model/points', WINDOW=oWindowPoints)

  oWindowPoints->GetProperty, DIMENSIONS=dims

  oPoints->GetProperty, DATA=pts

  temp = make_array(dims, /FLOAT, VALUE=0.0)

  pts = transpose(pts)

  temp[pts[*,0],pts[*,1]] = 1.0

  self->GetMinXY, X=xMin, Y=yMin

  imgHough = hough(temporary(temp), RHO=rho, THETA=theta)

  *self.pRho = rho

  *self.pTheta = theta

  dimHough = size(imgHough, /DIMENSIONS)

  oImage = self->GetObject('model/image', /HOUGH, VIEW=oViewHough, WINDOW=oWindowHough)

  if keyword_set(resize) then begin

    widget_control, self->GetWID('draw_hough'), XSIZE=dimHough[0], YSIZE=dimHough[1]

    oViewHough->SetProperty, VIEWPLANE_RECT=[0,0,dimHough]

  endif

  oImage->SetProperty, DATA=255-bytscl(imgHough)

  oWindowHough->Draw

  self->UpdateLineEquation

end

 

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

;+

; This method updates the Hough line-equation on the GUI.

;

; :Params:

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

;     A 2-element vector containing the xy-location in the Hough domain for

;     which the line will be calculated.

;-

pro hough_demo::UpdateHoughEquation, xy

  compile_opt idl2, logical_predicate

  mb = self->GetSlopeIntercept(xy, /HOUGH)

  str = 'y = '+string(mb[0], FORMAT='(f0.2)')+' * x + '+string(mb[1], FORMAT='(f0.2)')

  widget_control, self->GetWID('label_hough'), SET_VALUE=str

end

 

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

;+

; This method updates the image-domain line equation on the GUI according to

; the first two points.

;-

pro hough_demo::UpdateLineEquation

  compile_opt idl2, logical_predicate

  mb = self->GetSlopeIntercept()

  str = 'y = '+string(mb[0], FORMAT='(f0.2)')+' * x + '+string(mb[1], FORMAT='(f0.2)')

  widget_control, self->GetWID('label_line'), SET_VALUE=str

end

 

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

;+

; This method updates the line plot in the image-domain display using a point

; in the Hough domain.

;

; :Params:

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

;     A two-element vector containing the xy-location in the Hough domain for

;     which the line will be drawn in the image-domain display

;-

pro hough_demo::UpdateLinePlot, xy

  compile_opt idl2, logical_predicate

  oPlot = self->GetObject('model/plot', WINDOW=oWindow)

  oWindow->GetProperty, DIMENSIONS=dims

  rho = (*self.pRho)[xy[1]]

  theta = (*self.pTheta)[xy[0]]

  self->GetMinXY, X=xMin, Y=yMin

  m = -1.0/tan(theta)

  b = (rho - xMin*cos(theta) - yMin*sin(theta)) / sin(theta)

  x = findgen(dims[0])

  y = m*x+b

  oPlot->SetProperty, DATAX=x, DATAY=y, HIDE=0

  oWindow->Draw

  self->UpdateHoughEquation, xy

end

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

;+

; Class structure definition

;

; :Fields:

;   nPoints: The number of points in the image-domain display

;   oSelect: A reference to the selected graphics object.  This is used for

;     moving points in the image-domain display

;   pRho: Holds the rho values for the hough transform

;   pTheta: Holds the theta values for the hough transform

;   tlb: The widget ID of the top-level-base of the GUI

;-

pro hough_demo__define

  compile_opt idl2, logical_predicate

  void = {hough_demo     $

 

    ,nPoints : 0         $

    ,oSelect : obj_new() $

    ,pRho    : ptr_new() $

    ,pTheta  : ptr_new() $   

    ,tlb     : 0L        $

 

    }

end

 

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

;+

; This routine starts the Hough demo

;

; :Keywords:

;   NUM_POINTS: in, optional, type="integer"

;     Sets the numner of points to be displayed in the image-domain display.

;     The allowed range is [2,100].  The default is 2.  If more than 2 points

;     are displayed the first two will be red and the rest will be blue.  This

;     is because the slope/intercept calculation for this window is done using

;     the first two points.

;-

pro hough_demo, NUM_POINTS=nPoints

  compile_opt idl2, logical_predicate

  if n_elements(nPoints) && ((nPoints LT 2) || (nPoints GT 100)) then begin

    void = dialog_message('The number ofpoints must be in [2,100]', /ERROR)

    return

  endif

  oHoughDemo = obj_new('hough_demo', NUM_POINTS=nPoints)

  oHoughDemo->ConstructGUI

end

 

Comments (0) Number of views (226) Article rating: 2.0

Categories: IDL Blog | IDL Data Point

Tags:

14

Jul

2016

Coming in IDL 8.6 - Repeating Timers

Author: Jim Pendleton

A couple years ago I wrote a blog article on the topic of the new TIMER class in IDL 8.3. In the upcoming IDL 8.6 release, a new REPEAT keyword will be added to the Timer::Set method.

We like to write device control software in the Professional Services Group here at Harris so this new feature will come in handy.

The typical case for using a timer with REPEAT arises from the need to ensure processing at a regular interval, for example in the display of an animation at a specific frames-per-second rate, or in the consistent collection of data from a device.

This REPEAT keyword is a toggle to force the timer to fire at the specified interval. This relieves the callback code of the need for an explicit call to manually reset the timer during the processing of the preceding event. It produces a truly regular "heartbeat" of events.

It is most appropriate to use this style of timer when the processing time of the callback is less than the timer's interval. If the timer events cannot be drained at a rate greater than the interval at which they're added to the queue, this solution is not generally recommended, or the interval needs to be adjusted accordingly.

The IDL Code Profiler should be used to determine if there is a potential for conflict between the callback execution time and the sampling rate. Also be aware that your measurements will depend on the current platform and system load, so be judicious when deploying solutions like this to other computers with different performance characteristics from the device under test.

A timer callback can be thought of as a higher-priority interrupt to the main IDL interpreter loop. It will take precedence over the otherwise-executing code.

When the interpreter is between execution of lines of .pro code, if a timer event is found on the queue the timer's callback routine will be executed first, taking precedence over the next line of IDL code. This also applies to compiled code in IDL SAVE files.

When the callback is completed, the interpreter returns to its regularly-scheduled execution, which may include the processing of additional timer callbacks.

In other words, the IDL interpreter remains single-threaded. The timer callback will be executed as an interruption to the currently-executing context. In fact, a timer will interrupt even modal WIDGET_BASE dialogs. In many cases, it may be wise to use a mechanism such as TIMER's ::Block/::Unblock methods or a system semaphore to prevent the possibility of re-entrance.

Example

The following source code shows the behavior of the timer event queue when /REPEAT is set.

PRO myTimerCallback3, id, c
COMPILE_OPT IDL2
t = systime(1)
c.dt.add, t - c.t
c.t = t
c.counter++
wait, c.counter ge 20 && c.counter le 40 ? .51 : .001
print, 'in callback ', c.counter
done = c.counter eq 100
if (done) then begin
    p  = plot(c.dt.toarray(), ytitle = 'dt', xtitle = 'iteration', $
        title = 'Execution time per iteration')
    void = Timer.Cancel(id)
endif
END


PRO async_timer_example_repeat3
COMPILE_OPT IDL2
; Create a timer that will fire 5 times per second.
c = {counter : 0L, t : systime(1), dt : list()} 
id = Timer.Set( 0.2, 'myTimerCallback3', c, /REPEAT )
END

A new timer object is created that fires off events at an interval of 0.2 seconds, regardless of what else may be going on in the IDL main thread.

In the callback, the first 19 iterations are executed with just a 1-millisecond WAIT delay, simulating a light CPU load.

Iterations 20 through 40 are executed with a 0.51-second delay, over twice the timer object's repetition interval.  This simulates a heavier CPU load and helps to show that the timer continues to run and queue events at its own rate in the background.

The remaining steps revert to the shorter wait time.

Internally, the code keeps track of the clock time between callback execution at each step.

The plot of the result is shown below.

The labels indicate the dominant driver for the execution rate at each interval.

Initially, the timer's heartbeat keeps us on track with a regular 0.2 second interval between callbacks.

The next set of iterations are dominated by the 0.51 second WAIT inside each callback.  If we had been displaying a video playback, the frame rate would be decreased substantially during this time.

The third set of iterations shows the effect of emptying the queue of the accumulated timer events that could not be processed immediately at the time they were fired, due to the WAIT statements in the previous block. If this had been video playback, these frames would have all been displayed very rapidly.

After the queue is emptied, the regular heartbeat is attained again. Video playback would return to "real time".

If you were to remove the /REPEAT keyword and add a call to Timer::Set in the callback routine, you could compare the effects. Your choice in your own code will be based on your prioritization of "real time" versus "lagging but consistent" sampling.

Comments (0) Number of views (522) 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