I do not understand if sitk.ReadImage can read a list of images or not? I did not manage to find an example showing how to list of images should be inputed to the function.
But in the function documentations it say:
ReadImage(**VectorString fileNames**, itk::simple::PixelIDValueEnum outputPixelType) -> Image
ReadImage(std::string const & filename, itk::simple::PixelIDValueEnum outputPixelType) -> Image
ReadImage is a procedural interface to the ImageSeriesReader class which is convenient for most image reading tasks.
Note that when reading a series of images that have meta-data
associated with them (e.g. a DICOM series) the resulting image will
have an empty meta-data dictionary. It is possible to programmatically
add a meta-data dictionary to the compounded image by reading in one
or more images from the series using the ImageFileReader class,
analyzing the meta-dictionary associated with each of those images and
creating one that is relevant for the compounded image.
So it seems from the documentations that it is possible. Can someone show me a simple example.
EDIT:
I tried the following:
sitk.ReadImage(['volume00001.mhd','volume00002.mhd'])
but this is the error that I get:
RuntimeErrorTraceback (most recent call last)
<ipython-input-42-85abf82c3afa> in <module>()
1 files = [f for f in os.listdir('.') if 'mhd' in f]
2 print(sorted_files[1:25])
----> 3 sitk.ReadImage(['volume00001.mhd','volume00002.mhd'])
/gpfs/bbp.cscs.ch/home/amsalem/anaconda2/lib/python2.7/site-packages/SimpleITK/SimpleITK.pyc in ReadImage(*args)
8330
8331 """
-> 8332 return _SimpleITK.ReadImage(*args)
8333 class HashImageFilter(ProcessObject):
8334 """
RuntimeError: Exception thrown in SimpleITK ReadImage: /tmp/SimpleITK/Code/IO/src/sitkImageSeriesReader.cxx:145:
sitk::ERROR: The file in the series have unsupported 3 dimensions.
Thanks.
SimpleITK uses SWIG to wrap a C++ interface to Insight Segmentation and Registration Toolkit (ITK). As such the inline python documentation should be supplemented with the C++ Doxygen documentation. There is a mapping of C++ types to Python types with robust implicit conversion between them. You can find the documentation for the sitk::ReadImage methods here:
https://itk.org/SimpleITKDoxygen/html/namespaceitk_1_1simple.html#ae3b678b5b043c5a8c93aa616d5ee574c
Notice there are 2 ReadImage methods, and the Python docstring you listed appears is one of them.
From the SimpleITK examples here is a snippet to read a DICOM series:
print( "Reading Dicom directory:", sys.argv[1] )
reader = sitk.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames( sys.argv[1] )
reader.SetFileNames(dicom_names)
image = reader.Execute()
This uses the class interface as opposed to the procedural. Which would simply be:
image = sitk.ReadImage(dicom_names)
or generically with a list of string filenames:
image = sitk.ReadImage(["image1.png", "image2.png"...])
Many common array like type of strings will be implicitly converted to the SWIG VectorString type.
Related
I need to save an Image in Python (created as a Numpy array) as a JPEG file, while including a "comment" in the file with some specific metadata. This metadata will be used by another (third-party) application and is a simple ASCII string. I have a sample image including such a "comment", which I can read out using Pillow (PIL), via the image.info['comment'] or the image.app['COM'] property. However, when I try a simple round-trip, i.e. loading my sample image and save it again using a different file name, the comment is no longer preserved. Equally, I found no way to include a comment in a newly created image.
I am aware that EXIF tags are the preferred way to save metadata in JPEG images, but as mentioned, the third-party application only accepts this data as a "comment", not as EXIF, which I cannot change. After reading this question, I looked into the binary structure of my sample file and found the comment at the start of the file, after a few bytes of some other (meta)data. I do however not know a lot about binary file manipulation, and also I was wondering if there is a more elegant way, other than messing with the binary...
EDIT: minimum example:
from PIL import Image
img = Image.open(path) # where path is the path to the sample image
# this prints the desired metadata if it is correctly saved in loaded image
print(img.info["comment"])
img.save(new_path) # save with different file name
img.close()
# now open to see if it has been saved correctly
new_img = Image.open(new_path)
print(new_img.info['comment']) # now results in KeyError
I also tried img.save(new_path, info=img.info), but this does not seem to have an effect. Since img.info['comment'] appears identical to img.app['COM'], I tried img.save(new_path, app=img.app), again does not work.
Just been having a play with this and I couldn't see anything directly in Pillow to support this. I've found that the save() method supports a parameter called extra that can be used to pass arbitrary bytes to the output file.
We then just need a simple method to turn a comment into a valid JPEG segment, for example:
import struct
from PIL import Image
def make_jpeg_variable_segment(marker: int, payload: bytes) -> bytes:
"make a JPEG segment from the given payload"
return struct.pack('>HH', marker, 2 + len(payload)) + payload
def make_jpeg_comment_segment(comment: bytes) -> bytes:
"make a JPEG comment/COM segment"
return make_jpeg_variable_segment(0xFFFE, comment)
# open source image
with Image.open("foo.jpeg") as im:
# save out with new JPEG comment
im.save('bar.jpeg', extra=make_jpeg_comment_segment("hello world".encode()))
# read file back in to ensure comment round-trips
with Image.open('bar.jpeg') as im:
print(im.app['COM'])
print(im.info['comment'])
Note that in my initial attempts I tried appending the comment segment at the end of the file, but Pillow wouldn't load this comment even after calling the .load() method to force it to load the entire JPEG file.
Update: The upcoming version Pillow version 9.4.0 will support this by passing a comment parameter while saving, e.g.:
with Image.open("foo.jpeg") as im:
im.save('bar.jpeg', comment="hello world")
hopefully that makes things easier!
I'm trying to write a script to simplify my everyday life in the lab. I operate one ThermoFisher / FEI scanning electron microscope and I save all my pictures in the TIFF format.
The microscope software is adding an extensive custom TiffTag (code 34682) containing all the microscope / image parameters.
In my script, I would like to open an image, perform some manipulations and then save the data in a new file, including the original FEI metadata. To do so, I would like to use a python script using the tifffile module.
I can open the image file and perform the needed manipulations without problems. Retrieving the FEI metadata from the input file is also working fine.
I was thinking to use the imwrite function to save the output file and using the extratags optional argument to transfer to the output file the original FEI metadata.
This is an extract of the tifffile documentation about the extratags:
extratags : sequence of tuples
Additional tags as [(code, dtype, count, value, writeonce)].
code : int
The TIFF tag Id.
dtype : int or str
Data type of items in 'value'. One of TIFF.DATATYPES.
count : int
Number of data values. Not used for string or bytes values.
value : sequence
'Count' values compatible with 'dtype'.
Bytes must contain count values of dtype packed as binary data.
writeonce : bool
If True, the tag is written to the first page of a series only.
Here is a snippet of my code.
my_extratags = [(input_tags['FEI_HELIOS'].code,
input_tags['FEI_HELIOS'].dtype,
input_tags['FEI_HELIOS'].count,
input_tags['FEI_HELIOS'].value, True)]
tifffile.imwrite('output.tif', data, extratags = my_extratags)
This code is not working and complaining that the value of the extra tag should be ASCII 7-bit encoded. This looks already very strange to me because I haven't touched the metadata and I am just copying it to the output file.
If I convert the metadata tag value in a string as below:
my_extratags = [(input_tags['FEI_HELIOS'].code,
input_tags['FEI_HELIOS'].dtype,
input_tags['FEI_HELIOS'].count,
str(input_tags['FEI_HELIOS'].value), True)]
tifffile.imwrite('output.tif', data, extratags = my_extratags)
the code is working, the image is saved, the metadata corresponding to 'FEI_HELIOS' is created but it is empty!
Can you help me in finding what I am doing wrongly?
I don't need to use tifffile, but I would prefer to use python rather than ImageJ because I have already several other python scripts and I would like to integrate this new one with the others.
Thanks a lot in advance!
toto
ps. I'm a frequent user of stackoverflow, but this is actually my first question!
In principle the approach is correct. However, tifffile parses the raw values of certain tags, including FEI_HELIOS, to dictionaries or other Python types. To get the raw tag value for rewriting, it needs to be read from file again. In these cases, use the internal TiffTag._astuple function to get an extratag compatible tuple of the tag, e.g.:
import tifffile
with tifffile.TiffFile('FEI_SEM.tif') as tif:
assert tif.is_fei
page = tif.pages[0]
image = page.asarray()
... # process image
with tifffile.TiffWriter('copy1.tif') as out:
out.write(
image,
photometric=page.photometric,
compression=page.compression,
planarconfig=page.planarconfig,
rowsperstrip=page.rowsperstrip,
resolution=(
page.tags['XResolution'].value,
page.tags['YResolution'].value,
page.tags['ResolutionUnit'].value,
),
extratags=[page.tags['FEI_HELIOS']._astuple()],
)
This approach does not preserve Exif metadata, which tifffile cannot write.
Another approach, since FEI files seem to be written uncompressed, is to directly memory map the image data in the file to a numpy array and manipulate that array:
import shutil
import tifffile
shutil.copyfile('FEI_SEM.tif', 'copy2.tif')
image = tifffile.memmap('copy2.tif')
... # process image
image.flush()
Finally, consider tifftools for rewriting TIFF files where tifffile is currently failing, e.g. Exif metadata.
This question already has answers here:
Which DICOM UIDs should be replaced while overwriting pixel data in DICOM?
(1 answer)
Which DICOM tags other than UIDs should be replaced while overwriting pixel data in DICOM?
(2 answers)
Closed 1 year ago.
I have saved the Preprocessed DICOM in a folder total of 300 .dcm files, but when I open this DICOM folder path in RadiANT DICOM Viewer only One slice is displayed, here is my code is attached, Can you please help me how to display the whole scan. I think the main problem in Image Position and Slice location
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
basepath="/home/hammad/AssementTask/DICOM/"
des_path="/home/hammad/AssementTask/g/"
file_list = [f.path for f in os.scandir(basepath)]
ds = pydicom.dcmread(file_list[0])
for i in range(imgs_after_resamp.shape[0]):
out = imgs_after_resamp[i,:,:]
#Need to copy() to meet jpeg2k_encodes C contiguous requirement
arr_crop = out.copy()
out = out.astype(np.int16)
# 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[0x0018, 0x0050].value=np.round(spacing[0])
ds[0x0028, 0x0030].value=[np.round(spacing[1]),np.round(spacing[2])]
ds.InstanceNumber = i
ds.PixelData = encapsulate([arr_jpeg2k])
ds.save_as((des_path + str(i) + '.dcm'.format(i)))
I am not familiar with python. But some things seem to be obvious to me, so I will try an answer:
ds = pydicom.dcmread(file_list[0])
for i in range(imgs_after_resamp.shape[0]):
[...]
You are reading one file and use it as a template for all the resampled files. At minimum, you will have to create a new SOP Instance UID (0008,0018) for each file that you save. This is very likely the reason why the viewer only displays one image. The SOP Instance UID uniquely identifies the image. If all your resampled images have the same SOP Instance UID, this will tell the viewer that the same image is loaded over and over again. I.e. the newly loaded image is considered a duplicate.
And yes, to update the geometry information, further attributes need to be set to appropriate values. This partially depends on the type of image (SOP Class UID, 0008,0016). But here are the main suspects:
Image Position Patient (0020,0032)
Image Orientation Patient (0020,0037)
Slice Location (0020,1041)
Furthermore, make sure that the Frame Of Reference UID (0020,0052) is only kept from the original images if both image sets are using the same coordinate system (i.e. an Image Position Patient in your resampled stack must refer to the same origin as in the original images). In case of doubt, assign a new FOR-UID. Must be identical for all images in your stack.
Last point: This depends on the SOP Class even more, so I can just give you a general hint. The resampling is a derivation in terms of DICOM, so the Image Type (0008,0008), must be "DERIVED" in the second component. This unleashes a phletora of other requirements, depending on the SOP Class. Usually, you have to describe the type of derivation and reference the images from which you derived the resampled image.
Not everything of this will be necessary to have the images properly displayed in a viewer. But if you intend to write your implementation in product quality, you need to consider them. Look into the module table for your IOD in DICOM Part 3 as a starting point for updating the header information.
I'm interested in using Spectral Python (SPy) to visualize and classify multiband raster GeoTIFF (not hyperspectral data). Currently it appaers that only .lan, .gis File Formats are readable.
I've tried to convert files to .lan with gdal_translate but the image format is not supported( IOError: Unable to determine file type or type not supported).
Any idea how to use this library for non hypersperctral dataset?
Convert the GeoTIFF file to a compatible format (e.g. LAN). This can be done in one of two ways. From a system shell, use gdal_translate:
gdal_translate -of LAN file.tif file.lan
Or similar within Python:
from osgeo import gdal
src_fname = 'file.tif'
dst_fname = 'file.lan'
driver = gdal.GetDriverByName('LAN')
sds = gdal.Open(src_fname)
dst = driver.CreateCopy(dst_fname, sds)
dst = None # close dataset; the file can now be used by other processes
Note that the first method is actually better, as it also transfers other metadata, such as the spatial reference system and possibly other data. To correctly do the same in Python would require adding more lines of code.
I'm a newbie trying to write a small plugin for gimp using Python. It would generate an image and convert it to CMYK using the separate+ plugin
Code sample:
# some code that creates xcf image
layer = pdb.gimp_image_merge_visible_layers(newimage, CLIP_TO_IMAGE)
layer = pdb.plug_in_separate_light(some args here )
I have no idea how to call pdb.plug_in_separate_light() and then save the result image. I just know that pdb.plug_in_separate_light() requires 10 arguments and one of them should be a gimp drawable object(layer in my case) and two strings containing paths to icm/icc profiles. Any suggestions?