diff --git a/README.rst b/README.rst index ef05fe7..32c14dd 100644 --- a/README.rst +++ b/README.rst @@ -102,10 +102,10 @@ API Usage filenames = [os.path.join(src_path, f) for f in os.listdir(src_path) if f.lower().endswith(FILE_EXTS)] + cm = ColorMatcher() for i, fname in enumerate(filenames): img_src = load_img_file(fname) - obj = ColorMatcher(src=img_src, ref=img_ref, method='mkl') - img_res = obj.main() + img_res = cm.transfer(src=img_src, ref=img_ref, method='mkl') img_res = Normalizer(img_res).uint8_norm() save_img_file(img_res, os.path.join(os.path.dirname(fname), str(i)+'.png')) diff --git a/color_matcher/__init__.py b/color_matcher/__init__.py index 5c01ec5..516b5c2 100644 --- a/color_matcher/__init__.py +++ b/color_matcher/__init__.py @@ -16,7 +16,7 @@ along with this program. If not, see . """ -__version__ = '0.4.1' +__version__ = '0.5.0' from .top_level import ColorMatcher from .hist_matcher import HistogramMatcher diff --git a/color_matcher/baseclass.py b/color_matcher/baseclass.py index 0b86fdc..2273c22 100644 --- a/color_matcher/baseclass.py +++ b/color_matcher/baseclass.py @@ -17,6 +17,7 @@ """ import numpy as np +import warnings class MatcherBaseclass(object): @@ -25,6 +26,7 @@ def __init__(self, *args, **kwargs): self._src = None self._ref = None + self._funs = [] if len(args) == 2: self._src = args[0] @@ -34,8 +36,6 @@ def __init__(self, *args, **kwargs): self._src = kwargs['src'] if 'src' in kwargs else self._src self._ref = kwargs['ref'] if 'ref' in kwargs else self._ref - self.validate_img_dims() - def validate_img_dims(self): """ This function validates the image dimensions. It throws an exception if the dimension are unequal to 2 or 3. @@ -45,17 +45,61 @@ def validate_img_dims(self): self._src = self._src[..., np.newaxis] if len(self._src.shape) == 2 else self._src self._ref = self._ref[..., np.newaxis] if len(self._ref.shape) == 2 else self._ref - if len(self._src.shape) != 3 or len(self._ref.shape) != 3: - raise BaseException('Wrong image dimensions') + if len(self._src.shape) not in (2, 3) or len(self._ref.shape) not in (2, 3): + raise BaseException('Each image must have 2 or 3 dimensions') return True def validate_color_chs(self): """ - This function checks whether provided images consist of 3 color channels. An exception is thrown otherwise. + This function checks whether provided images consist of a valid number of color channels. """ - if self._src.shape[2] != 3 or self._ref.shape[2] != 3: - raise BaseException('Each image must have 3 color channels') + if len(self._src.shape) == 3 or len(self._ref.shape) == 3: + if self._src.shape[2] > 4 or self._ref.shape[2] > 4: + raise BaseException('Each image cannot have more than 4 color channels') + elif self._src.shape[2] == 3 and self._ref.shape[2] == 4: + self._ref = self._ref[..., :3] + elif self._src.shape[2] == 4 and self._ref.shape[2] == 3: + self._src = self._src[..., :3] + elif self._src.shape[2] == 1 and self._ref.shape[2] == 3: + self._ref = self.rgb2gray(self._ref) + elif self._src.shape[2] == 3 and self._ref.shape[2] == 1: + self._src = self.rgb2gray(self._src) + + if self._src.shape[2] == 1 and self._ref.shape[2] == 1: + # restrict monochromatic transfer to histogram matching + self._funs = [self.hist_match] + warnings.warn('Transfer restricted to histogram matching due to monochromatic input') return True + + @staticmethod + def rgb2gray(rgb: np.ndarray = None, standard: str = 'HDTV') -> np.ndarray: + """ Convert RGB color space to monochromatic color space + + :param rgb: input array in red, green and blue (RGB) space + :type rgb: :class:`~numpy:numpy.ndarray` + :param standard: option that determines whether head- and footroom are excluded ('HDTV') or considered otherwise + :type standard: :class:`string` + :return: array in monochromatic space + :rtype: ~numpy:np.ndarray + + """ + + # store shape + shape = rgb.shape + + # reshape image to channel vectors + rgb = rgb.reshape(-1, 3).T + + # choose standard + mat = np.array([0.2126, 0.7152, 0.0722]) if standard == 'HDTV' else np.array([0.299, 0.587, 0.114]) + + # convert to gray + arr = np.dot(mat, rgb) + + # reshape to 2-D image + arr = arr.reshape(shape[:2] + (1,)) + + return arr diff --git a/color_matcher/mvgd_matcher.py b/color_matcher/mvgd_matcher.py index 444c321..0eb150b 100644 --- a/color_matcher/mvgd_matcher.py +++ b/color_matcher/mvgd_matcher.py @@ -36,15 +36,14 @@ def __init__(self, *args, **kwargs): try: self._fun_name = [kw for kw in list(self._fun_dict.keys()) if kwargs['method'].__contains__(kw)][0] except (BaseException, IndexError): - # use MKL as default + # default function self._fun_name = 'mkl' self._fun_call = self._fun_dict[self._fun_name] if self._fun_name in self._fun_dict else self.mkl_solver # initialize variables self.r, self.z, self.cov_r, self.cov_z, self.mu_r, self.mu_z, self.transfer_mat = [None]*7 - self._init_vars() - def _init_vars(self): + def init_vars(self): # reshape source and reference images self.r, self.z = self._src.reshape([-1, self._src.shape[2]]).T, self._ref.reshape([-1, self._ref.shape[2]]).T @@ -55,14 +54,17 @@ def _init_vars(self): # compute color channel means self.mu_r, self.mu_z = self.r.mean(axis=1)[..., np.newaxis], self.z.mean(axis=1)[..., np.newaxis] - def transfer(self, src: np.ndarray = None, ref: np.ndarray = None, fun: FunctionType = None) -> np.ndarray: + # validate dimensionality + self.check_dims() + + def multivar_transfer(self, src: np.ndarray = None, ref: np.ndarray = None, fun: FunctionType = None) -> np.ndarray: """ Transfer function to map colors based on for Multi-Variate Gaussian Distributions (MVGDs). :param src: Source image that requires transfer :param ref: Palette image which serves as reference - :param fun: optional argument to pass a transfer function to solve for covariance matrices + :param fun: Optional argument to pass a transfer function to solve for covariance matrices :param res: Resulting image after the mapping :type src: :class:`~numpy:numpy.ndarray` @@ -82,7 +84,7 @@ def transfer(self, src: np.ndarray = None, ref: np.ndarray = None, fun: Function self.validate_color_chs() # re-initialize variables to account for change in src and ref when passed to self.transfer() - self._init_vars() + self.init_vars() # set solver function for transfer matrix self._fun_call = fun if fun is FunctionType else self._fun_call @@ -108,14 +110,17 @@ def mkl_solver(self): """ + # validate dimensionality + self.check_dims() + eig_val_r, eig_vec_r = np.linalg.eig(self.cov_r) eig_val_r[eig_val_r < 0] = 0 val_r = np.diag(np.sqrt(eig_val_r[::-1])) vec_r = np.array(eig_vec_r[:, ::-1]) inv_r = np.diag(1. / (np.diag(val_r + np.spacing(1)))) - mat_c = np.dot(val_r, np.dot(vec_r.T, np.dot(self.cov_z, np.dot(vec_r, val_r)))) - [eig_val_c, eig_vec_c] = np.linalg.eig(mat_c) + mat_c = val_r @ vec_r.T @ self.cov_z @ vec_r @ val_r + eig_val_c, eig_vec_c = np.linalg.eig(mat_c) eig_val_c[eig_val_c < 0] = 0 val_c = np.diag(np.sqrt(eig_val_c)) @@ -133,6 +138,11 @@ def analytical_solver(self) -> np.ndarray: """ + # validate dimensionality + self.check_dims() + if self.r.shape[-1] != self.z.shape[-1]: + raise Exception('Analytical MVGD solution requires spatial dimensions of both images to be equal') + cov_r_inv = np.linalg.pinv(self.cov_r) cov_z_inv = np.linalg.pinv(self.cov_z) @@ -164,3 +174,13 @@ def w2_dist(mu_a: np.ndarray, mu_b: np.ndarray, cov_a: np.ndarray, cov_b: np.nda vars_dist = np.trace(cov_a+cov_b - 2*(np.dot(np.abs(cov_b)**.5, np.dot(np.abs(cov_a), np.abs(cov_b)**.5))**.5)) return float(mean_dist + vars_dist) + + def check_dims(self): + """ + Catch error for wrong color channel number (e.g., gray scale image) + + :return: None + """ + + if np.ndim(self.cov_r) == 0 or np.ndim(self.cov_z) == 0: + raise Exception('Wrong color channel dimensionality for %s method' % self._fun_name) diff --git a/color_matcher/top_level.py b/color_matcher/top_level.py index 290d0e3..5447c77 100644 --- a/color_matcher/top_level.py +++ b/color_matcher/top_level.py @@ -34,36 +34,65 @@ def __init__(self, *args, **kwargs): super(ColorMatcher, self).__init__(*args, **kwargs) self._method = kwargs['method'] if 'method' in kwargs else 'default' + self._funs = [] - def main(self, method: str = None) -> np.ndarray: + def main(self) -> np.ndarray: """ - The main function is the high-level entry point performing the mapping. Valid methods are: + The main function is the high-level entry point performing the mapping based on instantiation arguments. + :return: Resulting image after color mapping + :rtype: np.ndarray + """ + + self.transfer() + + return self._src + + def transfer(self, src: np.ndarray = None, ref: np.ndarray = None, method: str = None) -> np.ndarray: + """ + + Transfer function to map colors based on provided transfer method. + + :param src: Source image that requires transfer + :param ref: Palette image which serves as reference :param method: ('default', 'hm', 'reinhard', 'mvgd', 'mkl', 'hm-mvgd-hm', 'hm-mkl-hm') determining color mapping + + :type src: :class:`~numpy:numpy.ndarray` + :type ref: :class:`~numpy:numpy.ndarray` :type method: :class:`str` :return: Resulting image after color mapping :rtype: np.ndarray + """ + # assign input arguments to variables (if provided) self._method = self._method.lower() if method is None else method.lower() + self._src = src if src is not None else self._src + self._ref = ref if ref is not None else self._ref # color transfer methods (to be iterated through) if self._method == METHODS[0]: - funs = [self.transfer] + self._funs = [self.multivar_transfer] elif self._method == METHODS[1]: - funs = [self.hist_match] + self._funs = [self.hist_match] elif self._method == METHODS[2]: - funs = [self.reinhard] + self._funs = [self.reinhard] elif self._method in METHODS[3:5]: - funs = [self.transfer] + self._funs = [self.multivar_transfer] elif self._method in METHODS[5:]: - funs = [self.hist_match, self.transfer, self.hist_match] + self._funs = [self.hist_match, self.multivar_transfer, self.hist_match] else: raise BaseException('Method type \'%s\' not recognized' % method) + # check if three color channels are provided + self.validate_img_dims() + + # check provided color channels + self.validate_color_chs() + # proceed with the color match - for fun in funs: + for fun in self._funs: self._src = fun(self._src, self._ref) return self._src diff --git a/docs/build/html/.buildinfo b/docs/build/html/.buildinfo index e683da1..ec0f432 100644 --- a/docs/build/html/.buildinfo +++ b/docs/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 36e65157a5b8d9f8afb88b0180e76b66 +config: d69c61c77e9336c13f9a1800c01ddd93 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/build/html/_static/documentation_options.js b/docs/build/html/_static/documentation_options.js index e9b7668..25eaf45 100644 --- a/docs/build/html/_static/documentation_options.js +++ b/docs/build/html/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.4.1', + VERSION: '0.5.0', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/build/html/apidoc.html b/docs/build/html/apidoc.html index ed8bdde..4d09d63 100644 --- a/docs/build/html/apidoc.html +++ b/docs/build/html/apidoc.html @@ -5,7 +5,7 @@ - API documentation — color-matcher 0.4.1 documentation + API documentation — color-matcher 0.5.0 documentation @@ -30,7 +30,7 @@

Navigation

  • previous |
  • - + @@ -63,11 +63,29 @@

    Class hierarchy
    -main(method: str = None)numpy.ndarray
    -

    The main function is the high-level entry point performing the mapping. Valid methods are:

    +main()numpy.ndarray +

    The main function is the high-level entry point performing the mapping based on instantiation arguments.

    +
    +
    Returns
    +

    Resulting image after color mapping

    +
    +
    Return type
    +

    np.ndarray

    +
    +
    +
    + +
    +
    +transfer(src: numpy.ndarray = None, ref: numpy.ndarray = None, method: str = None)numpy.ndarray
    +

    Transfer function to map colors based on provided transfer method.

    Parameters
    -

    method (str) – (‘default’, ‘hm’, ‘reinhard’, ‘mvgd’, ‘mkl’, ‘hm-mvgd-hm’, ‘hm-mkl-hm’) determining color mapping

    +
      +
    • src (ndarray) – Source image that requires transfer

    • +
    • ref (ndarray) – Palette image which serves as reference

    • +
    • method (str) – (‘default’, ‘hm’, ‘reinhard’, ‘mvgd’, ‘mkl’, ‘hm-mvgd-hm’, ‘hm-mkl-hm’) determining color mapping

    • +
    Returns

    Resulting image after color mapping

    @@ -136,6 +154,22 @@

    Class hierarchy +
    +check_dims()
    +

    Catch error for wrong color channel number (e.g., gray scale image)

    +
    +
    Returns
    +

    None

    +
    +
    +

    + +
    +
    +init_vars()
    +
    +
    mkl_solver()
    @@ -151,15 +185,15 @@

    Class hierarchy -
    -transfer(src: numpy.ndarray = None, ref: numpy.ndarray = None, fun: function = None)numpy.ndarray
    +
    +multivar_transfer(src: numpy.ndarray = None, ref: numpy.ndarray = None, fun: function = None)numpy.ndarray

    Transfer function to map colors based on for Multi-Variate Gaussian Distributions (MVGDs).

    Parameters
    • src (ndarray) – Source image that requires transfer

    • ref (ndarray) – Palette image which serves as reference

    • -
    • fun – optional argument to pass a transfer function to solve for covariance matrices

    • +
    • fun – Optional argument to pass a transfer function to solve for covariance matrices

    • res (ndarray) – Resulting image after the mapping

    @@ -238,10 +272,30 @@

    Class hierarchy +
    +static rgb2gray(rgb: numpy.ndarray = None, standard: str = 'HDTV')numpy.ndarray
    +

    Convert RGB color space to monochromatic color space

    +
    +
    Parameters
    +
      +
    • rgb (ndarray) – input array in red, green and blue (RGB) space

    • +
    • standard (string) – option that determines whether head- and footroom are excluded (‘HDTV’) or considered otherwise

    • +
    +
    +
    Returns
    +

    array in monochromatic space

    +
    +
    Return type
    +

    ndarray

    +
    +
    +

    +
    validate_color_chs()
    -

    This function checks whether provided images consist of 3 color channels. An exception is thrown otherwise.

    +

    This function checks whether provided images consist of a valid number of color channels.

    @@ -307,7 +361,7 @@

    Navigation

  • previous |
  • - + diff --git a/docs/build/html/color_matcher.html b/docs/build/html/color_matcher.html index 7a3ac5f..0777ed7 100644 --- a/docs/build/html/color_matcher.html +++ b/docs/build/html/color_matcher.html @@ -5,7 +5,7 @@ - color_matcher package — color-matcher 0.4.1 documentation + color_matcher package — color-matcher 0.5.0 documentation @@ -26,7 +26,7 @@

    Navigation

  • modules |
  • - + @@ -53,10 +53,30 @@

    Submodules +
    +static rgb2gray(rgb: numpy.ndarray = None, standard: str = 'HDTV')numpy.ndarray
    +

    Convert RGB color space to monochromatic color space

    +
    +
    Parameters
    +
      +
    • rgb (ndarray) – input array in red, green and blue (RGB) space

    • +
    • standard (string) – option that determines whether head- and footroom are excluded (‘HDTV’) or considered otherwise

    • +
    +
    +
    Returns
    +

    array in monochromatic space

    +
    +
    Return type
    +

    ndarray

    +
    +
    +

    +
    validate_color_chs()
    -

    This function checks whether provided images consist of 3 color channels. An exception is thrown otherwise.

    +

    This function checks whether provided images consist of a valid number of color channels.

    @@ -155,6 +175,22 @@

    Submodules +
    +check_dims()
    +

    Catch error for wrong color channel number (e.g., gray scale image)

    +
    +
    Returns
    +

    None

    +
    +
    +

    + +
    +
    +init_vars()
    +
    +
    mkl_solver()
    @@ -170,15 +206,15 @@

    Submodules -
    -transfer(src: numpy.ndarray = None, ref: numpy.ndarray = None, fun: function = None)numpy.ndarray
    +
    +multivar_transfer(src: numpy.ndarray = None, ref: numpy.ndarray = None, fun: function = None)numpy.ndarray

    Transfer function to map colors based on for Multi-Variate Gaussian Distributions (MVGDs).

    Parameters
    • src (ndarray) – Source image that requires transfer

    • ref (ndarray) – Palette image which serves as reference

    • -
    • fun – optional argument to pass a transfer function to solve for covariance matrices

    • +
    • fun – Optional argument to pass a transfer function to solve for covariance matrices

    • res (ndarray) – Resulting image after the mapping

    @@ -269,11 +305,29 @@

    Submodules
    -main(method: str = None)numpy.ndarray
    -

    The main function is the high-level entry point performing the mapping. Valid methods are:

    +main()numpy.ndarray +

    The main function is the high-level entry point performing the mapping based on instantiation arguments.

    +
    +
    Returns
    +

    Resulting image after color mapping

    +
    +
    Return type
    +

    np.ndarray

    +
    +
    +

    + +
    +
    +transfer(src: numpy.ndarray = None, ref: numpy.ndarray = None, method: str = None)numpy.ndarray
    +

    Transfer function to map colors based on provided transfer method.

    Parameters
    -

    method (str) – (‘default’, ‘hm’, ‘reinhard’, ‘mvgd’, ‘mkl’, ‘hm-mvgd-hm’, ‘hm-mkl-hm’) determining color mapping

    +
      +
    • src (ndarray) – Source image that requires transfer

    • +
    • ref (ndarray) – Palette image which serves as reference

    • +
    • method (str) – (‘default’, ‘hm’, ‘reinhard’, ‘mvgd’, ‘mkl’, ‘hm-mvgd-hm’, ‘hm-mkl-hm’) determining color mapping

    • +
    Returns

    Resulting image after color mapping

    @@ -344,7 +398,7 @@

    Navigation

  • modules |
  • - + diff --git a/docs/build/html/genindex.html b/docs/build/html/genindex.html index 6d84295..53e4e05 100644 --- a/docs/build/html/genindex.html +++ b/docs/build/html/genindex.html @@ -5,7 +5,7 @@ - Index — color-matcher 0.4.1 documentation + Index — color-matcher 0.5.0 documentation @@ -26,7 +26,7 @@

    Navigation

  • modules |
  • - + @@ -44,6 +44,7 @@

    Index

    | A | C | H + | I | L | M | N @@ -98,6 +99,12 @@

    A

    C

    +

    I

    + + +
    +

    L

    + @@ -245,11 +272,17 @@

    R

    +
    @@ -270,10 +303,10 @@

    S

    T

    @@ -364,7 +397,7 @@

    Navigation

  • modules |
  • - + diff --git a/docs/build/html/index.html b/docs/build/html/index.html index d858bde..79317a2 100644 --- a/docs/build/html/index.html +++ b/docs/build/html/index.html @@ -5,7 +5,7 @@ - color-matcher reference document — color-matcher 0.4.1 documentation + color-matcher reference document — color-matcher 0.5.0 documentation @@ -30,7 +30,7 @@

    Navigation

  • next |
  • - + @@ -109,7 +109,7 @@

    Navigation

  • next |
  • - + diff --git a/docs/build/html/objects.inv b/docs/build/html/objects.inv index e355c10..1fa537e 100644 Binary files a/docs/build/html/objects.inv and b/docs/build/html/objects.inv differ diff --git a/docs/build/html/py-modindex.html b/docs/build/html/py-modindex.html index 2b17489..848e1d3 100644 --- a/docs/build/html/py-modindex.html +++ b/docs/build/html/py-modindex.html @@ -5,7 +5,7 @@ - Python Module Index — color-matcher 0.4.1 documentation + Python Module Index — color-matcher 0.5.0 documentation @@ -29,7 +29,7 @@

    Navigation

  • modules |
  • - + @@ -118,7 +118,7 @@

    Navigation

  • modules |
  • - + diff --git a/docs/build/html/readme.html b/docs/build/html/readme.html index a8e02a0..24db725 100644 --- a/docs/build/html/readme.html +++ b/docs/build/html/readme.html @@ -5,7 +5,7 @@ - color-matcher — color-matcher 0.4.1 documentation + color-matcher — color-matcher 0.5.0 documentation @@ -34,7 +34,7 @@

    Navigation

  • previous |
  • - + @@ -151,10 +151,10 @@

    API Usagefilenames = [os.path.join(src_path, f) for f in os.listdir(src_path) if f.lower().endswith(FILE_EXTS)] +cm = ColorMatcher() for i, fname in enumerate(filenames): img_src = load_img_file(fname) - obj = ColorMatcher(src=img_src, ref=img_ref, method='mkl') - img_res = obj.main() + img_res = cm.transfer(src=img_src, ref=img_ref, method='mkl') img_res = Normalizer(img_res).uint8_norm() save_img_file(img_res, os.path.join(os.path.dirname(fname), str(i)+'.png')) @@ -256,7 +256,7 @@

    Navigation

  • previous |
  • - + diff --git a/docs/build/html/search.html b/docs/build/html/search.html index 704ca1d..e982ad2 100644 --- a/docs/build/html/search.html +++ b/docs/build/html/search.html @@ -5,7 +5,7 @@ - Search — color-matcher 0.4.1 documentation + Search — color-matcher 0.5.0 documentation @@ -31,7 +31,7 @@

    Navigation

  • modules |
  • - + @@ -82,7 +82,7 @@

    Navigation

  • modules |
  • - + diff --git a/docs/build/html/searchindex.js b/docs/build/html/searchindex.js index 375ae8e..87f24f4 100644 --- a/docs/build/html/searchindex.js +++ b/docs/build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["apidoc","color_matcher","index","readme"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,"sphinx.ext.todo":2,sphinx:56},filenames:["apidoc.rst","color_matcher.rst","index.rst","readme.rst"],objects:{"":{color_matcher:[1,0,0,"-"]},"color_matcher.ColorMatcher":{__init__:[0,2,1,""],main:[0,2,1,""]},"color_matcher.HistogramMatcher":{__init__:[0,2,1,""],hist_match:[0,2,1,""]},"color_matcher.MatcherBaseclass":{__init__:[0,2,1,""],validate_color_chs:[0,2,1,""],validate_img_dims:[0,2,1,""]},"color_matcher.ReinhardMatcher":{__init__:[0,2,1,""],reinhard:[0,2,1,""]},"color_matcher.TransferMVGD":{__init__:[0,2,1,""],analytical_solver:[0,2,1,""],mkl_solver:[0,2,1,""],transfer:[0,2,1,""],w2_dist:[0,2,1,""]},"color_matcher.baseclass":{MatcherBaseclass:[1,1,1,""]},"color_matcher.baseclass.MatcherBaseclass":{__init__:[1,2,1,""],validate_color_chs:[1,2,1,""],validate_img_dims:[1,2,1,""]},"color_matcher.hist_matcher":{HistogramMatcher:[1,1,1,""]},"color_matcher.hist_matcher.HistogramMatcher":{__init__:[1,2,1,""],hist_match:[1,2,1,""]},"color_matcher.io_handler":{load_img_file:[1,3,1,""],save_img_file:[1,3,1,""],select_file:[1,3,1,""],suppress_user_warning:[1,3,1,""]},"color_matcher.mvgd_matcher":{TransferMVGD:[1,1,1,""]},"color_matcher.mvgd_matcher.TransferMVGD":{__init__:[1,2,1,""],analytical_solver:[1,2,1,""],mkl_solver:[1,2,1,""],transfer:[1,2,1,""],w2_dist:[1,2,1,""]},"color_matcher.normalizer":{Normalizer:[1,1,1,""]},"color_matcher.normalizer.Normalizer":{__init__:[1,2,1,""],norm_fun:[1,2,1,""],type_norm:[1,2,1,""],uint16_norm:[1,2,1,""],uint8_norm:[1,2,1,""]},"color_matcher.top_level":{ColorMatcher:[1,1,1,""]},"color_matcher.top_level.ColorMatcher":{__init__:[1,2,1,""],main:[1,2,1,""]},color_matcher:{ColorMatcher:[0,1,1,""],HistogramMatcher:[0,1,1,""],MatcherBaseclass:[0,1,1,""],ReinhardMatcher:[0,1,1,""],TransferMVGD:[0,1,1,""],baseclass:[1,0,0,"-"],hist_matcher:[1,0,0,"-"],io_handler:[1,0,0,"-"],mvgd_matcher:[1,0,0,"-"],normalizer:[1,0,0,"-"],top_level:[1,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:function"},terms:{"class":[1,2],"default":[0,1],"float":[0,1],"function":[0,1,3],"import":3,"return":[0,1],"short":2,"static":[0,1],"switch":1,"throw":[0,1],The:[0,1,3],These:3,__init__:[0,1],abov:3,accur:[0,1],across:3,after:[0,1],aggoun:3,all:0,altern:3,amar:3,analyt:[0,1,3],analytical_solv:[0,1],api:2,approach:3,archiveprefix:[],arg:[0,1],argument:[0,1,3],arrai:1,articl:3,arxiv:[],author:2,automat:3,base:[0,1,3],batch:3,behind:3,below:[0,2,3],between:[1,3],bit:1,bool:1,both:[0,1],can:[0,3],categori:1,cdf:3,cdot:3,channel:[0,1],check:[0,1],christoph:3,citat:2,classic:3,cli:2,click:2,clone:3,color:[0,1],color_match:[0,3],colormatch:[0,1,3],colortransf:0,com:3,come:3,command:3,compound:3,comput:[0,1,3],conduct:[0,1],conjunct:3,consist:[0,1,3],content:2,correct:3,cov_a:[0,1],cov_b:[0,1],covari:[0,1],cumul:3,d_2:3,data:[1,3],densiti:3,depend:3,descript:2,detail:3,determin:[0,1],diagram:[0,3],dialog:1,dimens:[0,1],directori:3,dirnam:3,distanc:[0,1,3],distribut:[0,1,3],doi:3,download:3,eess:[],enabl:3,endswith:3,enter:3,entri:[0,1],enumer:3,eprint:[],equat:[0,1],equival:3,except:[0,1],exist:3,experiment:2,extens:0,field:3,file_ext:3,file_path:1,file_typ:1,filenam:3,filepath:1,film:3,finish:3,fname:3,follow:3,found:[0,3],framework:3,from:[1,3],full:2,fun:[0,1],futur:0,gaussian:[0,1,3],get:1,git:3,github:3,grade:3,guid:2,hahn:3,hahne2020plenopticam:[],hahne21:[],hahnec:3,handi:3,help:[0,1,3],hereaft:0,hierarchi:2,high:[0,1],higher:3,hist_match:0,histogram:[0,1,3],histogrammatch:[0,1],http:[0,3],ieee:3,illustr:3,imag:[0,1,3],imagepap:0,img:1,img_r:3,img_ref:3,img_src:3,indic:3,inform:3,init_dir:1,initi:[0,1],instal:2,integ:1,intens:3,interest:0,invari:[0,1],io_handl:3,join:3,journal:3,kantorovich:[0,1,3],kwarg:[0,1],level:[0,1,3],light:3,line:3,linear:[0,1,3],listdir:3,load:3,load_img_fil:[1,3],low:3,lower:3,main:[0,1,3],manual:3,map:[0,1,3],match:[0,1,3],matcherbaseclass:[0,1],mathbf:3,matric:[0,1],matrix:[0,1],max:1,mean:[0,1],measur:[0,1],method:[0,1,3],metric:[0,1,3],min:1,misc:[],mkl:[0,1,3],mkl_solver:[0,1],mong:[0,1,3],more:3,mu_a:[0,1],mu_b:[0,1],multi:[0,1,3],mvgd:[0,1,3],ndarrai:[0,1],new_max:1,new_min:1,none:[0,1],norm_fun:1,normal:3,note:3,number:[0,1,3],numpi:[0,1],obj:3,object:1,onc:3,option:[0,1,3],org:3,origin:0,otherwis:[0,1],our:3,outperform:3,overview:0,page:3,paint:3,palett:[0,1],paper:[0,3],paramet:[0,1,3],pass:[0,1,3],path:3,pdf:[0,3],perform:[0,1],photograph:3,pip3:3,pip:3,piti:3,plenopticam:3,png:3,point:[0,1],possibl:3,primari:0,primaryclass:[],principl:0,probabl:3,process:3,propos:[0,3],provid:[0,1,3],python3:3,python:3,ran:3,readm:2,ref:[0,1,3],refer:[0,1],reinhard:[0,1,3],reinhard_match:1,reinhardmatch:[0,1],repo:3,repres:3,requir:[0,1,3],res:[0,1],resolut:[0,1],respect:3,result:[0,1,2],root:3,run:3,same:[0,1],save_img_fil:[1,3],scalar:[0,1],schemat:0,scotland_hous:3,scotland_plain:3,see:[0,1],seen:0,select:3,select_fil:1,self:[0,1],sequenc:3,serv:[0,1],setup:3,shown:3,signatur:[0,1],similar:[0,1,3],smoothli:3,solut:[0,1,3],solv:[0,1],sourc:[0,1,3],specifi:3,src:[0,1,3],src_path:3,stopmot:3,str:[0,1,3],suppress_user_warn:1,system:[0,1,3],target:3,tau:0,test:3,thi:[0,1],thrown:[0,1],tip:3,titl:[1,3],tkinter:1,tool:3,transact:3,transfer:[0,1,3],transfer_mat:[0,1],transfermvgd:[0,1],turkel:0,txt:3,type:[0,1,3],type_norm:1,uint16_norm:1,uint8_norm:[1,3],unequ:[0,1],unix:3,unsign:1,usag:2,user:2,using:3,valid:[0,1],validate_color_ch:[0,1],validate_img_dim:[0,1],valu:[1,3],variat:[0,1,3],via:3,volum:3,w2_dist:[0,1],w_1:3,wasserstein:[0,1,3],well:3,where:3,whether:[0,1],which:[0,1,3],win:3,window:3,wise:[0,1],work:0,www:[0,3],year:3,you:3,your:3},titles:["API documentation","color_matcher package","color-matcher reference document","color-matcher"],titleterms:{"class":0,api:[0,3],author:3,baseclass:1,citat:3,cli:3,color:[2,3],color_match:1,command:[],content:1,descript:3,document:[0,2],experiment:3,hierarchi:0,hist_match:1,instal:3,io_handl:1,line:[],matcher:[2,3],modul:1,mvgd_matcher:1,normal:1,packag:1,refer:2,result:3,submodul:1,top_level:1,usag:3}}) \ No newline at end of file +Search.setIndex({docnames:["apidoc","color_matcher","index","readme"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.intersphinx":1,"sphinx.ext.todo":2,sphinx:56},filenames:["apidoc.rst","color_matcher.rst","index.rst","readme.rst"],objects:{"":{color_matcher:[1,0,0,"-"]},"color_matcher.ColorMatcher":{__init__:[0,2,1,""],main:[0,2,1,""],transfer:[0,2,1,""]},"color_matcher.HistogramMatcher":{__init__:[0,2,1,""],hist_match:[0,2,1,""]},"color_matcher.MatcherBaseclass":{__init__:[0,2,1,""],rgb2gray:[0,2,1,""],validate_color_chs:[0,2,1,""],validate_img_dims:[0,2,1,""]},"color_matcher.ReinhardMatcher":{__init__:[0,2,1,""],reinhard:[0,2,1,""]},"color_matcher.TransferMVGD":{__init__:[0,2,1,""],analytical_solver:[0,2,1,""],check_dims:[0,2,1,""],init_vars:[0,2,1,""],mkl_solver:[0,2,1,""],multivar_transfer:[0,2,1,""],w2_dist:[0,2,1,""]},"color_matcher.baseclass":{MatcherBaseclass:[1,1,1,""]},"color_matcher.baseclass.MatcherBaseclass":{__init__:[1,2,1,""],rgb2gray:[1,2,1,""],validate_color_chs:[1,2,1,""],validate_img_dims:[1,2,1,""]},"color_matcher.hist_matcher":{HistogramMatcher:[1,1,1,""]},"color_matcher.hist_matcher.HistogramMatcher":{__init__:[1,2,1,""],hist_match:[1,2,1,""]},"color_matcher.io_handler":{load_img_file:[1,3,1,""],save_img_file:[1,3,1,""],select_file:[1,3,1,""],suppress_user_warning:[1,3,1,""]},"color_matcher.mvgd_matcher":{TransferMVGD:[1,1,1,""]},"color_matcher.mvgd_matcher.TransferMVGD":{__init__:[1,2,1,""],analytical_solver:[1,2,1,""],check_dims:[1,2,1,""],init_vars:[1,2,1,""],mkl_solver:[1,2,1,""],multivar_transfer:[1,2,1,""],w2_dist:[1,2,1,""]},"color_matcher.normalizer":{Normalizer:[1,1,1,""]},"color_matcher.normalizer.Normalizer":{__init__:[1,2,1,""],norm_fun:[1,2,1,""],type_norm:[1,2,1,""],uint16_norm:[1,2,1,""],uint8_norm:[1,2,1,""]},"color_matcher.top_level":{ColorMatcher:[1,1,1,""]},"color_matcher.top_level.ColorMatcher":{__init__:[1,2,1,""],main:[1,2,1,""],transfer:[1,2,1,""]},color_matcher:{ColorMatcher:[0,1,1,""],HistogramMatcher:[0,1,1,""],MatcherBaseclass:[0,1,1,""],ReinhardMatcher:[0,1,1,""],TransferMVGD:[0,1,1,""],baseclass:[1,0,0,"-"],hist_matcher:[1,0,0,"-"],io_handler:[1,0,0,"-"],mvgd_matcher:[1,0,0,"-"],normalizer:[1,0,0,"-"],top_level:[1,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:function"},terms:{"catch":[0,1],"class":[1,2],"default":[0,1],"float":[0,1],"function":[0,1,3],"import":3,"return":[0,1],"short":2,"static":[0,1],"switch":1,"throw":[0,1],The:[0,1,3],These:3,__init__:[0,1],abov:3,accur:[0,1],across:3,after:[0,1],aggoun:3,all:0,altern:3,amar:3,analyt:[0,1,3],analytical_solv:[0,1],api:2,approach:3,archiveprefix:[],arg:[0,1],argument:[0,1,3],arrai:[0,1],articl:3,arxiv:[],author:2,automat:3,base:[0,1,3],batch:3,behind:3,below:[0,2,3],between:[1,3],bit:1,blue:[0,1],bool:1,both:[0,1],can:[0,3],categori:1,cdf:3,cdot:3,channel:[0,1],check:[0,1],check_dim:[0,1],christoph:3,citat:2,classic:3,cli:2,click:2,clone:3,color:[0,1],color_match:[0,3],colormatch:[0,1,3],colortransf:0,com:3,come:3,command:3,compound:3,comput:[0,1,3],conduct:[0,1],conjunct:3,consid:[0,1],consist:[0,1,3],content:2,convert:[0,1],correct:3,cov_a:[0,1],cov_b:[0,1],covari:[0,1],cumul:3,d_2:3,data:[1,3],densiti:3,depend:3,descript:2,detail:3,determin:[0,1],diagram:[0,3],dialog:1,dimens:[0,1],directori:3,dirnam:3,distanc:[0,1,3],distribut:[0,1,3],doi:3,download:3,eess:[],enabl:3,endswith:3,enter:3,entri:[0,1],enumer:3,eprint:[],equat:[0,1],equival:3,error:[0,1],except:[0,1],exclud:[0,1],exist:3,experiment:2,extens:0,field:3,file_ext:3,file_path:1,file_typ:1,filenam:3,filepath:1,film:3,finish:3,fname:3,follow:3,footroom:[0,1],found:[0,3],framework:3,from:[1,3],full:2,fun:[0,1],futur:0,gaussian:[0,1,3],get:1,git:3,github:3,grade:3,grai:[0,1],green:[0,1],guid:2,hahn:3,hahne2020plenopticam:[],hahne21:[],hahnec:3,handi:3,hdtv:[0,1],head:[0,1],help:[0,1,3],hereaft:0,hierarchi:2,high:[0,1],higher:3,hist_match:0,histogram:[0,1,3],histogrammatch:[0,1],http:[0,3],ieee:3,illustr:3,imag:[0,1,3],imagepap:0,img:1,img_r:3,img_ref:3,img_src:3,indic:3,inform:3,init_dir:1,init_var:[0,1],initi:[0,1],input:[0,1],instal:2,instanti:[0,1],integ:1,intens:3,interest:0,invari:[0,1],io_handl:3,join:3,journal:3,kantorovich:[0,1,3],kwarg:[0,1],level:[0,1,3],light:3,line:3,linear:[0,1,3],listdir:3,load:3,load_img_fil:[1,3],low:3,lower:3,main:[0,1],manual:3,map:[0,1,3],match:[0,1,3],matcherbaseclass:[0,1],mathbf:3,matric:[0,1],matrix:[0,1],max:1,mean:[0,1],measur:[0,1],method:[0,1,3],metric:[0,1,3],min:1,misc:[],mkl:[0,1,3],mkl_solver:[0,1],mong:[0,1,3],monochromat:[0,1],more:3,mu_a:[0,1],mu_b:[0,1],multi:[0,1,3],multivar_transf:[0,1],mvgd:[0,1,3],ndarrai:[0,1],new_max:1,new_min:1,none:[0,1],norm_fun:1,normal:3,note:3,number:[0,1,3],numpi:[0,1],obj:[],object:1,onc:3,option:[0,1,3],org:3,origin:0,otherwis:[0,1],our:3,outperform:3,overview:0,page:3,paint:3,palett:[0,1],paper:[0,3],paramet:[0,1,3],pass:[0,1,3],path:3,pdf:[0,3],perform:[0,1],photograph:3,pip3:3,pip:3,piti:3,plenopticam:3,png:3,point:[0,1],possibl:3,primari:0,primaryclass:[],principl:0,probabl:3,process:3,propos:[0,3],provid:[0,1,3],python3:3,python:3,ran:3,readm:2,red:[0,1],ref:[0,1,3],refer:[0,1],reinhard:[0,1,3],reinhard_match:1,reinhardmatch:[0,1],repo:3,repres:3,requir:[0,1,3],res:[0,1],resolut:[0,1],respect:3,result:[0,1,2],rgb2grai:[0,1],rgb:[0,1],root:3,run:3,same:[0,1],save_img_fil:[1,3],scalar:[0,1],scale:[0,1],schemat:0,scotland_hous:3,scotland_plain:3,see:[0,1],seen:0,select:3,select_fil:1,self:[0,1],sequenc:3,serv:[0,1],setup:3,shown:3,signatur:[0,1],similar:[0,1,3],smoothli:3,solut:[0,1,3],solv:[0,1],sourc:[0,1,3],space:[0,1],specifi:3,src:[0,1,3],src_path:3,standard:[0,1],stopmot:3,str:[0,1,3],string:[0,1],suppress_user_warn:1,system:[0,1,3],target:3,tau:0,test:3,thi:[0,1],thrown:[],tip:3,titl:[1,3],tkinter:1,tool:3,transact:3,transfer:[0,1,3],transfer_mat:[0,1],transfermvgd:[0,1],turkel:0,txt:3,type:[0,1,3],type_norm:1,uint16_norm:1,uint8_norm:[1,3],unequ:[0,1],unix:3,unsign:1,usag:2,user:2,using:3,valid:[0,1],validate_color_ch:[0,1],validate_img_dim:[0,1],valu:[1,3],variat:[0,1,3],via:3,volum:3,w2_dist:[0,1],w_1:3,wasserstein:[0,1,3],well:3,where:3,whether:[0,1],which:[0,1,3],win:3,window:3,wise:[0,1],work:0,wrong:[0,1],www:[0,3],year:3,you:3,your:3},titles:["API documentation","color_matcher package","color-matcher reference document","color-matcher"],titleterms:{"class":0,api:[0,3],author:3,baseclass:1,citat:3,cli:3,color:[2,3],color_match:1,command:[],content:1,descript:3,document:[0,2],experiment:3,hierarchi:0,hist_match:1,instal:3,io_handl:1,line:[],matcher:[2,3],modul:1,mvgd_matcher:1,normal:1,packag:1,refer:2,result:3,submodul:1,top_level:1,usag:3}}) \ No newline at end of file diff --git a/tests/unit_test.py b/tests/unit_test.py index 00146aa..a3484cf 100644 --- a/tests/unit_test.py +++ b/tests/unit_test.py @@ -73,10 +73,12 @@ def test_match_method(self, method=None, save=False): refer_val = self.avg_hist_dist(plain, refer) match_val = self.avg_hist_dist(plain, match) print('\nAvg. histogram distance of original %s vs. %s %s' % (round(refer_val, 3), method, round(match_val, 3))) - obj = ColorMatcher(src=house, ref=plain, method='mvgd') - mu_house, mu_plain, cov_house, cov_plain = obj.mu_r, obj.mu_z, obj.cov_r, obj.cov_z - obj = ColorMatcher(src=house, ref=match, method='mvgd') - mu_match, cov_match = obj.mu_z, obj.cov_z + cm = ColorMatcher(src=house, ref=plain, method='mvgd') + cm.init_vars() + mu_house, mu_plain, cov_house, cov_plain = cm.mu_r, cm.mu_z, cm.cov_r, cm.cov_z + cm = ColorMatcher(src=house, ref=match, method='mvgd') + cm.init_vars() + mu_match, cov_match = cm.mu_z, cm.cov_z refer_w2 = ColorMatcher.w2_dist(mu_a=mu_house, mu_b=mu_plain, cov_a=cov_house, cov_b=cov_plain) match_w2 = ColorMatcher.w2_dist(mu_a=mu_match, mu_b=mu_plain, cov_a=cov_match, cov_b=cov_plain) print('Wasserstein-2 distance of original %s vs. %s %s' % (round(refer_w2, 3), method, round(match_w2, 3))) @@ -170,26 +172,36 @@ def test_batch_process(self): self.assertEqual(True, ret) @idata(( - [np.ones([5, 5, 3, 1]), np.ones([5, 5, 3, 1]), False], - [np.ones([5, 5, 3]), np.ones([5, 5, 3]), True], - [np.ones([5, 5, 3]), np.ones([9, 9, 3]), True], - [np.ones([5, 5, 3]), np.ones([2, 2, 3]), True], - [np.ones([5, 5, 1]), np.ones([5, 5, 1]), False], - [np.ones([5, 5, 2]), np.ones([5, 5, 2]), False], - [np.ones([5, 5, 3]), np.ones([5, 5, 1]), False], - [np.ones([5, 5, 1]), np.ones([5, 5, 3]), False], - [np.ones([5, 5]), np.ones([5, 5]), False], - [np.ones([5]), np.ones([5]), False], - [np.ones([1]), np.ones([1]), False], - [np.ones([0]), np.ones([0]), False], - [None, None, False] + # gray scale images + [np.random.rand(5, 5, 1), np.random.rand(5, 5, 1), True], + [np.random.rand(5, 5), np.random.rand(5, 5), True], + # alpha channel images + [np.random.rand(5, 5, 4), np.random.rand(5, 5, 4), True], + [np.random.rand(5, 5, 3), np.random.rand(5, 5, 4), True], + [np.random.rand(5, 5, 4), np.random.rand(5, 5, 3), True], + # varying spatial resolutions + [np.random.rand(5, 5, 3), np.random.rand(5, 5, 3), True], + [np.random.rand(5, 5, 3), np.random.rand(9, 9, 3), True], + [np.random.rand(5, 5, 3), np.random.rand(2, 2, 3), True], + [np.random.rand(3, 3, 3), np.random.rand(5, 5, 3), True], + # different color channel number + [np.random.rand(5, 5, 3), np.random.rand(5, 5, 1), True], + [np.random.rand(5, 5, 1), np.random.rand(5, 5, 3), True], + [np.random.rand(5, 5, 2), np.random.rand(5, 5, 2), True], + # wrong dimensionality + [np.random.rand(5, 5, 3, 1), np.random.rand(5, 5, 3, 1), False], + [np.random.rand(5, 5, 5), np.random.rand(5, 5, 5), False], + [np.random.rand(5), np.random.rand(5), False], + [np.random.rand(1), np.random.rand(1), False], + [np.random.rand(0), np.random.rand(0), False], + [None, None, False] )) @unpack - def test_img_dims(self, src_img, img_ref, exp_val): + def test_img_dims(self, src_img, ref_img, exp_val): try: - obj = ColorMatcher(src=src_img, ref=img_ref) - res = obj.main() + cm = ColorMatcher() + res = cm.transfer(src=src_img, ref=ref_img, method='default') ret = res.mean().astype('bool') msg = '' except BaseException as e: