# Copyright 2018 United Kingdom Research and Innovation
# Copyright 2018 The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors:
# CIL Developers, listed at: https://github.com/TomographicImaging/CIL/blob/master/NOTICE.txt
import copy
import warnings
from numbers import Number
import numpy
from .image_data import ImageData
from .labels import ImageDimension, FillType
[docs]
class ImageGeometry:
@property
def CHANNEL(self):
warnings.warn("use ImageDimension.CHANNEL instead", DeprecationWarning, stacklevel=2)
return ImageDimension.CHANNEL
@property
def HORIZONTAL_X(self):
warnings.warn("use ImageDimension.HORIZONTAL_X instead", DeprecationWarning, stacklevel=2)
return ImageDimension.HORIZONTAL_X
@property
def HORIZONTAL_Y(self):
warnings.warn("use ImageDimension.HORIZONTAL_Y instead", DeprecationWarning, stacklevel=2)
return ImageDimension.HORIZONTAL_Y
@property
def RANDOM(self):
warnings.warn("use FillType.RANDOM instead", DeprecationWarning, stacklevel=2)
return FillType.RANDOM
@property
def RANDOM_INT(self):
warnings.warn("use FillType.RANDOM_INT instead", DeprecationWarning, stacklevel=2)
return FillType.RANDOM_INT
@property
def VERTICAL(self):
warnings.warn("use ImageDimension.VERTICAL instead", DeprecationWarning, stacklevel=2)
return ImageDimension.VERTICAL
@property
def shape(self):
shape_dict = {ImageDimension.CHANNEL: self.channels,
ImageDimension.VERTICAL: self.voxel_num_z,
ImageDimension.HORIZONTAL_Y: self.voxel_num_y,
ImageDimension.HORIZONTAL_X: self.voxel_num_x}
return tuple(shape_dict[label] for label in self.dimension_labels)
@shape.setter
def shape(self, val):
print("Deprecated - shape will be set automatically")
@property
def spacing(self):
spacing_dict = {ImageDimension.CHANNEL: self.channel_spacing,
ImageDimension.VERTICAL: self.voxel_size_z,
ImageDimension.HORIZONTAL_Y: self.voxel_size_y,
ImageDimension.HORIZONTAL_X: self.voxel_size_x}
return tuple(spacing_dict[label] for label in self.dimension_labels)
@property
def length(self):
return len(self.dimension_labels)
@property
def ndim(self):
return len(self.dimension_labels)
@property
def dimension_labels(self):
labels_default = ImageDimension.get_order_for_engine("cil")
shape_default = [ self.channels,
self.voxel_num_z,
self.voxel_num_y,
self.voxel_num_x]
try:
labels = self._dimension_labels
except AttributeError:
labels = labels_default
labels = list(labels)
for i, x in enumerate(shape_default):
if x == 0 or x==1:
try:
labels.remove(labels_default[i])
except ValueError:
pass #if not in custom list carry on
return tuple(labels)
@dimension_labels.setter
def dimension_labels(self, val):
self.set_labels(val)
def set_labels(self, labels):
if labels is not None:
self._dimension_labels = tuple(map(ImageDimension, labels))
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
if self.voxel_num_x == other.voxel_num_x \
and self.voxel_num_y == other.voxel_num_y \
and self.voxel_num_z == other.voxel_num_z \
and self.voxel_size_x == other.voxel_size_x \
and self.voxel_size_y == other.voxel_size_y \
and self.voxel_size_z == other.voxel_size_z \
and self.center_x == other.center_x \
and self.center_y == other.center_y \
and self.center_z == other.center_z \
and self.channels == other.channels \
and self.channel_spacing == other.channel_spacing \
and self.dimension_labels == other.dimension_labels:
return True
return False
@property
def dtype(self):
return self._dtype
@dtype.setter
def dtype(self, val):
self._dtype = val
def __init__(self,
voxel_num_x=0,
voxel_num_y=0,
voxel_num_z=0,
voxel_size_x=1,
voxel_size_y=1,
voxel_size_z=1,
center_x=0,
center_y=0,
center_z=0,
channels=1,
**kwargs):
self.voxel_num_x = int(voxel_num_x)
self.voxel_num_y = int(voxel_num_y)
self.voxel_num_z = int(voxel_num_z)
self.voxel_size_x = float(voxel_size_x)
self.voxel_size_y = float(voxel_size_y)
self.voxel_size_z = float(voxel_size_z)
self.center_x = center_x
self.center_y = center_y
self.center_z = center_z
self.channels = channels
self.channel_labels = None
self.channel_spacing = 1.0
self.dimension_labels = kwargs.get('dimension_labels', None)
self.dtype = kwargs.get('dtype', numpy.float32)
[docs]
def get_slice(self,channel=None, vertical=None, horizontal_x=None, horizontal_y=None):
'''
Returns a new ImageGeometry of a single slice of in the requested direction.
'''
geometry_new = self.copy()
if channel is not None:
geometry_new.channels = 1
try:
geometry_new.channel_labels = [self.channel_labels[channel]]
except:
geometry_new.channel_labels = None
if vertical is not None:
geometry_new.voxel_num_z = 0
if horizontal_y is not None:
geometry_new.voxel_num_y = 0
if horizontal_x is not None:
geometry_new.voxel_num_x = 0
return geometry_new
def get_order_by_label(self, dimension_labels, default_dimension_labels):
order = []
for i, el in enumerate(default_dimension_labels):
for j, ek in enumerate(dimension_labels):
if el == ek:
order.append(j)
break
return order
def get_min_x(self):
return self.center_x - 0.5*self.voxel_num_x*self.voxel_size_x
def get_max_x(self):
return self.center_x + 0.5*self.voxel_num_x*self.voxel_size_x
def get_min_y(self):
return self.center_y - 0.5*self.voxel_num_y*self.voxel_size_y
def get_max_y(self):
return self.center_y + 0.5*self.voxel_num_y*self.voxel_size_y
def get_min_z(self):
if not self.voxel_num_z == 0:
return self.center_z - 0.5*self.voxel_num_z*self.voxel_size_z
else:
return 0
def get_max_z(self):
if not self.voxel_num_z == 0:
return self.center_z + 0.5*self.voxel_num_z*self.voxel_size_z
else:
return 0
[docs]
def clone(self):
'''returns a copy of the ImageGeometry'''
return copy.deepcopy(self)
[docs]
def copy(self):
'''alias of clone'''
return self.clone()
def __str__ (self):
repres = ""
repres += "Number of channels: {0}\n".format(self.channels)
repres += "channel_spacing: {0}\n".format(self.channel_spacing)
if self.voxel_num_z > 0:
repres += "voxel_num : x{0},y{1},z{2}\n".format(self.voxel_num_x, self.voxel_num_y, self.voxel_num_z)
repres += "voxel_size : x{0},y{1},z{2}\n".format(self.voxel_size_x, self.voxel_size_y, self.voxel_size_z)
repres += "center : x{0},y{1},z{2}\n".format(self.center_x, self.center_y, self.center_z)
else:
repres += "voxel_num : x{0},y{1}\n".format(self.voxel_num_x, self.voxel_num_y)
repres += "voxel_size : x{0},y{1}\n".format(self.voxel_size_x, self.voxel_size_y)
repres += "center : x{0},y{1}\n".format(self.center_x, self.center_y)
return repres
[docs]
def allocate(self, value=0, **kwargs):
'''allocates an ImageData according to the size expressed in the instance
:param value: accepts numbers to allocate an uniform array, or a string as 'random' or 'random_int' to create a random array or None.
:type value: number or string, default None allocates empty memory block, default 0
:param dtype: numerical type to allocate
:type dtype: numpy type, default numpy.float32
'''
dtype = kwargs.get('dtype', self.dtype)
if kwargs.get('dimension_labels', None) is not None:
raise ValueError("Deprecated: 'dimension_labels' cannot be set with 'allocate()'. Use 'geometry.set_labels()' to modify the geometry before using allocate.")
out = ImageData(geometry=self.copy(),
dtype=dtype,
suppress_warning=True)
if isinstance(value, Number):
# it's created empty, so we make it 0
out.array.fill(value)
elif value in FillType:
if value == FillType.RANDOM:
seed = kwargs.get('seed', None)
if seed is not None:
numpy.random.seed(seed)
if numpy.iscomplexobj(out.array):
r = numpy.random.random_sample(self.shape) + 1j * numpy.random.random_sample(self.shape)
out.fill(r)
else:
out.fill(numpy.random.random_sample(self.shape))
elif value == FillType.RANDOM_INT:
seed = kwargs.get('seed', None)
if seed is not None:
numpy.random.seed(seed)
max_value = kwargs.get('max_value', 100)
if numpy.iscomplexobj(out.array):
out.fill(numpy.random.randint(max_value,size=self.shape, dtype=numpy.int32) + 1.j*numpy.random.randint(max_value,size=self.shape, dtype=numpy.int32))
else:
out.fill(numpy.random.randint(max_value,size=self.shape, dtype=numpy.int32))
elif value is None:
pass
else:
raise ValueError(f'Value {value} unknown')
return out