Pydicom unmatching length of the pixel data - python

I am trying to convert the image of DICOM files to arrays with the pydicom library with this code :
dcm_file = pydicom.dcmread(path)
print(dcm_file)
dcm_file.NumberOfFrames = 1
img = np.array(dcm_file.pixel_array)
Initially, the number of frames is 60. Images are 1024x1024, and here is the error I am getting :
The length of the pixel data in the dataset (1047504 bytes) doesn't match the expected length (2097152 bytes).
The dataset may be corrupted or there may be an issue with the pixel data handler.
For some files, the pixel data is an array of 2097152 elements and the code is working. I don't understand the differences between both as they are the same type of objects.
Some informations about the files for which it is not working :
(0028, 0008) Number of Frames IS: '60'
(0028, 0010) Rows US: 1024
(0028, 0011) Columns US: 1024
(0028, 0100) Bits Allocated US: 16
(0028, 0101) Bits Stored US: 16
(0028, 0102) High Bit US: 15
(0028, 0103) Pixel Representation US: 0
(7fe0, 0010) Pixel Data OW: Array of 1047504 elements
For all the files we also have :
(0002, 0010) Transfer Syntax UID UI: Explicit VR Little Endian

Related

Converting byte array to 16 bit grayscale image in Python

I'm working with large image datasets stored in a non-standard image format (.Tsm). Essentially it's a binary file with some headers at the start, very similar to FITS standard except stored in little-endian as opposed to FITS big-endian.
After reading the file header and formatting the metadata, I can read a single image using the following code
def __read_slice(self, file, img_num, dimensions):
"""Read a single image slice from .tsm file"""
pixel_range = self.metadata["pixel range"]
bytes_to_read = self.metadata["bytes to read"]
# position file pointer to correct byte
file.seek(self.HEADER_TOTAL_LEN + (bytes_to_read * img_num), 0)
all_bytes = file.read(bytes_to_read) # read image bytes
img = np.empty(len(pixels), dtype='uint16') # preallocate image vector
byte_idx = 0
for idx, pixel in enumerate(pixel_range):
img[idx] = (all_bytes[byte_idx + 1] << 8) + all_bytes[byte_idx]
byte_idx += 2
return np.reshape(img, (dimensions[1], dimensions[0])) # reshape array to correct dimensions
the trouble is the images can be very large (2048x2048) so even just loading in 20-30 frames for processing can take a significant amount of time. I'm new to python so i'm guessing the code here is pretty inefficient, especially the loop.
Is there a more efficient way to convert the byte data into 16bit integers?
You can try:
img= np.frombuffer(all_bytes, dtype='uint16')
Example:
>>> np.frombuffer(b'\x01\x02\x03\x04', dtype='uint16')
array([ 513, 1027], dtype=uint16)

What does wave_read.readframes() return if there are multiple channels?

I understand how the readframes() method works for mono audio input, however I don't know how it will work for stereo input. Would it give a tuple of two byte objects?
A wave file has:
sample rate of Wave_read.getframerate() per second (e.g 44100 if from an audio CD).
sample width of Wave_read.getsampwidth() bytes (i.e 1 for 8-bit samples, 2 for 16-bit samples)
Wave_read.getnchannels() channels (typically 1 for mono, 2 for stereo)
Every time you do a Wave_read.getframes(N), you get N * sample_width * n_channels bytes.

Reading MONO 16-bit images using PyCapture2

I am using the CMLN-13S2M-CS camera from PointGrey. This camera has a MONO 16-bit pixel format.Using the PyCapture2 wrapper from PointGrey I am unable to retrieve the image the camera is recording.
I have the following code:
import sys
import numpy
import PyCapture2
## Connect camera
bus = PyCapture2.BusManager()
c = PyCapture2.Camera()
c.connect(bus.getCameraFromIndex(0))
## Configure camera format7 settings
fmt7imgSet = PyCapture2.Format7ImageSettings(0, 0, 0, 1296, 964, PyCapture2.PIXEL_FORMAT.MONO16)
fmt7pktInf, isValid = c.validateFormat7Settings(fmt7imgSet)
c.setFormat7ConfigurationPacket(fmt7pktInf.recommendedBytesPerPacket, fmt7imgSet)
## Start capture and retrieve buffer
c.startCapture()
im = c.retrieveBuffer()
print im.getData().shape
print numpy.max(im.getData())
The following is returned by the print statements: (2498688,) and 240. The shape is exactly 2 x (964 x 1296). How should I reshape this? Also, the maximum value when saturated is 255. This is odd as this corresponds to MONO 8 Pixel format. What am I doing wrong?
Here's a quick demo that shows how to convert a 1D array of uint8 to a 2D array of uint16. The key function we need here is view.
import numpy as np
# Make 24 bytes of fake data
raw = np.arange(24, dtype=np.uint8)
#Convert
out = raw.view(np.uint16).reshape(3, 4)
print(out)
print(out.dtype)
output
[[ 256 770 1284 1798]
[2312 2826 3340 3854]
[4368 4882 5396 5910]]
uint16
Thanks to Andras Deak for his assistance!
If the resulting image doesn't look correct, you may need to swap the byte ordering of the 16 bit integers. You can read about byte ordering in Numpy here.
And if that still doesn't look correct, then the data may be organized as two planes, with one plane for the low-order bits of a pixel and the other plane for the high-order bits. That's also easy to deal with, but hopefully it won't come to that. ;)

delete pixel from the raster image with specific range value

update :
any idea how to delete pixel from specific range value raster image with
using numpy/scipy or gdal?
or how to can create new raster with some class using raster calculation expressions(better)
for example i have a raster image with the
5 class :
1. 0-100
2. 100-200
3. 200-300
4. 300-500
5. 500-1000
and i want to delete class 1 range value
or maybe class 1,2,4,5
i begin with this script :
import numpy as np
from osgeo import gdal
ds = gdal.Open("raster3.tif")
myarray = np.array(ds.GetRasterBand(1).ReadAsArray())
#print myarray.shape
#print myarray.size
#print myarray
new=np.delete(myarray[::2], 1)
but i cant to complete
the image
White in class 5 and black class 1
Rasters are 2-D arrays of values, with each value being stored in a pixel (which stands for picture element). Each pixel must contain some information. It is not possible to delete or remove pixels from the array because rasters are usually encoded as a simple 1-dimensional string of bits. Metadata commonly helps explain where line breaks are and the length of the bitstring, so that the 1-D bitstring can be understood as a 2-D array. If you "remove" a pixel, then you break the raster. The 2-D grid is no longer valid.
Of course, there are many instances where you do want to effectively discard or clean the raster of data. Such an example might be to remove pixels that cover land from a raster of sea-surface temperatures. To accomplish this goal, many geospatial raster formats hold metadata describing what are called NoData values. Pixels containing a NoData value are interpreted as not existing. Recall that in a raster, each pixel must contain some information. The NoData paradigm allows the structure and format of rasters to be met, while also giving a way to mask pixels from being displayed or analyzed. There is still data (bits, 1s and 0s) at the masked pixels, but it only serves to identify the pixel as invalid.
With this in mind, here is an example using gdal which will mask values in the range of 0-100 so they are NoData, and "do not exist". The NoData value will be specified as 0.
from osgeo import gdal
# open dataset to read, and get a numpy array
ds = gdal.Open("raster3.tif", 'r')
myarray = ds.GetRasterBand(1).ReadAsArray()
# modify numpy array to mask values
myarray[myarray <= 100] = 0
# open output dataset, which is a copy of original
driver = gdal.GetDriverByName('GTiff')
ds_out = driver.CreateCopy("raster3_with_nodata.tif", ds)
# write the modified array to the raster
ds_out.GetRasterBand(1).WriteArray(myarray)
# set the NoData metadata flag
ds_out.GetRasterBand(1).SetNoDataValue(0)
# clear the buffer, and ensure file is written
ds_out.FlushCache()
I want to contribute with an example for landsat data.
In this qick guide, you will be able to exclude Landsat cloud pixels.
Landsat offers the Quality Assessment Band (BQA), which includes int32 values (classes) regarding natural features such as Clouds, Rocks, Ice, Water, Cloud Shadow etc.
We will use the BQA to clip the cloud pixels in the other bands.
# Import Packages
import rasterio as rio
import earthpy.plot as ep
from matplotlib import pyplot
import rioxarray as rxr
from numpy import ma
# Open the Landsat Band 3
Landsat_Image = rxr.open_rasterio(r"C:\...\LC08_L1TP_223075_20210311_20210317_01_T1_B3.tif")
# Open the Quality Assessment Band
BQA = rxr.open_rasterio(r"C:\...\LC08_L1TP_223075_20210311_20210317_01_T1_BQA.tif").squeeze()
# Create a list with the QA values that represent cloud, cloud_shadow, etc.
Cloud_Values = [6816, 6848, 6896, 7072]
# Mask the data using the pixel QA layer
landsat_masked = Landsat_Image.where(~BQA.isin(Cloud_Values))
landsat_masked
# Plot the masked data
landsat_masked_plot = ma.masked_array(landsat_masked.values,landsat_masked.isnull())
# Plot
ep.plot_rgb(landsat_masked_plot, rgb=[2, 1, 0], title = "Masked Data")
plt.show()
###############################################################################
# Export the masked Landsat Scenes to Directory "Masked_Bands_QA"
out_img = landsat_masked
out_img.shape
out_transform = landsat_masked.rio.transform()
# Get a Band of the same Scene for reference
rastDat = rio.open(r"C:\Dados_Espaciais\NDVI_Usinas\Adeco\Indices\Imagens\LC08_L1TP_223075_20210311_20210317_01_T1\LC08_L1TP_223075_20210311_20210317_01_T1_B3.tif")
#copying metadata from original raster
out_meta = rastDat.meta.copy()
#amending original metadata
out_meta.update({'nodata': 0,
'height' : out_img.shape[1],
'width' : out_img.shape[2],
'transform' : out_transform})
# writing and then re-reading the output data to see if it looks good
for i in range(out_img.shape[0]):
with rio.open(rf"C:\Dados_Espaciais\DSM\Bare_Soil_Landsat\Teste_{i+1}_masked.tif",'w',**out_meta) as dst:
dst.write(out_img[i,:,:],1)
This way you tell the program:
Check the areas in BQA with these "Cloud_Values" and exclude these areas, but in the landsat image that I provided.
I hope it works.

readframes return 2 byte in python

When readframes() is used in python, the online documention says sampling frequency is returned it looks it returns 2 bytes. I think there are 4 byte on each frame:
left = 2 bytes
right = 2 bytes
Do I have to check if it is mono or stereo and if it is stereo, read 2 frames at a time and if it is mono, read 1 frame at a time?
A wave file has:
sample rate of Wave_read.getframerate() per second (e.g 44100 if from an audio CD).
sample width of Wave_read.getsampwidth() bytes (i.e 1 for 8-bit samples, 2 for 16-bit samples)
Wave_read.getnchannels() channels (typically 1 for mono, 2 for stereo)
Every time you do a Wave_read.getframes(N), you get N * sample_width * n_channels bytes.
So, if you read 2048 frames from a 44100Hz, 16-bit stereo file, you get 8192 bytes as a result.

Categories