I need to extract all image frames from a DICOM SC using fo-DICOM. I have a test app that extracts and displays the images, which works fine. However, I need to save the individual images to a database, and am running into problems.
I have the following code so far:
public void SetImages(DicomFile dicom, ThumbResults results)
{
var images = new DicomImage(dicom.Dataset);
for(var count = 0; count < images.NumberOfFrames; count++)
{
var image = images.RenderImage(count).As<Bitmap>();
using(var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Jpeg);
results.Images.Add(Convert.ToBase64String(stream.ToArray()));
}
}
}
I get a DicomImagingException, "Cannot cast to 'Bitmap'" on images.RenderImage. It works in my test code, when I call PictureBox.Image = _image.RenderImage(count).As<Bitmap>(); so I figure RenderImage must be specifically for rendering (as the name implies).
How should I go about extracting individual frames to a string that will be saved to the database?
In case someone else runs into this problem, the issue was the original code was in .NET Framework, but the new code was in .NET Core. In Core, the ImageManager does not use the WindowsImageManager by default, so you need to set it manually.
ImageManager.SetImplementation(WinFormsImageManager.Instance);
Related
I load a multiframe TIFF from a Stream in my C# application, and then save it using the Image.Save method. However, this only saves the TIFF with the first frame - how can I get it to save a multiframe tiff?
Since you don't provide any detailed information... just some general tips:
Multi-Frame TIFF are very complex files - for example every frame can have a different encoding... a single Bitmap/Image can't hold all frames with all relevant information (like encoding and similar) of such a file, only one at a time.
For loading you need to set parameter which tells the class which frame to load, otherwise it just loads the first... for some code see here.
Similar problems arise when saving multi-frame TIFFs - here you need to work with EncoderParameters and use SaveAdd etc. - for some working code see here.
Since the link to code provided by #Yahia is broken I have decided to post the code I ended up using.
In my case, the multi-frame TIFF already exists and all I need to do is to load the image, rotate by EXIF (if necessary) and save. I won't post the EXIF rotation code here, since it does not relate to this question.
using (Image img = System.Drawing.Image.FromStream(sourceStream))
{
using (FileStream fileStream = System.IO.File.Create(filePath))
{
int pages = img.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
if (pages == 1)
{
img.Save(fileStream, img.RawFormat); // if there is just one page, just save the file
}
else
{
var encoder = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders().First(x => x.MimeType == fileInfo.MediaType);
var encoderParams = new System.Drawing.Imaging.EncoderParameters(1);
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(System.Drawing.Imaging.EncoderValue.MultiFrame));
img.Save(fileStream, encoder, encoderParams); // save the first image with MultiFrame parameter
for (int f = 1; f < pages; f++)
{
img.SelectActiveFrame(FrameDimension.Page, f); // select active page (System.Drawing.Image.FromStream loads the first one by default)
encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, Convert.ToInt32(System.Drawing.Imaging.EncoderValue.FrameDimensionPage));
img.SaveAdd(img, encoderParams); // save add with FrameDimensionPage parameter
}
}
}
}
sourceStream is a System.IO.MemoryStream which holds the byte array of the file content
filePath is absolute path to cache directory (something like 'C:/Cache/multiframe.tiff')
fileInfo is a model holding the actual byte array, fileName, mediaType and other data
I'm reading in a .docx file using the Novacode API, and am unable to create or display any images within the file to a WinForm app due to not being able to convert from a Novacode Picture (pic) or Image to a system image. I've noticed that there's very little info inside the pic itself, with no way to get any pixel data that I can see. So I have been unable to utilize any of the usual conversion ideas.
I've also looked up how Word saves images inside the files as well as Novacode source for any hints and I've come up with nothing.
My question then is is there a way to convert a Novacode Picture to a system one, or should I use something different to gather the image data like OpenXML? If so, would Novacode and OpenXML conflict in any way?
There's also this answer that might be another place to start.
Any help is much appreciated.
Okay. This is what I ended up doing. Thanks to gattsbr for the advice. This only works if you can grab all the images in order, and have descending names for all the images.
using System.IO.Compression; // Had to add an assembly for this
using Novacode;
// Have to specify to remove ambiguous error from Novacode
Dictionary<string, System.Drawing.Image> images = new Dictionary<string, System.Drawing.Image>();
void LoadTree()
{
// In case of previous exception
if(File.Exists("Images.zip")) { File.Delete("Images.zip"); }
// Allow the file to be open while parsing
using(FileStream stream = File.Open("Images.docx", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using(DocX doc = DocX.Load(stream))
{
// Work rest of document
// Still parse here to get the names of the images
// Might have to drag and drop images into the file, rather than insert through Word
foreach(Picture pic in doc.Pictures)
{
string name = pic.Description;
if(null == name) { continue; }
name = name.Substring(name.LastIndexOf("\\") + 1);
name = name.Substring(0, name.Length - 4);
images[name] = null;
}
// Save while still open
doc.SaveAs("Images.zip");
}
}
// Use temp zip directory to extract images
using(ZipArchive zip = ZipFile.OpenRead("Images.zip"))
{
// Gather all image names, in order
// They're retrieved from the bottom up, so reverse
string[] keys = images.Keys.OrderByDescending(o => o).Reverse().ToArray();
for(int i = 1; ; i++)
{
// Also had to add an assembly for ZipArchiveEntry
ZipArchiveEntry entry = zip.GetEntry(String.Format("word/media/image{0}.png", i));
if(null == entry) { break; }
Stream stream = entry.Open();
images[keys[i - 1]] = new Bitmap(stream);
}
}
// Remove temp directory
File.Delete("Images.zip");
}
I'm having to fix a bug in some very old code that converts a Base64 string to a image using Memory Stream. Basically it loops through a list of images stored as base64 strings and converts them to images then draws them using ActiveReports.
The bug is that once it loads one image all following images will be a copy of the first image.
I found the code that is doing the conversion of string to image and immediately noticed it isn't disposing of the memory stream. If I wrap the memory stream in a using block I get a GDI exception. I'm guessing this is because the image isn't really read from memory yet or something, but I'd like to hear if anyone has a guess. Thanks in advance!
byte[] oGraphic = null;
try
{
oGraphic = Convert.FromBase64String(psGraphic);
DataDynamics.ActiveReports.Picture oImg = new Picture();
oImg.Top = this.Legend.Top + this.fTopFirst;
oImg.Visible = true;
oImg.Name = sLabelName;
oImg.PictureAlignment = PictureAlignment.Center;
oImg.Image = null;
if (oGraphic != null)
{
var oStream = new MemoryStream(oGraphic);
oImg.Image = System.Drawing.Image.FromStream(oStream);
oImg.Height = Convert.ToSingle(oImg.Image.Height)/(oImg.Image.VerticalResolution);
oImg.Width = Convert.ToSingle(oImg.Image.Width)/(oImg.Image.HorizontalResolution);
oImg.SizeMode = SizeModes.Zoom;
this.fGraphicHeight = oImg.Height;
this.fGraphicWidth = oImg.Width;
if (this.fConstantGraphic > this.fGraphicWidth)
oImg.Left = this.Legend.Left + this.fLeftFirst +
((this.fConstantGraphic - this.fGraphicWidth)/2);
else
oImg.Left = this.Legend.Left + this.fLeftFirst;
}
else
{
this.fGraphicHeight = 0f;
this.fGraphicWidth = 0f;
}
this.GHMap.Controls.Add(oImg);
}
catch (Exception oE)
{
.....
}
The only thing I can imagine is if there is a line of code missing from what you have here:
if (oGraphic == null) // missing line
oGraphic = Convert.FromBase64String(psGraphic);
There is no reason for this byte[] to be declared outside of the try { } block. That array gets wrapped into a MemoryStream which is then wrapped into an Image. That image is attached to a brand new Picture which is added to a Picture collection.
What is it we don't see?
Here's another guess (I'll leave the first guess alone for posterity):
I'm not familiar with Active Reports, but it looks like you're setting the Top and PictureAlignment properties of the Picture object to the same value and adding more than one Picture. Is it possible they are all there, but one on top of each other? So the result is a single picture?
Guess #3 (one of these is going to get a checkmark, I just know it!)
Everything looks OK in the code provided, therefore the problem is somewhere else (though it's still entirely possible I'm wrong about it being OK).
Are you certain that psGraphic is different each time this code is executed?
The cause of the problem is that the Picture control is a single control instance on a single section. So you're just overwriting the image on this single control over and over.
If the only thing you want to see in this report is the images, then the best thing to do is use ActiveReports' Unbound mode and treat each image as another "record". See this walkthrough for an example of using unbound mode (see the DataInitialize and FetchData events for the meat of the matter).
Using unbound mode, ActiveReports will render the image one after the other in sections treating each image like a new record. The code would be something like the following (sorry I don't have ActiveReports handy at the moment so I can't check this code, but this should be pretty close. Let me know if you run into any problems and I'll clean it up in the morning):
In ActiveReports' ReportStart Event:
DataDynamics.ActiveReports.Picture oImg = new Picture();
oImg.Top = 0;
oImg.Visible = true;
oImg.Name = sLabelName;
oImg.PictureAlignment = PictureAlignment.Center;
// setting DataField "binds" the Picture control to get it's data from the MyImageField field which we'll initialize and bind in the events below
oImg.DataField = "MyImageField";
this.Sections["Detail"].Controls.Add(oImg);
In ActiveReports' DataInitialize Event:
this.Fields.Add("MyImageField");
In ActiveReports FetchData Event:
var imageBytes = Convert.FromBase64String(_imageStrings.Current); // I'm not sure where the base64 image strings come from, some I'm assuming you can put them in an enumerator field in the report like "_imageStrings"
var imageStream = new MemoryStream(imageBytes);
var image = Image.FromStream(imageStream);
Fields["MyImageField"].Value = image;
// This tells ActiveReports if there are more records, and if it should raise the FetchData event again (allowing you to add another image).
eArgs.EOF = !_imageStrings.MoveNext();
If you need to resize the image control for each image, use the section's Format event for that. You could use something like the following:
In Detail_Format Event:
var pictureControl = this.Sections["Detail"].Controls["MyImageControl"] as DataDynamics.ActiveReports.Picture;
pictureControl.Width = Convert.ToSingle(pictureControl.Image.Width)/(pictureControl.Image.VerticalResolution);
pictureControl.Width = Convert.ToSingle(pictureControl.Image.Width)/(pictureControl.Image.HorizontalResolution);
Finally, ActiveReports will also just automatically bind to a set of POCO objects in an IEnumerable (or IList, I forget). So you could simply have a "MyImage" class with a property like "MyImage" and ActiveReports will read it and bind with it (you wouldn't have to write any code in DataInitialize and FetchData). I think you might also just be able to put the MemoryStream in there as the binding too and ActiveReports will read it, but I'm not positive on that.
BTW: The reason that GDI exception occurs when disposing the MemoryStream is because GDI attempts to just seek within that single MemoryStream for the image data rather than making a copy of it. So you'll need to supply each System.Drawing.Image instance with a new stream (don't worry MemoryStream will clean itself up when everything is released).
It turned out to be a problem with the way we were creating the image. Adding the code below fixed the problem.
oImg.Image = System.Drawing.Image.FromStream(oStream);
TO THIS
oImg.Image = ImageFromBase64String(psGraphic);
private Image ImageFromBase64String(string sBase64String)
{
using (var sStream = new MemoryStream(Convert.FromBase64String(sBase64String)))
using (var iSourceImage = Image.FromStream(sStream))
{
return new Bitmap(iSourceImage);
}
}
When attempting to add image frames using SelectActiveFrame to an image list I get an Out of Memory exception. All works 100% if I process less than 174 pages but anything over this produces this error. In it's simplest form my code is;
var scannedImage = (Bitmap)Image.FromFile(#"C:\Users\rbl\Documents\Modelware\City Prop\TIFF Files\barcodememory.tiff");
var dim = new FrameDimension(scannedImage.FrameDimensionsList[0]);
var imageCount = scannedImage.GetFrameCount(dim);
var currentBatch = new List<Image>();
for (var i = 0; i < imageCount; i++)
{
scannedImage.SelectActiveFrame(dim, i);
currentBatch.Add(new Bitmap(scannedImage));
// Above experiences following error after +/- 174 pages
//System.OutOfMemoryException was unhandled
//Message=Out of memory.
//Source=System.Drawing
}
You are running out of memory. You need to load the images in batches (just load what you need and unload it as you no longer need it.)
Anyways... I don't see why you need several bitmaps. Just do new Bitmap(...) once and reuse it (unless the code you pasted is actually not your real code and you're processing several scannedImage inside that for loop.)
It is interesting that I am able to resolve my problem by converting the frame to a bytestream and then back to an image. The following works 100% (just not sure why, or whether this is a good solution).
for (int i = 0; i < pageCount; i++)
{
image.SelectActiveFrame(dim, i);
var byteStream = new MemoryStream();
image.Save(byteStream, ImageFormat.Bmp);
frames[i] = Image.FromStream(byteStream);
}
I'm trying to generate a multipage TIFF file from an existing picture using code by Bob Powell:
picture.SelectActiveFrame(FrameDimension.Page, 0);
var image = new Bitmap(picture);
using (var stream = new MemoryStream())
{
ImageCodecInfo codecInfo = null;
foreach (var imageEncoder in ImageCodecInfo.GetImageEncoders())
{
if (imageEncoder.MimeType != "image/tiff") continue;
codecInfo = imageEncoder;
break;
}
var parameters = new EncoderParameters
{
Param = new []
{
new EncoderParameter(Encoder.SaveFlag, (long) EncoderValue.MultiFrame)
}
};
image.Save(stream, codecInfo, parameters);
parameters = new EncoderParameters
{
Param = new[]
{
new EncoderParameter(Encoder.SaveFlag, (long) EncoderValue.FrameDimensionPage)
}
};
for (var i = 1; i < picture.GetFrameCount(FrameDimension.Page); i++)
{
picture.SelectActiveFrame(FrameDimension.Page, i);
var img = new Bitmap(picture);
image.SaveAdd(img, parameters);
}
parameters = new EncoderParameters
{
Param = new[]
{
new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush)
}
};
image.SaveAdd(parameters);
stream.Flush();
}
But it's not working (only the first frame is included in the image) and I don't know why.
What I want to do is to change a particular frame of a TIFF file (add annotations to it).
I don't know if there's a simpler way to do it but what I have in mind is to create a multipage TIFF from the original picture and add my own picture instead of that frame.
[deleted first part after comment]
I'm working with multi-page TIFFs using LibTIFF.NET; I found many quicks in handling of TIFF using the standard libraries (memory related and also consistent crashes on 16-bit gray scale images).
What is your test image? Have you tried a many-frame tiff (preferably with a large '1' on the first frame, a '2 on the next etc; this could help you to be certain on the frame included in the file.
Another useful diagnosis may be tiffdump utility, as included in LibTiff binaries (also for windows). This will tell you exactly what frames you have.
See Using LibTiff from c# to access tiled tiff images
[Edit] If you want to understand the .NET stuff: I've found a new resource on multi-page tiffs using the standard .NET functionality (although I'll stick with LibTIFF.NET): TheCodeProject : Save images into a multi-page TIFF file... If you download it, the code snippet in Form1.cs function saveMultipage(..) is similar (but still slightly different) than your code. Especially the flushing at the end is done in a differnt way, and the file is deleted before the first frame...
[/Edit]
It seems that this process doesn't change image object but it changes the stream so I should get the memory stream buffer and build another image object:
var buffer=stream.GetBuffer();
using(var newStream=new MemoryStream(buffer))
{
var result=Image.FromStream(newStream);
}
Now result will include all frames.