Skip to content

Commit fc41f94

Browse files
gemmaellentboggs
authored andcommitted
Add denoising methods to class PrincipalComponents (#91)
* Update algorithms.py * Update algorithms.py * Update .travis.yml
1 parent 2952f86 commit fc41f94

File tree

2 files changed

+100
-14
lines changed

2 files changed

+100
-14
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
language: python
22
sudo: false
3+
dist: trusty
34
python:
45
- "2.6"
56
- "2.7"

spectral/algorithms/algorithms.py

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,15 @@ class PrincipalComponents:
389389
A method to return a reduced set of principal components based
390390
on either a fixed number of components or a fraction of total
391391
variance.
392+
393+
`denoise`:
394+
395+
A callable function to denoise data using a reduced set of
396+
principal components.
397+
398+
`get_denoising_transform`:
399+
400+
A callable function that returns a function for denoising data.
392401
'''
393402
def __init__(self, vals, vecs, stats):
394403
from .transforms import LinearTransform
@@ -462,6 +471,73 @@ def reduce(self, N=0, **kwargs):
462471
raise Exception('Must specify one of the following keywords:'
463472
'`num`, `eigs`, `fraction`.')
464473

474+
def denoise(self, X, **kwargs):
475+
'''Returns a de-noised version of `X`.
476+
477+
Arguments:
478+
479+
`X` (np.ndarray):
480+
481+
Data to be de-noised. Can be a single pixel or an image.
482+
483+
Keyword Arguments (one of the following must be specified):
484+
485+
`num` (integer):
486+
487+
Number of eigenvalues/eigenvectors to use. The top `num`
488+
eigenvalues will be used.
489+
490+
`eigs` (list):
491+
492+
A list of indices of eigenvalues/eigenvectors to be used.
493+
494+
`fraction` (float):
495+
496+
The fraction of total image variance to retain. Eigenvalues
497+
will be included (starting from greatest to smallest) until
498+
`fraction` of total image variance is retained.
499+
500+
Returns denoised image data with same shape as `X`.
501+
502+
Note that calling this method is equivalent to calling the
503+
`get_denoising_transform` method with same keyword and applying the
504+
returned transform to `X`. If you only intend to denoise data with the
505+
same parameters multiple times, then it is more efficient to get the
506+
denoising transform and reuse it, rather than calling this method
507+
multilple times.
508+
'''
509+
f = self.get_denoising_transform(**kwargs)
510+
return f(X)
511+
512+
def get_denoising_transform(self, **kwargs):
513+
'''Returns a function for denoising image data.
514+
515+
Keyword Arguments (one of the following must be specified):
516+
517+
`num` (integer):
518+
519+
Number of eigenvalues/eigenvectors to use. The top `num`
520+
eigenvalues will be used.
521+
522+
`eigs` (list):
523+
524+
A list of indices of eigenvalues/eigenvectors to be used.
525+
526+
`fraction` (float):
527+
528+
The fraction of total image variance to retain. Eigenvalues
529+
will be included (starting from greatest to smallest) until
530+
`fraction` of total image variance is retained.
531+
532+
Returns a callable :class:`~spectral.algorithms.transforms.LinearTransform`
533+
object for denoising image data.
534+
'''
535+
from .transforms import LinearTransform
536+
V = self.reduce(self, **kwargs).eigenvectors
537+
f = LinearTransform(V.dot(V.T), pre=-self.mean,
538+
post=self.mean)
539+
return f
540+
465541

466542
def principal_components(image):
467543
'''
@@ -500,6 +576,15 @@ def principal_components(image):
500576
`reduce`:
501577
502578
A method to reduce the number of eigenvalues.
579+
580+
`denoise`:
581+
582+
A callable function to denoise data using a reduced set of
583+
principal components.
584+
585+
`get_denoising_transform`:
586+
587+
A callable function that returns a function for denoising data.
503588
'''
504589
from numpy import sqrt, sum
505590

@@ -674,7 +759,7 @@ class GaussianStats(object):
674759
`principal_components`:
675760
676761
The principal components of the data, based on mean and cov.
677-
'''
762+
'''
678763

679764
def __init__(self, mean=None, cov=None, nsamples=None, inv_cov=None):
680765
self.cov = cov
@@ -698,7 +783,7 @@ def inv_cov(self):
698783
if self._inv_cov is None:
699784
self._inv_cov = np.linalg.inv(self._cov)
700785
return self._inv_cov
701-
786+
702787
def reset_derived_stats(self):
703788
self._cov = self._inv_cov = None
704789
self._sqrt_cov = self._sqrt_inv_cov = self._pcs = None
@@ -717,7 +802,7 @@ def sqrt_cov(self):
717802
pcs.eigenvectors),
718803
symmetric=True)
719804
return self._sqrt_cov
720-
805+
721806
@property
722807
def sqrt_inv_cov(self):
723808
'''Property method returning matrix square root of inverse of cov.
@@ -732,7 +817,7 @@ def sqrt_inv_cov(self):
732817
symmetric=True,
733818
inverse=True)
734819
return self._sqrt_inv_cov
735-
820+
736821
@property
737822
def principal_components(self):
738823
if self._pcs is None:
@@ -884,7 +969,7 @@ def size(self):
884969
else:
885970
return np.sum(np.not_equal(self.mask, 0).ravel())
886971

887-
972+
888973
def calc_stats(self):
889974
'''
890975
Calculates statistics for the class.
@@ -1019,7 +1104,7 @@ def save(self, filename, calc_stats=False):
10191104
c.calc_stats()
10201105
f = open(filename, 'wb')
10211106
ids = sorted(self.classes.keys())
1022-
pickle.dump(self.classes[ids[0]].mask, f)
1107+
pickle.dump(self.classes[ids[0]].mask, f)
10231108
pickle.dump(len(self), f)
10241109
for id in ids:
10251110
c = self.classes[id]
@@ -1048,7 +1133,7 @@ def load(self, filename, image):
10481133
c.nbands = len(mean)
10491134
self.add_class(c)
10501135
f.close
1051-
1136+
10521137

10531138
def create_training_classes(image, class_mask, calc_stats=False, indices=None):
10541139
'''
@@ -1376,9 +1461,9 @@ def msam(data, members):
13761461
'''
13771462
# The modifications to the `spectral_angles` function were contributed by
13781463
# Christian Mielke.
1379-
1464+
13801465
import math
1381-
1466+
13821467
assert members.shape[1] == data.shape[2], \
13831468
'Matrix dimensions are not aligned.'
13841469

@@ -1443,7 +1528,7 @@ def noise_from_diffs(X, direction='lowerright'):
14431528
deltas = X[:, :-1, :] - X[:, 1:, :]
14441529
else:
14451530
deltas = X[:-1, :, :] - X[1:, :, :]
1446-
1531+
14471532
stats = calc_stats(deltas)
14481533
stats.cov /= 2.0
14491534
return stats
@@ -1668,7 +1753,7 @@ def mnf(signal, noise):
16681753
wstats = GaussianStats(mean=np.zeros_like(L), cov=C)
16691754
napc = PrincipalComponents(L, V, wstats)
16701755
return MNFResult(signal, noise, napc)
1671-
1756+
16721757
def ppi(X, niters, threshold=0, centered=False, start=None, display=0,
16731758
**imshow_kwargs):
16741759
'''Returns pixel purity indices for an image.
@@ -1743,7 +1828,7 @@ def ppi(X, niters, threshold=0, centered=False, start=None, display=0,
17431828
from numbers import Integral
17441829
import spectral as spy
17451830
from spectral.algorithms.algorithms import calc_stats
1746-
1831+
17471832

17481833
if display is not None:
17491834
if not isinstance(display, Integral) or isinstance(display, bool) or \
@@ -1758,7 +1843,7 @@ def ppi(X, niters, threshold=0, centered=False, start=None, display=0,
17581843
shape = X.shape
17591844
X = X.reshape(-1, X.shape[-1])
17601845
nbands = X.shape[-1]
1761-
1846+
17621847
fig = None
17631848
updating = False
17641849

@@ -1814,7 +1899,7 @@ def ppi(X, niters, threshold=0, centered=False, start=None, display=0,
18141899
'values may be corrupt. Returning None'
18151900
spy._status.write(msg)
18161901
return None
1817-
1902+
18181903
spy._status.end_percentage()
18191904

18201905
return counts.reshape(shape[:2])

0 commit comments

Comments
 (0)