public Tracker90mmDish(int imageWidth, int imageHeight) { _foreground = new Image8(imageWidth, imageHeight); _calc = new Image8(imageWidth, imageHeight); _labelMarkers = new Image8(imageWidth, imageHeight); int bufferSize = 0; IppHelper.IppCheckCall(cv.ippiLabelMarkersGetBufferSize_8u_C1R(new IppiSize(imageWidth, imageHeight), &bufferSize)); _markerBuffer = (byte *)Marshal.AllocHGlobal(bufferSize); fixed(IppiMomentState_64s **ppState = &_momentState) { //let ipp decide whether to give accurate or fast results IppHelper.IppCheckCall(ip.ippiMomentInitAlloc_64s(ppState, IppHintAlgorithm.ippAlgHintNone)); } _frame = 0; //populate tracking parameters with default values _threshold = 5; _minArea = 10; _maxArea = 300; _fullTrustMinArea = 20; _imageROI = new IppiROI(0, 0, imageWidth, imageHeight); //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 * 240)); FramesInitialBackground = 2 * FramesInBackground; }
/// <summary> /// Method for parallelized tracking - depending on index /// performs tracking on an image chunk /// </summary> /// <param name="index">The chunk's index</param> private void TrackChunk(int index) { //each chunk has its own image region with its own wells to take care of //currently, only 4 chunks on a 24-well plate are supported if (index >= _parallelChunks) { System.Diagnostics.Debug.WriteLine("Fatal parallel tracking error. Tried to access non-existing chunk."); } //do image filtering, thresholding and closing if (DoMedianFiltering) { IppHelper.IppCheckCall(ip.ippiFilterMedianWeightedCenter3x3_8u_C1R(_calc[_parallelImageRegions[index].X + 1, _parallelImageRegions[index].Y + 1], _calc.Stride, _foreground[_parallelImageRegions[index].X + 1, _parallelImageRegions[index].Y + 1], _foreground.Stride, new IppiSize(_parallelImageRegions[index].Width - 2, _parallelImageRegions[index].Height - 2), 1)); } Im2Bw(_foreground, _parallelImageRegions[index]); Close3x3(_foreground, _parallelImageRegions[index]); //loop over the wells that belong to our region, extract the fish //and put into our result structure //Important: Need to put into the chunks section of the results section! var ourWells = _parallelChunkWells[index]; for (int i = 0; i < ourWells.Count; i++) { _parallelTrackResults[i + _parallelCumWellCount[index]] = ExtractWellParallel(ourWells[i], index); } }
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> /// Computes instants speeds from a coordinate trace and stores the result /// in the provided buffer /// </summary> /// <param name="path">The movement path</param> /// <param name="frameRate">The framerate at acquisition</param> /// <param name="isb">The buffer to store the speed in</param> public void ComputeInstantSpeeds(CentroidBuffer path, int frameRate, InstantSpeedBuffer isb) { if (IsDisposed) { throw new ObjectDisposedException(this.ToString()); } if (isb.Size.width != path.Size.width) { throw new ArgumentException("Path length and size of speed buffer must be the same!"); } //Check that our buffers are present and match, otherwise fix if (_calc1 == null) { _calc1 = new CentroidBuffer(path.Size.width); _calc2 = new CentroidBuffer(path.Size.width); } else if (_calc1.Size.width != path.Size.width) { _calc1.Dispose(); _calc2.Dispose(); _calc1 = new CentroidBuffer(path.Size.width); _calc2 = new CentroidBuffer(path.Size.width); } //compute difference trace subtracting the position at t from the position at t+1 by subtracting the unshifted input buffer from its shifted version //the result gets stored in calc1 with the first value being 0 ip.ippiSet_32f_C1R(0, _calc1.Buffer, _calc1.Stride, _calc1.Size); IppHelper.IppCheckCall(ip.ippiSub_32f_C1R(path.Buffer, path.Stride, (float *)((byte *)path.Buffer + 4), path.Stride, (float *)((byte *)_calc1.Buffer + 4), _calc1.Stride, new IppiSize(path.Size.width - 1, 2))); //square the difference trace, storing the result in _calc2 IppHelper.IppCheckCall(ip.ippiSqr_32f_C1R(_calc1.Buffer, _calc1.Stride, _calc2.Buffer, _calc2.Stride, _calc1.Size)); //Add the values of the x and y coordinates, storing the sum of squares in the first row of _calc1 IppHelper.IppCheckCall(ip.ippiAdd_32f_C1R(_calc2.Buffer, _calc2.Stride, (float *)((byte *)_calc2.Buffer + _calc2.Stride), _calc2.Stride, _calc1.Buffer, _calc1.Stride, new IppiSize(_calc1.Size.width, 1))); //Compute the square root of the sum-of-squares and copy the result to the first row of _calc2 IppHelper.IppCheckCall(ip.ippiSqrt_32f_C1R(_calc1.Buffer, _calc1.Stride, _calc2.Buffer, _calc2.Stride, new IppiSize(_calc1.Size.width, 1))); //Multiply the distances by the framerate and store the result in our instant speed buffer IppHelper.IppCheckCall(ip.ippiMulC_32f_C1R(_calc2.Buffer, _calc2.Stride, frameRate, isb.Buffer, isb.Stride, isb.Size)); }
/// <summary> /// Extracts a fish (candidate) from an image by performing background subtraction, noise filtering, thresholding and closing to obtain a foreground /// followed by marker extraction. /// </summary> /// <param name="im">The image to extract the fish from</param> /// <param name="region">The ROI to search</param> /// <returns>The most likely fish blob or null if no suitable candidate was found</returns> protected BlobWithMoments ExtractFish(/*Image8 im,*/ IppiROI region) { int nMarkers = 0; BlobWithMoments[] blobsDetected; //Copy foreground to marker and label connected components //IppHelper.IppCheckCall(ip.ippiCopy_8u_C1R(_foreground[region.TopLeft], _foreground.Stride, _labelMarkers[region.TopLeft], _labelMarkers.Stride, region.Size)); IppHelper.IppCheckCall(cv.ippiLabelMarkers_8u_C1IR(_foreground[region.TopLeft], _foreground.Stride, region.Size, 1, 254, IppiNorm.ippiNormInf, &nMarkers, _markerBuffer)); //loop over returned markers and use ipp to extract blobs if (nMarkers > 0) { if (nMarkers > 254) { nMarkers = 254; } blobsDetected = new BlobWithMoments[nMarkers]; for (int i = 1; i <= nMarkers; i++) { //label all pixels with the current marker as 255 and others as 0 IppHelper.IppCheckCall(ip.ippiCompareC_8u_C1R(_foreground[region.TopLeft], _foreground.Stride, (byte)i, _calc[region.TopLeft], _calc.Stride, region.Size, IppCmpOp.ippCmpEq)); //calculate image moments IppHelper.IppCheckCall(ip.ippiMoments64s_8u_C1R(_calc[region.TopLeft], _calc.Stride, region.Size, _momentState)); //retrieve moments long m00 = 0; long m10 = 0; long m01 = 0; long m20 = 0; long m02 = 0; long m11 = 0; long m30 = 0; long m03 = 0; long m21 = 0; long m12 = 0; ip.ippiGetSpatialMoment_64s(_momentState, 0, 0, 0, new IppiPoint(region.X, region.Y), &m00, 0); //since our input image is not 0s and 1s but 0s and 255s we have to divide by 255 in order to re-normalize our moments System.Diagnostics.Debug.Assert(m00 % 255 == 0, "M00 was not a multiple of 255"); m00 /= 255; //only retrieve other moments if this is a "fish candidate" if (m00 > MinArea && m00 <= MaxArea) { ip.ippiGetSpatialMoment_64s(_momentState, 1, 0, 0, new IppiPoint(region.X, region.Y), &m10, 0); m10 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 0, 1, 0, new IppiPoint(region.X, region.Y), &m01, 0); m01 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 2, 0, 0, new IppiPoint(region.X, region.Y), &m20, 0); m20 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 0, 2, 0, new IppiPoint(region.X, region.Y), &m02, 0); m02 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 1, 1, 0, new IppiPoint(region.X, region.Y), &m11, 0); m11 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 3, 0, 0, new IppiPoint(region.X, region.Y), &m30, 0); m30 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 0, 3, 0, new IppiPoint(region.X, region.Y), &m03, 0); m03 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 2, 1, 0, new IppiPoint(region.X, region.Y), &m21, 0); m21 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 1, 2, 0, new IppiPoint(region.X, region.Y), &m12, 0); m12 /= 255; blobsDetected[i - 1] = new BlobWithMoments(m00, m10, m01, m20, m11, m02, m30, m03, m21, m12); //Determine bounding box of the blob. The following seems kinda retarded as Ipp must already //have obtained that information before so maybe there is some way to actually retrieve it?? //Do linescans using ipp's sum function starting from the blobs centroid until we hit a line //the sum of which is 0 int xStart, xEnd, yStart, yEnd; double sum = 1; IppiPoint centroid = blobsDetected[i - 1].Centroid; xStart = centroid.x - 5; xEnd = centroid.x + 5; yStart = centroid.y - 5; yEnd = centroid.y + 5; //in the following loops we PRE-increment, whence we stop the loop if we are at one coordinate short of the ends //find xStart while (sum > 0 && xStart > region.X + 4) { xStart -= 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xStart, region.Y], _calc.Stride, new IppiSize(1, region.Height), &sum)); } xStart += 1;//we have a sum of 0, so go back one line towards the centroid //find xEnd sum = 1; while (sum > 0 && xEnd < region.X + region.Width - 6) { xEnd += 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xEnd, region.Y], _calc.Stride, new IppiSize(1, region.Height), &sum)); } xEnd -= 1;//we have sum of 0, so go back one line towards the centroid //find yStart - we can limit our x-search-space as we already have those boundaries sum = 1; while (sum > 0 && yStart > region.Y + 4) { yStart -= 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xStart, yStart], _calc.Stride, new IppiSize(xEnd - xStart + 1, 1), &sum)); } yStart += 1; //find yEnd - again limit summation to x-search-space sum = 1; while (sum > 0 && yEnd < region.Y + region.Height - 6) { yEnd += 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xStart, yEnd], _calc.Stride, new IppiSize(xEnd - xStart + 1, 1), &sum)); } yEnd -= 1; blobsDetected[i - 1].BoundingBox = new IppiRect(xStart, yStart, xEnd - xStart + 1, yEnd - yStart + 1); } else { blobsDetected[i - 1] = new BlobWithMoments(); } } } else { return(null); } long maxArea = 0; int maxIndex = -1; for (int i = 0; i < blobsDetected.Length; i++) { if (blobsDetected[i] == null) { break; } //Simply note down the largest blob if (blobsDetected[i].Area > maxArea) { maxArea = blobsDetected[i].Area; maxIndex = i; } } if (maxArea < MinArea) { return(null); } else { return(blobsDetected[maxIndex]); } }
/// <summary> /// Performs a 3x3 closing operation on an image /// </summary> /// <param name="im">The (thresholded) image to close</param> /// <param name="region">The ROI in which to perform the operation</param> protected void Close3x3(Image8 im, IppiROI region) { IppHelper.IppCheckCall(ip.ippiDilate3x3_8u_C1IR(im[region.TopLeft.x + 1, region.TopLeft.y + 1], im.Stride, new IppiSize(region.Width - 2, region.Height - 2))); IppHelper.IppCheckCall(ip.ippiErode3x3_8u_C1IR(im[region.TopLeft.x + 1, region.TopLeft.y + 1], im.Stride, new IppiSize(region.Width - 2, region.Height - 2))); }
/// <summary> /// Implements a "greater than" threshold like MATLABS /// im2bw function /// </summary> /// <param name="im">The image to threshold</param> /// <param name="region">The ROI in which to perform the operation</param> /// <param name="threshold">The threshold to apply</param> protected void Im2Bw(Image8 im, IppiROI region) { IppHelper.IppCheckCall(ip.ippiThreshold_LTVal_8u_C1IR(im[region.TopLeft], im.Stride, region.Size, (byte)(_threshold + 1), 0)); IppHelper.IppCheckCall(ip.ippiThreshold_GTVal_8u_C1IR(im[region.TopLeft], im.Stride, region.Size, _threshold, 255)); }
/// <summary> /// Tracks fish on a multiwell plate /// </summary> /// <param name="image">The current image to track and update the background with</param> /// <param name="updateBackground">If true the current image will be used to update the background</param> /// <param name="forceFullFrame">If set to true background updates won't exclude fish locations</param> /// <returns>A list of fish-blobs, one for each well or null if no fish was detected</returns> public BlobWithMoments[] TrackMultiWell(Image8 image, bool updateBackground = true, bool forceFullFrame = false) { if (_frame == 0) { ip.ippiSet_8u_C1R(0, Foreground.Image, Foreground.Stride, Foreground.Size); ip.ippiSet_8u_C1R(0, _calc.Image, _calc.Stride, _calc.Size); } BlobWithMoments[] currentFish = new BlobWithMoments[_wellnumber]; if (_frame > FramesInitialBackground && !forceFullFrame) { //do global stuff (everything except labelMarkers if we don't do parallel tracking otherwise only the inherently parallel background subtraction) //cache 8-bit representation of the background Image8 bg = _bgModel.Background; //Perform background subtraction if (DoMedianFiltering) { IppHelper.IppCheckCall(cv.ippiAbsDiff_8u_C1R(image.Image, image.Stride, bg.Image, bg.Stride, _calc.Image, _calc.Stride, image.Size)); } else { IppHelper.IppCheckCall(cv.ippiAbsDiff_8u_C1R(image.Image, image.Stride, bg.Image, bg.Stride, _foreground.Image, _foreground.Stride, image.Size)); } if (_trackMethod == TrackMethods.Parallel && _parallelChunks > 1) { if (!Parallel.For(0, _parallelChunks, TrackChunk).IsCompleted) { System.Diagnostics.Debug.WriteLine("Error in parallel tracking. Not all regions completed"); } //copy our result into currentFish for (int i = 0; i < _wellnumber; i++) { currentFish[i] = _parallelTrackResults[i]; } } else { if (DoMedianFiltering) { //remove noise via median filtering IppHelper.IppCheckCall(ip.ippiFilterMedianWeightedCenter3x3_8u_C1R(_calc[1, 1], _calc.Stride, _foreground[1, 1], _foreground.Stride, new IppiSize(image.Width - 2, image.Height - 2), 1)); } //Threshold and close Im2Bw(_foreground, new IppiROI(new IppiPoint(0, 0), image.Size)); Close3x3(_foreground, new IppiROI(new IppiPoint(0, 0), image.Size)); //label markers and extract - depending on the method selector //we label components on the whole image (new) or individual wells(old) if (_trackMethod == TrackMethods.OnWholeImage) { currentFish = ExtractAll(); } else { for (int i = 0; i < _wellnumber; i++) { currentFish[i] = ExtractFish(_wells[i]); } } } }//If We are past initial background frames //create/update background and advance frame counter if (_frame == 0) { _bgModel = new SelectiveUpdateBGModel(image, 1.0f / FramesInBackground); } else if (updateBackground || _frame <= FramesInitialBackground)//only allow reduction of background update rate AFTER initial background has been created { if (currentFish == null || forceFullFrame) { _bgModel.UpdateBackground(image); } else { _bgModel.UpdateBackground(image, currentFish); } } _frame++; return(currentFish); }
/// <summary> /// Extracts for each well the most likely fish-blob or null if no suitable candidate was found /// </summary> /// <returns>All fish blobs found in the image</returns> private BlobWithMoments[] ExtractAll() { int nMarkers = 0; //we have fixed bounding boxes for each fish that are square and 1.5 times the typical fish-length //with the fishes centroid centered in the box int bbOffset = (int)(_typicalFishLength * 0.75); int bbSize = (int)(_typicalFishLength * 1.5); //Copy foreground to marker and label connected components //IppHelper.IppCheckCall(ip.ippiCopy_8u_C1R(_foreground.Image, _foreground.Stride, _labelMarkers.Image, _labelMarkers.Stride, _foreground.Size)); IppHelper.IppCheckCall(cv.ippiLabelMarkers_8u_C1IR(_foreground.Image, _foreground.Stride, _foreground.Size, 1, 254, IppiNorm.ippiNormInf, &nMarkers, _markerBuffer)); if (nMarkers > 254) { nMarkers = 254; } //fish are identified by looping over all wells and computing the histogram of pixel values for each well. This will effectively tell us //which marker is the most abundant in the given well => this would be the fish by our general detection logic //The maximum value that ippiLabelMarkers has used is equal to nMarkers. Therefore, when we compute our histogram, we supply //(nMarkers+2) as the nLevels parameter - this will give us (nMarkers+1) bins containing the counts from 0->nMarkers with 0 being our background //loop over wells for (int i = 0; i < _wellnumber; i++) { IppiROI well = _wells[i]; //get well-specific maxvalue //byte maxVal = 0; //IppHelper.IppCheckCall(ip.ippiMax_8u_C1R(_labelMarkers[well.TopLeft], _labelMarkers.Stride, well.Size, &maxVal)); //compute histogram in this well - from 0 until maxval IppHelper.IppCheckCall(ip.ippiHistogramRange_8u_C1R(_foreground[well.TopLeft], _foreground.Stride, well.Size, _hist, _histogramLevels, nMarkers + 2)); //_hist now contains in position i the abundance of marker i => by looping over it from 1->nMarkers we can find the largest blob. If this blob is //larger than our minimum size, we compute the moments and initialize a BlobWithMoments structure for it int maxCount = 0; int maxIndex = -1; for (int j = 1; j <= nMarkers; j++) { if (_hist[j] > maxCount && _hist[j] > _minArea && _hist[j] <= _maxArea) { maxCount = _hist[j]; maxIndex = j; } } if (maxIndex == -1)//no suitable fish found { _allFish[i] = null; } else { //compare and compute moments //label all pixels with the current marker as 255 and others as 0 IppHelper.IppCheckCall(ip.ippiCompareC_8u_C1R(_foreground[well.TopLeft], _foreground.Stride, (byte)maxIndex, _wellCompare.Image, _wellCompare.Stride, well.Size, IppCmpOp.ippCmpEq)); //calculate image moments IppHelper.IppCheckCall(ip.ippiMoments64s_8u_C1R(_wellCompare.Image, _wellCompare.Stride, well.Size, _momentState)); //retrieve moments long m00 = 0; long m10 = 0; long m01 = 0; long m20 = 0; long m02 = 0; long m11 = 0; long m30 = 0; long m03 = 0; long m21 = 0; long m12 = 0; ip.ippiGetSpatialMoment_64s(_momentState, 0, 0, 0, well.TopLeft, &m00, 0); m00 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 1, 0, 0, well.TopLeft, &m10, 0); m10 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 0, 1, 0, well.TopLeft, &m01, 0); m01 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 2, 0, 0, well.TopLeft, &m20, 0); m20 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 0, 2, 0, well.TopLeft, &m02, 0); m02 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 1, 1, 0, well.TopLeft, &m11, 0); m11 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 3, 0, 0, well.TopLeft, &m30, 0); m30 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 0, 3, 0, well.TopLeft, &m03, 0); m03 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 2, 1, 0, well.TopLeft, &m21, 0); m21 /= 255; ip.ippiGetSpatialMoment_64s(_momentState, 1, 2, 0, well.TopLeft, &m12, 0); m12 /= 255; _allFish[i] = new BlobWithMoments(m00, m10, m01, m20, m11, m02, m30, m03, m21, m12); //assign bounding box IppiRect bBox = new IppiRect(_allFish[i].Centroid.x - bbOffset, _allFish[i].Centroid.y - bbOffset, bbSize, bbSize); if (bBox.x < well.X) { bBox.x = well.X; } if (bBox.y < well.Y) { bBox.y = well.Y; } if (bBox.x + bBox.width > well.X + well.Width) { bBox.width = well.X + well.Width - bBox.x; } if (bBox.y + bBox.height > well.Y + well.Height) { bBox.height = well.Y + well.Height - bBox.y; } _allFish[i].BoundingBox = bBox; } }//end looping over all wells return(_allFish); }
/// <summary> /// Constructs a new multi-well tracker /// </summary> /// <param name="imageWidth">The total width of the image we want to track</param> /// <param name="imageHeight">The total height of the image we want to track</param> /// <param name="plate_index">This index is appended to the ROI file name to load file for proper plate</param> /// <param name="parallelChunks">The number of parallel chunks to track</param> public TrackerMultiWell(int imageWidth, int imageHeight, int plate_index, int parallelChunks = 2) : base(imageWidth, imageHeight) { _typicalFishLength = 30; _trackMethod = TrackMethods.PerWell;//default to per-well tracking method //Load ROIs - the following way to assess how many lines we have and hence how many //ROIs we are dealing with is ugly but what the heck its quick to think up _wellnumber = 0; var assembly = Assembly.GetExecutingAssembly(); StreamReader roiStream = null; string fileToOpen = string.Format("SleepTracker.ROI_defs_{0}.txt", plate_index); //open for prescanning var s = assembly.GetManifestResourceStream(fileToOpen); roiStream = new StreamReader(s); while (!roiStream.EndOfStream) { string line = roiStream.ReadLine(); string[] values = line.Split('\t'); if (values.Length == 4) { _wellnumber++; } } roiStream.Dispose(); //reopen to load wells s = assembly.GetManifestResourceStream(fileToOpen); roiStream = new StreamReader(s); _wells = new IppiROI[_wellnumber]; //we use the following hash-table to keep track of wells belonging //to each row. This will give us the number of rows and their boundaries //for later parallel chunk boundary determination as well as which wells //should be tracked in which chunk Dictionary <int, List <IppiROI> > wellsPerRow = new Dictionary <int, List <IppiROI> >(); Dictionary <int, int> columnStarts = new Dictionary <int, int>(); for (int i = 0; i < _wellnumber; i++) { System.Diagnostics.Debug.Assert(!roiStream.EndOfStream, "Unexpectedly reached end of ROI file"); string line = roiStream.ReadLine(); string[] values = line.Split('\t'); System.Diagnostics.Debug.Assert(values.Length == 4, "Found line in ROI file that does not contain 4 tab-separated strings"); int[] numValues = new int[4]; for (int j = 0; j < 4; j++) { numValues[j] = int.Parse(values[j]); } _wells[i] = new IppiROI(numValues[1], numValues[0], numValues[2], numValues[3]); if (wellsPerRow.ContainsKey(_wells[i].Y)) { wellsPerRow[_wells[i].Y].Add(_wells[i]); } else { //create a new list for this row and append our first well wellsPerRow[_wells[i].Y] = new List <IppiROI>(); wellsPerRow[_wells[i].Y].Add(_wells[i]); } if (columnStarts.ContainsKey(_wells[i].X)) { columnStarts[_wells[i].X]++; } else { columnStarts[_wells[i].X] = 1; } } roiStream.Dispose(); //can't have more parallel regions than we have rows of wells if (parallelChunks > wellsPerRow.Count) { parallelChunks = wellsPerRow.Count; } _allFish = new BlobWithMoments[_wellnumber]; //initialize our histogram bin boundaries (=histogram levels) //the following assignment will allow us to study marker values from 0 to 254 //since: h[k] = countof(pLevels[k] <= pixels(x,y) < pLevels[k+1]) _histogramLevels = (int *)Marshal.AllocHGlobal(sizeof(int) * 256); for (int i = 0; i < 256; i++) { _histogramLevels[i] = i; } _hist = (int *)Marshal.AllocHGlobal(sizeof(int) * 255); //assume all wells have the same size _wellCompare = new Image8(_wells[1].Size.width, _wells[1].Size.height); _parallelChunks = parallelChunks; //Initialize result array for parallel tracking _parallelTrackResults = new BlobWithMoments[_wellnumber]; //Set up image regions for parallel tracking _parallelImageRegions = new IppiROI[_parallelChunks]; //populate image regions and parallel buffers if _parallelChunks is larger 1 if (_parallelChunks > 1) { _parallelMarkerBuffers = new byte *[_parallelChunks]; _parallelMomentStates = new IppiMomentState_64s *[_parallelChunks]; //determine the number of rows in each chunk - integer division and last chunk get's the remainder tucked on int nPerChunk = wellsPerRow.Count / _parallelChunks; int nLastChunk = wellsPerRow.Count - (_parallelChunks - 1) * nPerChunk; //obtain all our row-starting coordinates and sort ascending var rowCoordinates = wellsPerRow.Keys.ToArray(); Array.Sort(rowCoordinates); //do the same for column starting coordinates var colCoordinates = columnStarts.Keys.ToArray(); Array.Sort(colCoordinates); //Inititalize our parallel-well list array _parallelChunkWells = new List <IppiROI> [_parallelChunks]; for (int i = 0; i < _parallelChunks; i++) { //for each chunk initialize it's well-list _parallelChunkWells[i] = new List <IppiROI>(); int startRowInChunk = i * nPerChunk; int endRowInChunk; //are we dealing with the last chunk - this one potentially has a different number of rows! if (i == _parallelChunks - 1) { endRowInChunk = startRowInChunk + nLastChunk - 1; } else { endRowInChunk = startRowInChunk + nPerChunk - 1; } //add all wells of this chunk to the list by looping over rows //finding their start coordinate and using that to index into //our dictionary. Then loop over the list in the dictionary for (int j = startRowInChunk; j <= endRowInChunk; j++) { foreach (IppiROI wr in wellsPerRow[rowCoordinates[j]]) { _parallelChunkWells[i].Add(wr); } } //determine top-left corner as well as width and height of this chunk int y_top = wellsPerRow[rowCoordinates[0]][0].Y; int y_bottom = wellsPerRow[rowCoordinates[endRowInChunk]][0].Y + wellsPerRow[rowCoordinates[endRowInChunk]][0].Height - 1; int height = y_bottom - y_top + 1; System.Diagnostics.Debug.Assert(height > 1); int x_left = colCoordinates[0]; int x_right = colCoordinates[colCoordinates.Length - 1] + wellsPerRow[rowCoordinates[0]][0].Width - 1; int width = x_right - x_left + 1; System.Diagnostics.Debug.Assert(width > 1); _parallelImageRegions[i] = new IppiROI(x_left, y_top, width, height); //Initialize marker buffer for this chunk int bufferSize = 0; IppHelper.IppCheckCall(cv.ippiLabelMarkersGetBufferSize_8u_C1R(_parallelImageRegions[i].Size, &bufferSize)); _parallelMarkerBuffers[i] = (byte *)Marshal.AllocHGlobal(bufferSize); //initialize moment state for this chunk fixed(IppiMomentState_64s **ppState = &_parallelMomentStates[i]) { //let ipp decide whether to give accurate or fast results IppHelper.IppCheckCall(ip.ippiMomentInitAlloc_64s(ppState, IppHintAlgorithm.ippAlgHintNone)); } } //determine each chunks start index in the fish output array based on the number of wells //in lower chunks _parallelCumWellCount = new int[_parallelChunks]; for (int i = 1; i < _parallelChunks; i++) { _parallelCumWellCount[i] = _parallelCumWellCount[i - 1] + _parallelChunkWells[i - 1].Count; } } }
/// <summary> /// Extracts a fish (candidate) from an image by performing background subtraction, noise filtering, thresholding and closing to obtain a foreground /// followed by marker extraction. /// </summary> /// <param name="im">The image to extract the fish from</param> /// <param name="region">The ROI to search</param> /// <returns>The most likely fish blob or null if no suitable candidate was found</returns> BlobWithMoments ExtractFish(Image8 im, IppiROI region) { int nMarkers = 0; //Perform background subtraction - CASH BACKGROUND IMAGE POINTER - otherwise we actually do the whole costly //32f conversion twice - once for accessing the actual image, and once for accessing the stride... var bg = _bgModel.Background; IppHelper.IppCheckCall(cv.ippiAbsDiff_8u_C1R(im[region.TopLeft], im.Stride, bg[region.TopLeft], bg.Stride, _calc[region.TopLeft], _calc.Stride, region.Size)); //remove noise via median filtering _mFiltSize.width = region.Width - 2; _mFiltSize.height = region.Height - 2; IppHelper.IppCheckCall(ip.ippiFilterMedianWeightedCenter3x3_8u_C1R(_calc[region.X + 1, region.Y + 1], _calc.Stride, _bgSubtracted[region.X + 1, region.Y + 1], _bgSubtracted.Stride, _mFiltSize, 1)); //Threshold and close Im2Bw(_bgSubtracted, _foreground, region); //Do as two step to get information back into _foreground _strel3x3.Dilate(_foreground, _calc, region); _strel3x3.Erode(_calc, _foreground, region); //Label connected components IppHelper.IppCheckCall(cv.ippiLabelMarkers_8u_C1IR(_foreground[region.TopLeft], _foreground.Stride, region.Size, 1, 254, IppiNorm.ippiNormInf, &nMarkers, _markerBuffer)); //loop over returned markers and use ipp to extract blobs if (nMarkers > 0) { if (nMarkers > 254) { nMarkers = 254; } //create or update our intermediate blob storage to store the required number of marker representations if (_blobsDetected == null || _blobsDetected.Length < nMarkers) { _blobsDetected = new BlobWithMoments[nMarkers]; } for (int i = 1; i <= nMarkers; i++) { //label all pixels with the current marker as 255 and others as 0 IppHelper.IppCheckCall(ip.ippiCompareC_8u_C1R(_foreground[region.TopLeft], _foreground.Stride, (byte)i, _calc[region.TopLeft], _calc.Stride, region.Size, IppCmpOp.ippCmpEq)); //calculate image moments IppHelper.IppCheckCall(ip.ippiMoments64f_8u_C1R(_calc[region.TopLeft], _calc.Stride, region.Size, _momentState)); //retrieve moments double m00 = 0; double m10 = 0; double m01 = 0; double m20 = 0; double m02 = 0; double m11 = 0; double m30 = 0; double m03 = 0; double m21 = 0; double m12 = 0; ip.ippiGetSpatialMoment_64f(_momentState, 0, 0, 0, region.TopLeft, &m00); //since our input image is not 0s and 1s but 0s and 255s we have to divide by 255 in order to re-normalize our moments //System.Diagnostics.Debug.Assert(m00 % 255 == 0, "M00 was not a multiple of 255"); m00 /= 255; //only retrieve other moments if this is a "fish candidate" if (m00 >= MinArea) { ip.ippiGetSpatialMoment_64f(_momentState, 1, 0, 0, region.TopLeft, &m10); m10 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 0, 1, 0, region.TopLeft, &m01); m01 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 2, 0, 0, region.TopLeft, &m20); m20 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 0, 2, 0, region.TopLeft, &m02); m02 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 1, 1, 0, region.TopLeft, &m11); m11 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 3, 0, 0, region.TopLeft, &m30); m30 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 0, 3, 0, region.TopLeft, &m03); m03 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 2, 1, 0, region.TopLeft, &m21); m21 /= 255; ip.ippiGetSpatialMoment_64f(_momentState, 1, 2, 0, region.TopLeft, &m12); m12 /= 255; if (_blobsDetected[i - 1] == null) { _blobsDetected[i - 1] = new BlobWithMoments((long)m00, (long)m10, (long)m01, (long)m20, (long)m11, (long)m02, (long)m30, (long)m03, (long)m21, (long)m12); } else { _blobsDetected[i - 1].UpdateBlob((long)m00, (long)m10, (long)m01, (long)m20, (long)m11, (long)m02, (long)m30, (long)m03, (long)m21, (long)m12); } //Determine bounding box of the blob. The following seems kinda retarded as Ipp must already //have obtained that information before so maybe there is some way to actually retrieve it?? //Do linescans using ipp's sum function starting from the blobs centroid until we hit a line //the sum of which is 0 int xStart, xEnd, yStart, yEnd; double sum = 1; IppiPoint centroid = _blobsDetected[i - 1].Centroid; xStart = centroid.x - 1; xEnd = centroid.x + 1; yStart = centroid.y - 1; yEnd = centroid.y + 1; //in the following loops we PRE-increment, whence we stop the loop if we are at one coordinate short of the ends //find xStart _bboxScan.width = 1; _bboxScan.height = region.Height; while (sum > 0 && xStart > (region.X + 4)) { xStart -= 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xStart, region.Y], _calc.Stride, _bboxScan, &sum)); } xStart += 1;//we have a sum of 0, so go back one line towards the centroid //find xEnd sum = 1; while (sum > 0 && xEnd < region.X + region.Width - 5) { xEnd += 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xEnd, region.Y], _calc.Stride, _bboxScan, &sum)); } xEnd -= 1;//we have sum of 0, so go back one line towards the centroid //find yStart - we can limit our x-search-space as we already have those boundaries _bboxScan.width = xEnd - xStart + 1; _bboxScan.height = 1; sum = 1; while (sum > 0 && yStart > (region.Y + 4)) { yStart -= 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xStart, yStart], _calc.Stride, _bboxScan, &sum)); } yStart += 1; //find yEnd - again limit summation to x-search-space sum = 1; while (sum > 0 && yEnd < region.Y + region.Height - 5) { yEnd += 5; IppHelper.IppCheckCall(ip.ippiSum_8u_C1R(_calc[xStart, yEnd], _calc.Stride, _bboxScan, &sum)); } yEnd -= 1; _blobsDetected[i - 1].UpdateBoundingBox(xStart, yStart, xEnd - xStart + 1, yEnd - yStart + 1); } else { if (_blobsDetected[i - 1] == null) { _blobsDetected[i - 1] = new BlobWithMoments(); } else { _blobsDetected[i - 1].ResetBlob(); } } } } else { return(null); } //decide which of the detected objects is the fish //usually we pick the larger blob - however to avoid //tracking reflections on the wall, if the largest blob //and second largest blob are of comparable size //we pick the one which is closer to the center (as reflections //are always more eccentric) long maxArea = 0; long secondMaxArea = 0; int maxIndex = -1; int secondMaxIndex = -1; for (int i = 0; i < nMarkers; i++) { if (_blobsDetected[i] == null) { break; } //Note down the largest and second-largest blob - but only if those blobs aren't larger than the maxArea and if they eccentricity is at least MinEccentricity //this comparison allows that if we find two exactly same-sized blobs to consider both but to not consider any further blobs of this size (which we hopefully never have anyways) //Eccentricity and MaxAllowedArea checks removed at this point as they were mainly concieved to not track the laser. if (_blobsDetected[i].Area >= maxArea && _blobsDetected[i].Area > secondMaxArea /*&& blobsDetected[i].Area<=MaxAllowedArea && blobsDetected[i].Eccentricity>=MinEccentricity*/) { secondMaxArea = maxArea; maxArea = _blobsDetected[i].Area; secondMaxIndex = maxIndex; maxIndex = i; } } if (maxArea < MinArea) { return(null); } else { //if our second-largest blob is at least two-thirds the size //of the largest blob we also consider distance and swap accordingly if ((float)secondMaxArea * 1.5 >= (float)maxArea) { double distMax, distSecondMax; distMax = Distance.Euclidian(_blobsDetected[maxIndex].Centroid, DishCenter); distSecondMax = Distance.Euclidian(_blobsDetected[secondMaxIndex].Centroid, DishCenter); if (distMax > distSecondMax) { maxIndex = secondMaxIndex; } } return(_blobsDetected[maxIndex]); } }
/// <summary> /// Implements a "greater than" threshold like MATLABS /// im2bw function /// </summary> /// <param name="imIn">The image to threshold</param> /// <param name="imThresh">The image after thresholding</param> /// <param name="region">The ROI in which to perform the operation</param> /// <param name="threshold">The threshold to apply</param> void Im2Bw(Image8 imIn, Image8 imThresh, IppiROI region) { IppHelper.IppCheckCall(ip.ippiCompareC_8u_C1R(imIn[region.TopLeft], imIn.Stride, _threshold, imThresh[region.TopLeft], imThresh.Stride, region.Size, IppCmpOp.ippCmpGreater)); }
/// <summary> /// Tracks the tailsegments on the supplied image and returns /// the angles and distances of each segment from the tailstart /// </summary> /// <param name="image">The image on which to id the tail</param> /// <returns>NSegments number of TailPoints</returns> public TailSegment[] TrackTail(Image8 image) { lock (_regionLock) { //CURRENTLY ONLY DOWNWARD FACING TAILS ARE PROPERLY VERIFIED!!! //Generate background by morphology operation - 10 times a second or whenever our coordinates changed //in the default case where the tail is darker than the background, using closing operation otherwise opening if (_frameNumber % (_frameRate / 10) == 0 || !_bgValid) { if (!_bgValid) { //if our regions changed, reblank our images! ip.ippiSet_8u_C1R(0, _background.Image, _background.Stride, _background.Size); ip.ippiSet_8u_C1R(0, _foreground.Image, _foreground.Stride, _foreground.Size); ip.ippiSet_8u_C1R(0, _thresholded.Image, _thresholded.Stride, _thresholded.Size); } if (_lightOnDark) { BWImageProcessor.Open(image, _background, _calc1, _strel, _trackRegionOuter); } else { BWImageProcessor.Close(image, _background, _calc1, _strel, _trackRegionOuter); } _bgValid = true; } //Compute foreground IppHelper.IppCheckCall(cv.ippiAbsDiff_8u_C1R(_background[_trackRegionInner.TopLeft], _background.Stride, image[_trackRegionInner.TopLeft], image.Stride, _foreground[_trackRegionInner.TopLeft], _foreground.Stride, _trackRegionInner.Size)); //Threshold BWImageProcessor.Im2Bw(_foreground, _thresholded, _trackRegionInner, _threshold); //Fill small holes BWImageProcessor.Close3x3(_thresholded, _thresholded, _calc1, _trackRegionOuter); } //Tracking concept: We track the angle of each segment end (TailStart+SegmentLength:TailEnd) //as the angular displacement from the previous segment end //To do this we define one full-circle with radius SegmentLength and an angle step corresponding //to ~1 Pixel. Then for each angle we pre-compute in InitializeScanPoints the corresponding x //and y offsets. From the full circle set we track -90 to +90 degrees around the angle of the //previous segment - for the initial segment that angle will be 0. For each segment we will return //the segment angle and its associated end-point coordinate var retval = new TailSegment[_nSegments]; lock (_scanPointLock) { //The index of the absolute angle of the previous segment in our angle sweep array //determines which angles we sweep over for the next segment int prevAngleIndex = _scanAngles.Length / 2; int nAnglesPerHalfPi = prevAngleIndex / 2;//this is the number of entries in the array that we have to walk to cover 90 degrees //we scan beginning with one segment length away from tail start and then walk //down the tail rather than using circles fixed arount the tailstart IppiPoint prevSegmentEnd = TailStart; //loop over tail segments for (int i = 0; i < _nSegments; i++) { //loop over scan-points from -pi/2 to +pi/2 (i.e. nAnglesPerHalfPi) around previous segment angle //interpreting the scan points as offsets around the previous tail segment //end point //then find tail angle of this segment int pointsFound = 0;//the number of non-zero pixels identified for (int j = -1 * nAnglesPerHalfPi; j < nAnglesPerHalfPi + 1; j++) { //Determine the index to scan - usually this will simply be "prevAngleIndex+j" however we //have to loop around our angle array properly in case we screen more extreme angles int currIndex = prevAngleIndex + j; if (currIndex >= _scanAngles.Length) { currIndex = currIndex % _scanAngles.Length; } else if (currIndex < 0) { currIndex = currIndex + _scanAngles.Length; } //If current point is outside of the image, ignore it IppiPoint pt = new IppiPoint(_coordinateOffsets[currIndex].x + prevSegmentEnd.x, _coordinateOffsets[currIndex].y + prevSegmentEnd.y); if (pt.x < 0 || pt.y < 0 || pt.x >= _imageSize.width || pt.y >= _imageSize.height) { continue; } //if the value at the current point >0 we mark that angle as valid //by storing the index in our anglestore if (*_thresholded[pt] > 0) { _angleStore[pointsFound] = currIndex; pointsFound++; } } //find the median point in our angle store if we have more than 2 points stored //the value in our angle store will directly give us the absolute screen angle of the segment //as well as the coordinate offset which we can used together with the previous segment endpoint //to compute the tail segment end coordinate - to get the delta angle we need to get the difference //between the stored index and the previously stored index, prevAngleIndex //after computing the appropriate return values we update prevAngleIndex with the index from the angle store //and prevSegmentEnd with the returned coordinate of the current tail segment double deltaTailAngle; IppiPoint coordinate; int pos; //the index in the angle store that we determine to be the tail-center if (pointsFound == 0) //we have lost the tail - no reason to keep tracking - fill remaining positions in array with NaNs for their angle { for (int k = i; k < _nSegments; k++) { deltaTailAngle = double.NaN; coordinate = new IppiPoint(); retval[k] = new TailSegment(deltaTailAngle, coordinate); } break; } else if (pointsFound < 3) { pos = _angleStore[0]; } else { //we want the angle at the median position of valid points //we don't compute intermediate positions however (we should be able to afford this since our angle step is so small), so in case //of an even number of points, the top of the lower half currently //wins - since array indexing is zero based, we have to subtract 1 //from the computed median position pos = _angleStore[(int)(Math.Ceiling(pointsFound / 2.0) - 1)]; } coordinate = new IppiPoint(prevSegmentEnd.x + _coordinateOffsets[pos].x, prevSegmentEnd.y + _coordinateOffsets[pos].y); deltaTailAngle = (pos - prevAngleIndex) * _angleStep; //the condition above of wrapping around the "angle circle":if (currIndex >= _scanAngles.Length).... //results in one very large (close to +360 or -360) angle of opposite sign at the switch point //however, since we usually only scan over offsets from +90 to -90 this case is easy to spot //and remedy: if (deltaTailAngle > 90) { deltaTailAngle = deltaTailAngle - 360;//this should create the appropriate tail angle btw. 0 and -90 } else if (deltaTailAngle < -90) { deltaTailAngle = 360 + deltaTailAngle;//this should create the appropriate tail angle btw. 0 and +90 } prevAngleIndex = pos; prevSegmentEnd = coordinate; retval[i] = new TailSegment(deltaTailAngle, coordinate); }//loop over tail segments } _frameNumber++; return(retval); }
/// <summary> /// Implements coordinate smoothing analogous to using filtfilt in matlab with a step function kernel /// (moving average with no peak displacement) /// </summary> /// <param name="src">The source track to smoothen</param> /// <param name="dst">The destination buffer for the smoothened track</param> /// <param name="windowSize">The windowsize for averaging</param> public void SmoothenTrack(CentroidBuffer src, CentroidBuffer dst, int windowSize) { if (IsDisposed) { throw new ObjectDisposedException(this.ToString()); } if (src.Size.width != dst.Size.width) { throw new ArgumentException("Source and destination buffers need to have the same size!"); } //For the internal buffers we require a size that fits both the coordinate buffer we //intend to filter as well as the border pixels required for filtering int borderSize = (int)Math.Ceiling(windowSize / 2.0); int reqSize = src.Size.width + borderSize * 2; //Adjust internal buffers if necessary if (_calc1 == null) { _calc1 = new CentroidBuffer(reqSize); _calc2 = new CentroidBuffer(reqSize); } else if (_calc1.Size.width != reqSize) { _calc1.Dispose(); _calc2.Dispose(); _calc1 = new CentroidBuffer(reqSize); _calc2 = new CentroidBuffer(reqSize); } if (_kernel == null) { _kernelSize = windowSize; _kernel = (float *)Marshal.AllocHGlobal(_kernelSize * 4); int i = 0; while (i < _kernelSize) { _kernel[i++] = 1 / (float)_kernelSize; } } else if (_kernelSize != windowSize) { Marshal.FreeHGlobal((IntPtr)_kernel); _kernelSize = windowSize; _kernel = (float *)Marshal.AllocHGlobal(_kernelSize * 4); int i = 0; while (i < _kernelSize) { _kernel[i++] = 1 / (float)_kernelSize; } } //filter parameters IppiSize regionSize = new IppiSize(src.Size.width, 1); IppiPoint anchor = new IppiPoint(borderSize, 0); IppiSize kernelSize = new IppiSize(_kernelSize, 1); float * calc1XStart = (float *)((byte *)_calc1.Buffer + borderSize * 4); float * calc1YStart = (float *)((byte *)_calc1.Buffer + borderSize * 4 + _calc1.Stride); float * calc2XStart = (float *)((byte *)_calc2.Buffer + borderSize * 4); float * calc2YStart = (float *)((byte *)_calc2.Buffer + borderSize * 4 + _calc2.Stride); //Copy src buffer adding borders IppHelper.IppCheckCall(ip.ippiCopyConstBorder_32f_C1R(src.Buffer, src.Stride, src.Size, _calc1.Buffer, _calc1.Stride, _calc1.Size, 0, borderSize, 0)); //Fill calc2 to have borders ready after filtering IppHelper.IppCheckCall(ip.ippiSet_32f_C1R(0, _calc2.Buffer, _calc2.Stride, _calc2.Size)); //filter x-coordinates with our kernel IppHelper.IppCheckCall(ip.ippiFilter_32f_C1R(calc1XStart, _calc1.Stride, calc2XStart, _calc2.Stride, regionSize, _kernel, kernelSize, anchor)); //filter y-coordinates with our kernel IppHelper.IppCheckCall(ip.ippiFilter_32f_C1R(calc1YStart, _calc1.Stride, calc2YStart, _calc2.Stride, regionSize, _kernel, kernelSize, anchor)); //invert buffer - mirror on vertical axis IppHelper.IppCheckCall(ip.ippiMirror_32f_C1R(_calc2.Buffer, _calc2.Stride, _calc1.Buffer, _calc1.Stride, _calc2.Size, IppiAxis.ippAxsVertical)); //filter x-coordinates with our kernel - now on inverted buffer IppHelper.IppCheckCall(ip.ippiFilter_32f_C1R(calc1XStart, _calc1.Stride, calc2XStart, _calc2.Stride, regionSize, _kernel, kernelSize, anchor)); //filter y-coordinates with our kernel - now on inverted buffer IppHelper.IppCheckCall(ip.ippiFilter_32f_C1R(calc1YStart, _calc1.Stride, calc2YStart, _calc2.Stride, regionSize, _kernel, kernelSize, anchor)); //flip buffer back IppHelper.IppCheckCall(ip.ippiMirror_32f_C1R(_calc2.Buffer, _calc2.Stride, _calc1.Buffer, _calc1.Stride, _calc2.Size, IppiAxis.ippAxsVertical)); //copy to dest IppHelper.IppCheckCall(ip.ippiCopy_32f_C1R(calc1XStart, _calc1.Stride, dst.Buffer, dst.Stride, dst.Size)); }