Unable to get dicom image for display in python - python

I'm trying to display a DICOM image in opencv-python.I am using the pydicom library,And then adding API's to create a full fledged DICOM viewer with DOTNET, that runs python(C# calls python with process instance of course!!). I am unable to convert or see the uncompressed DICOM image. whenever i try to load or modify the pixel_array. I get error messges.
import dicom
import cv2
import numpy
df=dicom.read_file("IM-0001-0002.dcm")
df.pixel_array
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
df.pixel_array
File "C:\Python27\lib\site-packages\dicom\dataset.py", line 394, in pixel_array
return self._get_pixel_array()
File "C:\Python27\lib\site-packages\dicom\dataset.py", line 376, in _get_pixel_array
raise NotImplementedError("Pixel Data is compressed in a format pydicom does not yet handle. Cannot return array")
NotImplementedError: Pixel Data is compressed in a format pydicom does not yet handle. Cannot return array
Please suggest me good way to convert the image making cv2.imshow() function o display the image
Thanks in Advance!!

Try pydicom
One reason for the error can be: the .dcm file used may contain the format that is not supported (e.g. JPEG 2000 is not supported by pillow in case of pydicom). This issue can be solved. I was having the same issue (I am using pydicom instead of dicom) I guess you will get some direction from the solution that solved my problem:
1st Platforma Information:
I am using: pydicom to read .dcm files, Python 3.6, Anaconda and Ubuntu, 15 GB RAM
Solution:
Install pydicom using this command: pip install -U pydicom.
Information can be found here: (link: https://pydicom.github.io/pydicom/dev/getting_started.html)
Anaconda is necessary. Why?
Please check the official doc of pydicom (https://pydicom.github.io/pydicom/dev/getting_started.html) its mentioned "To install pydicom along with image handlers for compressed pixel data, we encourage you to use Miniconda or Anaconda"
If you are using Ubuntu directly open Terminal. If you are using Windows then on Anaconda Navigator go to Environment from here start terminal. Execute the following commands on it:
pip install -U git+https://github.com/pydicom/pydicom.git
conda install pydicom --channel conda-forge
conda install -c conda-forge gdcm
Cross Check:
Now restart the notebook and then try to execute your code using pydicom. It will display the output.
Also, you can use Matplotlib to display as follows:
import matplotlib.pyplot as plt
import pydicom
filename = 'IM-0001-0002.dcm'
ds = pydicom.dcmread(filename)
plt.imshow(ds.pixel_array, cmap=plt.cm.bone)
I hope it will help you.

Since pydicom do not support compressed dicom files, you will have to decompress it first.
You can use GDCM to do that.

You can use the python GDCM binding to decompress the file first, see for example here

you have to convert in RGB before, look at that for a monochrome dicom file:
https://github.com/twanmal/dicom_monochrome_to_opencv
# import the necessary packages
from imutils import contours
import scipy
from skimage import measure
import numpy as np # numeric library needed
import pandas as pd #for datafrome
import argparse # simple argparser
import imutils
import cv2 # for opencv image recognising tool
import dicom
filename = askopenfilename()
dicom_file = dicom.read_file(filename) ## original dicom File
#### a dicom monochrome-2 file has pixel value between approx -2000 and +2000, opencv doesn't work with it#####
#### in a first step we transform those pixel values in (R,G,B)
### to have gray in RGB, simply give the same values for R,G, and B,
####(0,0,0) will be black, (255,255,255) will be white,
## the threeshold to be automized with a proper quartile function of the pixel distribution
black_threeshold=0###pixel value below 0 will be black,
white_threeshold=1400###pixel value above 1400 will be white
wt=white_threeshold
bt=black_threeshold
###### function to transform a dicom to RGB for the use of opencv,
##to be strongly improved, as it takes to much time to run,
## and the linear process should be replaced with an adapted weighted arctan or an adapted spline interpolation.
def DicomtoRGB(dicomfile,bt,wt):
"""Create new image(numpy array) filled with certain color in RGB"""
# Create black blank image
image = np.zeros((dicomfile.Rows, dicomfile.Columns, 3), np.uint8)
#loops on image height and width
i=0
j=0
while i<dicomfile.Rows:
j=0
while j<dicomfile.Columns:
color = yaxpb(dicom_file.pixel_array[i][j],bt,wt) #linear transformation to be adapted
image[i][j] = (color,color,color)## same R,G, B value to obtain greyscale
j=j+1
i=i+1
return image
##linear transformation : from [bt < pxvalue < wt] linear to [0<pyvalue<255]: loss of information...
def yaxpb(pxvalue,bt,wt):
if pxvalue < bt:
y=0
elif pxvalue > wt:
y=255
else:
y=pxvalue*255/(wt-bt)-255*bt/(wt-bt)
return y
image=DicomtoRGB(dicom_file,bt=0,wt=1400)
## loading the RGB in a proper opencv format
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
## look at the gray file
cv2.imshow("gray", gray)
cv2.waitKey(0)
cv2.destroyWindow("gray")

Related

Is it true that different types do different rounding?

When I read images with cv2.imread and PIL.Image.open it gave different results (I have used old version (Pillow 8.4.0), now it fixed (Pillow >= 9.0.0). So when I read it into a numpy array with default type as uint8, the results were different, but when I changed the type to int16, the results became the same. So my question is: Is it true that different types do different rounding and why is it so?
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
img1 = cv2.cvtColor(cv2.imread('post_5c606e629f934.jpg'), cv2.COLOR_BGR2RGB)
img2 = Image.open('post_5c606e629f934.jpg')
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(img1)
ax[0].set_xlabel("cv2.imread")
ax[1].imshow(img2)
ax[1].set_xlabel("PIL.Image.open")
ax[2].imshow(img2 - img1)
ax[2].set_xlabel("difference")
plt.show()
Gives following result:
But if we change the type to int16 then:
img1 = img1.astype(np.int16)
img2 = np.array(img2, dtype=np.int16)
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(img1)
ax[0].set_xlabel("cv2.imread")
ax[1].imshow(img2)
ax[1].set_xlabel("PIL.Image.open")
ax[2].imshow(img2 - img1)
ax[2].set_xlabel("difference")
plt.show()
This gives the following result:
If I'd have to answer your question "Is it true that different types do different rounding?", I'd say that this is not the case, or at least that you can't draw that conclusion from the code you shared.
Unfortunately I cannot reproduce the issue. To understand what's happening, it's important to look at the facts:
You read an image using OpenCV (cv2).
You read an image using Pillow (PIL).
You extract those images (implicitly using numpy).
You draw that delta image using matplotlib.
The delta image is not fully black, and seems to indicate a "rounding issue" (not on my setup!).
Based on this facts, I'd say that there is a difference in the way that OpenCV and Pillow read the file. Or possibly there is something strange about the way that numpy handles the images. (You could try to add a line img2 = numpy.array(img2) to make sure that img and img2 are of the same type, but on my machine this doesn't make a difference).
You suggested that there is a difference between Pillow version 8.4.0 and version >= 9.0.0, but I do not observe such a difference.
The following Python script can be used to verify the actual image data after being read using OpenCV and/or Pillow:
image_test.py:
import hashlib
import sys
import cv2
import PIL.Image
import numpy
print('Python version : %s' % sys.version)
print('PIL version : %s' % PIL.__version__)
cv2_image = cv2.cvtColor(cv2.imread('post_5c606e629f934.jpg'), cv2.COLOR_BGR2RGB)
pil_image = PIL.Image.open('post_5c606e629f934.jpg')
if (numpy.array(pil_image) == cv2_image).all():
print('Images are the same.')
delta_image = pil_image - cv2_image
if delta_image.min() == delta_image.max() == 0:
print('Delta image is fully black.')
cv2_image_data = cv2_image.tobytes()
print('With CV2:')
print('- Size of image data : %d' % len(cv2_image_data))
print('- MD5 hash of image data: %a' % hashlib.md5(cv2_image_data).hexdigest())
pil_image_data = numpy.array(pil_image).tobytes()
print('With PIL:')
print('- Size of image data : %d' % len(pil_image_data))
print('- MD5 hash of image data: %a' % hashlib.md5(pil_image_data).hexdigest())
To test with different Pillow versions (assuming you use a virtual environment on Windows and the above file is stored as image_test.py), run:
python -m pip install Pillow==8.4.0 && python -m image_test.py
python -m pip install Pillow==9.2.0 && python -m image_test.py
On my machine, the output is:
Python version : 3.10.2 (tags/v3.10.2:a58ebcc, Jan 17 2022, 14:12:15) [MSC v.1929 64 bit (AMD64)]
PIL version : 9.2.0
Images are the same.
Delta image is fully black.
With CV2:
- Size of image data : 3600288
- MD5 hash of image data: '6cc4a9155c15a061660af28d1ac35807'
With PIL:
- Size of image data : 3600288
- MD5 hash of image data: '6cc4a9155c15a061660af28d1ac35807'
This proves that the image data is exactly the same when read with OpenCV or Pillow. I get the same results using Pillow version 8.4.0.
If you can reproduce a different output than this, you can determine where the image data is different (the MD5 hash helps in determining which images are the same and which are not — images with the same hash have exactly the same image data).
If the images are exactly the same using 8-bit data, they will be exactly the same when converted to 16-bit format. The only thing that is different in your last piece of code is the explicit conversion to a numpy array. So there might be something with the way that numpy deals with PIL images, although I couldn't find a version of numpy that has a different behavior. (Conversion functions from PIL Image to numpy array were added in numpy version 1.1.6, which was in 2008, so I can't imagine you're using a version older than that.)

Convert an SVG to grayscale in python

Scenario
I'm trying to convert a colored example.svg file into a grayscaled example_gs.svg in python 3.6 in Anaconda 4.8.3 on a Windows 10 device.
Attempts
First I tried to apply a regex to convert the 'rgb(xxx,yyy,zzz)' to black, but that created a black rectangle losing the image in the process. Next I installed inkscape and ran a grayscale command which appeared to be working but did not modify the example.svg. The third attempt with pillow Image did not load the .svg.
MWE
# conda install -c conda-forge inkscape
# https://www.commandlinefu.com/commands/view/2009/convert-a-svg-file-to-grayscale
# inkscape -f file.svg --verb=org.inkscape.color.grayscale --verb=FileSave --verb=FileClose
import re
import os
import fileinput
from PIL import Image
import cv2
# Doesn't work, creates a black square. Perhaps set a threshold to not convert rgb white/bright colors
def convert_svg_to_grayscale(filepath):
# Read in the file
with open(filepath, 'r') as file :
filedata = file.read()
# Replace the target string
filedata = re.sub(r'rgb\(.*\)', 'black', filedata)
# Write the file out again
with open(filepath, 'w') as file:
file.write(filedata)
# opens inkscape, converts to grayscale but does not actually export to the output file again
def convert_svg_to_grayscale_inkscape(filepath):
command = f'inkscape -f {filepath} --verb=org.inkscape.color.grayscale --verb=FileSave --verb=FileClose'
os.system(f'cmd /k {command}')
# Pillow Image is not able to import .svg files
def grayscale(filepath):
image = Image.open(filepath)
cv2.imwrite(f'{filepath}', image.convert('L'))
# walks through png files and calls function to convert the png file to .svg
def main():
filepath = 'example.svg'
convert_svg_to_grayscale_inkscape(filepath)
convert_svg_to_grayscale(filepath)
grayscale(filepath)
if __name__ == '__main__':
main()
Question
How could I change a colored .svg file into a grayscaled image in python 3.6 on a windows device?
You have chosen the right tool for converting to grayscale. Your last attempt is good but you need to import cairosvg that provides the svg2png function. Then, load the png file with Pillow and convert it to an np.array and then you can easily load it with openCV and convert it to grayscale as you did. At last you can use svglib and reportlab to export the images in svg.
Use this snippet as an example:
https://stackoverflow.com/a/62345450/13605264

How to load *.hdr files using python

I would like to read an environment map in *.hdr file format. It seems that very popular libraries doesn't support .hdr file reading, for example, OpenCV, PIL etc.. So how to read a .hdr file into a numpy array?
I found ImageIO very useful. It can handle many image file formats including .hdr images. Here is the list: ImageIO Formats
It can be easily installed using easy_install or pip.
For some reason when I was trying to load a MRI image in .hdr format using format='HDR-FI' it was returning Could not load bitmap <path to image>: : RGBE read error
But if you type imageio.show_formats() it returns a list of formats including "ITK - Insight Segmentation and Registration Toolkit", where it shows that it can handle .hdr images as well.
So my alternative was to use:
pip install itk
hdr_path = "<path to image>"
img = imageio.imread(hdr_path, 'ITK') # returns a tuple
img = np.array(img) # transforms to numpy array

Python OpenCV drawing errors after manipulating array with numpy

I'm reading in an image with OpenCV, and trying to do something with it in numpy (rotate 90deg). Viewing the result with imshow from matplotlib, it all seems to be working just fine - image is rotated. I can't use drawing methods from OpenCV on the new image, however. In the following code (I'm running this in a sagemath cloud worksheet):
%python
import cv2
import matplotlib.pyplot as plt
import numpy as np
import os, sys
image = np.array( cv2.imread('imagename.png') )
plt.imshow(image,cmap='gray')
image = np.array(np.rot90(image,3) ) # put it right side up
plt.imshow(image,cmap='gray')
cv2.rectangle(image,(0,0),(100,100),(255,0,0),2)
plt.imshow(image,cmap='gray')
I get the following error on the cv2.rectangle() command:
TypeError: Layout of the output array img is incompatible with cv::Mat (step[ndims-1] != elemsize or step[1] != elemsize*nchannels)
The error goes away if I use np.array(np.rot90(image,4) ) instead (i.e. rotate it 360). So it appears that the change in dimensions is messing it up. Does OpenCV store the dimensions somewhere internally that I need to update or something?
EDIT: Adding image = image.copy() after rot90() solved the problem. See rayryeng's answer below.
This is apparently a bug in the Python OpenCV wrapper. If you look at this question here: np.rot90() corrupts an opencv image, apparently doing a rotation that doesn't result back in the original dimensions corrupts the image and the OP in that post experiences the same error you are having. FWIW, I also experienced the same bug.... no idea why.
A way around this is to make a copy of the image after you rotate, and then show the image. This I can't really explain, but it seems to work. Also, make sure you call plt.show() at the end of your code to show the image:
import cv2
import matplotlib.pyplot as plt
import numpy as np
import os, sys
image = np.array( cv2.imread('imagename.png') )
plt.imshow(image,cmap='gray')
image = np.array(np.rot90(image,3) ) # put it right side up
image = image.copy() # Change
plt.imshow(image,cmap='gray')
cv2.rectangle(image,(0,0),(100,100),(255,0,0),2)
plt.imshow(image,cmap='gray')
plt.show() # Show image
I faced the same problem with numpy 1.11.2 and opencv 3.3.0. Not sure why, but this did the job for me.
Before using cv2.rectangle, add the line below:
image1 = image1.transpose((1,0)).astype(np.uint8).copy()
Reference
Convert data type works for my problem.
The image is of type np.int64 before the convert.
image = image.astype(np.int32) # convert data type

Python PIL struggles with uncompressed 16-bit TIFF images

My system is Mac OS X v10.8.2. I have several 2560x500 uncompressed 16-bit TIFF images (grayscale, unsigned 16-bit integers). I first attempt to load them using PIL (installed via Homebrew, version 1.7.8):
from PIL import Image
import numpy as np
filename = 'Rocks_2ptCal_750KHz_20ms_1ma_120KV_2013-03-06_20-02-12.tif'
img = Image.open(filename)
# >>> img
# <PIL.TiffImagePlugin.TiffImageFile image mode=I;16B size=2560x500 at 0x10A383C68>
img.show()
# almost all pixels displayed as white. Not correct.
# MatLab, EZ-draw, even Mac Preview show correct images in grayscale.
imgdata = list(img.getdata())
# most values negative:
# >>> imgdata[0:10]
# [-26588, -24079, -27822, -26045, -27245, -25368, -26139, -28454, -30675, -28455]
imgarray = np.asarray(imgdata, dtype=np.uint16)
# values now correct
# >>> imgarray
# array([38948, 41457, 37714, ..., 61922, 59565, 60035], dtype=uint16)
The negative values are off by 65,536... probably not a coincidence.
If I pretend to alter pixels and revert back to TIFF image via PIL (by just putting the array back as an image):
newimg = Image.fromarray(imgarray)
I get errors:
File "/usr/local/lib/python2.7/site-packages/PIL/Image.py", line 1884, in fromarray
raise TypeError("Cannot handle this data type")
TypeError: Cannot handle this data type
I can't find Image.fromarray() in the PIL documentation. I've tried loading via Image.fromstring(), but I don't understand the PIL documentation and there is little in the way of example.
As shown in the code above, PIL seems to "detect" the data as I;16B. From what I can tell from the PIL docs, mode I is:
*I* (32-bit signed integer pixels)
Obviously, that is not correct.
I find many posts on SX suggesting that PIL doesn't support 16-bit images. I've found suggestions to use pylibtiff, but I believe that is Windows only?
I am looking for a "lightweight" way to work with these TIFF images in Python. I'm surprised it is this difficult and that leads me to believe the problem will be obvious to others.
It turns out that Matplotlib handles 16-bit uncompressed TIFF images in two lines of code:
import matplotlib.pyplot as plt
img = plt.imread(filename)
# >>> img
# array([[38948, 41457, 37714, ..., 61511, 61785, 61824],
# [39704, 38083, 36690, ..., 61419, 60086, 61910],
# [41449, 39169, 38178, ..., 60192, 60969, 63538],
# ...,
# [37963, 39531, 40339, ..., 62351, 62646, 61793],
# [37462, 37409, 38370, ..., 61125, 62497, 59770],
# [39753, 36905, 38778, ..., 61922, 59565, 60035]], dtype=uint16)
Et voila. I suppose this doesn't meet my requirements as "lightweight" since Matplotlib is (to me) a heavy module, but it is spectacularly simple to get the image into a Numpy array. I hope this helps someone else find a solution quickly as this wasn't obvious to me.
Try Pillow, the “friendly” PIL fork. They've somewhat recently added better support for 16- and 32-bit images including in the numpy array interface. This code will work with the latest Pillow:
from PIL import Image
import numpy as np
img = Image.open('data.tif')
data = np.array(img)

Categories

Resources