/// <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; } } } } }
/// <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]; } }
/// <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; } } } } }
/// <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); }
/// <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); } }
/// <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(); }
/// <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 } } } } } }
/// <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]; } } } } } }
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(); }
/// <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 } } }
public void RangeCheck_OnPixelAccess() { var ims = new ImageStack8(5, 5, 5, 5, ImageStack.SliceOrders.ZBeforeT); var p = ims[5, 0, 0, 0]; ims.Dispose(); }
/// <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; } } }
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(); }
/// <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; } } } } } } }
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(); }
public void Construction_WithInvalidHeight() { var ims = new ImageStack8(20, 0, 40, 50, ImageStack.SliceOrders.TBeforeZ); ims.Dispose(); }
/// <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; }
/// <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; } }
/// <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; } } }