Example #1
0
 /// <summary>
 /// Divide by a scaled 8-bit stack pixel-wise
 /// </summary>
 /// <param name="ims">The stack to divide by</param>
 /// <param name="outMax">255 will be assigned to this value</param>
 public void Divide(ImageStack8 ims, float outMax = byte.MaxValue)
 {
     DisposeGuard();
     if (ims.IsDisposed)
     {
         throw new ArgumentException("Can't divide by disposed image");
     }
     if (!IsCompatible(ims))
     {
         throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");
     }
     for (int z = 0; z < ZPlanes; z++)
     {
         for (int t = 0; t < TimePoints; t++)
         {
             for (int y = 0; y < ImageHeight; y++)
             {
                 for (int x = 0; x < ImageWidth; x++)
                 {
                     *this[x, y, z, t] /= (float)*ims[x, y, z, t] / byte.MaxValue * outMax;
                 }
             }
         }
     }
 }
Example #2
0
 /// <summary>
 /// Construct image stack 16 with values
 /// copied from 8-bit stack
 /// </summary>
 /// <param name="ims">The source image to copy from</param>
 /// <param name="rescale">If true values will be re-scaled into 0-65535 range</param>
 public ImageStack16(ImageStack8 ims, bool rescale)
 {
     if (ims == null)
         throw new ArgumentNullException(nameof(ims));
     if (ims.IsDisposed)
         throw new ArgumentException("Can't copy disposed stack");
     SliceOrder = ims.SliceOrder;
     //initialize buffer and dimension properties according to source stack
     InitializeImageBuffer(ims.ImageWidth, ims.ImageHeight, ims.ZPlanes, ims.TimePoints, 2);
     //loop over pixels, assigning values
     for (int z = 0; z < ZPlanes; z++)
         for (int t = 0; t < TimePoints; t++)
             for (int y = 0; y < ImageHeight; y++)
                 for (int x = 0; x < ImageWidth; x++)
                 {
                     if (rescale)
                     {
                         float temp = *ims[x, y, z, t];
                         temp = (temp / byte.MaxValue) * ushort.MaxValue;
                         System.Diagnostics.Debug.Assert(temp <= ushort.MaxValue);
                         *this[x, y, z, t] = (ushort)temp;
                     }
                     else
                         *this[x, y, z, t] = *ims[x, y, z, t];
                 }
 }
Example #3
0
 /// <summary>
 /// Constructs a new floating point image stack with an 8 bit stack as
 /// a source, optionally rescaling the maximum
 /// </summary>
 /// <param name="ims">The 8bit source stack</param>
 /// <param name="outMax">255 will be assigned to this value in the float stack</param>
 public ImageStack32F(ImageStack8 ims, float outMax = byte.MaxValue)
 {
     if (ims == null)
     {
         throw new ArgumentNullException(nameof(ims));
     }
     if (ims.IsDisposed)
     {
         throw new ArgumentException("Can't copy disposed stack");
     }
     //initialize buffer and dimension properties according to source stack
     InitializeImageBuffer(ims.ImageWidth, ims.ImageHeight, ims.ZPlanes, ims.TimePoints, 4);
     //loop over pixels, assigning values
     for (int z = 0; z < ZPlanes; z++)
     {
         for (int t = 0; t < TimePoints; t++)
         {
             for (int y = 0; y < ImageHeight; y++)
             {
                 for (int x = 0; x < ImageWidth; x++)
                 {
                     float temp = *ims[x, y, z, t];
                     temp = temp / byte.MaxValue * outMax;
                     if (temp > outMax)
                     {
                         temp = outMax;
                     }
                     *this[x, y, z, t] = temp;
                 }
             }
         }
     }
 }
Example #4
0
 /// <summary>
 /// Copy constructor
 /// </summary>
 /// <param name="ims">The image to copy</param>
 public ImageStack8(ImageStack8 ims)
 {
     if (ims == null)
         throw new ArgumentNullException(nameof(ims));
     if (ims.IsDisposed)
         throw new ArgumentException("Can't copy disposed stack");
     InitializeAsCopy(ims);
 }
Example #5
0
        /// <summary>
        /// Performs pixel-by-pixel subtraction of the given image
        /// stack from the current stack clipping at 0
        /// </summary>
        /// <param name="ims">The stack to subtract</param>
        public void Subtract(ImageStack8 ims)
        {
            DisposeGuard();
            if (ims.IsDisposed)
            {
                throw new ArgumentException("Can't add disposed image");
            }
            if (!IsCompatible(ims))
            {
                throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");
            }
            if (this.Stride == ims.Stride)
            {
                long  intIter = ImageNB / 4;
                uint *iData   = (uint *)ImageData;
                uint *iSub    = (uint *)ims.ImageData;
                for (long i = 0; i < intIter; i++)
                {
                    iData[i] = SubBytesAsUint(iData[i], iSub[i]);
                }

                //For all images we create, we expect the following to be 0 because of the 4-byte aligned stride
                int restIter = (int)(ImageNB % 4);//NOTE: Could implement via mask over lowest two bits.
                for (long i = ImageNB - restIter; i < ImageNB; i++)
                {
                    byte prev = ImageData[i];
                    ImageData[i] -= ims.ImageData[i];
                    if (ImageData[i] > prev)
                    {
                        ImageData[i] = 0;
                    }
                }
            }
            else
            {
                for (int t = 0; t < TimePoints; t++)
                {
                    for (int z = 0; z < ZPlanes; z++)
                    {
                        for (int y = 0; y < ImageHeight; y++)
                        {
                            for (int x = 0; x < ImageWidth; x++)
                            {
                                byte *pixel  = this[x, y, z, t];
                                byte  prev   = *pixel;
                                *     pixel -= *ims[x, y, z, t];
                                if (*pixel > prev)//indicates that wrap-around occured
                                {
                                    *pixel = byte.MinValue;
                                }
                            }
                        }
                    }
                }
            }
        }
 /// <summary>
 /// Compares every pixel in an image to a given value
 /// </summary>
 /// <param name="value">The value each pixel should have</param>
 /// <param name="image">The image to compare</param>
 private void CompareValImage(byte value, ImageStack8 image)
 {
     byte* imStart = image.ImageData;
     //only compare outside of stride padding
     for (long i = 0; i < image.ImageNB; i++)
     {
         if (i % image.Stride < image.ImageWidth)
             Assert.AreEqual(value, imStart[i], "Found non-matching pixel at position {0}", i);
     }
 }
Example #7
0
 /// <summary>
 /// Copy constructor
 /// </summary>
 /// <param name="ims">The image to copy</param>
 public ImageStack8(ImageStack8 ims)
 {
     if (ims == null)
     {
         throw new ArgumentNullException(nameof(ims));
     }
     if (ims.IsDisposed)
     {
         throw new ArgumentException("Can't copy disposed stack");
     }
     InitializeAsCopy(ims);
 }
 public void Construction_WithValidArguments_DimCorrect()
 {
     int w = 20;
     int h = 30;
     int z = 40;
     int t = 50;
     var ims = new ImageStack8(w, h, z, t, ImageStack.SliceOrders.TBeforeZ);
     Assert.AreEqual(ims.ImageWidth, w, "Image width not correct.");
     Assert.AreEqual(ims.ImageHeight, h, "Image height not correct.");
     Assert.AreEqual(ims.ZPlanes, z, "Image z plane number not correct.");
     Assert.AreEqual(ims.TimePoints, t, "Number of timepoints not correct.");
     ims.Dispose();
 }
Example #9
0
        /// <summary>
        /// Performs pixel-by-pixel division of the given image
        /// stack to the current stack clipping at 255
        /// </summary>
        /// <param name="ims">The stack to divide by element-wise</param>
        public void Divide(ImageStack8 ims)
        {
            DisposeGuard();
            if (ims.IsDisposed)
            {
                throw new ArgumentException("Can't add disposed image");
            }
            if (!IsCompatible(ims))
            {
                throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");
            }

            //if the strides of the two images aren't equal (pixels not aligned in memory) we have
            //to laboriously loop over individual pixels otherwise we can move through the buffer in 32bit blocks
            //for all images created using this code stride should always be the same if the width is the same
            //but we could be dealing with a foreign memory block via shallow copy
            if (this.Stride == ims.Stride)
            {
                long  intIter = ImageNB / 4;
                uint *iData   = (uint *)ImageData;
                uint *iDiv    = (uint *)ims.ImageData;
                for (long i = 0; i < intIter; i++)
                {
                    iData[i] = DivBytesAsUint(iData[i], iDiv[i]);
                }

                //For all images we create, we expect the following to be 0 because of the 4-byte aligned stride
                int restIter = (int)(ImageNB % 4);
                for (long i = ImageNB - restIter; i < ImageNB; i++)
                {
                    ImageData[i] /= ims.ImageData[i];
                }
            }
            else
            {
                for (int z = 0; z < ZPlanes; z++)
                {
                    for (int t = 0; t < TimePoints; t++)
                    {
                        for (int y = 0; y < ImageHeight; y++)
                        {
                            for (int x = 0; x < ImageWidth; x++)
                            {
                                *this[x, y, z, t] /= *ims[x, y, z, t];//no chance of roll-over on this division
                            }
                        }
                    }
                }
            }
        }
Example #10
0
 /// <summary>
 /// Construct image stack 16 with values
 /// copied from 8-bit stack
 /// </summary>
 /// <param name="ims">The source image to copy from</param>
 /// <param name="rescale">If true values will be re-scaled into 0-65535 range</param>
 public ImageStack16(ImageStack8 ims, bool rescale)
 {
     if (ims == null)
     {
         throw new ArgumentNullException(nameof(ims));
     }
     if (ims.IsDisposed)
     {
         throw new ArgumentException("Can't copy disposed stack");
     }
     SliceOrder = ims.SliceOrder;
     //initialize buffer and dimension properties according to source stack
     InitializeImageBuffer(ims.ImageWidth, ims.ImageHeight, ims.ZPlanes, ims.TimePoints, 2);
     //loop over pixels, assigning values
     for (int z = 0; z < ZPlanes; z++)
     {
         for (int t = 0; t < TimePoints; t++)
         {
             for (int y = 0; y < ImageHeight; y++)
             {
                 for (int x = 0; x < ImageWidth; x++)
                 {
                     if (rescale)
                     {
                         float temp = *ims[x, y, z, t];
                         temp = (temp / byte.MaxValue) * ushort.MaxValue;
                         System.Diagnostics.Debug.Assert(temp <= ushort.MaxValue);
                         *this[x, y, z, t] = (ushort)temp;
                     }
                     else
                     {
                         *this[x, y, z, t] = *ims[x, y, z, t];
                     }
                 }
             }
         }
     }
 }
Example #11
0
 public void PixelPointerNull_AfterDispose()
 {
     var ims = new ImageStack8(5, 5, 5, 5, ImageStack.SliceOrders.ZBeforeT);
     ims.Dispose();
     Assert.IsTrue(ims[4, 0, 0, 0]==null);
 }
 public void From8bit_Constructor_Correct()
 {
     Random rnd = new Random();
     var ims8 = new ImageStack8(43, 43, 41, 41, ImageStack.SliceOrders.ZBeforeT);
     //quickly fill image with random values
     int* buffer = (int*)ims8.ImageData;
     long iter = ims8.ImageNB / 4;
     for (long i = 0; i < iter; i++)
     {
         buffer[i] = rnd.Next();
     }
     var ims32 = new ImageStack32F(ims8,2500);
     Assert.AreEqual(ims8.SliceOrder, ims32.SliceOrder);
     for (int z = 0; z < ims8.ZPlanes; z++)
         for (int t = 0; t < ims8.TimePoints; t++)
             for (int y = 0; y < ims8.ImageHeight; y++)
                 for (int x = 0; x < ims8.ImageWidth; x++)
                     Assert.AreEqual(*ims8[x, y, z, t], *ims32[x, y, z, t] / 2500 * 255, *ims8[x, y, z, t] / 1000.0);
     ims8.Dispose();
     ims32.Dispose();
 }
 public void UpscaleConstructor_Correct()
 {
     Random rnd = new Random();
     var ims8 = new ImageStack8(43, 43, 41, 41, ImageStack.SliceOrders.ZBeforeT);
     //quickly fill image with random values
     int* buffer = (int*)ims8.ImageData;
     long iter = ims8.ImageNB / 4;
     for (long i = 0; i < iter; i++)
     {
         buffer[i] = rnd.Next();
     }
     var ims16 = new ImageStack16(ims8, true);
     Assert.AreEqual(ims8.SliceOrder, ims16.SliceOrder);
     for (int z = 0; z < ims8.ZPlanes; z++)
         for (int t = 0; t < ims8.TimePoints; t++)
             for (int y = 0; y < ims8.ImageHeight; y++)
                 for (int x = 0; x < ims8.ImageWidth; x++)
                 {
                     float value = *ims8[x, y, z, t];
                     value = (value / 255) * ushort.MaxValue;
                     Assert.AreEqual((ushort)Math.Floor(value), *ims16[x, y, z, t]);
                 }
     ims8.Dispose();
     ims16.Dispose();
 }
Example #14
0
        /// <summary>
        /// Performs pixel-by-pixel division of the given image
        /// stack to the current stack clipping at 255
        /// </summary>
        /// <param name="ims">The stack to divide by element-wise</param>
        public void Divide(ImageStack8 ims)
        {
            DisposeGuard();
            if (ims.IsDisposed)
                throw new ArgumentException("Can't add disposed image");
            if (!IsCompatible(ims))
                throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");

            //if the strides of the two images aren't equal (pixels not aligned in memory) we have
            //to laboriously loop over individual pixels otherwise we can move through the buffer in 32bit blocks
            //for all images created using this code stride should always be the same if the width is the same
            //but we could be dealing with a foreign memory block via shallow copy
            if (this.Stride == ims.Stride)
            {
                long intIter = ImageNB / 4;
                uint* iData = (uint*)ImageData;
                uint* iDiv = (uint*)ims.ImageData;
                for (long i = 0; i < intIter; i++)
                {
                    iData[i] = DivBytesAsUint(iData[i], iDiv[i]);
                }

                //For all images we create, we expect the following to be 0 because of the 4-byte aligned stride
                int restIter = (int)(ImageNB % 4);
                for (long i = ImageNB - restIter; i < ImageNB; i++)
                {
                    ImageData[i] /= ims.ImageData[i];
                }
            }
            else
            {
                for (int z = 0; z < ZPlanes; z++)
                    for (int t = 0; t < TimePoints; t++)
                        for (int y = 0; y < ImageHeight; y++)
                            for (int x = 0; x < ImageWidth; x++)
                            {
                                *this[x, y, z, t] /= *ims[x, y, z, t];//no chance of roll-over on this division
                            }
            }
        }
Example #15
0
 public void RangeCheck_OnPixelAccess()
 {
     var ims = new ImageStack8(5, 5, 5, 5, ImageStack.SliceOrders.ZBeforeT);
     var p = ims[5, 0, 0, 0];
     ims.Dispose();
 }
Example #16
0
        /// <summary>
        /// Performs pixel-by-pixel addition of the given image
        /// stack to the current stack clipping at 255
        /// </summary>
        /// <param name="ims">The stack to add</param>
        public void Add(ImageStack8 ims)
        {
            DisposeGuard();
            if (ims.IsDisposed)
                throw new ArgumentException("Can't add disposed image");
            //NOTE: Not clear whether we should require same z/t ordering for compatibility
            if (!IsCompatible(ims))
                throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");

            //if the strides of the two images aren't equal (pixels not aligned in memory) we have
            //to laboriously loop over individual pixels otherwise we can move through the buffer in 32bit blocks
            //for all images created using this code stride should always be the same if the width is the same
            //but we could be dealing with a foreign memory block via shallow copy
            if (this.Stride == ims.Stride)
            {
                long intIter = ImageNB / 4;
                uint* iData = (uint*)ImageData;
                uint* iAdd = (uint*)ims.ImageData;
                for (long i = 0; i < intIter; i++)
                {
                    iData[i] = AddBytesAsUint(iData[i], iAdd[i]);
                }

                //For all images we create, we expect the following to be 0 because of the 4-byte aligned stride
                int restIter = (int)(ImageNB % 4);//NOTE: Could implement via mask over lowest two bits.
                for (long i = ImageNB - restIter; i < ImageNB; i++)
                {
                    byte prev = ImageData[i];
                    ImageData[i] += ims.ImageData[i];
                    if (ImageData[i] < prev)
                        ImageData[i] = 255;
                }
            }
            else
            {
                for (int z = 0; z < ZPlanes; z++)
                    for (int t = 0; t < TimePoints; t++)
                        for (int y = 0; y < ImageHeight; y++)
                            for (int x = 0; x < ImageWidth; x++)
                            {
                                byte* pixel = this[x, y, z, t];
                                byte prev = *pixel;
                                *pixel += *ims[x, y, z, t];
                                if (*pixel < prev)//indicates that wrap-around occured
                                    *pixel = byte.MaxValue;
                            }
            }
        }
Example #17
0
 public void CopyConstructor_Correct()
 {
     var ims = CreateDefaultStack();
     ims.SetAll(33);
     var copy = new ImageStack8(ims);
     Assert.IsFalse(ims.ImageData == copy.ImageData,"Source and its copy point to the same buffer");
     byte* sourceStart = ims.ImageData;
     byte* copyStart = copy.ImageData;
     for (long i = 0; i < ims.ImageNB; i++)
         Assert.AreEqual(sourceStart[i], copyStart[i], "Found non-matching pixel");
     ims.Dispose();
     copy.Dispose();
 }
Example #18
0
        /// <summary>
        /// Performs pixel-by-pixel addition of the given image
        /// stack to the current stack clipping at 255
        /// </summary>
        /// <param name="ims">The stack to add</param>
        public void Add(ImageStack8 ims)
        {
            DisposeGuard();
            if (ims.IsDisposed)
            {
                throw new ArgumentException("Can't add disposed image");
            }
            //NOTE: Not clear whether we should require same z/t ordering for compatibility
            if (!IsCompatible(ims))
            {
                throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");
            }

            //if the strides of the two images aren't equal (pixels not aligned in memory) we have
            //to laboriously loop over individual pixels otherwise we can move through the buffer in 32bit blocks
            //for all images created using this code stride should always be the same if the width is the same
            //but we could be dealing with a foreign memory block via shallow copy
            if (this.Stride == ims.Stride)
            {
                long  intIter = ImageNB / 4;
                uint *iData   = (uint *)ImageData;
                uint *iAdd    = (uint *)ims.ImageData;
                for (long i = 0; i < intIter; i++)
                {
                    iData[i] = AddBytesAsUint(iData[i], iAdd[i]);
                }

                //For all images we create, we expect the following to be 0 because of the 4-byte aligned stride
                int restIter = (int)(ImageNB % 4);//NOTE: Could implement via mask over lowest two bits.
                for (long i = ImageNB - restIter; i < ImageNB; i++)
                {
                    byte prev = ImageData[i];
                    ImageData[i] += ims.ImageData[i];
                    if (ImageData[i] < prev)
                    {
                        ImageData[i] = 255;
                    }
                }
            }
            else
            {
                for (int z = 0; z < ZPlanes; z++)
                {
                    for (int t = 0; t < TimePoints; t++)
                    {
                        for (int y = 0; y < ImageHeight; y++)
                        {
                            for (int x = 0; x < ImageWidth; x++)
                            {
                                byte *pixel  = this[x, y, z, t];
                                byte  prev   = *pixel;
                                *     pixel += *ims[x, y, z, t];
                                if (*pixel < prev)//indicates that wrap-around occured
                                {
                                    *pixel = byte.MaxValue;
                                }
                            }
                        }
                    }
                }
            }
        }
Example #19
0
 public void Downscale_Construction_Correct()
 {
     var rnd = new Random();
     ImageStack16 source = new ImageStack16(50, 50, 50, 50, ImageStack.SliceOrders.TBeforeZ);
     //quickly fill image with random values
     int* buffer = (int*)source.ImageData;
     long iter = source.ImageNB / 4;
     for (long i = 0; i < iter; i++)
     {
         buffer[i] = rnd.Next();
     }
     ushort rangeMin = 10;
     ushort rangeMax = 1000;
     ImageStack8 ims8 = new ImageStack8(source, rangeMin, rangeMax);
     Assert.AreEqual(source.SliceOrder, ims8.SliceOrder);
     for (int z = 0; z < source.ZPlanes; z++)
         for (int t = 0; t < source.TimePoints; t++)
             for (int y = 0; y < source.ImageHeight; y++)
                 for (int x = 0; x < source.ImageWidth; x++)
                 {
                     float pixel = *source[x, y, z, t];
                     pixel = pixel - rangeMin;
                     pixel = pixel / (rangeMax - rangeMin) * byte.MaxValue;
                     pixel = pixel < 0 ? 0 : pixel;
                     pixel = pixel > byte.MaxValue ? byte.MaxValue : pixel;
                     Assert.AreEqual((byte)pixel, *ims8[x, y, z, t]);
                 }
     source.Dispose();
     ims8.Dispose();
 }
Example #20
0
 public void Construction_WithInvalidHeight()
 {
     var ims = new ImageStack8(20, 0, 40, 50, ImageStack.SliceOrders.TBeforeZ);
     ims.Dispose();
 }
Example #21
0
 /// <summary>
 /// Divide by a scaled 8-bit stack pixel-wise
 /// </summary>
 /// <param name="ims">The stack to divide by</param>
 /// <param name="outMax">255 will be assigned to this value</param>
 public void Divide(ImageStack8 ims, float outMax = byte.MaxValue)
 {
     DisposeGuard();
     if (ims.IsDisposed)
         throw new ArgumentException("Can't divide by disposed image");
     if (!IsCompatible(ims))
         throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");
     for (int z = 0; z < ZPlanes; z++)
         for (int t = 0; t < TimePoints; t++)
             for (int y = 0; y < ImageHeight; y++)
                 for (int x = 0; x < ImageWidth; x++)
                     *this[x, y, z, t] /= (float)*ims[x, y, z, t] / byte.MaxValue * outMax;
 }
Example #22
0
 /// <summary>
 /// Constructs a new floating point image stack with an 8 bit stack as
 /// a source, optionally rescaling the maximum
 /// </summary>
 /// <param name="ims">The 8bit source stack</param>
 /// <param name="outMax">255 will be assigned to this value in the float stack</param>
 public ImageStack32F(ImageStack8 ims, float outMax=byte.MaxValue)
 {
     if (ims == null)
         throw new ArgumentNullException(nameof(ims));
     if (ims.IsDisposed)
         throw new ArgumentException("Can't copy disposed stack");
     //initialize buffer and dimension properties according to source stack
     InitializeImageBuffer(ims.ImageWidth, ims.ImageHeight, ims.ZPlanes, ims.TimePoints, 4);
     //loop over pixels, assigning values
     for (int z = 0; z < ZPlanes; z++)
         for (int t = 0; t < TimePoints; t++)
             for (int y = 0; y < ImageHeight; y++)
                 for (int x = 0; x < ImageWidth; x++)
                 {
                     float temp = *ims[x, y, z, t];
                     temp = temp / byte.MaxValue * outMax;
                     if (temp > outMax)
                         temp = outMax;
                     *this[x, y, z, t] = temp;
                 }
 }
Example #23
0
        /// <summary>
        /// Performs pixel-by-pixel subtraction of the given image
        /// stack from the current stack clipping at 0
        /// </summary>
        /// <param name="ims">The stack to subtract</param>
        public void Subtract(ImageStack8 ims)
        {
            DisposeGuard();
            if (ims.IsDisposed)
                throw new ArgumentException("Can't add disposed image");
            if (!IsCompatible(ims))
                throw new ArgumentException("Given image has wrong dimensions or z versus t ordering");
            if (this.Stride == ims.Stride)
            {
                long intIter = ImageNB / 4;
                uint* iData = (uint*)ImageData;
                uint* iSub = (uint*)ims.ImageData;
                for (long i = 0; i < intIter; i++)
                {
                    iData[i] = SubBytesAsUint(iData[i], iSub[i]);
                }

                //For all images we create, we expect the following to be 0 because of the 4-byte aligned stride
                int restIter = (int)(ImageNB % 4);//NOTE: Could implement via mask over lowest two bits.
                for (long i = ImageNB - restIter; i < ImageNB; i++)
                {
                    byte prev = ImageData[i];
                    ImageData[i] -= ims.ImageData[i];
                    if (ImageData[i] > prev)
                        ImageData[i] = 0;
                }
            }
            else
            {
                for (int t = 0; t < TimePoints; t++)
                    for (int z = 0; z < ZPlanes; z++)
                        for (int y = 0; y < ImageHeight; y++)
                            for (int x = 0; x < ImageWidth; x++)
                            {
                                byte* pixel = this[x, y, z, t];
                                byte prev = *pixel;
                                *pixel -= *ims[x, y, z, t];
                                if (*pixel > prev)//indicates that wrap-around occured
                                    *pixel = byte.MinValue;
                            }
            }
        }