New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Serialize fit results #445
Changes from 4 commits
762631a
a19399d
14c4da2
1153706
ddbccff
18be9bd
87ccf01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from __future__ import division | ||
import abc | ||
from hdf5able import HDF5able | ||
|
||
from menpo.shape.pointcloud import PointCloud | ||
from menpo.image import Image | ||
|
@@ -9,25 +10,21 @@ | |
|
||
class FittingResult(Viewable): | ||
r""" | ||
Object that holds the state of a :map:`Fitter` object before, during | ||
and after it has fitted a particular image. | ||
Object that holds the state of a single fitting object, during and after it | ||
has fitted a particular image. | ||
|
||
Parameters | ||
----------- | ||
image : :map:`Image` or subclass | ||
The fitted image. | ||
fitter : :map:`Fitter` | ||
The fitter object used to fitter the image. | ||
gt_shape: :map:`PointCloud` | ||
gt_shape : :map:`PointCloud` | ||
The ground truth shape associated to the image. | ||
error_type : 'me_norm', 'me' or 'rmse', optional. | ||
Specifies the way in which the error between the fitted and | ||
ground truth shapes is to be computed. | ||
""" | ||
def __init__(self, image, fitter, gt_shape=None): | ||
|
||
def __init__(self, image, gt_shape=None): | ||
self.image = image | ||
self.fitter = fitter | ||
self._gt_shape = gt_shape | ||
self.parameters = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All fitting results have parameters now, it just defaults to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I wonder if it's better if this constructor takes
then it's clear that subclasses have to provide the parametrization on initialization. Or is this a problem with |
||
|
||
@property | ||
def n_iters(self): | ||
|
@@ -95,7 +92,7 @@ def fitted_image(self): | |
@property | ||
def iter_image(self): | ||
r""" | ||
Returns a copy of the fitted image with a as many landmark groups as | ||
Returns a copy of the fitted image with as many landmark groups as | ||
iteration run by fitting procedure: | ||
- ``iter_0``, containing the initial shape. | ||
- ``iter_1``, containing the the fitted shape at the first | ||
|
@@ -107,7 +104,8 @@ def iter_image(self): | |
""" | ||
image = Image(self.image.pixels) | ||
for j, s in enumerate(self.shapes()): | ||
image.landmarks['iter_'+str(j)] = s | ||
key = 'iter_{}'.format(j) | ||
image.landmarks[key] = s | ||
return image | ||
|
||
def errors(self, error_type='me_norm'): | ||
|
@@ -185,6 +183,27 @@ def _view(self, figure_id=None, new_figure=False, **kwargs): | |
return FittingViewer(figure_id, new_figure, self.image.n_dims, pixels, | ||
targets).render(**kwargs) | ||
|
||
def as_serializable(self): | ||
r"""" | ||
Returns a serializable version of the fitting result. This is a much | ||
lighter weight object than the initial fitting result. For example, | ||
it won't contain the original fitting object. | ||
|
||
Returns | ||
------- | ||
serializable_fitting_result : :map:`SerializableFittingResult` | ||
The lightweight serializable version of this fitting result. | ||
""" | ||
if self.parameters is not None: | ||
parameters = [p.copy() for p in self.parameters] | ||
else: | ||
parameters = [] | ||
gt_shape = self.gt_shape.copy() if self.gt_shape else None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how the hell have I missed that this is a valid Python construct the last 2 years?! Nice There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (have tried the old ternary if |
||
return SerializableFittingResult(self.image.copy(), | ||
parameters, | ||
[s.copy() for s in self.shapes()], | ||
gt_shape) | ||
|
||
|
||
class NonParametricFittingResult(FittingResult): | ||
r""" | ||
|
@@ -199,39 +218,42 @@ class NonParametricFittingResult(FittingResult): | |
The Fitter object used to fitter the image. | ||
shapes : `list` of :map:`PointCloud` | ||
The list of fitted shapes per iteration of the fitting procedure. | ||
gt_shape: :map:`PointCloud` | ||
gt_shape : :map:`PointCloud` | ||
The ground truth shape associated to the image. | ||
""" | ||
|
||
def __init__(self, image, fitter, shapes=None, gt_shape=None): | ||
super(NonParametricFittingResult, self).__init__( | ||
image, fitter, gt_shape=gt_shape) | ||
super(NonParametricFittingResult, self).__init__(image, | ||
gt_shape=gt_shape) | ||
self.fitter = fitter | ||
self._shapes = shapes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename to shapes instead of parameters |
||
# The parameters are the shapes for Non-Parametric algorithms | ||
self.parameters = shapes | ||
|
||
def shapes(self, as_points=False): | ||
if as_points: | ||
return [s.points.copy() for s in self.parameters] | ||
|
||
return [s.points.copy() for s in self._shapes] | ||
else: | ||
return self.parameters | ||
return self._shapes | ||
|
||
@property | ||
def final_shape(self): | ||
return self.parameters[-1].copy() | ||
return self._shapes[-1].copy() | ||
|
||
@property | ||
def initial_shape(self): | ||
return self.parameters[0].copy() | ||
return self._shapes[0].copy() | ||
|
||
@FittingResult.gt_shape.setter | ||
def gt_shape(self, value): | ||
r""" | ||
Setter for the ground truth shape associated to the image. | ||
""" | ||
if type(value) is PointCloud: | ||
if isinstance(value, PointCloud): | ||
self._gt_shape = value | ||
else: | ||
raise ValueError("Accepted values for gt_shape setter are " | ||
"`menpo.shape.PointClouds`.") | ||
"PointClouds.") | ||
|
||
|
||
class SemiParametricFittingResult(FittingResult): | ||
|
@@ -248,12 +270,13 @@ class SemiParametricFittingResult(FittingResult): | |
parameters : `list` of `ndarray` | ||
The list of optimal transform parameters per iteration of the fitting | ||
procedure. | ||
gt_shape: :map:`PointCloud` | ||
gt_shape : :map:`PointCloud` | ||
The ground truth shape associated to the image. | ||
""" | ||
|
||
def __init__(self, image, fitter, parameters=None, gt_shape=None): | ||
super(SemiParametricFittingResult, self).__init__( | ||
image, fitter, gt_shape=gt_shape) | ||
FittingResult.__init__(self, image, gt_shape=gt_shape) | ||
self.fitter = fitter | ||
self.parameters = parameters | ||
|
||
@property | ||
|
@@ -307,7 +330,7 @@ def gt_shape(self, value): | |
self._gt_shape = transform.target | ||
else: | ||
raise ValueError("Accepted values for gt_shape setter are " | ||
"`menpo.shape.PointClouds` or float lists" | ||
"PointClouds or float lists " | ||
"specifying transform shapes.") | ||
|
||
|
||
|
@@ -328,14 +351,13 @@ class ParametricFittingResult(SemiParametricFittingResult): | |
weights : `list` of `ndarray` | ||
The list of optimal appearance parameters per iteration of the fitting | ||
procedure. | ||
gt_shape: :map:`PointCloud` | ||
gt_shape : :map:`PointCloud` | ||
The ground truth shape associated to the image. | ||
""" | ||
def __init__(self, image, fitter, parameters=None, weights=None, | ||
gt_shape=None): | ||
super(ParametricFittingResult, self).__init__( | ||
image, fitter, gt_shape=gt_shape) | ||
self.parameters = parameters | ||
SemiParametricFittingResult.__init__(self, image, fitter, parameters, | ||
gt_shape=gt_shape) | ||
self.weights = weights | ||
|
||
@property | ||
|
@@ -351,7 +373,6 @@ def warped_images(self): | |
return [self.image.warp_to_mask(mask, transform.from_vector(p)) | ||
for p in self.parameters] | ||
|
||
|
||
@property | ||
def appearance_reconstructions(self): | ||
r""" | ||
|
@@ -385,3 +406,45 @@ def error_images(self): | |
error_images.append(error_image) | ||
|
||
return error_images | ||
|
||
|
||
class SerializableFittingResult(HDF5able, FittingResult): | ||
r""" | ||
Designed to allow the fitting results to be easily serializable. In | ||
comparison to the other fitting result objects, the serializable fitting | ||
results contain a much stricter set of data. For example, the major data | ||
components of a serializable fitting result are the fitted shapes, the | ||
parameters and the fitted image. | ||
|
||
Parameters | ||
----------- | ||
image : :map:`Image` | ||
The fitted image. | ||
parameters : `list` of `ndarray` | ||
The list of optimal transform parameters per iteration of the fitting | ||
procedure. | ||
shapes : `list` of :map:`PointCloud` | ||
The list of fitted shapes per iteration of the fitting procedure. | ||
gt_shape : :map:`PointCloud` | ||
The ground truth shape associated to the image. | ||
""" | ||
def __init__(self, image, parameters, shapes, gt_shape): | ||
FittingResult.__init__(self, image, gt_shape=gt_shape) | ||
HDF5able.__init__(self) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to initialize |
||
|
||
self.parameters = parameters | ||
self._shapes = shapes | ||
|
||
def shapes(self, as_points=False): | ||
if as_points: | ||
return [s.points.copy() for s in self._shapes] | ||
else: | ||
return self._shapes | ||
|
||
@property | ||
def initial_shape(self): | ||
return self._shapes[0] | ||
|
||
@property | ||
def final_shape(self): | ||
return self._shapes[-1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this really need to be underscored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm ok I see we want setters in subclasses.