/* * @(#)/SlicerControl.java 1.7 96/03/31 by Andrew Barclay abb@nuccard.eushc.org * * Copyright (c) 1995 Andrew B. Barclay All Rights Reserved. */ import java.applet.Applet ; import java.awt.Button ; import java.awt.Color ; import java.awt.Dimension ; import java.awt.Event ; import java.awt.FlowLayout ; import java.awt.Graphics ; import java.awt.Image ; import java.awt.image.ImageObserver ; import java.util.Enumeration ; import java.util.Hashtable ; import java.util.StringTokenizer ; import java.util.Vector ; import SlicerDisplay ; import SlicerDisplayGraphic ; import SlicerDisplayInfo ; import Volume ; /** * An applet for orthogonal slicing of volume images. * * @see Volume * @see SliceVolumeFilter * @see SlicerDisplay * * @version %I% %D% * @author Andrew Barclay * * This could evolve into an interface with the register, incrImage, * and setSelected methods. For now, I'm experimenting. */ public class SlicerControl extends Applet implements ImageObserver { Image src = null ; int srcwidth = 0, srcheight = 0 ; int vdims[] = new int[3] ; double vsize[] = new double[3] ; Volume vol = null ; // State info Hashtable displayinfo = new Hashtable() ; // When updating hash marks, need to have a one-to-one axis->SDI mapping. SlicerDisplayInfo axisdisplayinfo[] = new SlicerDisplayInfo[3] ; SlicerDisplayInfo selectedSDI = null ; final int littlewidth = 20, littleheight = 20 ; final boolean debug = false ; public String getAppletInfo() { return "SlicerControl by Andrew Barclay" ; } public String[][] getParameterInfo() { String[][] info = { {"src", "url", "XY multi-slice image"}, {"vdims", "WIDTHxHEIGHTxDEPTH","volume dimensions (pixels)"}, {"vsize", "WIDTHxHEIGHTxDEPTH","voxel size (mm)"}, {"bgcolor", "int-base16", "background color, no # or 0x"}, } ; return info ; } public void init() { // Release src and vol to let getVolume know // that we're initializing (important for restarts). src = null ; vol = null ; String param = getParameter( "VDIMS" ) ; vdims[0] = vdims[1] = vdims[2] = 0 ; StringTokenizer st = new StringTokenizer( param, "x", false ) ; for( int i = 0 ; st.hasMoreTokens() && i < 3 ; i++ ) { vdims[i] = Integer.parseInt( st.nextToken() ) ; } dbg( "init - param="+param+" vdims="+vdims[0]+","+vdims[1]+","+vdims[2] ) ; param = getParameter( "VSIZE" ) ; vsize[0] = vsize[1] = vsize[2] = 0 ; st = new StringTokenizer( param, "x", false ) ; for( int i = 0 ; st.hasMoreTokens() && i < 3 ; i++ ) { vsize[i] = parseDouble( st.nextToken() ) ; } dbg( "init - param="+param+" vsize="+vsize[0]+","+vsize[1]+","+vsize[2] ) ; // I'm taking the easy way out on this. (just assume hex int // with no leading "0x" or "#"). param = getParameter( "BGCOLOR" ) ; if( param != null ) { dbg( "init - color string="+param ) ; Color bgcolor = new Color( Integer.parseInt( param, 16 ) ) ; dbg( "init - color="+bgcolor ) ; setBackground( bgcolor ) ; } // do this last, 'cause getVolume() must wait for it // before staring consumption (is there a better way?) param = getParameter( "SRC" ) ; src = getImage( getDocumentBase(), param ) ; dbg( "init - param="+param+" src="+src ) ; src.getWidth(this) ; src.getHeight(this) ; prepareImage( src, littlewidth, littleheight, this ) ; // Let the display applets know that we're ready. synchronized( this ) { notifyAll() ; } setLayout( new FlowLayout( FlowLayout.CENTER, 0, 0 ) ) ; add( new Button("Stop") ) ; } // why is there no parseDouble?? private double parseDouble( String s ) { return (Double.valueOf(s)).doubleValue() ; } private synchronized void getVolume() { while( src == null ) { try { showStatus( "SlicerControl: waiting for a src image..." ) ; wait(1000); } catch (InterruptedException ex) { } } // Volume will get the src image's width and height // synchronously, so may be waiting here. vol = new Volume( src, vdims, vsize ) ; } /* * This is a 2D array that maps a slice,axis pair to a volume axis. * The first index is the axis number of the slice (x->0, y->1). * The second index is the slice type, or the axis to which the * slice is orthogonal in the volume (x->0, y->1, z->2). * e.g: The x axis of the x slice, [0][0], maps onto the z axis of * the volume. The y axis of the z slice, [1][2], maps onto the y * axis of the volume. */ static int slicetovol[][] = { { 2, 0, 0 }, { 1, 2, 1 } } ; // These are the border/hashmark colors (dimmed until selected). static Color displaycolor[] = { Color.red, Color.green, Color.yellow } ; // @return SlicerDisplayInfo to SlicerDisplay's public SlicerDisplayInfo register( SlicerDisplay display, int axis, double thickness ) { // bug: need to use a string for the axis and do some real // error checking here. if( axis < 0 || axis >= 3 ) { return (SlicerDisplayInfo)null ; } // Must synchronize with the init and make sure we have the // volume sizes before proceeding. if( vol == null ) { getVolume() ; } if( thickness == 0.0 ) { thickness = vsize[axis] ; } // Initialize in the SlicerDisplayInfo object. SlicerDisplayInfo di = new SlicerDisplayInfo() ; di.axis = axis ; // border/hashmark colors are darker until they're selected. di.color = displaycolor[axis].darker() ; di.vsize = new double[3] ; di.vsize[0] = vsize[slicetovol[0][axis]] ; di.vsize[1] = vsize[slicetovol[1][axis]] ; di.vsize[2] = thickness ; // Set position to the center slice to start. di.slices = new Hashtable() ; di.nslices = (int)Math.ceil( ((double)vdims[axis] * vsize[axis])/thickness ) ; di.position = di.vsize[2] * (di.nslices - 1)/2 ; di.display = display ; // Add to the hashtable of displays. // should this be synchronized? displayinfo.put( display, di ) ; axisdisplayinfo[axis] = di ; updateHashmarks( di ) ; return di ; } private Object positionkey( double position ) { /* * controls precision of hashing (1/100th of a mm) * (otherwise we get rounding errors) */ return( new Double( Math.rint( 100.0*position ) ) ) ; } // Update all the hash marks in the displays. public void updateHashmarks() { // Loop through the displayinfo hashtable. for( Enumeration en = displayinfo.elements() ; en.hasMoreElements() ; ) { SlicerDisplayInfo di = (SlicerDisplayInfo)en.nextElement() ; if( di != null ) { updateHashmarks( di ) ; } } } // Update all the hash marks in the displays. public void updateHashmarks( SlicerDisplayInfo di ) { SlicerDisplayGraphic dg = new SlicerDisplayGraphic() ; // Set the x hash mark position and color. int xcnum = slicetovol[0][di.axis] ; SlicerDisplayInfo dix = axisdisplayinfo[xcnum] ; if( dix != null ) { // Hash width is proportional to slice thickness. dg.xhashwidth = 1.0 / dix.nslices ; if( dix == selectedSDI ) { // Selected display marked by continuous line... dg.xhashlength = 1.0 ; } else { // otherwise, 1/10th of display width. dg.xhashlength = 0.1 ; } dg.xhashpos = dix.position / (vdims[xcnum] * vsize[xcnum]) ; dg.xhashcolor = dix.color ; } else { dg.xhashcolor = null ; } // Set the y hash mark position and color. int ycnum = slicetovol[1][di.axis] ; SlicerDisplayInfo diy = axisdisplayinfo[ycnum] ; if( diy != null ) { // Hash width is proportional to slice thickness. dg.yhashwidth = 1.0 / diy.nslices ; if( diy == selectedSDI ) { // Selected display marked by continuous line... dg.yhashlength = 1.0 ; } else { // otherwise, 1/10th of display width. dg.yhashlength = 0.1 ; } dg.yhashpos = diy.position / (vdims[ycnum] * vsize[ycnum]) ; dg.yhashcolor = diy.color ; } else { dg.yhashcolor = null ; } dg.bordercolor = di.color ; di.display.updateHashmarks( dg ) ; } // Get a slice for a display object. Slices are cached as built. // @return the slice at the given position in the volume public synchronized Image getSlice( Object key, double position ) { SlicerDisplayInfo di = (SlicerDisplayInfo)displayinfo.get( key ) ; if( di == null ) { return null ; } // bounds check if( position < 0.0 ) { di.position = 0.0 ; } else if( position > di.vsize[2]*(di.nslices-1) ) { di.position = di.vsize[2]*(di.nslices-1) ; } else { di.position = position ; } Object slicekey = positionkey(di.position) ; Image theimage = (Image)di.slices.get( slicekey ) ; if( theimage == null ) { showStatus( "building axis"+di.axis+" at pos="+di.position+" mm" ) ; theimage = vol.Slice( this, di.position, di.axis ) ; di.slices.put( slicekey, theimage ) ; } else { showStatus( "moving to axis"+di.axis+" at pos="+di.position+" mm" ) ; } updateHashmarks() ; return theimage ; } // Updates the SlicerDisplay's to reflect current volume position. public synchronized void setSelected( Object key ) { SlicerDisplayInfo di = (SlicerDisplayInfo)displayinfo.get( key ) ; if( di == null ) { return ; } dbg( "selected display="+di.axis ) ; // darken the old. if( selectedSDI != null ) { selectedSDI.color = displaycolor[selectedSDI.axis].darker() ; } selectedSDI = di ; // brighten the new. selectedSDI.color = selectedSDI.color.brighter() ; updateHashmarks() ; } public synchronized boolean imageUpdate( Image img, int infoflags, int x, int y, int w, int h ) { if( img != src ) { return false ; } if ((infoflags & WIDTH) != 0) { dbg( "*** SlicerControl: Got src width = " + w ) ; srcwidth = w ; } if ((infoflags & HEIGHT) != 0) { dbg( "*** SlicerControl: Got src height = " + h ) ; srcheight = h ; } if ((infoflags & (FRAMEBITS | ALLBITS)) != 0) { dbg( "*** SlicerControl: Finished loading volume." ) ; } else if ((infoflags & SOMEBITS) != 0) { //dbg( "Got some bits, x=" + x + " y=" + y ) ; dbg( "*** SlicerControl: Loading volume data ..." ) ; if( srcwidth > 0 && srcheight > 0 ) { int totalk = (srcwidth * srcheight)/1024 ; int imsize = littlewidth * littleheight ; int sofar = x + y * littlewidth ; int pcnt = (int)(100.0 * (double)sofar / (double)imsize ) ; showStatus( "SlicerControl "+pcnt+"% of "+totalk+"K") ; } } if( (infoflags & ERROR) != 0 ) { dbg( "*** SlicerControl: Error loading volume data." ) ; } return true ; } // The stop button's handler. public boolean action(Event evt, Object arg) { dbg( "SlicerControl.action() arg="+arg ) ; if( "Stop".equals(arg) && vol != null ) { /* I don't see any other way to stop the download??? * Volume.StopConsuming() didn't work. * SliceStack.stopInYourTracks() didn't work. */ /* * This sometimes takes a couple shots, but it gets * the job done. NOTHING works after that. If there * were only some way to find the image loading threads, * but they're not under my control... */ /* Thread threads[] = new Thread[100] ; int nt = Thread.enumerate( threads ) ; showStatus( "SlicerControl: Stopping "+nt+" threads." ) ; for( int i = 0 ; i < nt ; i++ ) { threads[i].stop() ; } */ showStatus( "Help!!! I can't stop this crazy thing." ) ; } return true ; } void dbg( String s ) { if( debug ) { System.out.println( s ) ; showStatus( s ) ; } } }