I'm trying to rotate raw pixel data from a DICOM file by 180 degrees (or flipped). I've successfully flipped the image correctly, however, upon writing the pixel data back to the file (in this case it's a DICOM file) and displaying it. The final output of the image is not correct.
Below is the sample a sample of the image I'm trying to flip 180 /mirror.
Here's the code I'm using to perform the flipping:
string file = #"adicomfile.dcm";
DicomFile df = new DicomFile();
df.Load(file);
// Get the amount of bits per pixel from the DICOM header.
int bitsPerPixel = df.DataSet[DicomTags.BitsAllocated].GetInt32(0, 0);
// Get the raw pixel data from the DICOM file.
byte[] bytes = df.DataSet[DicomTags.PixelData].Values as byte[];
// Get the width and height of the image.
int width = df.DataSet[DicomTags.Columns].GetInt32(0, 0);
int height = df.DataSet[DicomTags.Rows].GetInt32(0, 0);
byte[] original = bytes;
byte[] mirroredPixels = new byte[width * height * (bitsPerPixel / 8)];
width *= (bitsPerPixel / 8);
// The mirroring / image flipping.
for (int i = 0; i < original.Length; i++)
{
int mod = i % width;
int x = ((width - mod - 1) + i) - mod;
mirroredPixels[i] = original[x];
}
df.DataSet[DicomTags.PixelData].Values = mirroredPixels;
df.Save(#"flippedicom.dcm", DicomWriteOptions.Default);
And here's my output (incorrect). The white and distortion is not the desired output.
I'm using ClearCanvas DICOM library, however this shouldn't matter as I'm only trying to manipulate the raw pixel data contained within the file itself.
The desired output would preferably look like the original, but flipped 180 / mirrored.
Some assistance would be greatly appreciated. I've tried my best searching SO, but to no avail.
It took a while, but I ended up solving my problem by using a method from a Java library. You can see the class here.
string file = #"adicomfile.dcm";
DicomFile df = new DicomFile();
df.Load(file);
// Get the amount of bits per pixel from the DICOM header.
int bitsPerPixel = df.DataSet[DicomTags.BitsAllocated].GetInt32(0, 0);
// Get the raw pixel data from the DICOM file.
byte[] bytes = df.DataSet[DicomTags.PixelData].Values as byte[];
// Get the width and height of the image.
int width = df.DataSet[DicomTags.Columns].GetInt32(0, 0);
int height = df.DataSet[DicomTags.Rows].GetInt32(0, 0);
byte[] newBytes = new byte[height * width * (bitsPerPixel / 8)];
int stride = bitsPerPixel / 8;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width * stride; x++)
{
newBytes[((height - y - 1) * (width * stride)) + x] = bytes[(y * (width * stride)) + x];
}
}
// Set patient orientation.
df.DataSet[DicomTags.PatientOrientation].Values = #"A\L";
// The pixel data of the DICOM file to the flipped/mirrored data.
df.DataSet[DicomTags.PixelData].Values = mirroredPixels;
// Save the DICOM file.
df.Save(#"flippedicom.dcm", DicomWriteOptions.Default);
The output was correct and I was able to continue other modifications to the raw pixel data.
Thank you all for the pointers.
Related
I have a 32bit .tif file which is displayed in the image below by first ImageJ, and secondly my program. As you can guess, the way ImageJ displays the picture is correct.
I am converting the file to a Bitmap like this:
private Bitmap TiffToBmp()
{
Bitmap bmp;
int width;
int height;
float[] scanline32Bit;
float[] buffer;
using (Tiff original = Tiff.Open(file, "r"))
{
width = original.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
height = original.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
byte[] scanline = new byte[original.ScanlineSize()];
scanline32Bit = new float[original.ScanlineSize() / 4];
buffer = new float[width * height];
for (int i = 0; i < height; i++) //loading the data into a buffer
{
original.ReadScanline(scanline, i);
Buffer.BlockCopy(scanline, 0, scanline32Bit, 0, scanline.Length);
for (int j = 0; j < width; j++)
{
buffer[i * width + j] = scanline32Bit[j];
}
}
}
bmp = new Bitmap(width, height);
BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);
byte[] bytes = new byte[data.Height * data.Stride];
for (int y = 0; y < data.Height; y++) //creating Bitmap from buffer
{
for (int x = 0; x < data.Stride; x += 4)
{
bytes[y * data.Stride + x] = (byte)buffer[(y * data.Stride + x) / 4];
bytes[y * data.Stride + x + 1] = (byte)buffer[(y * data.Stride + x) / 4];
bytes[y * data.Stride + x + 2] = (byte)buffer[(y * data.Stride + x) / 4];
bytes[y * data.Stride + x + 3] = 255;
}
}
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
bmp.UnlockBits(data);
return bmp;
}
Using a RGBA Bitmap might seem a bit naive but it's necessary in further steps of the program. Also worth mentioning: XnView displays the file similar to mine, just the negative (from white to black instead of black to white). When opening the file it notices me that it converts the image to RGB with 8bits per channel (the same thing I'm doing) and falsely claims the .tif is 16bit instead of 32bit.
Has someone an idea of what I am doing wrong?
It seems to me that the error comes purely from some kind of misuse of structs or wrongly converting between them (from float to byte and so on).
I am using BitMiracle.LibTiff.Classic from the the .NET version of original libtiff library
edit: after some research I found out that original.ReadScanline(scanline, i) returns seemingly weird values and converts them via Buffer.BlockCopy(...) to the stripes appearing in the image. For example the 4 bytes (read from scanline) of the pixel (x = 0, y = 0) are 0, 0, 200, 6, the corresponding 8bit pixel value (read from scanline32Bit) turns out to be 110. Of Pixel (x = width, y = 0) the 4 bytes are 0, 128, 111, 68 and the 8bit value displayed is 958 corresponding to 190. So now I think there's something wrong with one of those steps but I actually have no idea what's going on.
Assuming you don't mind using existing code, there is this: https://www.codeproject.com/Articles/8279/ImageConverter-Converts-images-to-a-specific-image
The author (not me, BTW) provides both the C# source and an executable. I used the executable to convert a TIF (created using paint.net) to a BMP, so I presume the source will be useful to you. I was able to open the author's solution using VS 2017 (VS upgraded both the solution and project to work in the current VS environment). The conversion is based on the System.Drawing.Imaging.ImageFormat class
We are trying to use this American Sign Language dataset. This dataset has pictures of American Sign Language letters, both RGB and the Depth images.
I downloaded the dataset from the link. The RGB images seems fine, but the depth images are fully solid black. Something is wrong.
Since all the dataset is big, and it takes time to download all of them; I'm uploading an example RGB image and an example depth image here:
Since the depth images should have the depth data, I expect it to have float values (They say they used Kinect and Kinect provides float values). How can I read these float pixels using C#? I tried the following:
Bitmap bmp = new Bitmap("depth_0_0002.png");
int R = bmp.GetPixel(0,0).R;
int G = bmp.GetPixel(0,0).G;
int B = bmp.GetPixel(0,0).B;
However, I need float pixels, these are integer and they have nonsense values.
Do I need to include a 3rd party library?
I've tried it myself. Normally the depth datas are 16bit values.
The 13 high-order bits contain the distance and the 3 low-order bits contain the user segmentation map.
The user segmentation map is only built if skeleton tracking is active, which I believe was not in your example. Although the rgb values are 24bit it seems to work. I get an image from the segmented hand.
Bitmap bmpOrg = new Bitmap("bKawM.png");
Bitmap bmp = new Bitmap(106, 119);
for (int i = 0; i < 106;i++ )
{
for (int j = 0; j < 119;j++ )
{
Color rgb = bmpOrg.GetPixel(i, j);
int bit24 = (rgb.B << 16 + rgb.G << 8 + rgb.R);
int user = bit24 & 0x07;
int realDepth = bit24 >> 3;
bmp.SetPixel(i, j, Color.FromArgb(realDepth));
}
}
pictureBox1.Image = bmp;
My output:
I've played with it again. First I increased the brightness and contrast in Photoshop.
So the rgb values are usable if you don't need the real depth values in millimeters.
Then I tried to get the 16bit values from image with WPF because the image is 16bit grayscale encoded.
Stream imageStreamSource = new FileStream("bKawM.png", FileMode.Open, FileAccess.Read, FileShare.Read);
PngBitmapDecoder decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
int height = bitmapSource.PixelHeight;
int width = bitmapSource.PixelWidth;
int stride = width * ((bitmapSource.Format.BitsPerPixel + 7) / 8);
byte[] bytes = new byte[height * stride];
bitmapSource.CopyPixels(bytes, stride, 0);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
byte low = bytes[y * stride + x + 0];
byte high = bytes[y * stride + x + 1];
ushort bit16 = (ushort)((high << 8) | low);
int user = bit16 & 0x07;
int realDepth = bit16 >> 3;
}
}
I created a new image with the depth values and it looked very strange. I don't find any information
what data the image contains. I don't know if it contains the userdata (3 bits) or if the depth is converted somehow before saving to file.
I'm trying to simply use an offset to select tiles from one tiff image and write them to another image. I am using libtiff.net. Instead of each pixel having an ARGB value, there is one pixel that is alpha, then the pixel to the right of that is a red pixel, the one to the right of that is green and of course the one to the right of that is blue. I need to store those 4 pixels in one and something is happening along the way. Can you see anything wrong with this logic?
using (Tiff output = Tiff.Open(#"C:\base.tif", "w"))
{
if (output == null)
{
System.Console.Error.WriteLine("Could not open outgoing image");
return;
}
// We need to know the width and the height before we can malloc
int[] raster = new int[tileWidth * tileHeight];
// Write the tiff tags to the file
output.SetField(TiffTag.IMAGEWIDTH, 1024);
output.SetField(TiffTag.IMAGELENGTH, 1024);
output.SetField(TiffTag.TILEWIDTH, 256);
output.SetField(TiffTag.TILELENGTH, 256);
output.SetField(TiffTag.COMPRESSION, Compression.DEFLATE);
output.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
output.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
output.SetField(TiffTag.BITSPERSAMPLE, 8);
output.SetField(TiffTag.SAMPLESPERPIXEL, numOfBands);
byte[] inputBuffer = new byte[tileWidth * tileHeight * numOfBands];
// loop through every tile column
for (int x = 0; x < 1024 / tileWidth; x++)
{
// loop through every tile row in the current column
for (int y = 0; y < 1024 / tileHeight; y++)
{
image.ReadRGBATile((x + xOffset) * tileWidth, (y + yOffset) * tileHeight, raster);
Tiff.IntsToByteArray(raster, 0, raster.Length, inputBuffer, 0);
output.WriteEncodedTile(getTileIndex(x, y, 1024 / tileWidth), inputBuffer, 0, raster.Length);
}
}
}
I am trying to convert object of type iTextSharp.text.Image back to System.Drawing.Image.
Here is a chunk of code that is not working:
System.Drawing.Image img = System.Drawing.Image.FromStream(new MemoryStream(itextImg.RawData));
I could be going about this all wrong, but I won't know unless I consult the experts, and after two hours of fruitless searching online, I am finally posting it myself as a question.
I'm pretty sure that will work occasionally, but will fail in the general case... it depends on what compression filters the image is using.
I believe JPEG image streams are exactly what you'd see in a .jpeg file... but for most (all?) other compression types, the image information (height, width, bits per component, number of components, etc) is vital.
So it'll be possible, but Not Like That.
PS: There's at least one image format that iText cannot decompress, CITTFAXDecode (JBIG2, probably others). In those cases, you'll need some Other Software that will get the raw pixel data out so you can wrap it in a Drawing.Image.
Yes, I found the solution by rewriting BarcodeQRCode class of ITextSharp and GetImageMethod() as described below:
using System;
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using iTextSharp.text.pdf.qrcode;
using iTextSharp.text.pdf.codec;
/*
Class rewritted to Convert ByteMatrix to BMP image by rewritting GetImage method
from ITextSharp
author: Luis Claudio Souza
*/
namespace iTextSharp.text.pdf{
/**
* A QRCode implementation based on the zxing code.
* #author Paulo Soares
* #since 5.0.2
*/
public class BarcodeQRCode {
ByteMatrix bm;
/**
* Creates the QR barcode. The barcode is always created with the smallest possible size and is then stretched
* to the width and height given. Set the width and height to 1 to get an unscaled barcode.
* #param content the text to be encoded
* #param width the barcode width
* #param height the barcode height
* #param hints modifiers to change the way the barcode is create. They can be EncodeHintType.ERROR_CORRECTION
* and EncodeHintType.CHARACTER_SET. For EncodeHintType.ERROR_CORRECTION the values can be ErrorCorrectionLevel.L, M, Q, H.
* For EncodeHintType.CHARACTER_SET the values are strings and can be Cp437, Shift_JIS and ISO-8859-1 to ISO-8859-16. The default value is
* ISO-8859-1.
* #throws WriterException
*/
public BarcodeQRCode(String content, int width, int height, IDictionary<EncodeHintType, Object> hints) {
QRCodeWriter qc = new QRCodeWriter();
bm = qc.Encode(content, width, height, hints);
}
private byte[] GetBitMatrix() {
int width = bm.GetWidth();
int height = bm.GetHeight();
int stride = (width + 7) / 8;
byte[] b = new byte[stride * height];
sbyte[][] mt = bm.GetArray();
for (int y = 0; y < height; ++y) {
sbyte[] line = mt[y];
for (int x = 0; x < width; ++x) {
if (line[x] != 0) {
int offset = stride * y + x / 8;
b[offset] |= (byte)(0x80 >> (x % 8));
}
}
}
return b;
}
/** Gets an <CODE>Image</CODE> with the barcode.
* #return the barcode <CODE>Image</CODE>
* #throws BadElementException on error
*/
public void GetImage()
{
sbyte[][] imgNew = bm.GetArray();
Bitmap bmp1 = new Bitmap(bm.GetWidth(), bm.GetHeight());
Graphics g1 = Graphics.FromImage(bmp1);
g1.Clear(Color.White);
for (int i = 0; i <= imgNew.Length - 1; i++)
{
for (int j = 0; j <= imgNew[i].Length - 1; j++)
{
if (imgNew[j][i] == 0)
{
g1.FillRectangle(Brushes.Black, i, j, 1, 1);
}
else
{
g1.FillRectangle(Brushes.White, i, j, 1, 1);
}
}
}
bmp1.Save("D:\\QREncode.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
After that, I used this code to call the method:
var hints = new Dictionary<EncodeHintType, object>();
hints.Add(EncodeHintType.ERROR_CORRECTION, iTextSharp.text.pdf.qrcode.ErrorCorrectionLevel.H);
BarcodeQRCode code = new BarcodeQRCode("98134979213479523874952873", 100, 100, hints);
code.GetImage();
In new GetImage method, you can choose what to do with the bmp class. In this case, it saves a JPEG image file but the method can either return a memorystream to be used by the caller.
I am working on constructing and saving a bitmap, and i have a loop that sets the pixels in the bitmap to their proper values. However it crashes after a short period of ime with an IndexOutOfRange exception at the noted point in the code.
//data is an array of bytes of size (image width * image height) * 2;
Bitmap b = new Bitmap(width, height, PixelFormat.Format32bppArgb);
for (int i = 0; i < data.Length; i += 2)
{
int luminance = ((int)data[i] << 8) | (int)data[i + 1];
Color c = Color.FromArgb(luminance,luminance,luminance,luminance);
int x = i / 2;
int y = x / width;
x %= width;
b.SetPixel(x, y, c);//crashes here when Y is at 513, should only go to 512
}
b.Save(Path.GetFileNameWithoutExtension(fileName) + ".bmp");
I'm stumped as to why this happens.Why does this happen and how can i fix it?
(a note ot all of those that reommend unsafe code: I am going for a working program then a fast one. I'll be sure to write up 3 questions on the subject when i start! ;) )
When Length is odd, then at some point i+1 == Length will be true.
for (int i = 0; i < data.Length; i += 2)
{
int luminance = ((int)data[i] << 8) | (int)data[i + 1];
int x = (i + 1) / 2;
}
I would suggest replacing
//data is an array of bytes of size (image width * image height) * 2;
with
System.Diagnostics.Debug.Assert(data.Length == width * height * 2);
System.Diagnostics.Debug.Assert((data.Length % 2) == 0);
It's hard to tell what might be wrong without knowing what your data actually is. I suspect that it might be organised into rows like a bitmap, but sometimes bitmap format data requires that rows be a multiple of 4 bytes in length (with unused padding at the end, see BMP file format). If this is the case, your y value might become larger than you expect. You may need to take such padding into account.