# Path: creamas/rules/feature.py
# class Feature:
#     """Base feature class that is callable after initialization.
# 
#     Each feature takes as an input an artifact, and returns feature's
#     value for that artifact. If artifact type is not supported, feature's
#     evaluation should return ``None``.
# 
#     Returned feature values can be of any type, but rules
#     (:py:class:`~creamas.rules.rule.Rule`) should have appropriate mappers to
#     map possible feature values to the interval [-1, 1].
# 
#     Usage example:
# 
#     .. code-block:: python
# 
#         from myfeat import MyFeature
#         from myartifact import MyArtifact
#         myart = MyArtifact(*myparams)
#         myart.domain = mytype
#         f = MyFeature()
#         mytype in f.domains == True # True
#         ret = f(myart)
#         type(ret) == f.rtype # True
#     """
#     def __init__(self, name, domains, rtype):
#         """
#         :param str name:
#             Feature's name
# 
#         :param list domains:
#             A list of all artifact domains
#             (:py:attr:`~creamas.core.artifact.type`) that can be evaluated with
#             the feature.
# 
#         :param rtype:
#             Value type returned by this feature. This can be combined with
#             mappers accepting this type as the input parameter.
#         """
#         self.__domains = domains
#         self.__rtype = rtype
#         self.__name = name
# 
#     def __call__(self, artifact, mapper=None, **kwargs):
#         if artifact.domain not in self.__domains:
#             return None
#         if mapper is None:
#             return self.extract(artifact, **kwargs)
#         return mapper(self.extract(artifact, **kwargs))
# 
#     def __str__(self):
#         return self.__name
# 
#     @property
#     def name(self):
#         """Human readable name of the feature.
#         """
#         return self.__name
# 
#     @property
#     def domains(self):
#         """Set of acceptable artifact domains for this feature.
# 
#         When artifacts with other domains are used as parameters for
#         :meth:`extract`, the function should return ``None``.
#         """
#         return self.__domains
# 
#     @property
#     def rtype(self):
#         """Value type returned by this feature.
#         """
#         return self.__rtype
# 
#     def extract(self, artifact, **kwargs):
#         """Extract feature's value from an artifact.
# 
#         If artifact with a domain not in :attr:`domains` is used as a
#         parameter, the function should return ``None``.
# 
#         :returns: Value extracted from the artifact.
#         :rtype: :attr:`rtype` or None
#         """
#         return None


import numpy as np
import cv2
from creamas.rules.feature import Feature


class ImageBenfordsLawFeature(Feature):
    def __init__(self, ):
        """Feature that computes the Benford's Law for the image.

        .. seealso::
            `Benford's Law <https://en.wikipedia.org/wiki/Benford%27s_law>`_
        """
        super().__init__("image_Benfords_law", ['image'], float)
        # Histogram bin values for Benford
        self.b = [0.301, 0.176, 0.125, 0.097, 0.079, 0.067, 0.058, 0.051, 0.046]
        self.b_max = (1.0 - self.b[0]) + np.sum(self.b[1:])

    def extract(self, artifact):
        """Extract Benford's law from the image.
        """
        img = artifact.obj
        # Convert color image to black and white
        if len(img.shape) == 3:
            img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        hist = cv2.calcHist([img], [0], None, [9], [0, 256])
        # Sort, reverse and rescale to give the histogram a sum of 1.0
        h2 = np.sort(hist, 0)[::-1] * (1.0 / np.sum(hist))
        # Compute Benford's Law feature
        total = np.sum([np.abs(h2[i] - self.b[i]) for i in range(len(h2))])
        benford = float(1.0 - (total / self.b_max))
        return 0.0 if benford < 0 else benford



# Path: creamas/rules/feature.py
# class Feature:
#     """Base feature class that is callable after initialization.
# 
#     Each feature takes as an input an artifact, and returns feature's
#     value for that artifact. If artifact type is not supported, feature's
#     evaluation should return ``None``.
# 
#     Returned feature values can be of any type, but rules
#     (:py:class:`~creamas.rules.rule.Rule`) should have appropriate mappers to
#     map possible feature values to the interval [-1, 1].
# 
#     Usage example:
# 
#     .. code-block:: python
# 
#         from myfeat import MyFeature
#         from myartifact import MyArtifact
#         myart = MyArtifact(*myparams)
#         myart.domain = mytype
#         f = MyFeature()
#         mytype in f.domains == True # True
#         ret = f(myart)
#         type(ret) == f.rtype # True
#     """
#     def __init__(self, name, domains, rtype):
#         """
#         :param str name:
#             Feature's name
# 
#         :param list domains:
#             A list of all artifact domains
#             (:py:attr:`~creamas.core.artifact.type`) that can be evaluated with
#             the feature.
# 
#         :param rtype:
#             Value type returned by this feature. This can be combined with
#             mappers accepting this type as the input parameter.
#         """
#         self.__domains = domains
#         self.__rtype = rtype
#         self.__name = name
# 
#     def __call__(self, artifact, mapper=None, **kwargs):
#         if artifact.domain not in self.__domains:
#             return None
#         if mapper is None:
#             return self.extract(artifact, **kwargs)
#         return mapper(self.extract(artifact, **kwargs))
# 
#     def __str__(self):
#         return self.__name
# 
#     @property
#     def name(self):
#         """Human readable name of the feature.
#         """
#         return self.__name
# 
#     @property
#     def domains(self):
#         """Set of acceptable artifact domains for this feature.
# 
#         When artifacts with other domains are used as parameters for
#         :meth:`extract`, the function should return ``None``.
#         """
#         return self.__domains
# 
#     @property
#     def rtype(self):
#         """Value type returned by this feature.
#         """
#         return self.__rtype
# 
#     def extract(self, artifact, **kwargs):
#         """Extract feature's value from an artifact.
# 
#         If artifact with a domain not in :attr:`domains` is used as a
#         parameter, the function should return ``None``.
# 
#         :returns: Value extracted from the artifact.
#         :rtype: :attr:`rtype` or None
#         """
#         return None


# Path: creamas/domains/image/features.py
import numpy as np
import cv2
from creamas.rules.feature import Feature



class ImageBenfordsLawFeature(Feature):
    def __init__(self, ):
        """Feature that computes the Benford's Law for the image.

        .. seealso::
            `Benford's Law <https://en.wikipedia.org/wiki/Benford%27s_law>`_
        """
        super().__init__("image_Benfords_law", ['image'], float)
        # Histogram bin values for Benford
        self.b = [0.301, 0.176, 0.125, 0.097, 0.079, 0.067, 0.058, 0.051, 0.046]
        self.b_max = (1.0 - self.b[0]) + np.sum(self.b[1:])

    def extract(self, artifact):
        """Extract Benford's law from the image.
        """
        img = artifact.obj
        # Convert color image to black and white
        if len(img.shape) == 3:
            img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        hist = cv2.calcHist([img], [0], None, [9], [0, 256])
        # Sort, reverse and rescale to give the histogram a sum of 1.0
        h2 = np.sort(hist, 0)[::-1] * (1.0 / np.sum(hist))
        # Compute Benford's Law feature
        total = np.sum([np.abs(h2[i] - self.b[i]) for i in range(len(h2))])
        benford = float(1.0 - (total / self.b_max))
        return 0.0 if benford < 0 else benford



class ImageEntropyFeature(Feature):