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.
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].
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)
;------------------------------------------------------------------------------
;+
; 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