Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
source = ife
Empty file added .github/ISSUE_TEMPLATE.md
Empty file.
Empty file.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Python byte code
__pycache__/
*.py[cod]

# PyCharm
.idea/

.coverage

# Poetry build file
.dist/
dist/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json
31 changes: 31 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
language: python

python:
- "3.6"
- "3.6-dev"
- "3.7"
- "3.7-dev"

branches:
only:
- master
- develop

before_install:
- pip install --upgrade pip
- pip install black
- pip install poetry

before_script:
- flake8 ife --ignore E501
- black --diff ife
- mypy --strict-optional --disallow-untyped-defs --disallow-untyped-calls --ignore-missing-imports --no-incremental ife

install:
- poetry install -v
- pip install coveralls
script:
- coverage run -m unittest discover

after_success:
- coverallspoetrey
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2019, Collonville. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Collonville nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
Empty file added MANIFEST.in
Empty file.
105 changes: 103 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,103 @@
# ImageFeatureExtractor
Want a great image features??? Use IFE package!!!
# Image Feature Extractor(IFE)
[![Coverage Status](https://coveralls.io/repos/github/Collonville/ImageFeatureExtractor/badge.svg)](https://coveralls.io/github/Collonville/ImageFeatureExtractor)
[![Build Status](https://travis-ci.org/Collonville/ImageFeatureExtractor.svg?branch=develop)](https://travis-ci.org/Collonville/ImageFeatureExtractor)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/115c65043153459cbfc5026ea53be08d)](https://www.codacy.com/app/Collonville/ImageFeatureExtractor?utm_source=github.com&utm_medium=referral&utm_content=Collonville/ImageFeatureExtractor&utm_campaign=Badge_Grade)

## What is this?
`IFE` is a package to get an image feature more easily for Python. It contains many kinds of feature extract algorithms.

## 1. Features
### Color Moment
- Mean, Median, Variance, Skewness, Kurtosis of `RGB, HSV, HSL, CMY`

## 2. Examples
Import the basic image reader of IFE.
```python
from ife.io.io import ImageReader
```

### 2.1 Get Moment
Add a image file path to `read_from_single_file()`. This will return basic features class.

And now! You can get a RGB color moment feature from image!!
```python
>>> features = ImageReader.read_from_single_file("ife/data/small_rgb.jpg")
>>> features.moment()
array([[ 0.57745098, 0.52156863, 0.55980392],
[ 0.58823529, 0.48823529, 0.54901961],
[ 0.15220588, 0.12136101, 0.12380911],
[-0.01944425, 0.18416571, 0.04508015],
[-1.94196824, -1.55209335, -1.75586748]])
```

Also, you can get an `flatten vector, dictionary, or pandas`
```python
>>> features.moment(output_type="one_col")
array([ 0.57745098, 0.52156863, 0.55980392, 0.58823529, 0.48823529,
0.54901961, 0.15220588, 0.12136101, 0.12380911, -0.01944425,
0.18416571, 0.04508015, -1.94196824, -1.55209335, -1.75586748])

>>> features.moment(output_type="dict")
defaultdict(<class 'dict'>, {'mean': {'R': 0.57745098039215681, 'G': 0.52156862745098043, 'B': 0.55980392156862746}, 'median': {'R': 0.58823529411764708, 'G': 0.48823529411764705, 'B': 0.5490196078431373}, 'var': {'R': 0.15220588235294119, 'G': 0.12136101499423299, 'B': 0.12380911188004615}, 'skew': {'R': -0.019444250980856902, 'G': 0.18416570783012232, 'B': 0.045080152334687214}, 'kurtosis': {'R': -1.9419682406751135, 'G': -1.5520933544103905, 'B': -1.7558674751807395}})

>>> features.moment(output_type="pandas")
mean median var skew kurtosis
R 0.577451 0.588235 0.152206 -0.019444 -1.941968
G 0.521569 0.488235 0.121361 0.184166 -1.552093
B 0.559804 0.549020 0.123809 0.045080 -1.755867
```

> No! I want a HSV Color space feature :(

It can set another color space! Default will be RGB.
```python
>>> features.moment(output_type="one_col", color_space="CMY")
array([ 0.42254902, 0.47843137, 0.44019608, 0.41176471, 0.51176471,
0.45098039, 0.15220588, 0.12136101, 0.12380911, 0.01944425,
-0.18416571, -0.04508015, -1.94196824, -1.55209335, -1.75586748])

>>> features.moment(output_type="dict", color_space="HSL")
defaultdict(<class 'dict'>, {'mean': {'H': 0.50798329143793874, 'S': 0.52775831413836383, 'L': 0.61421568627450984}, 'median': {'H': 0.51915637553935423, 'S': 0.62898601603182969, 'L': 0.52156862745098043}, 'var': {'H': 0.13290200013401141, 'S': 0.10239897927552907, 'L': 0.051550124951941563}, 'skew': {'H': -0.078898095002588917, 'S': -0.83203104238315984, 'L': 1.0202366337483093}, 'kurtosis': {'H': -1.2599104562470791, 'S': -0.87111810912637022, 'L': -0.7502836585891588}})

>>> features.moment(output_type="pandas", color_space="HSV")
mean median var skew kurtosis
H 0.507983 0.519156 0.132902 -0.078898 -1.259910
S 0.595236 0.749543 0.122723 -1.028366 -0.768867
V 0.855882 0.864706 0.013867 -0.155656 -1.498179
```

## 3. Future work
### IO
- Read from URL links
- Read from Base64
- Sliding window
- Video files

### Color space
- CMYK
- CIE Lab
- XYZ

### Features
- Value normalize
- Average Gradient
- LBP
- Histogram
- Color harmony
- Entropy
- Brightness measure
- Contrast measure
- Saturation measure
- Colourfulness
- Naturalness
- Color fidelity metric
- Saliency map
- Fisher vector
- VGG16, 19 layer feature
- and more...

## 4. Author
@Collonville

## 5. Licence
BSD-3-Clause
Empty file added ife/__init__.py
Empty file.
Empty file added ife/data/__init__.py
Empty file.
Binary file added ife/data/small_rgb.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ife/data/strawberry.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions ife/features/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .features import moment

__all__ = ["moment"]
46 changes: 46 additions & 0 deletions ife/features/features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from collections import defaultdict
from typing import Optional, Union, List

import numpy as np
import pandas as pd

from ife.util.array import to_2d_array, convert_color_space_from_rgb
from . import moment


class Features:
np_image: np.ndarray

def __init__(self, np_image: np.ndarray) -> None:
self.np_image = np_image

def moment(
self,
methods: Optional[List[str]] = None,
color_space: Optional[str] = None,
output_type: Optional[str] = None,
) -> Union[np.ndarray, dict, pd.DataFrame]:
color_space = "RGB" if color_space is None else color_space

np_2d_image = convert_color_space_from_rgb(
to_2d_array(self.np_image), color_space
)

moments, method_list = moment.get_moments(methods, np_2d_image)

if output_type is None or output_type == "":
return moments
elif output_type == "one_col":
return moments.flatten()

dict_result = defaultdict(dict) # type: defaultdict
for method_idx, method in enumerate(method_list):
for index in range(moments.shape[1]):
dict_result[method][color_space[index]] = moments[method_idx, index]

if output_type == "dict":
return dict_result
elif output_type == "pandas":
return pd.DataFrame(dict_result)
else:
raise ValueError("Undefined output type.")
44 changes: 44 additions & 0 deletions ife/features/moment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import Optional, List, Tuple

import numpy as np
from scipy import stats


def get_moments(
methods: Optional[List[str]], np_2d_image: np.ndarray
) -> Tuple[np.ndarray, List[str]]:
if methods is None:
methods = ["mean", "median", "var", "skew", "kurtosis"]

moments = np.array([METHODS[method](np_2d_image) for method in methods])

return moments, methods


def mean(np_2d_image: np.ndarray) -> np.ndarray:
return np.mean(np_2d_image, axis=0)


def median(np_2d_image: np.ndarray) -> np.ndarray:
return np.median(np_2d_image, axis=0)


def var(np_2d_image: np.ndarray) -> np.ndarray:
return np.var(np_2d_image, axis=0)


def skew(np_2d_image: np.ndarray) -> np.ndarray:
return stats.skew(np_2d_image, axis=0)


def kurtosis(np_2d_image: np.ndarray) -> np.ndarray:
return stats.kurtosis(np_2d_image, axis=0)


METHODS = {
"mean": mean,
"median": median,
"var": var,
"skew": skew,
"kurtosis": kurtosis,
}
Empty file added ife/features/tests/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions ife/features/tests/test_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest
from collections import defaultdict

import numpy as np
import pandas as pd

from ife.io.io import ImageReader


class TestMomentFeatures(unittest.TestCase):
def test_output_type(self) -> None:
features = ImageReader.read_from_single_file("ife/data/small_rgb.jpg")

moment = features.moment()
self.assertIs(np.ndarray, type(moment))

moment = features.moment(output_type="")
self.assertIs(np.ndarray, type(moment))

moment = features.moment(output_type="one_col")
self.assertIs(np.ndarray, type(moment))
self.assertEqual(np.zeros(15).shape, moment.shape) # type: ignore

moment = features.moment(output_type="dict")
self.assertIs(defaultdict, type(moment))

moment = features.moment(output_type="pandas")
self.assertIs(pd.DataFrame, type(moment))
18 changes: 18 additions & 0 deletions ife/features/tests/test_moment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import unittest


class TestMoment(unittest.TestCase):
def test_mean(self) -> None:
pass

def test_median(self) -> None:
pass

def test_var(self) -> None:
pass

def test_skew(self) -> None:
pass

def test_kurtosis(self) -> None:
pass
Empty file added ife/io/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions ife/io/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os

import numpy as np
from PIL import Image

from ife.features.features import Features


class ImageReader:
@classmethod
def read_from_single_file(cls, file_path: str) -> Features:
if not isinstance(file_path, str):
raise ValueError("Set string or list file path.")

if not os.path.exists(file_path):
raise FileExistsError("File {} does not exist.".format(file_path))

np_image = np.array(Image.open(file_path).convert("RGB")) / 255.0

return Features(np_image)
Empty file added ife/io/tests/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions ife/io/tests/test_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import unittest

from ife.io.io import ImageReader, Features


class TestIO(unittest.TestCase):
def test_undefined_type(self) -> None:
with self.assertRaises(ValueError):
ImageReader().read_from_single_file(["", ""]) # type: ignore

def test_unreachable_file(self) -> None:
with self.assertRaises(FileExistsError):
ImageReader().read_from_single_file("../../data/none")

def test_reachable_file(self) -> None:
expected = Features
actual = ImageReader().read_from_single_file("ife/data/small_rgb.jpg")

self.assertIs(expected, type(actual))
Empty file added ife/util/__init__.py
Empty file.
Loading