Skip to content

Commit

Permalink
Merge pull request #3 from jankukacka/dev
Browse files Browse the repository at this point in the history
Release 1.0.0-beta2
  • Loading branch information
jankukacka authored Feb 7, 2022
2 parents c7733c7 + c5ff82f commit d523487
Show file tree
Hide file tree
Showing 41 changed files with 2,032 additions and 666 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ Viewer for spectral images, in particular suitable from multispectral optoacoust
## Installation

```
pip install image-viewer-mk2
pip install image-viewer-mk2==1.0.0b2
```

Additional dependencies must be installed separately: `happy` (currently not publicly available), optional: `PyTorch` (for GPU-based rendering), `pywin32` (Windows clipboard functionality)
Additional dependencies must be installed separately: `happy` (currently not publicly available), optional: `pywin32` (Windows clipboard functionality)

See [releases](https://github.com/jankukacka/image_viewer_mk2/releases) for older versions.

## Usage

**From command line as a standalone application.**
```
> imvmk2 [-i filename] [-c config_filename] [-g (GPU) | -ng (No GPU)] [-d (debug)]
> imvmk2 [-i filename] [-c config_filename] [-d (debug)]
```

**From within python scripts and interactive sessions.** The viewer can be either used as an interactive image viewer, giving the user the ability to manually adjust the settings. The rendered image is returned back so that it can be further used inside the script.
Expand Down Expand Up @@ -46,7 +48,7 @@ TclError: image "pyimageXX" doesn't exist
When using in interactive session, all other matplotlib figures have to be closed. Calling `matplotlib.pyplot.close(fig='all')` should do the trick.

## Credits
This software reuses code and icons produced by: cilame, Benjamin Johnson, Remin Emonet, Icon home, Gregor Cresnar, Freepik, Google, Uptal Barman and Pancracysdh
This software reuses code and icons produced by: Alistair Muldal, cilame, Benjamin Johnson, Remin Emonet, [Icon home](https://www.flaticon.com/authors/icon-home), [Gregor Cresnar](https://www.flaticon.com/authors/gregor-cresnar), [Freepik](https://www.flaticon.com/authors/Freepik), [Google](https://www.flaticon.com/authors/google), Uptal Barman, [Arkinasi](https://www.flaticon.com/authors/arkinasi), [Royyan Wijaya](https://www.flaticon.com/authors/royyan-wijaya), and Pancracysdh.

## License

Expand All @@ -55,5 +57,5 @@ The software is provided under the [MIT open license](LICENSE.txt).
## Citation
If you use this software for your research, please cite it as:
```
Kukačka, Jan (2021). Image Viewer MK2 (v0.2.4) [Computer software]. https://github.com/jankukacka/image_viewer_mk2
Kukačka, Jan (2021). Image Viewer MK2 (v1.0.0) [Computer software]. https://github.com/jankukacka/image_viewer_mk2
```
Binary file modified doc/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[metadata]
name = image-viewer-mk2
author = Jan Kukacka
version = 0.2.4
version = 1.0.0b2
description = Image viewer for spectral images
long_description = file: README.md
long_description_content_type = text/markdown
Expand All @@ -12,7 +12,7 @@ classifiers =
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Operating System :: Microsoft :: Windows

[options]
package_dir =
Expand All @@ -23,7 +23,9 @@ python_requires = >=3.7
install_requires =
matplotlib
numpy
scipy
Pillow
scikit-image

include_package_data = True

Expand Down
37 changes: 37 additions & 0 deletions src/image_viewer_mk2/ObservableCollections/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ------------------------------------------------------------------------------
# File: utils.py
# Author: Jan Kukacka
# Date: 2/2022
# ------------------------------------------------------------------------------
# Helping functions for working with observable collections
# ------------------------------------------------------------------------------

try:
from .observablelist import ObservableList
from .observabledict import ObservableDict
except ImportError:
from ObservableCollections.observablelist import ObservableList
from ObservableCollections.observabledict import ObservableDict


def make_observable(obj):
if isinstance(obj, list):
return ObservableList([make_observable(item) for item in obj])
elif isinstance(obj, dict):
return ObservableDict({key:make_observable(val) for key,val in obj.items()})
elif isinstance(obj, set):
raise NotImplementedError('make_observable does not support sets.')
else:
return obj

def make_plain(obj):
if isinstance(obj, (list,ObservableList)):
return [make_plain(item) for item in obj]
if isinstance(obj, tuple):
return tuple(make_plain(item) for item in obj)
elif isinstance(obj, (dict,ObservableDict)):
return {key:make_plain(val) for key,val in obj.items()}
# elif isinstance(obj, ObservableSet):
# raise NotImplementedError('make_plain does not support sets.')
else:
return obj
Empty file.
188 changes: 188 additions & 0 deletions src/image_viewer_mk2/filters/anisotropic_denoising.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# ------------------------------------------------------------------------------
# File: anisotropic_denoising.py
# Author: Jan Kukacka
# Date: 2/2022
# ------------------------------------------------------------------------------
# Implementation of anisotropic denoising
# Based on code by Alistair Muldal (c) 2012
# - source: https://pastebin.com/sBsPX4Y7
# ------------------------------------------------------------------------------

import numpy as np
from cv2.ximgproc import anisotropicDiffusion

try:
from . import filter
except ImportError:
from filters import filter

class AnisotropicDenoising(filter.Filter):
'''
'''

name = 'anisotropic_denoising'

def __init__(self, step_size=.15, sensitivity=.1, n_iter=10):
'''
# Arguments:
- step_size:
- sensitivity:
- n_iter:
'''
super().__init__()
self.step_size = step_size
self.sensitivity = sensitivity
self.n_iter = n_iter

def __call__(self, img):
'''
# Arguments:
- img: array of data to be normalized
# Returns:
- normalized array.
'''
result = super().__call__(img)
if result is not None:
return result
else:
self.cache = self.call(img, self.step_size, self.sensitivity, self.n_iter)
return self.cache


@staticmethod
def call(img, step_size, sensitivity, n_iter, **kwargs):
img_min, img_max = img.min(), img.max()

norm_img = anisodiff(img, step_size, sensitivity, n_iter)

norm_min, norm_max = norm_img.min(), norm_img.max()
scale = (img_max-img_min) / (norm_max-norm_min)
return img_min + (norm_img-norm_min) * scale


def serialize(self):
base_dict = super().serialize()
base_dict['params']['step_size'] = self.step_size
base_dict['params']['sensitivity'] = self.sensitivity
base_dict['params']['n_iter'] = self.n_iter
return base_dict

@staticmethod
def deserialize(serialization):
step_size = serialization['step_size']
sensitivity = serialization['sensitivity']
n_iter = serialization['n_iter']
obj = AnisotropicDenoising(step_size, sensitivity, n_iter)
obj._deserialize_parent(serialization)
return obj

# ------------------------------------------------------------------------------
# Based on code by Alistair Muldal (c) 2012
# - source: https://pastebin.com/sBsPX4Y7

# Original work: Copyright (c) 1995-2012 Peter Kovesi pk@peterkovesi.com
# Modified work: Copyright (c) 2012 Alistair Muldal
# Modified work: Copyright (c) 2022 Jan Kukacka
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# The software is provided "as is", without warranty of any kind, express or
# implied, including but not limited to the warranties of merchantability,
# fitness for a particular purpose and noninfringement. In no event shall the
# authors or copyright holders be liable for any claim, damages or other
# liability, whether in an action of contract, tort or otherwise, arising from,
# out of or in connection with the software or the use or other dealings in the
# software.

from scipy.ndimage import gaussian_filter

def anisodiff(img, gamma=0.1, kappa=50, niter=1, sigma=0, option=1):
"""
Anisotropic diffusion.
Usage:
imgout = anisodiff(im, niter, kappa, gamma, option)
Arguments:
img - input image
gamma - max value of .25 for stability
kappa - conduction coefficient 20-100 ?
niter - number of iterations
step - tuple, the distance between adjacent pixels in (y,x)
option - 1 Perona Malik diffusion equation No 1
2 Perona Malik diffusion equation No 2
Returns:
imgout - diffused image.
kappa controls conduction as a function of gradient. If kappa is low
small intensity gradients are able to block conduction and hence diffusion
across step edges. A large value reduces the influence of intensity
gradients on conduction.
gamma controls speed of diffusion (you usually want it at a maximum of
0.25)
Diffusion equation 1 favours high contrast edges over low contrast ones.
Diffusion equation 2 favours wide regions over smaller ones.
"""

# initialize output array
imgout = img.copy()

# initialize some internal variables
deltaS = np.zeros_like(imgout)
deltaE = deltaS.copy()
NS = deltaS.copy()
EW = deltaS.copy()
gS = np.ones_like(imgout)
gE = gS.copy()

for ii in np.arange(1,niter):

# calculate the diffs
deltaS[:-1,: ] = np.diff(imgout,axis=0)
deltaE[: ,:-1] = np.diff(imgout,axis=1)

if sigma > 0:
deltaSf=gaussian_filter(deltaS,sigma)
deltaEf=gaussian_filter(deltaE,sigma)
else:
deltaSf=deltaS
deltaEf=deltaE

# conduction gradients (only need to compute one per dim!)
if option == 1:
gS = np.exp(-(deltaSf/kappa)**2.)
gE = np.exp(-(deltaEf/kappa)**2.)
elif option == 2:
gS = 1./(1.+(deltaSf/kappa)**2.)
gE = 1./(1.+(deltaEf/kappa)**2.)

# update matrices
E = gE*deltaE
S = gS*deltaS

# subtract a copy that has been shifted 'North/West' by one
# pixel. don't as questions. just do it. trust me.
NS[:] = S
EW[:] = E
NS[1:,:] -= S[:-1,:]
EW[:,1:] -= E[:,:-1]

# update the image
imgout += gamma*(NS+EW)

return imgout

# ------------------------------------------------------------------------------
37 changes: 37 additions & 0 deletions src/image_viewer_mk2/filters/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ------------------------------------------------------------------------------
# File: filter.py
# Author: Jan Kukacka
# Date: 2/2022
# ------------------------------------------------------------------------------
# Base class for image processing filters
# ------------------------------------------------------------------------------

class Filter(object):
'''
Base class for filters.
'''

def __init__(self):
self.active = True
self.cache = None

def __call__(self, img):
'''
Base class call handler. Child classes need to take care of case when
this returns None.
'''
if not self.active:
return img
if self.cache is not None:
return self.cache

def serialize(self):
return {'name': self.name,
'params': {'active': self.active}}

@staticmethod
def deserialize(serialization):
raise NotImplementedError()

def _deserialize_parent(self, serialization):
self.active = serialization['active']
46 changes: 46 additions & 0 deletions src/image_viewer_mk2/filters/filter_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ------------------------------------------------------------------------------
# File: filter_factory.py
# Author: Jan Kukacka
# Date: 2/2022
# ------------------------------------------------------------------------------
# Filter factory
# ------------------------------------------------------------------------------

try:
from . import local_norm
from . import sigmoid_norm
from . import unsharp_mask
from . import gamma_correction
from . import frangi
from . import minmax_norm
from . import gaussian_blur
from . import anisotropic_denoising
except ImportError:
from filters import local_norm
from filters import sigmoid_norm
from filters import unsharp_mask
from filters import gamma_correction
from filters import frangi
from filters import minmax_norm
from filters import gaussian_blur
from filters import anisotropic_denoising

__available_filters = (local_norm.LocalNorm,
sigmoid_norm.SigmoidNorm,
unsharp_mask.UnsharpMask,
gamma_correction.GammaCorrection,
frangi.Frangi,
minmax_norm.MinMaxNorm,
gaussian_blur.GaussianBlur,
anisotropic_denoising.AnisotropicDenoising)

def get_filter_by_name(name):
'''
Return type matching given name
'''
for T_filter in __available_filters:
if name == T_filter.name:
return T_filter

def get_available_filters():
return __available_filters
Loading

0 comments on commit d523487

Please sign in to comment.