/****************************************************************/ /* Copyright 1993 : Johns Hopkins University */ /* Department of Computer Science */ /****************************************************************/ /* Contact : murthy@cs.jhu.edu */ /****************************************************************/ /* File Name : display.c */ /* Author : Sreerama K. Murthy */ /* Last modified : October 1993 */ /* Contains modules : main */ /* set_extremes */ /* display_point */ /* display_edge */ /* find_edge */ /* prepare_ps */ /* recursive_draw */ /* finish_ps */ /* make_box */ /* intersection */ /* onedge */ /* correct_side */ /* display_help */ /* Uses modules in : util.c */ /* oc1.h */ /* load_data.c */ /* classify_util.c */ /* Is used by modules in : None. */ /* Remarks : This file contains modules to display */ /* datasets and/or decision trees, as */ /* PostScript(R) files. */ /****************************************************************/ #include "oc1.h" char *pname; char point_file[LINESIZE]; int unlabeled=FALSE,no_of_samples,no_of_dimensions=0; int no_of_categories=0,no_of_coeffs; struct point **train_points; struct tree_node *box; float xmax,xmin,ymax,ymin; int pminx=72, pmaxx=540, pminy=72, pmaxy = 640; /************************************************************************/ /* Module name : main */ /* Functionality : Accepts & interprets command line options for */ /* the "display" command. Invokes the routines to */ /* read/display data, read/display decision trees. */ /* Parameters : argc,argv : See any C-reference manual. */ /* Returns : Nothing. */ /* Calls modules : display_help */ /* read_tree (classify_util.c) */ /* error (util.c) */ /* load_points (load_data.c) */ /* prepare_ps */ /* set_extremes */ /* display_point */ /* make_box */ /* recursive_draw */ /* finish_ps */ /* Is called by modules : None. */ /************************************************************************/ main(argc,argv) int argc; char *argv[]; { extern char *optarg; extern int optind; int c1,i,j; int verbose=FALSE; int leaf_count(),tree_depth(); int load_points(); char decision_tree[LINESIZE]; char ps_file[LINESIZE]; char title[LINESIZE]; struct tree_node *root,*cur_node,*read_tree(); FILE *psfile, *infile; strcpy(point_file,"\0"); strcpy(decision_tree,"\0"); strcpy(ps_file,"\0"); strcpy(title,"\0"); pname = argv[0]; while ((c1 = getopt (argc, argv, "D:t:T:d:o:vh:x:X:y:Y:")) != EOF) switch (c1) { case 'D': /*Decision Tree */ strcpy(decision_tree,optarg); break; /* A decision tree, when no point_file is specified, is displayed in the unit square. [[0..1],[0..1]] */ case 't': /*File containing data points.*/ if (strlen(point_file)) display_help(); strcpy(point_file,optarg); break; case 'T': /*File containing data points.*/ if (strlen(point_file)) display_help(); strcpy(point_file,optarg); break; case 'd': no_of_dimensions = atoi (optarg); /* If a decision tree is supplied with the D option, the no_of_dimensions in there override this value. */ break; case 'o': /*File into which the postscript output is written. default = stdout */ strcpy(ps_file,optarg); break; case 'x': pminx = atoi(optarg); break; case 'X': pmaxx = atoi(optarg); break; case 'y': pminy = atoi(optarg); break; case 'Y': pmaxy = atoi(optarg); break; case 'v': verbose = TRUE; break; case 'h': /*Header (title) of the display.*/ strcpy(title,optarg); break; default: display_help(); } if (!strlen(decision_tree) && !strlen(point_file)) display_help(); if (pmaxx <= pminx || pmaxy <= pminy) display_help(); /* set title */ if (!strlen(title)) { if (strlen(point_file)) strcat(title,point_file); if (strlen(decision_tree)) { strcat(title,"-"); strcat(title,decision_tree);} } if (strlen(decision_tree)) { root = read_tree(decision_tree); if (verbose) { fprintf(stderr,"Decision tree read from %s.\n",decision_tree); fprintf(stderr,"Leaf Count = %d, Tree Depth = %d\n", leaf_count(root),tree_depth(root)); } } if (strlen(point_file)) { if ((infile = fopen(point_file,"r")) == NULL) error("Display : Data file can not be opened. "); no_of_samples = load_points(infile,&train_points); if (verbose) { fprintf(stderr,"%d examples loaded from %s.\n", no_of_samples,point_file); fprintf(stderr,"#dimensions = %d, #categories = %d\n",no_of_dimensions, no_of_categories); } } if (no_of_dimensions != 2) error("DISPLAY : Only planar datasets and decision trees can be displayed. "); no_of_coeffs = no_of_dimensions+1; if (!strlen(ps_file)) psfile = stdout; else if ((psfile = fopen(ps_file,"w")) == NULL) { fprintf(stderr,"DISPLAY: Output file can not be opened."); psfile=stdout; } prepare_ps(psfile,title); set_extremes(); if (no_of_samples != 0) for (i=1;i<=no_of_samples;i++) display_point(psfile,train_points[i]); if (root != NULL) { make_box(); recursive_draw(psfile,root); } finish_ps(psfile); } /************************************************************************/ /* Module name : set_extremes */ /* Functionality : Sets the global variables xmin,xmax,ymin and */ /* ymax as the minimum and maximum values in the */ /* x and y coordinates. These values are */ /* subsequently used for scaling. */ /* Parameters : None. */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : main */ /************************************************************************/ set_extremes() { int i; float x1,y1,xmargin,ymargin,xrange,yrange; if (no_of_samples == 0) { xmin = pminx; xmax = pmaxx; ymin = pminy; ymax = pmaxy; return; } xmax = xmin = train_points[1]->dimension[1]; ymax = ymin = train_points[1]->dimension[2]; for (i=1;i<=no_of_samples;i++) { x1 = train_points[i]->dimension[1]; y1 = train_points[i]->dimension[2]; if (xmax < x1) xmax = x1; if (xmin > x1) xmin = x1; if (ymax < y1) ymax = y1; if (ymin > y1) ymin = y1; } xrange = xmax-xmin; yrange = ymax-ymin; if ((xmargin = xrange * 0.01) == 0) xmargin = 1; if ((ymargin = yrange * 0.01) == 0) ymargin = 1; xmin -= xmargin; xmax += xmargin; ymin -= ymargin; ymax += ymargin; } /************************************************************************/ /* Module name : display_point */ /* Functionality : Displays the category number of a data point */ /* at coordinates specified by its two attribute */ /* values. */ /* Parameters : psfile : File to write PostScript(R) output */ /* p : Pointer to the POINT structure */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : main */ /* Important Variables used : translatex, translatey : Inline function*/ /* calls, defined in oc1.h, to scale */ /* coordinates. */ /************************************************************************/ display_point(psfile,p) FILE *psfile; POINT *p; { float x1,y1; x1 = translatex(p->dimension[1]); y1 = translatey(p->dimension[2]); fprintf(psfile,"%f %f moveto (%d) show stroke\n", x1,y1,p->category); } /************************************************************************/ /* Module name : recursive_draw */ /* Functionality : Recursively draws a node of the decision tree. */ /* ie., Determines the edge corresponding to the */ /* hyperplane at the current node, displays it, */ /* and recurses on the left and right children. */ /* Parameters : psfile : File to write PostScript(R) output */ /* cur_node : Pointer to anode in the decision tree. */ /* Returns : Nothing. */ /* Calls modules : find_edge */ /* display_edge */ /* recursive_draw */ /* Is called by modules : main */ /* recursive_draw */ /************************************************************************/ recursive_draw(psfile,cur_node) struct tree_node *cur_node; FILE *psfile; { EDGE l,find_edge(); if (cur_node == NULL) return; l = find_edge(cur_node); if (cur_node->parent == NULL) display_edge(psfile,l, "Root"); else display_edge(psfile,l, cur_node->label); recursive_draw(psfile,cur_node->left); recursive_draw(psfile,cur_node->right); } /************************************************************************/ /* Module name : prepare_ps */ /* Functionality : Prepares the PostScript(R) file. i.e., draws */ /* a bounding box, displays the title of the */ /* box, if any; sets the initial font etc. */ /* Parameters : psfile : File to write PostScript(R) output */ /* title : Character string title for the display. */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : main */ /* Important Variables used : pmaxx,pminx,pmaxy,pminy : coordinates */ /* of the bounding box. Also used in */ /* scaling. */ /************************************************************************/ prepare_ps (psfile,title) char *title; FILE *psfile; { fprintf (psfile, "%%!\ngsave\n"); fprintf (psfile, "0.5 setlinewidth\n"); fprintf(psfile,"%d %d moveto %d %d lineto stroke\n", pminx,pminy,pmaxx,pminy); fprintf(psfile,"%d %d moveto %d %d lineto stroke\n", pmaxx,pminy,pmaxx,pmaxy); fprintf(psfile,"%d %d moveto %d %d lineto stroke\n", pmaxx,pmaxy,pminx,pmaxy); fprintf(psfile,"%d %d moveto %d %d lineto stroke\n", pminx,pmaxy,pminx,pminy); fprintf (psfile,"/Helvetica findfont 7 scalefont setfont\n"); fprintf(psfile,"%d %d moveto (%s) show stroke\n", pminx+10,pmaxy,title); } /************************************************************************/ /* Module name : finish_ps */ /* Functionality : Completes the PostScript(R) file, and closes it.*/ /* Parameters : psfile : File to write PostScript(R) output */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : main */ /************************************************************************/ finish_ps (psfile) FILE *psfile; { int i; fprintf (psfile, "showpage\ngrestore\n"); fclose (psfile); } /************************************************************************/ /* Module name : find_edge */ /* Functionality : Determines the edge (defined by two end points) */ /* corresponding to the hyperplane at the current */ /* node of the decision tree. */ /* Parameters : cur_node: Node of the decision tree under consideration */ /* Returns : an EDGE structure */ /* Calls modules : intersection */ /* onedge */ /* correct_side */ /* error (util.c) */ /* Is called by modules : recursive_draw */ /* Important Variables used : box : Root of a tree of four nodes, */ /* each representing an edge of the */ /* bounding box. */ /* first, second : Boolean variables to */ /* record whether the first (second) end */ /* point of the edge has been found. */ /* Remarks : Two main principles used in finding the edge */ /* corresponding to a hyperplane are */ /* 1. A valid endpoint is formed when the current hyperplane*/ /* intersects the hyperplanes at one of its ancestors */ /* or the bounding box. */ /* 2. A valid end point lies on the "correct" side of each */ /* of the hyperplanes on the path from the root to the */ /* current node's parent. */ /* An underlying assumption is that the decision tree is */ /* being traversed in pre-order. */ /************************************************************************/ EDGE find_edge(cur_node) struct tree_node *cur_node; { EDGE l; struct endpoint p,intersection(); struct tree_node *cur_ancestor; int onedge(),correct_side(),first=FALSE,second = FALSE; cur_ancestor = cur_node; while (cur_ancestor->parent != NULL) cur_ancestor = cur_ancestor->parent; cur_ancestor->parent = box; cur_ancestor = cur_node->parent; while (cur_ancestor != NULL) { p = intersection(cur_node->coefficients,cur_ancestor->coefficients); if ( p.x != HUGE && p.y != HUGE && onedge(p,cur_ancestor->edge) && correct_side(p,cur_node)) { if (first == FALSE) { l.from = p; first = TRUE; cur_ancestor = cur_ancestor->parent; continue; } if (second == FALSE) { second = TRUE; l.to = p; cur_ancestor = cur_ancestor->parent; break; } } else cur_ancestor = cur_ancestor->parent; } if (first == FALSE || second == FALSE) { if (no_of_samples == 0) { fprintf(stderr,"Can't draw the decision tree in the Unit Square.\n"); fprintf(stderr,"Try normalizing. \n"); error("GET_EDGE"); } else error("GET_EDGE : Decision tree can't be drawn. Try normalizing."); } cur_ancestor = cur_node; while (cur_ancestor->parent != box) cur_ancestor = cur_ancestor->parent; cur_ancestor->parent = NULL; cur_node->edge = l; return(l); } /************************************************************************/ /* Module name : make_box */ /* Functionality : Creates four hypothetical decision tree nodes */ /* to contain the left, right, bottom and top */ /* edges of the bounding box, and links them up */ /* as a 4-level tree. Sets the global variable */ /* "box" to point to the root of this tree. */ /* Parameters : None. */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : main */ /* Remarks : The "box" tree comes handy while trying to find the */ /* the end points of the edge representing a hyperplane, */ /* in the "find_edge" module. */ /************************************************************************/ make_box() { struct tree_node *left,*right,*top,*bottom; left = (struct tree_node *)malloc(sizeof(struct tree_node)); left->coefficients = vector(1,no_of_coeffs); left->coefficients[1]=1; left->coefficients[2]=0; left->coefficients[3]=-1.0*xmin; top = (struct tree_node *)malloc(sizeof(struct tree_node)); top->coefficients = vector(1,no_of_coeffs); top->coefficients[1]=0; top->coefficients[2]=1; top->coefficients[3]=-1.0*ymax; right = (struct tree_node *)malloc(sizeof(struct tree_node)); right->coefficients = vector(1,no_of_coeffs); right->coefficients[1]=1; right->coefficients[2]=0; right->coefficients[3]=-1.0*xmax; bottom = (struct tree_node *)malloc(sizeof(struct tree_node)); bottom->coefficients = vector(1,no_of_coeffs); bottom->coefficients[1]=0; bottom->coefficients[2]=1; bottom->coefficients[3]=-1.0*ymin; (left->edge).from.x = (bottom->edge).to.x = xmin; (left->edge).from.y = (bottom->edge).to.y = ymin; (left->edge).to.x = (top->edge).from.x = xmin; (left->edge).to.y = (top->edge).from.y = ymax; (top->edge).to.x = (right->edge).from.x = xmax; (top->edge).to.y = (right->edge).from.y = ymax; (right->edge).to.x = (bottom->edge).from.x = xmax; (right->edge).to.y = (bottom->edge).from.y = ymin; bottom->parent = right; right->parent = top; top->parent = left; left->parent = NULL; box = bottom; } /************************************************************************/ /* Module name : display_edge */ /* Functionality : Displays an edge, with an optional character */ /* label. */ /* Parameters : psfile : File to write PostScript(R) output */ /* e : Pointer to the EDGE structure */ /* label : Character label to be displayed at the centre */ /* of the edge. */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : recursive_draw */ /* Important Variables used : translatex, translatey : Inline function*/ /* calls, defined in oc1.h, to scale */ /* coordinates. */ /************************************************************************/ display_edge (psfile, e, label) FILE *psfile; EDGE e; char *label; { int i; struct endpoint p1, p2; double x,y; double angle; p1 = e.from; p2 = e.to; y = (double)(p2.y - p1.y); x = (double)(p2.x - p1.x); angle = atan2pi (y,x) * 180; if (angle > 90) angle -= 180; if (angle < -90) angle += 180; fprintf (psfile, "%f %f moveto %f %f lineto stroke\n", translatex (p1.x), translatey (p1.y), translatex (p2.x), translatey (p2.y) ); if (strlen(label)) { for (i=1;i<=strlen(label);i++) label[i-1] = toupper(label[i-1]); fprintf (psfile,"/Helvetica findfont 12 scalefont setfont\n"); fprintf (psfile, "gsave %f %f moveto %f ", translatex ((p1.x + p2.x)/2), translatey ((p1.y + p2.y)/2), angle); fprintf (psfile, "rotate 0 1 rmoveto (%s) show stroke grestore\n", label); fprintf (psfile,"/Helvetica findfont 7 scalefont setfont\n"); } } /************************************************************************/ /* Module name : intersection */ /* Functionality : Computes the intersection point of two */ /* hyperplanes. */ /* Parameters : c1,c2 : two coefficient arrays, each of length 3. */ /* Returns : the intersection point, stored in an "endpoint" struct. */ /* (HUGE,HUGE) if there is no intersection. */ /* Calls modules : None. */ /* Is called by modules : find_edge */ /************************************************************************/ struct endpoint intersection(c1,c2) float *c1,*c2; { float denom; struct endpoint p; denom = c2[2] * c1[1] - c2[1] * c1[2]; if (!denom) { p.x = HUGE; p.y = HUGE; return(p); } p.x = (c2[3] * c1[2] - c2[2] * c1[3]) / denom; p.y = (c2[1] * c1[3] - c2[3] * c1[1]) / denom; return(p); } /************************************************************************/ /* Module name : onedge */ /* Functionality : Checks if a point lies on an edge. */ /* Parameters : p : pointer to a POINT structure. */ /* e : pointer to an EDGE structure. */ /* Returns : 1: if p lies on e */ /* 0: otherwise. */ /* Calls modules : None. */ /* Is called by modules : find_edge */ /************************************************************************/ int onedge(p,e) struct endpoint p; EDGE e; { struct endpoint p1,p2; p1=e.from; p2=e.to; if (((p1.x >= p.x && p2.x <= p.x) || (p1.x <= p.x && p2.x >= p.x)) && ((p1.y >= p.y && p2.y <= p.y) || (p1.y <= p.y && p2.y >= p.y))) return(TRUE); return(FALSE); } /************************************************************************/ /* Module name : correct_side */ /* Functionality : Checks if a point lies on the "correct" side of */ /* the hyperplanes at all nodes on the path from */ /* the root of the decision tree to the parent of */ /* the node under consideration. */ /* ie., the following should hold recursively: if */ /* "cur_node" is the right child of "cur_node-> */ /* parent", then the point should lie on the right */ /* side of the hyperplane at "cur_node->parent". */ /* Same for the left child. */ /* This test makes sure that we draw each hyperplane*/ /* in the appropriate region. */ /* Parameters : p : pointer to an "endpoint" structure. */ /* Is an intersection point of the hyperplane at */ /* cur_node with that at one of its ancestors, or with */ /* the bounding box. */ /* cur_node : Decision tree node under consideration. */ /* Returns : 1: If p is on the correct side of all of cur_node's */ /* ancestors. */ /* 0: otherwise. */ /* Calls modules : None. */ /* Is called by modules : find_edge */ /* Important Variables used : TOLERANCE : A very small positive number */ /* defined in oc1.h, needed to compensate */ /* for peculiarities in floating point */ /* arithmetic on Unix. */ /* Remarks : This assumes that the decision tree is displayed in a */ /* pre-order traversal. */ /************************************************************************/ int correct_side(p,cur_node) struct endpoint p; struct tree_node *cur_node; { float sum; struct tree_node *node,*parent; node = cur_node; while ((parent = node->parent) != box) { sum = 0; sum = parent->coefficients[1] * p.x + parent->coefficients[2] * p.y + parent->coefficients[3]; if ((sum <= TOLERANCE && sum >= -1.0 * TOLERANCE) || (node == parent->left && sum < 0) || (node == parent->right && sum > 0)) node = parent; else break; } if (parent == box) return (TRUE); else return(FALSE); } /************************************************************************/ /* Module name : display_help */ /* Functionality : Displays the command line options for the */ /* "display" command. */ /* Parameters : None. */ /* Returns : Nothing. */ /* Calls modules : None. */ /* Is called by modules : main */ /************************************************************************/ display_help() { fprintf (stderr,"\n\nUsage : display -D:t:o:d:vT:x:X:y:Y:"); fprintf (stderr,"\nOptions :"); fprintf (stderr,"\n -D"); fprintf (stderr,"\n (Default: None)"); fprintf (stderr,"\n -t or -T "); fprintf (stderr,"\n (Default: None)"); fprintf (stderr,"\n -o"); fprintf (stderr,"\n (Default=stdout)"); fprintf (stderr,"\n -d<#dimensions> (Default=2)"); fprintf (stderr,"\n -v : Verbose (Default=FALSE)"); fprintf (stderr,"\n -h
"); fprintf (stderr,"\n (Default=-)"); fprintf (stderr,"\n -x"); fprintf (stderr,"\n (Default=72)"); fprintf (stderr,"\n -X"); fprintf (stderr,"\n (Default=540)"); fprintf (stderr,"\n -y"); fprintf (stderr,"\n (Default=72)"); fprintf (stderr,"\n -Y"); fprintf (stderr,"\n (Default=640)"); fprintf (stderr,"\n\n"); exit(0); } /************************************************************************/ /************************************************************************/