Files
penracourses/helpers/images.py
T
Ross 73bc0fe7ff .
2023-12-26 17:34:19 +00:00

207 lines
8.3 KiB
Python

import base64
import hashlib
import mimetypes
from io import BytesIO
from PIL import Image, ImageFile
from blake3 import blake3
import pydicom
import sys
import glob
import itertools
try:
from .pydicom_PIL import get_PIL_image
except:
from pydicom_PIL import get_PIL_image
def image_as_base64(image_file):
"""
:param `image_file` for the complete path of image.
:param `format` is format for image, eg: `png` or `jpg`.
"""
# if not os.path.isfile(image_file):
# return None
try:
encoded_string = ""
# with open(image_file, 'rb') as img_f:
# encoded_string = base64.b64encode(img_f.read())
encoded_string = base64.b64encode(image_file.file.read())
mimetype, enc = mimetypes.guess_type(image_file.path)
# Treat unknown files as dicom
if None == mimetype:
return "data:application/dicom;base64,{}".format(encoded_string.decode("utf-8"))
if "dicom" in mimetype or "octet-stream" in mimetype:
return "data:{};base64,{}".format(mimetype, encoded_string.decode("utf-8"))
return "data:image/{};base64,{}".format(mimetype, encoded_string.decode("utf-8"))
except FileNotFoundError:
return "data:image/png;base64,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"
def pil_dicom_image(source, exif_orientation=True, **options):
"""
Try to open the source file directly using PIL, ignoring any errors.
exif_orientation
If EXIF orientation data is present, perform any required reorientation
before passing the data along the processing pipeline.
"""
# Use a BytesIO wrapper because if the source is an incomplete file like
# object, PIL may have problems with it. For example, some image types
# require tell and seek methods that are not present on all storage
# File objects.
#if not source:
# return
#source = BytesIO(source.read())
# open file with pydicom
ds = pydicom.read_file(source)
# return the image file
return get_PIL_image(ds)
#with Image.open(source) as image:
# # Fully load the image now to catch any problems with the image contents.
# try:
# ImageFile.LOAD_TRUNCATED_IMAGES = True
# image.load()
# finally:
# ImageFile.LOAD_TRUNCATED_IMAGES = False
#if exif_orientation:
# image = utils.exif_orientation(image)
def show_PIL(dataset):
"""Display an image using the Python Imaging Library (PIL)"""
im = get_PIL_image(dataset)
im.show()
def get_dicom_order(path):
# load the DICOM files
files = []
for fname in glob.glob(path, recursive=False):
print("loading: {}".format(fname))
files.append((fname, pydicom.dcmread(fname)))
print("file count: {}".format(len(files)))
# skip files with no SliceLocation (eg scout views)
slices = []
map = {}
skipcount = 0
for fname, f in files:
print(dir(f))
print(fname)
if hasattr(f, 'SliceLocation'):
slices.append(f)
map[f.SliceLocation] = fname
else:
skipcount = skipcount + 1
print("skipped, no SliceLocation: {}".format(skipcount))
# ensure they are in the correct order
slices = sorted(slices, key=lambda s: s.SliceLocation)
#print(slices)
for i in slices:
print(map[i.SliceLocation])
#get_dicom_order("/home/ross/Downloads/DICOM HEAD/STD4/SER1/*.dcm")
def pretty_print_dicom(dataset, indent=0):
l = print_dicom(dataset, indent)
print(l)
return "".join(l)
def print_dicom(dataset, indent=0, include_tag_ids=True):
"""Go through all items in the dataset and print them with custom format
Modelled after Dataset._pretty_str()
"""
dont_print = ['Pixel Data', 'File Meta Information Version']
indent_string = "---" * indent
next_indent_string = "---" * (indent + 1)
l = []
for data_element in dataset:
if data_element.VR == "SQ": # a sequence
l.append(f"{data_element.tag}: {indent_string}{data_element.name}")
for sequence_item in data_element.value:
l.append(print_dicom(sequence_item, indent + 1))
l.append(next_indent_string + "---------")
else:
if data_element.name in dont_print:
l.append("""item not printed -- in the "don't print" list""")
else:
repr_value = repr(data_element.value)
if len(repr_value) > 50:
repr_value = repr_value[:50] + "..."
l.append(f"{data_element.tag}: {indent_string:s} {data_element.name:s} = {repr_value:s}")
return "\n<br/>".join(l)
def get_image_dicom_hash(img, dataset=None, hash_type="md5", direct_pixel_data=True) -> (str, bool):
if dataset is None:
dataset = pydicom.dcmread(img)
# TODO: improve?
match hash_type:
case "md5":
hasher = hashlib.md5()
case "blake3":
hasher = blake3()
case _:
raise NotImplemented
if direct_pixel_data:
hasher.update(dataset.PixelData)
else:
first = True
for i in dataset.pixel_array.astype(str).flatten():
if first:
first = False
hasher.update(f"{i}".encode())
else:
hasher.update(f",{i}".encode())
hash = hasher.hexdigest()
return hash
def get_image_hash(img, hash_type="blake3", direct_pixel_data=True) -> (str, bool):
is_dicom = False
# Try and read the file as a dicom
hash = "1234"
try:
# and generate a hash from the pixel data
hash = get_image_dicom_hash(img, hash_type=hash_type, direct_pixel_data=direct_pixel_data)
is_dicom = True
# ----
except pydicom.errors.InvalidDicomError as e:
print("dicom error", img)
try: # This is horrible (but needed for current unit tests)
# (we use a temporary file that breaks here)
img.file.open()
match hash_type:
case "md5":
hash = hashlib.md5(img.read()).hexdigest()
case "blake3":
hash = blake3(img.read()).hexdigest()
except AttributeError:
return hash, False
return hash, is_dicom