{ Routines for Transforming Between Coordinate Systems Copyright (C) 2004, 2007 Kevan Hashemi, hashemi@brandeis.edu, Brandeis University This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. } unit transforms; { This unit contains routines to transform points, line, and rectangles between the three coordinate systems we use in image analysis. It also contains routines to transform display commands in these coordinate systems into drawing commands in an image overlay. CCD coordinates specify a pixel in an image. They are named after the type of image sensor we used for most of our cameras. The letters CCD stand for Charge-Coupled Device. Our CCD images are rectangular with square pixels. The pixels are arranged in rows and columns. We specify a pixel in the image with its column and row number. A ccd point has the form (i,j) where i is the column number and j is the row number. Pixel (0,0) is the top-left pixel. Column number increases from left to right and row number increases from top to bottom. CCD Coordinates are therefore left-handed. We use ij_point_type for points in CCD Coordinates. You will find ij_point_type defined in our utils unit. Image coordinates specify a point in an image. An image point has the form (x,y), where x and y are real numbers, x is horizontal distance from left to right and y is vertical distance from top to bottom. The units of both x and y are pixels. Image point (0,0) is at the top-left corner of the top-left pixel. Point (1,1) is the bottom-right corner of the top-left pixel, and also the top-left corner of the second pixel in the second row. We have constants ccd_origin_x and ccd_origin_y that define the location of the ccd coordinate origin in image coordinates. The center values declared below for these two constants place the ccd origin at image point (0.5,0.5). Pattern coordinates specify a point in a pattern superimposed on an image. Pattern points are of the form (x,y), where x and y are the real. We assume the pattern is periodic in two orthogonal directions. A chessboard rasnik mask is such a pattern, and so is a sequence of encoder stripes. The chessboard has a finite period in two directions, while the encoder stripes have a finite period in one direction and a large period in the other direction. The x-axis is parallel to one direction in the orthogonal pattern, and the y-axis is parallel to the other direction. Pattern coordinates are left-handed for better cooperation with ccd and image coordinates, both of which are left-handed. We define a pattern coordinate system with five numbers. These numbers represent a translation, rotation, and scaling in two directions between image coordinates and pattern coordinates. In pattern_type, the origin field gives the image coordinates of the pattern coordinate origin. The pattern coordinate origin will have some significance in the pattern. It might represent the top-left corner of a chessboard square, or the center of a wire shadow. The rotation parameter gives the rotation of the pattern coordinates anticlockwise with respect to image coordinates. The pattern_x_width parameter gives the length of one period of the pattern along the pattern x-axis, as measured in image coordinates. Imagine the pattern x-axis inclined at a slight angle with respect to the image rows, and therefore to the image x-axis. A square in the pattern has one edge parallel to the pattern x-axis. The length of this edge, as measured in the image coordinates, is pattern_x_width. We have pattern_y_width as well, because a pattern can, in general, be rectangular in its geometry. If the point (0,0) in pattern coordinates marks the top-left corner of a square in a chessboard pattern, then point (1,0) marks the top-right corner of the same square, and the top-left corner of the square to its right. Here were defining right and left as +ve and -ve in the x-direction, and top and bottom as +ve and -ve in the y direction. Point (0,1) is the bottom-left corner of the same square, and the top-left corner of the square immediately below. Moving around through a chessboard pattern is easy in pattern coordinates, because we simply add one to our x-coordinate to move to the square on the right, and add one to our y-coordinate to move to the square below. The pattern_type is the basis of any record used with the pattern coordinate tranform routines defined in this unit. But the pattern_type is not public. All transform routines refer to patterns through generic pointers. When another unit declares its own pattern record for use with the routines declared in this unit, the new record must begin with the same fields as pattern_type, so these fields may be referred to through a pointer as if the new record were a genuine pattern_type. See rasnik_pattern_type in rasnik.pas for an example of such a pattern type. } interface uses utils,images;{by KSH} const {for ccd coordinates} ccd_origin_x=0.5;{pixels} ccd_origin_y=0.5;{pixels} {geometry transformations} function i_from_c(point:ij_point_type):xy_point_type; function c_from_i(point:xy_point_type):ij_point_type; function p_from_i(point:xy_point_type;pattern_ptr:pointer):xy_point_type; function i_from_p(point:xy_point_type;pattern_ptr:pointer):xy_point_type; function c_from_i_line(line:xy_line_type):ij_line_type; function i_from_p_line(line:xy_line_type;pattern_ptr:pointer):xy_line_type; function i_from_c_line(line:ij_line_type):xy_line_type; function p_from_i_line(line:xy_line_type;pattern_ptr:pointer):xy_line_type; function i_from_c_rectangle(rect:ij_rectangle_type):xy_rectangle_type; function c_from_i_rectangle(rect:xy_rectangle_type):ij_rectangle_type; function i_from_c_ellipse(ellipse:ij_ellipse_type):xy_ellipse_type; function c_from_i_ellipse(ellipse:xy_ellipse_type):ij_ellipse_type; {display transformations} procedure display_ccd_cross(ip:image_ptr_type; cross_point:ij_point_type;color:overlay_pixel_type); procedure display_ccd_line(ip:image_ptr_type;line:ij_line_type;color:integer); procedure display_ccd_pixel(ip:image_ptr_type;pixel:ij_point_type;color:integer); procedure display_ccd_rectangle(ip:image_ptr_type;rect:ij_rectangle_type;color:integer); procedure display_ccd_rectangle_cross(ip:image_ptr_type; rect:ij_rectangle_type;color:integer); procedure display_ccd_rectangle_ellipse(ip:image_ptr_type; rect:ij_rectangle_type;color:integer); procedure display_ccd_ellipse(ip:image_ptr_type; ellipse:ij_ellipse_type;color:integer); procedure display_profile_row(ip:image_ptr_type;profile_ptr:x_graph_ptr_type;color:integer); procedure display_profile_column(ip:image_ptr_type;profile_ptr:x_graph_ptr_type; color:integer); procedure display_real_graph(ip:image_ptr_type;graph_ptr:xy_graph_ptr_type; color:integer;x_min,x_max,y_min,y_max,x_div,y_div:real); implementation type {for pattern coordinates} pattern_type=record valid:boolean;{valid pattern} padding:array [1..7] of byte; {force origin field to eight-byte boundary} origin:xy_point_type; {pattern coordinate origin in image coordinates} rotation:real; {radians} pattern_x_width:real; {scaling factor going from pattern x to image} pattern_y_width:real; {scaling factor going from pattern y to image} end; pattern_ptr_type=^pattern_type; { i_from_c converts ccd coordinates to image coordinates. } function i_from_c(point:ij_point_type):xy_point_type; var p:xy_point_type; begin with point,p do begin x:=i+ccd_origin_x; y:=j+ccd_origin_y; end; i_from_c:=p; end; { c_from_i converts image coordinates to ccd coordinates. } function c_from_i(point:xy_point_type):ij_point_type; const max=30000; min=-30000; var q:real;p:ij_point_type; begin q:=point.x-ccd_origin_x; if q>max then q:=max; if qmax then q:=max; if qx then x_min:=x; end; end; end; if x_min>=x_max then begin report_error('x_min>=x_max in '+CurrentRoutineName+'.'); exit; end; if (y_min=0) and (y_max=0) then begin with graph_ptr^[0] do begin y_min:=y; y_max:=y; end; for index:=1 to graph_ptr^.num_points-1 do begin with graph_ptr^[index] do begin if y_maxy then y_min:=y; end; end; end; if y_min>=y_max then begin report_error('y_min>=y_max in '+CurrentRoutineName+'.'); exit; end; with ip^.analysis_bounds,line do begin if (x_div>0) then begin if ((x_max-x_min)/x_div > min_divs) then begin x:=x_min; a.j:=top; b.j:=bottom; while (x0) then begin if ((y_max-y_min)/y_div > min_divs) then begin y:=y_min; a.i:=left; b.i:=right; while (ymax_integer then b.i:=max_integer else if xx<-max_integer then b.i:=-max_integer else b.i:=round(xx); if yy>max_integer then b.j:=max_integer else if yy<-max_integer then b.j:=-max_integer else b.j:=round(yy); end; if index=0 then a:=b; display_ccd_line(ip,line,color); a:=b; end; end; end; { display_profile_row takes an instensity-profile and plots it in the overlay. The intensity-profile must be presented as a sequence of real values in an x_graph_type. The size of the x_graph_type must be exactly equal to the number of columns from left to right in the image's analysis bounds. If the x_graph_type represents some property of the image as we move from left to right, its values will be displayed so that they coincide with the image features that gave rise to them. The simplest row profile is the sum of the intensities of each column in the analysis bounds. Larger row-values are higher up in the image. Look from the bottom of the image for a standard x-y graph. } procedure display_profile_row(ip:image_ptr_type;profile_ptr:x_graph_ptr_type;color:integer); var graph_ptr:xy_graph_ptr_type; index:integer; r:real; begin if not valid_image_ptr(ip) then exit; if profile_ptr=nil then exit; with profile_ptr^,ip^.analysis_bounds do begin if num_points<>(right-left+1) then begin report_error('Found num_points<>(right-left+1) in '+CurrentRoutineName+'.'); exit; end; end; graph_ptr:=new_xy_graph(profile_ptr^.num_points); for index:=0 to graph_ptr^.num_points-1 do begin with graph_ptr^[index] do begin x:=index; y:=profile_ptr^[index]; end; end; display_real_graph(ip,graph_ptr,color,0,0,0,0,0,0); dispose_xy_graph(graph_ptr); end; { display_profile_column is like display_profile_row, but in the vertical direction. Larger column values are farther to the left in the image. Look from the right side of the image for a standard x-y graph. } procedure display_profile_column(ip:image_ptr_type;profile_ptr:x_graph_ptr_type; color:integer); var graph_ptr:xy_graph_ptr_type; index:integer; begin if not valid_image_ptr(ip) then exit; if profile_ptr=nil then exit; with profile_ptr^,ip^.analysis_bounds do begin if num_points<>(bottom-top+1) then begin report_error('Found num_points<>(bottom-top+1) in '+CurrentRoutineName+'.'); exit; end; end; graph_ptr:=new_xy_graph(profile_ptr^.num_points); for index:=0 to graph_ptr^.num_points-1 do begin with graph_ptr^[index] do begin y:=ip^.analysis_bounds.bottom-index; x:=-profile_ptr^[index]; end; end; display_real_graph(ip,graph_ptr,color,0,0,0,0,0,0); dispose_xy_graph(graph_ptr); end; end.