How do I open geotiff images with GDAL in Python? - python

I am trying to run the following code:
from osgeo import gdal
import sys
# This allows GDAL to throw Python exceptions
src_ds = gdal.Open("fused.tif")
src_ds.show()
But I receive the following error:
Traceback (most recent call last):
File ".../gdalopen1.py", line 5, in module src_ds.show()
AttributeError: 'Dataset' object has no attribute 'show'
Why does this happen?

The following code opens a raster file and reads a band of the raster into a NumPy array.
from osgeo import gdal
ds = gdal.Open('input.tif', gdal.GA_ReadOnly)
rb = ds.GetRasterBand(1)
img_array = rb.ReadAsArray()

You have already opened the dataset, as Spacedman answered. GDAL is not a visualization library (at its core).
You can read the data with:
data = src_ds.ReadAsArray()
And then pass it on the your favourite plotting library.
Alternatively you could simply output to a more common 'picture' format (PNG for example), and use any viewer you like to display the result.
vmin = 0 # minimum value in your data (will be black in the output)
vmax = 1 # minimum value in your data (will be white in the output)
ds = gdal.Translate('fused.png', 'fused.tif', format='PNG', outputType=gdal.GDT_Byte, scaleParams=[[vmin,vmax]])
ds = None
The scaling is necessary to convert your data values to the 8-bit range (0-255) which commonly used for pictures.

You can do it by following.
from osgeo import gdal
gdal.UseExceptions()
ds = gdal.Open('Your Geotif image')
band = ds.getRasterBand(1)

Related

Trying to crop image and save dicom with pydicom,

Trying to load a chest x-ray DICOM file that has JPEG2000 compression, extract the pixel array, crop it, and then save as a new DICOM file. Tried this on a Windows10 and MacOS machine, but getting similar errors. Running Python 3.6.13, GDCM 2.8.0, OpenJpeg 2.3.1, Pillow 8.1.2 in a conda environment (installed OpenJPEG and GDCM first before installing Pillow and Pydicom).
My initial code:
file_list = [f.path for f in os.scandir(basepath)]
ds = pydicom.dcmread(file_list[0])
arr = ds.pixel_array
arr = arr[500:1500,500:1500]
ds.Rows = arr.shape[0]
ds.Columns = arr.shape[1]
ds.PixelData = arr.tobytes()
outputpath = os.path.join(basepath, "test.dcm")
ds.save_as(outputpath)
Subsequent error: ValueError: With tag (7fe0, 0010) got exception: (7FE0,0010) Pixel Data has an undefined length indicating that it's compressed, but the data isn't encapsulated as required. See pydicom.encaps.encapsulate() for more information
I then tried modifying the ds.PixelData line to ds.PixelData = pydicom.encaps.encapsulate([arr.tobytes()]) which creates the .dcm without error, but when I open the .dcm to view it doesn't show any image (all black).
My next attempt was to see if I needed to somehow compress back to JPEG2000, so I attempted:
arr = Image.fromarray(arr)
output = io.BytesIO()
arr.save(output, format='JPEG2000')
but then I get error: OSError: encoder jpeg2k not available. I also tried format='JPEG' but then it tells me OSError: cannot write mode I;16 as JPEG ...
Any help much appreciated!
Was able to get this working by using the imagecodecs library and the jpeg2k_encode function. One potential pitfall is you need to .copy() the array to meet the function's C contiguous requirement, which you can confirm by running arr_crop.flag if you needed to. Here is the updated code that worked best for me:
import os
import numpy as np
import matplotlib.pyplot as plt
import pydicom
from pydicom.encaps import encapsulate
from pydicom.uid import JPEG2000
from imagecodecs import jpeg2k_encode
file_list = [f.path for f in os.scandir(basepath)]
ds = pydicom.dcmread(file_list[0])
arr = ds.pixel_array
#Need to copy() to meet jpeg2k_encodes C contiguous requirement
arr_crop = arr[500:1500,500:1500].copy()
# jpeg2k_encode to perform JPEG2000 compression
arr_jpeg2k = jpeg2k_encode(arr_crop)
# convert from bytearray to bytes before saving to PixelData
arr_jpeg2k = bytes(arr_jpeg2k)
ds.Rows = arr_crop.shape[0]
ds.Columns = arr_crop.shape[1]
ds.PixelData = encapsulate([arr_jpeg2k])
outputpath = os.path.join(basepath, "test.dcm")
ds.save_as(outputpath)
I also ended up using the interactivecrop package to relatively quickly get the crop indices I needed (a tip in case future folks try this in jupyter). In case it's helpful, here's a snippet of code for that (which is run before the above):
from interactivecrop.interactivecrop import main as crop
file_names = [os.path.split(f)[1].split(".")[0] for f in file_list]
image_list = []
for x in file_list:
ds = pydicom.dcmread(x)
arr = ds.pixel_array
image_list.append(arr)
crop(image_list, file_names, optimize=True)
#After cropping all images, will print a dictionary
#copied and pasted this dictionary to a new cell as crop_dict
#use the below function to convert the output to actual indices
def convert_crop_to_index(fname, crop_dict):
x = [crop_dict[fname][1], crop_dict[fname][1] + crop_dict[fname][3]]
y = [crop_dict[fname][0], crop_dict[fname][0] + crop_dict[fname][2]]
return x, y
arr_crop = arr[x[0]:x[1],y[0]:y[1]].copy()
Was never able to quite figure out why ds.decompress() and saving the decompressed dicom was generating an all black image. I feel like that should have been the easiest method, but the above ended up working for me, so I'm happy was able to figure it out.

Reading 3D DICOM volume into Python gives "sitk::ERROR: The file in the series have unsupported 3 dimensions"

I am using SimpleITK on Python 3.7.3 on Anaconda Spyder on CentOS 7. I have also installed Aliza and am trying to read a sample volume, /usr/share/aliza/datasets/DICOM/00_MR/PS_0.dcm into Python to process it with numpy. However the following Python code.
import SimpleITK as sitk
reader = sitk.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames( inputSeriesName )
reader.SetFileNames(dicom_names)
image = reader.Execute()
results in
Traceback (most recent call last):
File "<ipython-input-38-8c1737986203>", line 1, in <module>
image = reader.Execute()
File "/home/peter/anaconda3/lib/python3.7/site-packages/SimpleITK/SimpleITK.py", line 8473, in Execute
return _SimpleITK.ImageSeriesReader_Execute(self)
RuntimeError: Exception thrown in SimpleITK ImageSeriesReader_Execute: ../../Code/IO/src/sitkImageSeriesReader.cxx:163:
sitk::ERROR: The file in the series have unsupported 3 dimensions.
I can read in a series of 2D images, that can stack to a volume, but not all DICOM volumes come that way
I guess that when you use the ImageSeriesReader class, SimpleITK is expecting a series of 2d images. Since you don't have a series of images, but rather a single 3d image, try using the ReadImage function, like so:
import SimpleITK as sitk
image = sitk.ReadImage('/usr/share/aliza/datasets/DICOM/00_MR/PS_0.dcm' )

Saving vector with complex components in a nifti file

I generate a 4D complex numpy array
for example:
numpy_array = np.tile(np.array([3, 4, 0], dtype=np.complex64), (100,100,100,1))
i want to save the numpy array as a nifti file
I tried using vtk and SimpleITK, but both of them don't support complex numbers (only vectors which are real numbers)
It seems that only nibabel support complex vectors and i manage to save the file, but i can only load it with nibabel, when i try to load it with ITK-SNAP or with Slicer it doesn't open. I know that ITK-SNAP can open complex vector of nifti files because i already have those files saved with another script using matlab
import numpy as np
import nibabel as nib
import SimpleITK as sitk
import vtk
from vtk.util.numpy_support import numpy_to_vtk
numpy_array = np.tile(np.array([3, 4, 0], dtype=np.complex64), (100,100,100,1))
nibabel save try:
image = nib.Nifti2Image(numpy_array, affine=np.eye(4))
nib.save(image , r'file_name.nii')
SimpleITK save try:
image = sitk.GetImageFromArray(numpy_array)
sitk.writeimage(image, r'file_name.nii')
vtk save try:
array = np.concatenate((np.real(numpy_array), np.imag(numpy_array)), axis=3)
stacked_array = array.reshape(-1, array.shape[-1])
vtk_array = numpy_to_vtk(stacked_array, deep=True,
array_type=vtk.VTK_FLOAT)
vtk_image = vtk.vtkImageData()
vtk_image.SetDimensions(numpy_array.shape[0], numpy_array.shape[1],
numpy_array.shape[2])
vtk_image.GetPointData().SetScalars(vtk_array)
writer = vtk.vtkNIFTIImageWriter()
writer.SetFileName(file_name)
writer.SetInputData(vtk_image)
writer.Write()
nibabel output:
nibabel creates a nifti file with vector but with other programs like ITK-SNAP it doens't open
ITK-SNAP error:
Error: Unsupported or missing image file format. ITK-SNAP failed to create an
ImageIO object for the image 'file_name.nii' using format ''.
SimpleITK error:
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\site-packages\SimpleITK\SimpleITK.py", line 3366, in _get_sitk_vector_pixelid
return _np_sitk[numpy_array_type.dtype]
KeyError: dtype('complex64')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "C:\ProgramData\Anaconda3\lib\site-packages\SimpleITK\SimpleITK.py", line 3431, in GetImageFromArray
id = _get_sitk_vector_pixelid( z )
File "C:\ProgramData\Anaconda3\lib\site-packages\SimpleITK\SimpleITK.py", line 3371, in _get_sitk_vector_pixelid
raise TypeError('dtype: {0} is not supported.'.format(numpy_array_type.dtype))
TypeError: dtype: complex64 is not supported.
vtk output:
vtk creates a vector nifti file but with 6 components instead of 3
(consider the imaginary part as components too), i saw in the
documentation of numpy_to_vtk that it doesn't support copmlex arrays,
maybe someone know about a workaround.
The SimpleITK 1.2 release supports writing 4D complex images. The missing feature is support for complex number in stk.GetImageFromArray, but it's already added to the development branch and your code works if you a compiled version of the current SimpleITK master branch or wait for the 1.3 release scheduled for October 2019.
In the mean time you can convert the complex number components to SimpleITK separately like this:
image = sitk.RealAndImaginaryToComplexImageFilter(sitk.GetImageFromArray(numpy_array.real),
sitk.GetImageFromArray(numpy_array.imag))
sitk.WriteImage(image, r'file_name.nii')

How to transform .grib file into a GeoTIFF with correct projection using Python, GDAL, ArcPy

I am trying to transform a .grib file into a GeoTIFF to be used in a GIS (ArcGIS to be particular), but am having trouble getting the image to project properly. I have been able to create a GeoTIFF, using GDAL in Python, that shows the data but is not showing up in the correct location when brought into ArcGIS. The resulting image is below.
The data I am working with can be downloaded from: https://gimms.gsfc.nasa.gov/SMOS/SMAP/L05/
I am trying to project the data into WGS84 Web Mercator (Auxiliary Sphere), EPSG: 3857
Note: I have tried bringing in the data via ArcMap by creating a Raster Mosaic which should be able to work with .grib data, but I didn't have any luck.
Update: I have also tried using the Project Raster tool, but ArcGIS does not like the default projection that comes from the .grib file and gives an error.
The code I'm using:
import gdal
src_filename = r"C:\att\project\arcshare\public\disaster_response\nrt_products\smap\20150402_20150404_anom1.grib"
dst_filename = r"C:\att\project\arcshare\public\disaster_response\nrt_products\smap\smap_py_test1.tif"
#Open existing dataset
src_ds = gdal.Open(src_filename)
#Open output format driver, see gdal_translate --formats for list
format = "GTiff"
driver = gdal.GetDriverByName( format )
#Output to new format
dst_file = driver.CreateCopy( dst_filename, src_ds, 0 )
#Properly close the datasets to flush to disk
dst_ds = None
src_ds = None
I am not very well versed in using GDAL or GDAL in Python, so any help or tips would be greatly appreciated.
Try using gdal.Translate (in Python) or gdal_translate (from command line). Here are two examples of how I have used each approach in the past:
Option 1: Python approach
from osgeo import gdal
# Open existing dataset
src_ds = gdal.Open(src_filename)
# Ensure number of bands in GeoTiff will be same as in GRIB file.
bands = [] # Set up array for gdal.Translate().
if src_ds is not None:
bandNum = src_ds.RasterCount # Get band count
for i in range(bandNum+1): # Update array based on band count
if (i==0): #gdal starts band counts at 1, not 0 like the Python for loop does.
pass
else:
bands.append(i)
# Open output format driver
out_form= "GTiff"
# Output to new format using gdal.Translate. See https://gdal.org/python/ for osgeo.gdal.Translate options.
dst_ds = gdal.Translate(dst_filename, src_ds, format=out_form, bandList=bands)
# Properly close the datasets to flush to disk
dst_ds = None
src_ds = None
Option 2: Command line gdal_translate (called from Python) approach
import os
# Open output format driver, see gdal_translate --formats for list
out_form = "GTiff"
# Pull out specific band of interest
band=3
# Convert from GRIB to GeoTIFF using system gdal_translate
src_ds = src_filename
dst_ds = dst_filename
os.system("gdal_translate -b %s -of %s %s %s" %(str(band), out_form, src_ds, dst_ds))
I've had trouble in the past creating a multi-band GeoTiff using option 2, so I recommend using option 1 when possible.
Something like this should transform your native coordinates into your desired projection. This is not tested, yet. (Could by latitude instead of latitudes).
from cfgrib import xarray_store
from pyproj import Proj, transform
grib_data = xarray_store.open_dataset('your_grib_file.grib')
lat = grib_data.latitudes.value
lon = grib_data.longitudes.value
lon_transformed, lat_transformed = transform (Proj(init='init_projection'),
Proj(init='target_projection', lon, lat)

Unable to get dicom image for display in 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")

Categories

Resources