X
9483 Rate this article:
2.3

Hough Transform Demo GUI

Anonym

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