public PointAnalysis(IppiPoint coordinate, IppiPoint_32f smoothenedCoordinate, float instantSpeed) { CompletedBout = null; OriginalCoordinate = coordinate; SmoothenedCoordinate = smoothenedCoordinate; InstantSpeed = instantSpeed; }
/// <summary> /// Based on the current segment length computes the /// scanpoint offsets for each angle used during tracking /// </summary> protected virtual void InitializeScanPoints() { //Compute the angle step. Ideally we want to scan every pixel //on a 360 degree circle around the current point with radius //SegmentLength - so set angle step and NAngles to roughly //give us this coverage bool Up = false;//true indicates both a vertical up and a horizontal left facing tail //TODO: Experimental support for horizontal and upward facing tails added - however, left/right might be wrong! if (!TailIsVertical()) { System.Diagnostics.Debug.WriteLine("Warning: Horizontal tail implementation is experimental."); if (TailStart.x > TailEnd.x) { Up = true; } } else if (TailStart.y > TailEnd.y) { Up = true; } int circumference = (int)Math.Floor(2 * Math.PI * SegmentLength); _angleStep = 2 * Math.PI / circumference; lock (_scanPointLock) { //Create angle and offset arrays _scanAngles = new double[circumference]; _coordinateOffsets = new IppiPoint[circumference]; //establish scan angles and offsets: //loop over sweep angles - points are assigned correctly for downward facing tail //use hack for horizontal tail (swap x and y) as well as for upward facing tail (invert both x and y in place) for (int j = 0; j < _scanAngles.Length; j++) { //facing down (up, left, right) is centered in the array _scanAngles[j] = j * _angleStep - Math.PI; int offX = (int)Math.Floor(SegmentLength * Math.Sin(_scanAngles[j])); int offY = (int)Math.Floor(SegmentLength * Math.Cos(_scanAngles[j])); if (Up)//rotate coordinates by 180 { offX *= -1; offY *= -1; } if (!TailIsVertical())//swap role of x and y coordinates { int o = offX; offX = offY; offY = o; } _coordinateOffsets[j] = new IppiPoint(offX, offY); } }//release lock }
public Tracker90mmDish(int imageWidth, int imageHeight, IppiPoint dishCenter) { _foreground = new Image8(imageWidth, imageHeight); _bgSubtracted = new Image8(imageWidth, imageHeight); _calc = new Image8(imageWidth, imageHeight); _dishCenter = dishCenter; int bufferSize = 0; IppHelper.IppCheckCall(cv.ippiLabelMarkersGetBufferSize_8u_C1R(new IppiSize(imageWidth, imageHeight), &bufferSize)); _markerBuffer = (byte *)Marshal.AllocHGlobal(bufferSize); int momentSize = 0; IppHelper.IppCheckCall(ip.ippiMomentGetStateSize_64f(IppHintAlgorithm.ippAlgHintNone, &momentSize)); _momentState = (IppiMomentState_64f *)Marshal.AllocHGlobal(momentSize); //let ipp decide whether to give accurate or fast results IppHelper.IppCheckCall(ip.ippiMomentInit_64f(_momentState, IppHintAlgorithm.ippAlgHintNone)); _frame = 0; //populate tracking parameters with default values _threshold = 6; _minArea = 11; _maxAllowedArea = 120; _minEccentricity = 0.3; _fullTrustMinArea = 20; _imageROI = new IppiROI(0, 0, imageWidth, imageHeight); _searchRegionSize = 90; _removeCMOSISBrightLineArtefact = false; _strel3x3 = Morphology.Generate3x3Mask(_foreground.Size); //The following calculation for FramesInBackground means that after ~30s of movie //a stationary object will have dissappeared into the background (at 63% level) FramesInBackground = (int)((30 * 250)); FramesInitialBackground = 2 * 30 * 250; BGUpdateEvery = 2; }
/// <summary> /// Generates a circular mask to use in morphological operations /// with the anchor in the center /// </summary> /// <param name="radius">The radius of the mask as maximum distance from center pixel (i.e. radius=0 would be 1-pixel mask)</param> /// <returns>The mask</returns> public static MorphologyMask GenerateDiskMask(int radius) { if (radius < 1) { throw new ArgumentOutOfRangeException("radius"); } //Masks in ipps morphological functions don't really allow for stride //unless it is guaranteed, that the pixels within the stride are of value0 //therefore we generate the mask with the minimum possible width and height //that is dividable by 4 int diameter = 1 + 2 * radius;//center pixel is extra int imageWidth = (int)(Math.Ceiling(diameter / 4.0) * 4); IppiPoint center = new IppiPoint(radius, radius); Image8 mask = new Image8(imageWidth, diameter); //Set all pixels to 0 IppHelper.IppCheckCall(ip.ippiSet_8u_C1R(0, mask.Image, mask.Stride, mask.Size)); //Loop over pixels, check distance and set to 1 if within circle for (int x = 0; x < mask.Width; x++) { for (int y = 0; y < mask.Width; y++) { if (Distance.Euclidian(new IppiPoint(x, y), center) <= radius) { *mask[x, y] = 1; } } } //default anchor is in the circle's center return(new MorphologyMask(mask, center)); }
/// <summary> /// Three-point calibration of coordinate-voltage mapping. Using the origin at 0V/0V and two points /// for averaging purposes /// </summary> /// <param name="origin">The coordinates of the origin at 0V/0V</param> /// <param name="p1">First calibration point</param> /// <param name="p2">Second calibration point</param> public void Calibrate(IppiPoint origin, PointVoltagePair p1, PointVoltagePair p2) { //distances from plane to mirror calculated for each point double x1d, x2d, y1d, y2d; //distance from the point in question to the origin double pointdist; //Calculate first distance measure for x-mirror pointdist = p1.Coordinate.x - origin.x; x1d = pointdist / Math.Tan(4 * Math.PI * p1.XVoltage / 180); //Calculate second distance measure for x-mirror pointdist = p2.Coordinate.x - origin.x; x2d = pointdist / Math.Tan(4 * Math.PI * p2.XVoltage / 180); //Calculate first distance measure for y-mirror pointdist = p1.Coordinate.y - origin.y; y1d = pointdist / Math.Tan(4 * Math.PI * p1.YVoltage / 180); //Calculate second distance measure for y-mirror pointdist = p2.Coordinate.y - origin.y; y2d = pointdist / Math.Tan(4 * Math.PI * p2.YVoltage / 180); //Calculate distance for x-mirror XDistance = (x1d + x2d) / 2; //Calculate distance for y-mirror YDistance = (y1d + y2d) / 2; //Create x-lookup table for (int x = 0; x < _imageWidth; x++) { _xVolts[x] = (float)(Math.Atan((x - origin.x) / XDistance) * 45 / Math.PI); } //Create y-lookup table for (int y = 0; y < _imageHeight; y++) { _yVolts[y] = (float)(Math.Atan((y - origin.y) / YDistance) * 45 / Math.PI); } Complete = true; }
/// <summary> /// Safely copies region around given centroid /// </summary> /// <param name="centroid">The centroid around which image region should be copied</param> /// <param name="regionImage">The target of the copy</param> /// <param name="image">The source of the copy operation</param> public static void CopyRegionImage(IppiPoint centroid, Image8 regionImage, Image8 image) { IppiPoint copyStart = new IppiPoint(centroid.x - regionImage.Width / 2, centroid.y - regionImage.Height / 2); int destX = 0; int destY = 0; if (copyStart.x < 0) { destX -= copyStart.x; copyStart.x = 0; } if (copyStart.y < 0) { destY -= copyStart.y; copyStart.y = 0; } IppiSize copySize = new IppiSize(regionImage.Width - destX, regionImage.Height - destY); if (copyStart.x + copySize.width > image.Width) { copySize.width = image.Width - copyStart.x; } if (copyStart.y + copySize.height > image.Height) { copySize.height = image.Height - copyStart.y; } if (copySize.width > 0 && copySize.height > 0) { ip.ippiCopy_8u_C1R(image[copyStart], image.Stride, regionImage[destX, destY], regionImage.Stride, copySize); } }
/// <summary> /// Adds a new pixel to the blob /// </summary> /// <param name="x">The x-coordinate of the pixel</param> /// <param name="y">The y-coordinate of the pixel</param> /// <param name="value">The intensity value of the pixel</param> public void AddPixel(int x, int y) { //Update bounding points of bounding box and values important for contrast calculation if (Moment00 < 1) { //we are adding the first element _boundingBoxUpperLeft = new IppiPoint(x, y); _boundingBoxLowerRight = new IppiPoint(x, y); } else { _boundingBoxUpperLeft.x = Math.Min(_boundingBoxUpperLeft.x, x); _boundingBoxLowerRight.x = Math.Max(_boundingBoxLowerRight.x, x); _boundingBoxUpperLeft.y = Math.Min(_boundingBoxUpperLeft.y, y); _boundingBoxLowerRight.y = Math.Max(_boundingBoxLowerRight.y, y); } //Update moments Moment00++; Moment10 += x; Moment01 += y; Moment20 += x * x; Moment11 += x * y; Moment02 += y * y; Moment30 += x * x * x; Moment03 += y * y * y; Moment21 += x * x * y; Moment12 += x * y * y; }
/// <summary> /// Process the next frame /// </summary> /// <param name="frameNumber">The frame index</param> /// <param name="camImage">The camera image</param> /// <param name="poi">The fish location</param> /// <returns>Whether experiment should continue or not</returns> public override bool ProcessNext(int frameNumber, Image8 camImage, out IppiPoint?poi) { base.ProcessNext(frameNumber, camImage, out poi); if (_scanner == null) { System.Diagnostics.Debug.WriteLine("Scanner was not initialized. Terminating experiment"); return(false); } int writeEvery = Properties.Settings.Default.FrameRate * 4; if (frameNumber % writeEvery == writeEvery - 1) { _laser.LaserPower = Properties.Settings.Default.LaserCalibPowermW; } else { _laser.LaserPower = 0; } _lastFrame = frameNumber; if (frameNumber >= _totalFrames) { return(false); } BlobWithMoments fish = null; // Every 4s we turn on the laser - those frames won't be tracked but the image will be saved if (frameNumber % writeEvery != 0) { fish = Tracker.Track(camImage); } if (fish != null) { poi = new IppiPoint(fish.Centroid.x, fish.Centroid.y); if (_scanner != null) { _scanner.Hit(poi.GetValueOrDefault()); } } if (_trackWriter != null) { if (fish != null) { _trackWriter.WriteLine("{0}\t{1}\t{2}\t{3}", frameNumber, fish.Centroid.x, fish.Centroid.y, fish.Angle); } else { _trackWriter.WriteLine("NaN\tNaN\tNaN\tNaN"); } // Write image of Laser On frames and the two surrounding frames on each side to file if (frameNumber % writeEvery <= 2 || frameNumber % writeEvery >= writeEvery - 2) { _imageWriter.WriteFrame(camImage); } } return(true); }
/// <summary> /// Draws a multiplicative mask for positioning a circular disk highlighting the center /// and circumference of a defined circle. Marked pixels are set to 0 all other to 1 /// </summary> /// <param name="maskImage">The image on which the mask is drawn</param> /// <param name="center">The center of the circle - will be marked with 10px sized filled circle</param> /// <param name="maskRadius">The radius of the circle - will be marked with 3px wide line on the outside of maskRadius</param> public static void CreateCircularMask(Image8 maskImage, IppiPoint center, int maskRadius) { if (maskImage == null) { throw new ArgumentNullException(nameof(maskImage)); } if (maskImage.IsDisposed) { throw new ObjectDisposedException(nameof(maskImage)); } //Set all pixels to 1 ip.ippiSet_8u_C1R(1, maskImage.Image, maskImage.Stride, maskImage.Size); //Loop over pixels that are within the outer square of the mask, considering the line thickness as well int minX = center.x - maskRadius - 3; if (minX < 0) { minX = 0; } int maxX = center.x + maskRadius + 3; if (maxX >= maskImage.Size.width) { maxX = maskImage.Size.width - 1; } int minY = center.y - maskRadius - 3; if (minY < 0) { minY = 0; } int maxY = center.y + maskRadius + 3; if (maxY >= maskImage.Size.height) { maxY = maskImage.Size.height - 1; } IppiPoint testP = new IppiPoint(); for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { testP.x = x; testP.y = y; //Test if we are either in the boundary line or within the innner marker var dist = Distance.Euclidian(center, testP); if (dist <= 10 || (dist >= maskRadius && dist <= maskRadius + 3)) { byte *pixel = maskImage.Image + x + maskImage.Stride * y; * pixel = 0; } } } }
/// <summary> /// The BGR components of a given pixel in the image /// </summary> /// <param name="point">The point referencing the pixel</param> /// <returns>Pointer to the pixel in the buffer</returns> public byte[] this[IppiPoint point] { get { return(this[point.x, point.y]); } set { this[point.x, point.y] = value; } }
/// <summary> /// Pointer to a given pixel in the image buffer /// </summary> /// <param name="point">The point referencing the pixel</param> /// <returns>Pointer to the pixel in the buffer</returns> public byte *this[IppiPoint point] { get { if (point.x >= Width || point.y >= Height) { throw new IndexOutOfRangeException("The indexed point lies outside of the image"); } return(Image + point.x + point.y * Stride); } }
/// <summary> /// Process the next preview frame /// </summary> /// <param name="frameNumber">The current frame number (ignored)</param> /// <param name="camImage">The current camera image</param> /// <param name="poi">The centroid of the fish if found</param> /// <returns>True</returns> public override bool ProcessNext(int frameNumber, Image8 camImage, out IppiPoint?poi) { base.ProcessNext(frameNumber, camImage, out poi); var fish = Tracker.Track(camImage); if (fish != null) { poi = new IppiPoint(fish.Centroid.x, fish.Centroid.y); } return(true);//Preview only stopped from outside }
/// <summary> /// Creates a new morhpology mask /// </summary> /// <param name="mask">The mask image</param> /// <param name="anchor">The anchor</param> public MorphologyMask(Image8 mask, IppiPoint anchor) { if (mask == null) { throw new ArgumentNullException("mask"); } if (anchor.x >= mask.Width || anchor.y >= mask.Height || anchor.x < 0 || anchor.y < 0) { throw new ArgumentOutOfRangeException("anchor", "Anchor point has to lie within the image"); } _mask = mask; _anchor = anchor; }
/// <summary> /// Based on the current position computes the next /// position in the 8-connected neighborhood /// </summary> /// <param name="current">The current position</param> /// <returns>The next position</returns> public static IppiPoint Next(IppiPoint current) { var decider = _decider.Next(0, 8); IppiPoint retval = new IppiPoint(current.x, current.y); switch (decider) { case 0: retval.y++; break; case 1: retval.x++; retval.y++; break; case 2: retval.x++; break; case 3: retval.x++; retval.y--; break; case 4: retval.y--; break; case 5: retval.x--; retval.y--; break; case 6: retval.x--; break; case 7: retval.x--; retval.y++; break; default: System.Diagnostics.Debug.Assert(false, "Unrecognized decider in random walk"); break; } return(retval); }
public override IppiPoint_32f this[IppiPoint p] { get { if (p.x >= _imageWidth || p.x < 0 || p.y >= _imageHeight || p.y < 0) { throw new ArgumentOutOfRangeException("Requested coordinate is outside of the calibrated image dimensions"); } if (!Complete) { throw new NotSupportedException("Can't obtain voltages before calibration"); } IppiPoint_32f retval = new IppiPoint_32f(_xVolts[p.x], _yVolts[p.y]); return(retval); } }
/// <summary> /// Uses three point calibration data to suggest mirror voltages to hit a given point /// </summary> /// <param name="coordinates"></param> /// <returns></returns> private IppiPoint_32f GetMirrorVolts(IppiPoint coordinates_desired) { //Subract origin coordinates from the desired coordinates IppiPoint coordinates = new IppiPoint(coordinates_desired.x - _p0.x, coordinates_desired.y - _p0.y); var desired_rotation = _camera_theta * -1;//back rotation var dx_mirror = coordinates.x * Math.Cos(desired_rotation) - coordinates.y * Math.Sin(desired_rotation); var dy_mirror = coordinates.x * Math.Sin(desired_rotation) + coordinates.y * Math.Cos(desired_rotation); var vx = Math.Atan(dx_mirror / _camera_height); var vy = Math.Atan(dy_mirror / _camera_height); if (_isYReflected) { vy *= -1; } return(new IppiPoint_32f((float)MirrorVoltage(vx), (float)MirrorVoltage(vy))); }
/// <summary> /// Adds a new point to the calibration table without checking its validity. Returns /// true until the table is complete and provides the next point to target /// </summary> /// <param name="calibrationPoint">The new calibration point to add to the table</param> /// <param name="nextPoint">The point expected next in the calibration</param> /// <returns>True until the table is complete</returns> public bool ForceAddNext(PointVoltagePair calibrationPoint, out IppiPoint nextPoint) { if (_addIndex >= _xVolts.Length) { System.Diagnostics.Debug.WriteLine("Tried to add new point to already complete calibration table"); nextPoint = new IppiPoint(-1, -1); return(false); } int x, y; if (_addIndex == -1) { _addIndex++; //Compute next point x = _addIndex % _c; x = x * _spacing + _scanRoi.X; y = (_addIndex - _addIndex % _c) / _c; y = y * _spacing + _scanRoi.Y; nextPoint = new IppiPoint(x, y); return(true); } //Add point to table _xVolts[_addIndex] = calibrationPoint.XVoltage; _yVolts[_addIndex] = calibrationPoint.YVoltage; _addIndex++; if (_addIndex >= _xVolts.Length) { nextPoint = new IppiPoint(-1, -1); _complete = true; return(false); } else { //Compute next point x = _addIndex % _c; x = x * _spacing + _scanRoi.X; y = (_addIndex - _addIndex % _c) / _c; y = y * _spacing + _scanRoi.Y; nextPoint = new IppiPoint(x, y); return(true); } }
/// <summary> /// Inverses a coordinate rotation within a given reference frame /// </summary> /// <param name="original">The rotated coordinate</param> /// <param name="size">The dimensions of the reference frame</param> /// <param name="rot">The original rotation</param> /// <returns>The original point</returns> public static IppiPoint RotateInverse(IppiPoint rotated, IppiSize size, Rotation rot) { switch (rot) { case Rotation.None: return(rotated); case Rotation.Clock90: return(new IppiPoint(rotated.x, size.height - rotated.y)); case Rotation.Clock180: return(new IppiPoint(size.width - rotated.x, size.height - rotated.y)); default: return(new IppiPoint(size.width - rotated.y, rotated.x)); } }
/// <summary> /// Rotates a coordinate within a given reference frame /// </summary> /// <param name="original">The original coordinate</param> /// <param name="size">The dimensions of the reference frame</param> /// <param name="rot">The desired rotation</param> /// <returns>The rotated coordinate</returns> public static IppiPoint Rotate(IppiPoint original, IppiSize size, Rotation rot) { switch (rot) { case Rotation.None: return(original); case Rotation.Clock90: return(new IppiPoint(size.height - original.y, original.x)); case Rotation.Clock180: return(new IppiPoint(size.width - original.x, size.height - original.y)); default: return(new IppiPoint(original.y, size.width - original.x)); } }
/// <summary> /// Creates a new tail tracker /// </summary> /// <param name="regionToTrack">The ROI in which we should track the tail</param> /// <param name="tailStart">The designated starting point of the tail</param> /// <param name="tailEnd">The designated end point of the tail</param> /// <param name="nsegments">The number of tail segments to track btw. start and end</param> public TailTracker(IppiSize imageSize, IppiPoint tailStart, IppiPoint tailEnd, int nsegments) { _threshold = 20; _morphSize = 8; _frameRate = 200; _strel = BWImageProcessor.GenerateDiskMask(_morphSize); _nSegments = 5; _imageSize = imageSize; _tailStart = tailStart; _tailEnd = tailEnd; //set up our track regions based on the tail positions DefineTrackRegions(); NSegments = nsegments; InitializeImageBuffers(); //set up image buffers InitializeScanPoints(); //create scan points appropriate for the tail parameters //Initialize our angle store for tracking (size never changes) _angleStore = (int *)System.Runtime.InteropServices.Marshal.AllocHGlobal(900 * 4); }
/// <summary> /// Process the next frame /// </summary> /// <param name="frameNumber">The frame index</param> /// <param name="camImage">The camera image</param> /// <param name="poi">The fish location</param> /// <returns>Whether experiment should continue or not</returns> public override bool ProcessNext(int frameNumber, Image8 camImage, out IppiPoint?poi) { base.ProcessNext(frameNumber, camImage, out poi); _lastFrame = frameNumber; if (frameNumber >= _totalFrames) { return(false); } var fish = Tracker.Track(camImage); if (fish != null) { poi = new IppiPoint(fish.Centroid.x, fish.Centroid.y); } if (_trackWriter != null) { if (fish != null) { _trackWriter.WriteLine("{0}\t{1}\t{2}\t{3}", frameNumber, fish.Centroid.x, fish.Centroid.y, fish.Angle); if (_writeFishImages) { //blank and copy ip.ippiSet_8u_C1R(0, _camRegion.Image, _camRegion.Stride, _camRegion.Size); MainViewModel.CopyRegionImage(fish.Centroid, _camRegion, camImage); _imageWriter.WriteFrame(_camRegion); MainViewModel.CopyRegionImage(fish.Centroid, _camRegion, Tracker.Background); _backgroundWriter.WriteFrame(_camRegion); } } else { _trackWriter.WriteLine("NaN\tNaN\tNaN\tNaN"); if (_writeFishImages) { //blank ip.ippiSet_8u_C1R(0, _camRegion.Image, _camRegion.Stride, _camRegion.Size); _imageWriter.WriteFrame(_camRegion); _backgroundWriter.WriteFrame(_camRegion); } } } return(true); }
/// <summary> /// Adds a new point to the calibration table, verifies that it has the expected coordinates /// and returns true until the table is complete /// </summary> /// <param name="calibrationPoint">The new calibration point to add to the table</param> /// <param name="nextPoint">The point expected next in the calibration</param> /// <returns>True while still expecting new points</returns> /// <exception cref="ArgumentExcpetion">Thrown if the wrong calibration point is provided</exception> public bool AddNext(PointVoltagePair calibrationPoint, out IppiPoint nextPoint) { int x, y; //Verify new point if (_addIndex > -1 && _addIndex < _xVolts.Length) { x = _addIndex % _c; x = x * _spacing + _scanRoi.X; y = (_addIndex - _addIndex % _c) / _c; y = y * _spacing + _scanRoi.Y; if (calibrationPoint.Coordinate.x != x || calibrationPoint.Coordinate.y != y) { throw new ArgumentException("Wrong calibration point provided"); } } return(ForceAddNext(calibrationPoint, out nextPoint)); }
/// <summary> /// Tries to target a given coordinate /// </summary> /// <param name="coordinate">The 2D point coordinate which should be targeted</param> /// <returns>The status code of the targeting attempt</returns> public HitStatus Hit(IppiPoint coordinate) { if (CoordinateConverter == null) { //No lookup table present. Try to interpret coordinates as raw voltages if (coordinate.x < VMin || coordinate.y < VMin || coordinate.x > VMax || coordinate.y > VMax) { return(HitStatus.OutOfRange); } //_xWriter.WriteSingleSample(true, coordinate.x); //_yWriter.WriteSingleSample(true, coordinate.y); double[] voltages = new double[2]; voltages[0] = coordinate.x; voltages[1] = coordinate.y; _multiWriter.WriteSingleSample(true, voltages); return(HitStatus.NoConversion); } return(Hit(CoordinateConverter[coordinate])); }
/// <summary> /// Generates a rectangular mask to use in morphological operations /// with the anchor in the center /// </summary> /// <param name="width">The width of the rectangle</param> /// <param name="height">The height of the rectangle</param> /// <returns>The morphology mask</returns> public static MorphologyMask GenerateRectMask(int width, int height) { if (width < 1) { throw new ArgumentOutOfRangeException("width"); } if (height < 1) { throw new ArgumentOutOfRangeException("height"); } int imageWidth = (int)(Math.Ceiling(width / 4.0) * 4); IppiPoint center = new IppiPoint((int)Math.Floor(width / 4.0), (int)Math.Floor(height / 4.0)); Image8 mask = new Image8(imageWidth, height); //Set all pixels to zero IppHelper.IppCheckCall(ip.ippiSet_8u_C1R(0, mask.Image, mask.Stride, mask.Size)); //Set rectangle to 1 IppHelper.IppCheckCall(ip.ippiSet_8u_C1R(1, mask.Image, mask.Stride, new IppiSize(width, height))); return(new MorphologyMask(mask, center)); }
/// <summary> /// Three point calibration for coordinate voltage mapping. Uses three arbitrary points to calculate the mapping. /// The three points are used to form two pairs for mapping /// </summary> /// <param name="p1">The first point and its corresponding voltage</param> /// <param name="p2">The second point and its corresponding voltage</param> /// <param name="p3">The third point and its corresponding voltage</param> public void Calibrate(PointVoltagePair p1, PointVoltagePair p2, PointVoltagePair p3) { //distance from the point in question to the first point double pointdist; //To build the lookup table we need the coordinates of the origin //to have a rectangular triangle reference //We try linear interpolation based on the points we got IppiPoint origin = new IppiPoint(); pointdist = p2.Coordinate.x - p1.Coordinate.x; double distpervolts = pointdist / (p2.XVoltage - p1.XVoltage); origin.x = (int)(p1.Coordinate.x - p1.XVoltage * distpervolts); pointdist = p2.Coordinate.y - p1.Coordinate.y; distpervolts = pointdist / (p2.YVoltage - p1.YVoltage); origin.y = (int)(p1.Coordinate.y - p1.YVoltage * distpervolts); Calibrate(origin, p2, p3); return; }
/// <summary> /// Calculates the rotation of the camera FOV relative to the mirror FOV /// </summary> /// <param name="pAlign">The pixel coordinates of the alignment beam</param> /// <param name="is_y_displacement">If this is calculated for y-excursion on the mirror need to adjust angle</param> /// <returns>The rotation angle of the camera axes relative to the mirror axes in radians</returns> private double CalculateCameraTheta(IppiPoint pAlign, bool is_y_displacement) { double dx = pAlign.x - _p0.x; double dy = pAlign.y - _p0.y; double radius = Math.Sqrt(dx * dx + dy * dy); if (radius == 0) { throw new ArgumentException("Alignment radius is 0. Increase alignment mirror excursion voltages."); } double offset = is_y_displacement ? Math.PI / 2 : 0; double theta = Math.Acos(dx / radius); if (dy >= 0) { return(theta - offset); } else { return(2 * Math.PI - theta - offset); } }
/// <summary> /// Based on the current position computes the next /// position in the 8-connected neighborhood only returning /// points within the given rectangular limits /// </summary> /// <param name="current">The current position</param> /// <param name="xmin">The minimal allowed x-coordinate (inclusive)</param> /// <param name="xmax">The maximal allowed x-coordinate (inclusive)</param> /// <param name="ymin">The minimal allowed y-coordinate (inclusive)</param> /// <param name="ymax">The maximal allowed y-coordinate (inclusive)</param> /// <returns>The new point within the limits</returns> public static IppiPoint Next(IppiPoint current, int xmin, int xmax, int ymin, int ymax) { if (xmin >= xmax || ymin >= ymax) { throw new ArgumentException("Minimal limit must be less than maximal limit"); } if (current.x < xmin || current.x > xmax || current.y < ymin || current.y > ymax) { throw new ArgumentException("Current point can't be outside the set limits"); } //increment our recursion counter _recurseDepth++; IppiPoint retval = Next(current); //if the returned point is outside of the limits, retry recursively if (retval.x < xmin || retval.x > xmax || retval.y < ymin || retval.y > ymax) { if (_recurseDepth > MaxRecurseDepth) { //have reached recursion limit - don't walk _recurseDepth--; return(current); } retval = Next(current, xmin, xmax, ymin, ymax); //we are about to return, decrement recursion counter _recurseDepth--; return(retval); } else { //return, decrement recursion counter _recurseDepth--; return(retval); } }
/// <summary> /// Based on the current position computes the next /// position in the 8-connected neighborhood only returning /// points within the given circle /// </summary> /// <param name="current">The current position</param> /// <param name="center">The center of the circle of valid positions</param> /// <param name="radius">The radius of the valid area</param> /// <returns>The new point within the limits</returns> public static IppiPoint Next(IppiPoint current, IppiPoint center, float radius) { if (radius < 2) { throw new ArgumentOutOfRangeException("radius", "The radius has to be two or more pixels"); } if (Distance.Euclidian(current, center) > radius) { throw new ArgumentException("The current point can't be outside of the valid circle."); } //increment our recursion counter _recurseDepth++; IppiPoint retval = Next(current); //if the returned point is outside of the limits, retry recursively if (Distance.Euclidian(retval, center) > radius) { if (_recurseDepth > MaxRecurseDepth) { //have reached recursion limit - don't walk _recurseDepth--; return(current); } retval = Next(current, center, radius); //we are about to return, decrement recursion counter _recurseDepth--; return(retval); } else { //return, decrement recursion counter _recurseDepth--; return(retval); } }
public PointVoltagePair(IppiPoint coordinate, float xvoltage, float yvoltage) { Coordinate = coordinate; XVoltage = xvoltage; YVoltage = yvoltage; }
/// <summary> /// Processes the next point, filtering its position /// computing speeds and detecting bouts /// </summary> /// <param name="coordinate">The coordinate to analyze</param> /// <returns>The analysis of the given coordinate</returns> public PointAnalysis ProcessNextPoint(IppiPoint coordinate) { //set up locals for processing float x = coordinate.x; float y = coordinate.y; float xSmooth, ySmooth, speed; xSmooth = ySmooth = speed = 0; //optional filtered speed float spdSmooth = 0; //filter x-coordinate sp.ippsFIR_32f(&x, &xSmooth, 1, _filterStateX); //filter y-coordinate sp.ippsFIR_32f(&y, &ySmooth, 1, _filterStateY); //compute instant speed speed = (float)Math.Sqrt((xSmooth - _lastX) * (xSmooth - _lastX) + (ySmooth - _lastY) * (ySmooth - _lastY)); speed *= 240; //if requested, also filter the instant-speed value if (_spdTapLength > 0) { sp.ippsFIR_32f(&speed, &spdSmooth, 1, _filterStateSpd); } else { spdSmooth = speed; } //update last coordinates _lastX = xSmooth; _lastY = ySmooth; //Bout detection if (_boutDetectionState.InPutativeBout) //we are currently adding frames to a new putative bout { if (spdSmooth < _speedThreshold) //putative bout is finished { _boutDetectionState.InPutativeBout = false; //reset if (_boutDetectionState.FramesInBout >= _minFramesPerBout && _boutDetectionState.FramesAtPeak <= _maxFramesAtPeak) //bout was valid, fill bout structure and return it { Bout bout = new Bout(); bout.BoutEnd = _index - 1; bout.BoutStart = _boutDetectionState.StartFrame; bout.BoutPeak = _boutDetectionState.PeakFrame; if (bout.BoutPeak == bout.BoutStart) { bout.BoutStart--;//don't start bout on peakframe } bout.PeakSpeed = _boutDetectionState.PeakSpeed; bout.TotalDisplacement = _boutDetectionState.TotalDisplacement; bout.StartingCoordinate = _boutDetectionState.StartingCoordinate; bout.FinishCoordinate = new IppiPoint_32f(xSmooth, ySmooth); //don't forget to update the index before we return _index++; return(new PointAnalysis(bout, coordinate, new IppiPoint_32f(xSmooth, ySmooth), spdSmooth)); } } else//putative bout continues { _boutDetectionState.FramesInBout++; _boutDetectionState.TotalDisplacement += spdSmooth / _frameRate; if (spdSmooth > _boutDetectionState.PeakSpeed)//new peak-speed detected { _boutDetectionState.PeakSpeed = spdSmooth; _boutDetectionState.PeakFrame = _index; _boutDetectionState.FramesAtPeak = 1; //reset } else if (spdSmooth == _boutDetectionState.PeakSpeed) //maybe not the best criterion - equality of float values very unlikely { _boutDetectionState.FramesAtPeak++; } } } else//currently the fish is stationary { if (spdSmooth > _speedThreshold)//we should start a new putative bout { _boutDetectionState.StartBout(spdSmooth, _index, _frameRate); _boutDetectionState.StartingCoordinate.x = xSmooth; _boutDetectionState.StartingCoordinate.y = ySmooth; } } //advance index _index++; //return our analysis return(new PointAnalysis(coordinate, new IppiPoint_32f(xSmooth, ySmooth), spdSmooth)); }