/// <summary> /// Returns a Tensor constructed from the data in the Bitmap. The Tensor's dimensions are /// ordered CHW (channel x height x width). The color channel dimension is in the same order /// as the original Bitmap data. For 24bit bitmaps, this will be BGR. For 32bit bitmaps this /// will be BGRA. /// </summary> /// <param name="bitmap">The bitmap.</param> /// <param name="allocator">The allocator.</param> /// <returns>Tensor.</returns> /// <exception cref="InvalidOperationException">Bitmap must be 24bit or 32bit</exception> public static NDArray ToTensor(this Bitmap bitmap, IAllocator allocator) { var cpuAllocator = new Cpu.CpuAllocator(); int bytesPerPixel = 0; if (bitmap.PixelFormat == PixelFormat.Format24bppRgb) { bytesPerPixel = 3; } else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb || bitmap.PixelFormat == PixelFormat.Format32bppPArgb || bitmap.PixelFormat == PixelFormat.Format32bppRgb) { bytesPerPixel = 4; } else { throw new InvalidOperationException("Bitmap must be 24bit or 32bit"); } var lockData = bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat); try { var sizes = new long[] { bitmap.Height, bitmap.Width, bytesPerPixel }; var strides = new long[] { lockData.Stride, bytesPerPixel, 1 }; using (var cpuByteTensor = new NDArray(cpuAllocator, DType.UInt8, sizes, strides)) { cpuByteTensor.Storage.CopyToStorage(cpuByteTensor.StorageOffset, lockData.Scan0, cpuByteTensor.Storage.ByteLength); using (var permutedTensor = cpuByteTensor.Transpose(2, 0, 1)) { using (var cpuFloatTensor = new NDArray(cpuAllocator, DType.Float32, permutedTensor.Shape)) { Ops.Copy(cpuFloatTensor, permutedTensor); // TODO this could be made more efficient by skipping a the following copy if allocator is a CpuAllocator, // but make sure that in that case the result tensor is not disposed before returning. var result = new NDArray(allocator, DType.Float32, permutedTensor.Shape); Ops.Copy(result, cpuFloatTensor); Ops.Div(result, result, 255); return(result); } } } } finally { bitmap.UnlockBits(lockData); } }
/// <summary> /// Fills the one hot. /// </summary> /// <param name="result">The result.</param> /// <param name="labelCount">The label count.</param> /// <param name="labels">The labels.</param> public static void FillOneHot(NDArray src, int labelCount, int[] labels) { if (src.Storage is Cpu.CpuStorage) { DoFillOneHot(src, labelCount, labels); } else { //If the result is not on the CPU, it is much faster to build the tensor on the CPU and then copy //An alternative to this would be building a specific GPU kernel for this operation var cpuAlloc = new Cpu.CpuAllocator(); using (var cpuResult = new NDArray(cpuAlloc, src.ElementType, src.Shape)) { DoFillOneHot(cpuResult, labelCount, labels); Ops.Copy(src, cpuResult); } } }
/// <summary> /// Converts a Tensor to a Bitmap. Elements of the tensor are assumed to be normalized in the range [0, 1] /// The tensor must have one of the following structures: /// * 2D tensor - output is a 24bit BGR bitmap in greyscale /// * 3D tensor where first dimension has length 1 - output is 24bit BGR bitmap in greyscale /// * 3D tensor where first dimension has length 3 - output is 24bit BGR bitmap /// * 3D tensor where first dimension has length 4 - output is 32bit BGRA bitmap /// 2D tensors must be in HW (height x width) order; /// 3D tensors must be in CHW (channel x height x width) order. /// </summary> /// <param name="tensor">The tensor.</param> /// <returns>Bitmap.</returns> /// <exception cref="InvalidOperationException"> /// tensor must have 2 or 3 dimensions /// or /// 3D tensor's first dimension (color channels) must be of length 1, 3 or 4 /// </exception> public static Bitmap ToBitmap(this NDArray tensor) { if (tensor.DimensionCount != 2 && tensor.DimensionCount != 3) { throw new InvalidOperationException("tensor must have 2 or 3 dimensions"); } if (tensor.DimensionCount == 3 && (tensor.Shape[0] != 1 && tensor.Shape[0] != 3 && tensor.Shape[0] != 4)) { throw new InvalidOperationException("3D tensor's first dimension (color channels) must be of length 1, 3 or 4"); } NDArray src; if (tensor.DimensionCount == 2) { src = tensor.RepeatTensor(3, 1, 1); } else if (tensor.DimensionCount == 3 && tensor.Shape[0] == 1) { src = tensor.RepeatTensor(3, 1, 1); } else { src = tensor.CopyRef(); } var cpuAllocator = new Cpu.CpuAllocator(); var bytesPerPixel = src.Shape[0]; try { using (var cpuFloatTensor = new NDArray(cpuAllocator, DType.Float32, src.Shape)) using (var permutedFloatTensor = cpuFloatTensor.Transpose(1, 2, 0)) { Ops.Copy(cpuFloatTensor, src); Ops.Mul(cpuFloatTensor, cpuFloatTensor, 255); var resultFormat = bytesPerPixel == 3 ? PixelFormat.Format24bppRgb : PixelFormat.Format32bppArgb; var result = new Bitmap((int)src.Shape[2], (int)src.Shape[1], resultFormat); var lockData = result.LockBits( new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.WriteOnly, result.PixelFormat); var sizes = new long[] { result.Height, result.Width, bytesPerPixel }; var strides = new long[] { lockData.Stride, bytesPerPixel, 1 }; var resultTensor = new NDArray(cpuAllocator, DType.UInt8, sizes, strides); // Re-order tensor and convert to bytes Ops.Copy(resultTensor, permutedFloatTensor); var byteLength = lockData.Stride * lockData.Height; resultTensor.Storage.CopyFromStorage(lockData.Scan0, resultTensor.StorageOffset, byteLength); result.UnlockBits(lockData); return(result); } } finally { src.Dispose(); } }