diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
index ec70354..ae55fc2 100644
--- a/.github/workflows/python-publish.yml
+++ b/.github/workflows/python-publish.yml
@@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
@@ -33,7 +33,7 @@ jobs:
- name: Build package
run: python -m build
- name: Publish package
- uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+ uses: pypa/gh-action-pypi-publish@v1.8.11
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..6206838
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,25 @@
+# this file is *not* meant to cover or endorse the use of GitHub Actions, but rather to
+# help test this project
+
+name: Test
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ strategy:
+ matrix:
+ python: ['3.9', '3.10', '3.11', '3.12']
+ platform: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.platform }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python }}
+ - name: Install test dependencies
+ run: python -m pip install -U tox
+ - name: Test
+ run: python -m tox -e py
diff --git a/.gitignore b/.gitignore
index 13d936b..8f42c0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -140,4 +140,5 @@ dmypy.json
.pyre/
images/
-temp*
\ No newline at end of file
+temp*
+excluded/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..cefcd89
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,175 @@
+# Changelogs
+
+## v1.2.3
+
+
+ Click here to show more.
+
+In this version, we have made the following changes:
+
+1. 🧬 **CHANGE!**: We change the GUI mode to **optional**.
+ * Now, you can install the GUI mode by running:
+ * ```bash
+ pip install skin-tone-classifier[all] --upgrade
+ ```
+ * It will support both the **CLI** mode and the **GUI** mode.
+ * If you don't specify the `[all]` option, the app will install the CLI mode only.
+2. 🧬 **CHANGE!**: [For developer]. We base the project to `project.toml` instead of `setup.py`.
+
+
+
+
+## v1.2.0
+
+
+ Click here to show more.
+
+In this version, we have made the following changes:
+
+1. ✨ **NEW!**: We add a GUI version of `stone` for users who are not familiar with the command line interface.
+ * You can use the config GUI of `stone` to process the images.
+ * See more information at [here](#use-stone-in-a-gui).
+2. ✨ **NEW!**: We add new **patterns** in the `-l` (or `--labels`) option to set the skin tone labels.
+ * Now, you can use the following patterns to set the skin tone labels:
+ * **Default value**: the uppercase alphabet list leading by the image type (`C` for `color`; `B`
+ for `Black&White`).
+ * Specify the labels directly using _a space_ as delimiters, e.g., `-l A B C D E` or `-l 1 2 3 4 5`.
+ * Specify the range of labels using _a hyphen_ as delimiters, e.g.,
+ * `-l A-E` (equivalent to `-l A B C D E`);
+ * `-l A-E-2` (equivalent to `-l A C E`);
+ * `-l 1-5` (equivalent to `-l 1 2 3 4 5`);
+ * `-l 1-10-3` (equivalent to `-l 1 4 7 10`);
+ * **NB**: The number of skin tone labels should be equal to the number of colors in the palette.
+
+
+
+## v1.1.2
+
+
+ Click here to show more.
+
+In this version, we have made the following changes:
+
+1. 🐛 **FIX!**: We fixed a bug where the app will crash when using the `-bw` option.
+ Error message: `cannot reshape array of size 62500 into shape (3)`.
+2. 🐛 **FIX!**: We fixed a bug where the app may identify the image type as `color` when using the `-bw` option.
+
+
+
+## v1.1.1
+
+
+ Click here to show more.
+
+In this version, we have made the following changes:
+
+1. ✨ **NEW!**: We add the `-v` (or `--version`) option to show the version number.
+2. ✨ **NEW!**: We add the `-r` (or `--recursive`) option to **enable** recursive search for images.
+ * For example, `stone -i ./path/to/images/ -r` will search all images in the `./path/to/images/` directory **and its
+ subdirectories**.
+ * `stone -i ./path/to/images/` will only search images in the `./path/to/images/` directory.
+3. 🐛 **FIX!**: We fixed a bug where the app cannot correctly identify the current folder if `-i` option is not
+ specified.
+
+
+
+## v1.1.0
+
+
+ Click here to show more.
+
+In this version, we have made the following changes:
+
+1. ✨ **NEW!**: Now, `stone` can not only be run on **the command line**, but can also be **imported** into other
+ projects for use. Check [this](#9-used-as-a-library-by-importing-into-other-projects) for more details.
+ * We expose the `process` and `show` functions in the `stone` package.
+2. ✨ **NEW!**: We add `URL` support for the input images.
+ * Now, you can specify the input image as a URL, e.g., `https://example.com/images/pic.jpg`. Of course, you can mix
+ the URLs and local filenames.
+3. ✨ **NEW!**: We add **recursive search** support for the input images.
+ * Now, when you specify the input image as a directory, e.g., `./path/to/images/`.
+ The app will search all images in the directory recursively.
+4. 🧬 **CHANGE!**: We change the column header in `result.csv`:
+ * `prop` => `percent`
+ * `PERLA` => `tone label`
+5. 🐛 **FIX!**: We fixed a bug where the app would not correctly sort files that did not contain numbers in their
+ filenames.
+
+
+
+## v1.0.1
+
+
+ Click here to show more.
+
+1. 👋 **BYE**: We have removed the function to pop up a resulting window when processing a **single** image.
+
+ * It can raise an error when running the app in a **web browser** environment, e.g., Jupyter Notebook or Google
+ Colab.
+ * If you want to see the processed image, please use the `-d` option to store the report image in the `./debug`
+ folder.
+
+
+
+## v1.0.0
+
+
+ Click here to show more.
+
+🎉**We have officially released the 1.0.0 version of the library!** In this version, we have made the following changes:
+
+1. ✨ **NEW!**: We add the `threshold` parameter to control the minimum percentage of required face areas (Defaults to
+ 0.15).
+ * In previous versions, the library could incorrectly identify non-face areas as faces, such as shirts, collars,
+ necks, etc.
+ In order to improve its accuracy, the new version will further calculate the proportion of skin in the recognized
+ area
+ after recognizing the facial area. If it is less than the `threshold` value, the recognition area will be ignored.
+ (While it's still not perfect, it's an improvement over what it was before.)
+2. ✨ **NEW!**: Now, we will back up the previous results if it already exists.
+ The backup file will be named as `result_bak_.csv`.
+3. 🐛 **FIX!**: We fix the bug that the `image_type` option does not work in the previous version.
+4. 🐛 **FIX!**: We fix the bug that the library will create an empty `log` folder when checking the help information by
+ running `stone -h`.
+
+
+
+## v0.2.0
+
+
+ Click here to show more.
+
+In this version, we have made the following changes:
+
+1. ✨ **NEW!**: Now we support skin tone classification for **black and white** images.
+ * In this case, the app will use different skin tone palettes for color images and black/white images.
+ * We use a new parameter `-t` or `--image_type` to specify the type of the input image.
+ It can be `color`, `bw` or `auto`(default).
+ `auto` will let the app automatically detect whether the input is color or black/white image.
+ * We use a new parameter `-bw` or `--black_white` to specify whether to convert the input to black/white image.
+ If so, the app will convert the input to black/white image and then classify the skin tones based on the
+ black/white palette.
+
+ For example:
+
+
+
+
+
+2. ✨ **NEW!**: Now we support **multiprocessing** for processing the images. It will largely speed up the processing.
+ * The number of processes is set to the number of CPU cores by default.
+ * You can specify the number of processes by `--n_workers` parameter.
+3. 🧬 **CHANGE!**: We add more details in the report image to facilitate the debugging, as shown above.
+ * We add the face id in the report image.
+ * We add the effective face or skin area in the report image. In this case, the other areas are blurred.
+4. 🧬 **CHANGE!**: Now, we save the report images into different folders based on their `image_type` (color or
+ black/white) and the number of detected faces.
+ * For example, if the input image is **color** and there are **2 faces** detected, the report image will be saved
+ in `./debug/color/faces_2/` folder.
+ * If the input image is **black/white** and no face has been detected, the report image will be saved
+ in `./debug/bw/faces_0/` folder.
+ * You can easily to tune the parameters and rerun the app based on the report images in the corresponding folder.
+5. 🐛 **FIX!**: We fix the bug that the app will crash when the input image has dimensionality errors.
+ * Now, the app won't crash and will report the error message in `./result.csv`.
+
+
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index bda61f6..51295e0 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,3 +3,8 @@ include LICENSE
include MANIFEST.in
recursive-include src/stone/ui *
recursive-exclude * *.py[co]
+exclude .idea/*
+exclude CHANGELOG.md
+exclude _config.yml
+exclude docs/*
+exclude requirements.txt
diff --git a/README.md b/README.md
index fd955cd..5dd1fe5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
+
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/skin-tone-classifier)
@@ -18,6 +19,8 @@ The detected skin tones are then classified into the specified color categories.
The library finally generates results to report the detected faces (if any),
dominant skin tones and color categories.
+Check out the [Changelog](https://github.com/ChenglongMa/SkinToneClassifier/blob/main/CHANGELOG.md) for the latest updates.
+
*If you find this project helpful, please
consider [giving it a star](https://github.com/ChenglongMa/SkinToneClassifier)* ⭐. *It would be a great encouragement
for me!*
@@ -36,6 +39,8 @@ for me!*
- [4. Use `stone` in Python scripts](#4-use-stone-in-python-scripts)
- [Installation](#installation)
- [Install from pip](#install-from-pip)
+ - [Install the CLI mode only](#install-the-cli-mode-only)
+ - [Install the CLI mode and the GUI mode](#install-the-cli-mode-and-the-gui-mode)
- [Install from source](#install-from-source)
- [HOW TO USE](#how-to-use)
- [Quick Start](#quick-start)
@@ -54,15 +59,6 @@ for me!*
- [8. Tune parameters of face detection](#8-tune-parameters-of-face-detection)
- [9. Multiprocessing settings](#9-multiprocessing-settings)
- [10. Used as a library by importing into other projects](#10-used-as-a-library-by-importing-into-other-projects)
-- [Changelogs](#changelogs)
- - [v1.2.0](#v120)
- - [v1.1.3](#v113)
- - [v1.1.2](#v112)
- - [v1.1.1](#v111)
- - [v1.1.0](#v110)
- - [v1.0.1](#v101)
- - [v1.0.0](#v100)
- - [v0.2.0](#v020)
- [Citation](#citation)
- [Contributing](#contributing)
- [Disclaimer](#disclaimer)
@@ -110,12 +106,30 @@ _More videos are coming soon..._
# Installation
+> [!TIP]
+>
+> Since v1.2.3, we have made the GUI mode **optional**.
+>
+
+
## Install from pip
+### Install the CLI mode only
+
```shell
pip install skin-tone-classifier --upgrade
```
+It is useful for users who want to use this library in non-GUI environments, e.g., servers or [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1k-cryEZ9PInJRXWIi17ib66ufYV2Ikwe?usp=sharing).
+
+### Install the CLI mode and the GUI mode
+
+```shell
+pip install skin-tone-classifier[all] --upgrade
+```
+
+It is useful for users who are not familiar with the command line interface and want to use the GUI mode.
+
## Install from source
```shell
@@ -141,7 +155,6 @@ pip install -e . --verbose
> You can combine the following documents, [the video tutorials above](#video-tutorials)
> and the running examples [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1k-cryEZ9PInJRXWIi17ib66ufYV2Ikwe?usp=sharing)
> to understand the usage of this library more intuitively.
->
>
## Quick Start
@@ -166,12 +179,21 @@ Hopefully, this can make it easier for you to use `stone` 🍻!
> [!TIP]
>
-> It is recommended to install v1.2.1, which supports Python 3.9+.
+> 1. It is recommended to install v1.2.3+, which supports Python 3.9+.
>
-> If you have installed v1.2.0, please upgrade to v1.2.1 by running
+> If you have installed v1.2.0, please upgrade to v1.2.3+ by running
>
-> `pip install skin-tone-classifier --upgrade`
+> `pip install skin-tone-classifier[all] --upgrade`
+>
+> 2. If you encounter the following problem:
+> > This program needs access to the screen. Please run with a Framework
+> > build of python, and only when you are logged in on the main display
+> > of your Mac.
>
+> Please launch the GUI by running `pythonw -m stone` in the terminal.
+> References:
+> * [stackoverflow](https://stackoverflow.com/a/52732858/8860079)
+> * [python-using-mac](https://docs.python.org/3/using/mac.html)
### Use `stone` in command line interface (CLI)
@@ -569,172 +591,6 @@ The `result_json` will be like:
}
```
-# Changelogs
-
-## v1.2.0
-
-
- Click here to show more.
-
-In this version, we have made the following changes:
-
-1. ✨ **NEW!**: We add a GUI version of `stone` for users who are not familiar with the command line interface.
- * You can use the config GUI of `stone` to process the images.
- * See more information at [here](#use-stone-in-a-gui).
-
-
-
-## v1.1.3
-
-
- Click here to show more.
-
-In this version, we have made the following changes:
-
-1. ✨ **NEW!**: We add new **patterns** in the `-l` (or `--labels`) option to set the skin tone labels.
- * Now, you can use the following patterns to set the skin tone labels:
- * **Default value**: the uppercase alphabet list leading by the image type (`C` for `color`; `B`
- for `Black&White`).
- * Specify the labels directly using _a space_ as delimiters, e.g., `-l A B C D E` or `-l 1 2 3 4 5`.
- * Specify the range of labels using _a hyphen_ as delimiters, e.g.,
- * `-l A-E` (equivalent to `-l A B C D E`);
- * `-l A-E-2` (equivalent to `-l A C E`);
- * `-l 1-5` (equivalent to `-l 1 2 3 4 5`);
- * `-l 1-10-3` (equivalent to `-l 1 4 7 10`);
- * **NB**: The number of skin tone labels should be equal to the number of colors in the palette.
-
-
-
-## v1.1.2
-
-
- Click here to show more.
-
-In this version, we have made the following changes:
-
-1. 🐛 **FIX!**: We fixed a bug where the app will crash when using the `-bw` option.
- Error message: `cannot reshape array of size 62500 into shape (3)`.
-2. 🐛 **FIX!**: We fixed a bug where the app may identify the image type as `color` when using the `-bw` option.
-
-
-
-## v1.1.1
-
-
- Click here to show more.
-
-In this version, we have made the following changes:
-
-1. ✨ **NEW!**: We add the `-v` (or `--version`) option to show the version number.
-2. ✨ **NEW!**: We add the `-r` (or `--recursive`) option to **enable** recursive search for images.
- * For example, `stone -i ./path/to/images/ -r` will search all images in the `./path/to/images/` directory **and its
- subdirectories**.
- * `stone -i ./path/to/images/` will only search images in the `./path/to/images/` directory.
-3. 🐛 **FIX!**: We fixed a bug where the app cannot correctly identify the current folder if `-i` option is not
- specified.
-
-
-
-## v1.1.0
-
-
- Click here to show more.
-
-In this version, we have made the following changes:
-
-1. ✨ **NEW!**: Now, `stone` can not only be run on **the command line**, but can also be **imported** into other
- projects for use. Check [this](#9-used-as-a-library-by-importing-into-other-projects) for more details.
- * We expose the `process` and `show` functions in the `stone` package.
-2. ✨ **NEW!**: We add `URL` support for the input images.
- * Now, you can specify the input image as a URL, e.g., `https://example.com/images/pic.jpg`. Of course, you can mix
- the URLs and local filenames.
-3. ✨ **NEW!**: We add **recursive search** support for the input images.
- * Now, when you specify the input image as a directory, e.g., `./path/to/images/`.
- The app will search all images in the directory recursively.
-4. 🧬 **CHANGE!**: We change the column header in `result.csv`:
- * `prop` => `percent`
- * `PERLA` => `tone label`
-5. 🐛 **FIX!**: We fixed a bug where the app would not correctly sort files that did not contain numbers in their
- filenames.
-
-
-
-## v1.0.1
-
-
- Click here to show more.
-
-1. 👋 **BYE**: We have removed the function to pop up a resulting window when processing a **single** image.
-
- * It can raise an error when running the app in a **web browser** environment, e.g., Jupyter Notebook or Google
- Colab.
- * If you want to see the processed image, please use the `-d` option to store the report image in the `./debug`
- folder.
-
-
-
-## v1.0.0
-
-
- Click here to show more.
-
-🎉**We have officially released the 1.0.0 version of the library!** In this version, we have made the following changes:
-
-1. ✨ **NEW!**: We add the `threshold` parameter to control the minimum percentage of required face areas (Defaults to
- 0.15).
- * In previous versions, the library could incorrectly identify non-face areas as faces, such as shirts, collars,
- necks, etc.
- In order to improve its accuracy, the new version will further calculate the proportion of skin in the recognized
- area
- after recognizing the facial area. If it is less than the `threshold` value, the recognition area will be ignored.
- (While it's still not perfect, it's an improvement over what it was before.)
-2. ✨ **NEW!**: Now, we will back up the previous results if it already exists.
- The backup file will be named as `result_bak_.csv`.
-3. 🐛 **FIX!**: We fix the bug that the `image_type` option does not work in the previous version.
-4. 🐛 **FIX!**: We fix the bug that the library will create an empty `log` folder when checking the help information by
- running `stone -h`.
-
-
-
-## v0.2.0
-
-
- Click here to show more.
-
-In this version, we have made the following changes:
-
-1. ✨ **NEW!**: Now we support skin tone classification for **black and white** images.
- * In this case, the app will use different skin tone palettes for color images and black/white images.
- * We use a new parameter `-t` or `--image_type` to specify the type of the input image.
- It can be `color`, `bw` or `auto`(default).
- `auto` will let the app automatically detect whether the input is color or black/white image.
- * We use a new parameter `-bw` or `--black_white` to specify whether to convert the input to black/white image.
- If so, the app will convert the input to black/white image and then classify the skin tones based on the
- black/white palette.
-
- For example:
-
-
-
-
-
-2. ✨ **NEW!**: Now we support **multiprocessing** for processing the images. It will largely speed up the processing.
- * The number of processes is set to the number of CPU cores by default.
- * You can specify the number of processes by `--n_workers` parameter.
-3. 🧬 **CHANGE!**: We add more details in the report image to facilitate the debugging, as shown above.
- * We add the face id in the report image.
- * We add the effective face or skin area in the report image. In this case, the other areas are blurred.
-4. 🧬 **CHANGE!**: Now, we save the report images into different folders based on their `image_type` (color or
- black/white) and the number of detected faces.
- * For example, if the input image is **color** and there are **2 faces** detected, the report image will be saved
- in `./debug/color/faces_2/` folder.
- * If the input image is **black/white** and no face has been detected, the report image will be saved
- in `./debug/bw/faces_0/` folder.
- * You can easily to tune the parameters and rerun the app based on the report images in the corresponding folder.
-5. 🐛 **FIX!**: We fix the bug that the app will crash when the input image has dimensionality errors.
- * Now, the app won't crash and will report the error message in `./result.csv`.
-
-
# Citation
@@ -782,9 +638,8 @@ If you are interested in our work, please cite:
# Disclaimer
-The images used in this project are from [Flickr-Faces-HQ Dataset (FFHQ)](https://github.com/NVlabs/ffhq-dataset), which
-is licensed under
-the [Creative Commons BY-NC-SA 4.0 license](https://github.com/NVlabs/ffhq-dataset/blob/master/LICENSE.txt)
+The images used in this project are from [Flickr-Faces-HQ Dataset (FFHQ)](https://github.com/NVlabs/ffhq-dataset),
+which is licensed under the [Creative Commons BY-NC-SA 4.0 license](https://github.com/NVlabs/ffhq-dataset/blob/master/LICENSE.txt).
-Thank you for considering contributing to **SkinToneClassifier**. We value your input and look forward to collaborating
-with you!
+Thank you for considering contributing to **SkinToneClassifier**.
+We value your input and look forward to collaborating with you!
diff --git a/docs/illustration.svg b/docs/illustration.svg
new file mode 100644
index 0000000..841b5ac
--- /dev/null
+++ b/docs/illustration.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..427ecf3
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,179 @@
+[project]
+# This is the name of your project. The first time you publish this
+# package, this name will be registered for you. It will determine how
+# users can install this project, e.g.:
+#
+# $ pip install sampleproject
+#
+# And where it will live on PyPI: https://pypi.org/project/sampleproject/
+#
+# There are some restrictions on what makes a valid project name
+# specification here:
+# https://packaging.python.org/specifications/core-metadata/#name
+name = "skin-tone-classifier" # Required
+
+dynamic = ["version"]
+# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata
+# "dependencies", "optional-dependencies" are BETA features currently
+#dynamic = ["version", "dependencies", "optional-dependencies"]
+
+# Versions should comply with PEP 440:
+# https://www.python.org/dev/peps/pep-0440/
+#
+# For a discussion on single-sourcing the version, see
+# https://packaging.python.org/guides/single-sourcing-package-version/
+#https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers
+#version = "1.2.3" # Required
+
+# This is a one-line description or tagline of what your project does. This
+# corresponds to the "Summary" metadata field:
+# https://packaging.python.org/specifications/core-metadata/#summary
+description = "An easy-to-use library for skin tone classification" # Optional
+
+# This is an optional longer description of your project that represents
+# the body of text which users will see when they visit PyPI.
+#
+# Often, this is the same as your README, so you can just read it in from
+# that file directly (as we have already done above)
+#
+# This field corresponds to the "Description" metadata field:
+# https://packaging.python.org/specifications/core-metadata/#description-optional
+readme = "README.md" # Optional
+
+# Specify which Python versions you support. In contrast to the
+# 'Programming Language' classifiers above, 'pip install' will check this
+# and refuse to install the project if the version does not match. See
+# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
+requires-python = ">=3.9"
+
+# This is either text indicating the license for the distribution, or a file
+# that contains the license
+# https://packaging.python.org/en/latest/specifications/core-metadata/#license
+#license = { file = "LICENSE" }
+
+# This field adds keywords for your project which will appear on the
+# project page. What does your project relate to?
+#
+# Note that this is a list of additional keywords, separated
+# by commas, to be used to assist searching for the distribution in a
+# larger catalog.
+keywords = ["skin tone", "image recognition", "face detection", "skin detection", "image segmentation"] # Optional
+
+# This should be your name or the name of the organization who originally
+# authored the project, and a valid email address corresponding to the name
+# listed.
+authors = [
+ { name = "Chenglong Ma", email = "chenglong.m@outlook.com" } # Optional
+]
+
+# This should be your name or the names of the organization who currently
+# maintains the project, and a valid email address corresponding to the name
+# listed.
+maintainers = [
+ { name = "Chenglong Ma", email = "chenglong.m@outlook.com" } # Optional
+]
+
+# Classifiers help users find your project by categorizing it.
+#
+# For a list of valid classifiers, see https://pypi.org/classifiers/
+classifiers = [# Optional
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: End Users/Desktop",
+ "Intended Audience :: Information Technology",
+ "Intended Audience :: Science/Research",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+ "Operating System :: OS Independent",
+ "Topic :: Scientific/Engineering :: Image Recognition",
+ "Topic :: Scientific/Engineering :: Image Processing",
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
+ "Topic :: Scientific/Engineering :: Information Analysis",
+ "Topic :: Scientific/Engineering :: Visualization",
+ "Topic :: Sociology",
+ "Topic :: Multimedia :: Graphics",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Terminals",
+ "Environment :: Console",
+ "Environment :: Web Environment",
+ "Environment :: Win32 (MS Windows)",
+ "Environment :: MacOS X",
+ "Environment :: Other Environment",
+]
+
+# This field lists other packages that your project depends on to run.
+# Any package you put here will be installed by pip when your project is
+# installed, so they must be valid existing projects.
+#
+# For an analysis of this field vs pip's requirements files see:
+# https://packaging.python.org/discussions/install-requires-vs-requirements/
+dependencies = [# Optional
+ "opencv-python>=4.9.0.80",
+ "numpy>=1.21.5",
+ "colormath>=3.0.0",
+ "tqdm>=4.64.0",
+ "colorama>=0.4.6",
+ "packaging>=23.1",
+ "requests>=2.31.0",
+]
+
+# List additional groups of dependencies here (e.g. development
+# dependencies). Users will be able to install these using the "extras"
+# syntax, for example:
+#
+# $ pip install sampleproject[dev]
+#
+# Similar to `dependencies` above, these must be valid existing
+# projects.
+[project.optional-dependencies] # Optional
+all = [
+ "gooey>=1.0.8.1",
+ "re-wx==0.0.10",
+ "colored==1.3.93",
+]
+
+# List URLs that are relevant to your project
+#
+# This field corresponds to the "Project-URL" and "Home-Page" metadata fields:
+# https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use
+# https://packaging.python.org/specifications/core-metadata/#home-page-optional
+#
+# Examples listed include a pattern for specifying where the package tracks
+# issues, where the source is hosted, where to say thanks to the package
+# maintainers, and where to support the project financially. The key is
+# what's used to render the link text on PyPI.
+[project.urls] # Optional
+"Homepage" = "https://chenglongma.com/SkinToneClassifier/"
+"Bug Reports" = "https://github.com/ChenglongMa/SkinToneClassifier/issues"
+"Funding" = "https://github.com/sponsors/ChenglongMa"
+"Say Thanks!" = "https://saythanks.io/to/ChenglongMa"
+"Repository" = "https://github.com/ChenglongMa/SkinToneClassifier/"
+Changelog = "https://github.com/ChenglongMa/SkinToneClassifier/blob/main/CHANGELOD.md"
+
+# The following would provide a command line executable called `sample`
+# which executes the function `main` from this package when invoked.
+[project.scripts] # Optional
+stone = "stone.__main__:main"
+
+[project.gui-scripts]
+stone-gui = "stone.__main__:main"
+
+# This is configuration specific to the `setuptools` build backend.
+# If you are using a different build backend, you will need to change this.
+[tool.setuptools]
+# If there are data files included in your packages that need to be
+# installed, specify them here.
+package-dir = { "" = "src" }
+license-files = ["LICENSE"]
+
+[tool.setuptools.dynamic]
+version = { attr = "stone.package.__version__" }
+
+[build-system]
+# These are the assumed default build requirements from pip:
+# https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support
+requires = ["setuptools>=66.1.0", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/requirements.txt b/requirements.txt
index e1afbce..f715ad8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,7 @@ setuptools>=65.6.3
colorama>=0.4.6
packaging>=23.1
requests>=2.31.0
+# For GUI
gooey>=1.0.8.1
colored==1.3.93
re-wx==0.0.10
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 8af290b..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,3 +0,0 @@
-[metadata]
-license_files = LICENSE
-description_file = README.md
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 4bdacab..0000000
--- a/setup.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from setuptools import setup
-from setuptools import find_packages
-
-PACKAGE = {}
-with open("src/stone/package.py", "r", encoding="utf-8") as fp:
- exec(fp.read(), PACKAGE)
-
-with open("README.md", "r", encoding="utf-8") as f:
- LONG_DESCRIPTION = f.read()
-
-setup(
- name=PACKAGE["__package_name__"],
- version=PACKAGE["__version__"],
- description=PACKAGE["__description__"],
- long_description=LONG_DESCRIPTION,
- long_description_content_type="text/markdown",
- packages=find_packages("src"),
- package_dir={"": "src"},
- include_package_data=True,
- zip_safe=False,
- author=PACKAGE["__author__"],
- author_email=PACKAGE["__author_email__"],
- keywords="skin-tone image-recognition face-detection",
- url=PACKAGE["__url__"],
- project_urls={
- "Documentation": PACKAGE["__code__"],
- "Code": PACKAGE["__code__"],
- "Issue tracker": PACKAGE["__issues__"],
- },
- entry_points={
- "console_scripts": ["stone = stone.__main__:main"],
- },
- python_requires=">=3.9",
- install_requires=[
- "opencv-python>=4.9.0.80",
- "numpy>=1.21.5",
- "colormath>=3.0.0",
- "tqdm>=4.64.0",
- "colorama>=0.4.6",
- "packaging>=23.1",
- "requests>=2.31.0",
- "gooey>=1.0.8.1",
- "re-wx==0.0.10",
- "colored==1.3.93",
- ],
- classifiers=[
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
- "Operating System :: OS Independent",
- "Topic :: Scientific/Engineering :: Image Recognition",
- "Topic :: Scientific/Engineering :: Image Processing",
- "Environment :: Console",
- "Environment :: Web Environment",
- "Environment :: Win32 (MS Windows)",
- "Environment :: MacOS X",
- "Environment :: Other Environment",
- ],
-)
diff --git a/src/stone/__main__.py b/src/stone/__main__.py
index 0a9fd7b..d7720de 100644
--- a/src/stone/__main__.py
+++ b/src/stone/__main__.py
@@ -226,7 +226,11 @@ def write_to_csv(row: list):
sys.argv.remove("--gui") if "--gui" in sys.argv else None
if not use_cli and "--ignore-gooey" not in sys.argv:
- from gooey import Gooey
+ try:
+ from gooey import Gooey
+ except ImportError:
+ # If gooey is not installed, use a dummy decorator
+ from stone.utils import Gooey
from importlib.resources import files
diff --git a/src/stone/package.py b/src/stone/package.py
index 78ab4ae..2df6915 100644
--- a/src/stone/package.py
+++ b/src/stone/package.py
@@ -1,4 +1,4 @@
-__version__ = "1.2.2"
+__version__ = "1.2.3"
__package_name__ = "skin-tone-classifier"
__app_name__ = "Skin Tone Classifier"
__description__ = "An easy-to-use library for skin tone classification"
diff --git a/src/stone/utils.py b/src/stone/utils.py
index 478f7d7..cc18635 100644
--- a/src/stone/utils.py
+++ b/src/stone/utils.py
@@ -22,8 +22,22 @@ class ArgumentError(ValueError):
pass
-# @functools.cache # Python 3.9+
-@functools.lru_cache(maxsize=128) # Python 3.2+
+def Gooey(*args, **kwargs):
+ """
+ Dummy decorator for Gooey.
+ Used in CLI mode to avoid the import error when the Gooey package is not installed.
+ :param args:
+ :param kwargs:
+ :return:
+ """
+
+ def inner(func):
+ return func
+
+ return inner
+
+
+@functools.cache
def alphabet_id(n):
letters = string.ascii_uppercase
n_letters = len(letters)
@@ -104,161 +118,36 @@ def is_debugging():
return gettrace is not None and gettrace()
-def build_arguments_deprecated():
- print("Using CLI mode.")
- LOG.info("LOG: Using CLI mode.")
- parser = argparse.ArgumentParser(
- description=f"{__app_name__} v{__version__}",
- formatter_class=argparse.RawTextHelpFormatter,
- )
- parser.add_argument(
- "-i",
- "--images",
- nargs="+",
- default=["./"],
- metavar="IMAGE FILENAME",
- help="Image filename(s) or URLs to process;\n"
- 'Supports multiple values separated by space, e.g., "a.jpg b.png";\n'
- 'Supports directory or file name(s), e.g., "./path/to/images/ a.jpg";\n'
- 'Supports URL(s), e.g., "https://example.com/images/pic.jpg" since v1.1.0+.\n'
- "The app will search all images in current directory in default.",
- )
- parser.add_argument(
- "-r",
- "--recursive",
- action="store_true",
- help="Whether to search images recursively in the specified directory.",
- )
-
- parser.add_argument(
- "-t",
- "--image_type",
- default="auto",
- metavar="IMAGE TYPE",
- help="Specify whether the input image(s) is/are colored or black/white.\n"
- 'Valid choices are: "auto", "color" or "bw",\n'
- 'Defaults to "auto", which will be detected automatically.',
- choices=["auto", "color", "bw"],
- )
- parser.add_argument(
- "-p",
- "--palette",
- nargs="+",
- metavar="PALETTE",
- help="Skin tone palette;\n"
- 'Supports RGB hex value leading by "#" or RGB values separated by comma(,),\n'
- 'E.g., "-p #373028 #422811" or "-p 255,255,255 100,100,100"',
- )
- parser.add_argument(
- "-l",
- "--labels",
- nargs="+",
- metavar="LABELS",
- help="Skin tone labels;\n"
- "Default values are the uppercase alphabet list leading by the image type ('C' for 'color'; 'B' for 'Black&White'), "
- "e.g., ['CA', 'CB', ..., 'CZ'] or ['BA', 'BB', ..., 'BZ'].\n"
- "Since v1.2.0, supports range of labels, e.g., 'A-Z' or '1-10'.\n"
- "Refer to https://github.com/ChenglongMa/SkinToneClassifier for more details.",
- )
- parser.add_argument(
- "-d",
- "--debug",
- action="store_true",
- help="Whether to generate report images, used for debugging and verification."
- "The report images will be saved in the './debug' directory.",
- )
- parser.add_argument(
- "-bw",
- "--black_white",
- action="store_true",
- help="Whether to convert the input to black/white image(s).\n"
- "If true, the app will use the black/white palette to classify the image.",
- )
- parser.add_argument(
- "-o",
- "--output",
- default="./",
- metavar="DIRECTORY",
- help="The path of output file, defaults to current directory.",
- )
- parser.add_argument(
- "--n_workers",
- type=int,
- metavar="WORKERS",
- help="The number of workers to process the images, defaults to the number of CPUs in the system.",
- default=0,
- )
-
- parser.add_argument(
- "--n_colors",
- type=int,
- metavar="COLORS",
- help="CONFIG: the number of dominant colors to be extracted, defaults to 2.",
- default=2,
- )
- parser.add_argument(
- "--new_width",
- type=int,
- metavar="WIDTH",
- help="CONFIG: resize the images with the specified width. Negative value will be ignored, defaults to 250.",
- default=250,
- )
-
- # For the next parameters, refer to https://stackoverflow.com/a/20805153/8860079
- parser.add_argument(
- "--scale",
- type=float,
- metavar="SCALE",
- help="CONFIG: how much the image size is reduced at each image scale, defaults to 1.1",
- default=1.1,
- )
- parser.add_argument(
- "--min_nbrs",
- type=int,
- metavar="NEIGHBORS",
- help="CONFIG: how many neighbors each candidate rectangle should have to retain it.\n"
- "Higher value results in less detections but with higher quality, defaults to 5.",
- default=5,
- )
- parser.add_argument(
- "--min_size",
- type=int,
- nargs="+",
- metavar=("WIDTH", "HEIGHT"),
- help='CONFIG: minimum possible face size. Faces smaller than that are ignored, defaults to "90 90".',
- default=(90, 90),
- )
- parser.add_argument(
- "--threshold",
- type=float,
- metavar="THRESHOLD",
- help="CONFIG: what percentage of the skin area is required to identify the face, defaults to 0.15.",
- default=0.15,
- )
- parser.add_argument(
- "-v",
- "--version",
- action="version",
- version=f"%(prog)s {__version__}",
- help="Show the version number and exit.",
- )
+def build_arguments():
+ try:
+ from gooey import GooeyParser
- return parser.parse_args()
+ in_gui = True
+ except ImportError:
+ from argparse import ArgumentParser as GooeyParser
+ in_gui = False
-def build_arguments():
- from gooey import GooeyParser
-
+ kwargs = dict(formatter_class=argparse.RawTextHelpFormatter) if not in_gui else {}
parser = GooeyParser(
description=__description__,
+ **kwargs,
+ )
+ kwargs = (
+ {
+ "gooey_options": {"show_border": False, "columns": 1},
+ }
+ if in_gui
+ else {}
)
files = parser.add_argument_group(
"Images to process",
"The locations of images to process, which can be directories, files, or URLs.\n"
"Multiple values are separated by space;\n"
'You can mix folders, filenames and web links together, e.g., "/path/to/dir1 /path/to/pic.jpg https://example.com/pic.png".\n',
- gooey_options={"show_border": False, "columns": 1},
+ **kwargs,
)
+ kwargs = {"gooey_options": {"visible": False}} if in_gui else {}
files.add_argument(
"-i",
@@ -266,80 +155,82 @@ def build_arguments():
nargs="+",
default=[os.getcwd()],
metavar="Image Filenames",
- help="Image filename(s), Directories or URLs to process.",
- gooey_options={"visible": False},
- )
- files.add_argument(
- "--image_dirs",
- nargs="+",
- metavar="Image Directories",
- widget="DirChooser",
- # widget="MultiDirChooser", # fixme: enable this widget when issues are fixed
- gooey_options={
- "message": "Select directories to process",
- "initial_value": os.getcwd(),
- "default_path": os.getcwd(),
- "placeholder": "e.g., /path/to/dir1 /path/to/dir2",
- },
- )
+ help="Image filename(s), Directories or URLs to process. Separated by space.",
+ **kwargs,
+ )
+ if in_gui:
+ files.add_argument(
+ "--image_dirs",
+ nargs="+",
+ metavar="Image Directories",
+ widget="DirChooser",
+ # widget="MultiDirChooser", # fixme: enable this widget when issues are fixed
+ gooey_options={
+ "message": "Select directories to process",
+ "initial_value": os.getcwd(),
+ "default_path": os.getcwd(),
+ "placeholder": "e.g., /path/to/dir1 /path/to/dir2",
+ },
+ )
+ kwargs = dict(metavar="Recursive Search") if in_gui else {}
files.add_argument(
"-r",
"--recursive",
- metavar="Recursive Search",
action="store_true",
help="Search images recursively in the specified directory.",
- )
-
- files.add_argument(
- "--image_files",
- nargs="+",
- metavar="Image Filenames",
- help="Add individual image file(s)",
- widget="MultiFileChooser",
- gooey_options={
- "wildcard": "All images|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tif;*.webp|"
- "JPG (*.jpg)|*.jpg|"
- "JPEG (*.jpeg)|*.jpeg|"
- "PNG (*.png)|*.png|"
- "BMP (*.bmp)|*.bmp|"
- "GIF (*.gif)|*.gif|"
- "TIFF (*.tif)|*.tif|"
- "WEBP (*.webp)|*.webp|"
- "All files (*.*)|*.*",
- "message": "Select the image file(s) to process",
- "default_dir": os.getcwd(),
- "full_width": False,
- "placeholder": "e.g., a.jpg b.png",
- },
- )
+ **kwargs,
+ )
+ if in_gui:
+ files.add_argument(
+ "--image_files",
+ nargs="+",
+ metavar="Image Filenames",
+ help="Add individual image file(s)",
+ widget="MultiFileChooser",
+ gooey_options={
+ "wildcard": "All images|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tif;*.webp|"
+ "JPG (*.jpg)|*.jpg|"
+ "JPEG (*.jpeg)|*.jpeg|"
+ "PNG (*.png)|*.png|"
+ "BMP (*.bmp)|*.bmp|"
+ "GIF (*.gif)|*.gif|"
+ "TIFF (*.tif)|*.tif|"
+ "WEBP (*.webp)|*.webp|"
+ "All files (*.*)|*.*",
+ "message": "Select the image file(s) to process",
+ "default_dir": os.getcwd(),
+ "full_width": False,
+ "placeholder": "e.g., a.jpg b.png",
+ },
+ )
- files.add_argument(
- "--image_urls",
- nargs="+",
- metavar="Image URLs",
- help="Add image URLs",
- gooey_options={
- "full_width": False,
- "placeholder": "e.g., https://example.com/a.jpg https://example.com/b.png",
- },
- )
+ files.add_argument(
+ "--image_urls",
+ nargs="+",
+ metavar="Image URLs",
+ help="Add image URLs",
+ gooey_options={
+ "full_width": False,
+ "placeholder": "e.g., https://example.com/a.jpg https://example.com/b.png",
+ },
+ )
+ kwargs = {"gooey_options": {"show_border": False, "columns": 2}} if in_gui else {}
images = parser.add_argument_group(
"Image Settings",
- gooey_options={
- "show_border": False,
- "columns": 2,
- },
+ **kwargs,
)
+ bw_option = "black/white" if in_gui else "bw"
images.add_argument(
"-t",
"--image_type",
default="auto",
metavar="Image Type",
help="Specify whether the input image(s) is/are colored or black/white.\n"
- 'Defaults to "auto", which will be detected automatically.',
- choices=["auto", "color", "bw"],
+ f'Defaults to "auto", which will be detected automatically. Other options are "color" and "{bw_option}".\n',
+ choices=["auto", "color", bw_option],
)
+ kwargs = {"gooey_options": {"full_width": True}} if in_gui else {}
images.add_argument(
"-p",
"--palette",
@@ -349,7 +240,7 @@ def build_arguments():
'Input RGB hex values leading by "#" or RGB values separated by comma(,),\n'
"E.g., #373028 #422811 or 255,255,255 100,100,100\n"
"Leave blank to use the default palette as mentioned in the document.\n",
- gooey_options={"full_width": True},
+ **kwargs,
)
images.add_argument(
"-l",
@@ -361,18 +252,24 @@ def build_arguments():
"e.g., ['CA', 'CB', ..., 'CZ'] or ['BA', 'BB', ..., 'BZ'].\n"
"Since v1.2.0, supports range of labels, e.g., 'A-Z' or '1-10'.\n"
"Refer to https://github.com/ChenglongMa/SkinToneClassifier#3-specify-category-labels for more details.",
- gooey_options={"full_width": True},
+ **kwargs,
)
+ kwargs = dict(metavar="Convert to Black/White") if in_gui else {}
images.add_argument(
"-bw",
"--black_white",
- metavar="Convert to Black/White",
action="store_true",
help="Whether to convert the input to black/white image(s)?\n"
- "If true, the app will use the black/white palette to classify the image.",
+ "If true, the app will convert the input to black/white image(s) and use the black/white palette for classification.",
+ **kwargs,
)
+ kwargs = (
+ {"gooey_options": {"initial_value": 2, "min": 1, "max": 99999, "full_width": False}, "widget": "IntegerField"}
+ if in_gui
+ else {}
+ )
images.add_argument(
"--n_colors",
metavar="Number of Dominant Colors",
@@ -380,8 +277,16 @@ def build_arguments():
help="Specify the number of dominant colors to be extracted.\n"
"The colors will be used to compare with the colors in the palette.\n",
default=2,
- widget="IntegerField",
- gooey_options={"initial_value": 2, "min": 1, "max": 99999, "full_width": False},
+ **kwargs,
+ )
+
+ kwargs = (
+ {
+ "gooey_options": {"initial_value": 250, "min": 10, "max": 99999, "full_width": False},
+ "widget": "IntegerField",
+ }
+ if in_gui
+ else {}
)
images.add_argument(
"--new_width",
@@ -391,37 +296,49 @@ def build_arguments():
"Sometimes smaller images will be processed faster and more accurately.\n"
"No resizing will be performed if the value is negative.",
default=250,
- widget="IntegerField",
- gooey_options={"initial_value": 250, "min": 10, "max": 99999, "full_width": False},
+ **kwargs,
)
- outputs = parser.add_argument_group("Output Settings", gooey_options={"show_border": True})
+ kwargs = {"gooey_options": {"show_border": True}} if in_gui else {}
+ outputs = parser.add_argument_group("Output Settings", **kwargs)
+
+ kwargs = (
+ {
+ "gooey_options": {"message": "Select the output directory", "default_path": os.getcwd()},
+ "widget": "DirChooser",
+ }
+ if in_gui
+ else {}
+ )
outputs.add_argument(
"-o",
"--output",
metavar="Output Directory",
default=os.getcwd(),
help="Specify the path of output file, defaults to current directory.",
- widget="DirChooser",
- gooey_options={
- "message": "Select the output directory",
- "default_path": os.getcwd(),
- },
+ **kwargs,
)
+
+ kwargs = dict(metavar="Generate Report Images") if in_gui else {}
outputs.add_argument(
"-d",
"--debug",
- metavar="Generate Report Images",
action="store_true",
- default=True,
+ default=in_gui,
help="Whether to generate report images?\n"
"If true, the report images will be saved in the '/debug' directory.",
+ **kwargs,
)
+ kwargs = {"gooey_options": {"show_border": False, "columns": 2}} if in_gui else {}
advanced = parser.add_argument_group(
"Advanced Settings",
"For advanced users only, please refer to https://stackoverflow.com/a/20805153/8860079",
- gooey_options={"show_border": False, "columns": 2},
+ **kwargs,
+ )
+
+ kwargs = (
+ {"gooey_options": {"initial_value": 1.1, "min": 0.1, "max": 2.0}, "widget": "DecimalField"} if in_gui else {}
)
advanced.add_argument(
"--scale",
@@ -429,9 +346,10 @@ def build_arguments():
metavar="Scale",
help="Specify how much the image size is reduced at each image scale.",
default=1.1,
- widget="DecimalField",
- gooey_options={"initial_value": 1.1, "min": 0.1, "max": 2.0},
+ **kwargs,
)
+
+ kwargs = {"gooey_options": {"initial_value": 5, "min": 1, "max": 99999}, "widget": "IntegerField"} if in_gui else {}
advanced.add_argument(
"--min_nbrs",
type=int,
@@ -439,59 +357,61 @@ def build_arguments():
help="Specify how many neighbors each candidate rectangle should have to retain it.\n"
"Higher value results in less detections but with higher quality.",
default=5,
- widget="IntegerField",
- gooey_options={"initial_value": 5, "min": 1, "max": 99999},
+ **kwargs,
)
default_min_width = 90
default_min_height = 90
+ kwargs = {"gooey_options": {"visible": False}} if in_gui else {}
advanced.add_argument(
"--min_size",
type=int,
nargs="+",
metavar="Minimum Possible Face Size, format: ",
- help=f'[Alias --min_width, --min_height] Specify the minimum possible face size. Faces smaller than that are ignored, defaults to "{default_min_width} {default_min_height}".',
+ help=f'Specify the minimum possible face size. Faces smaller than that are ignored, defaults to "{default_min_width} {default_min_height}".',
default=(default_min_width, default_min_height),
- gooey_options={
- "visible": False,
- },
+ **kwargs,
)
+ if in_gui:
+ min_size = advanced.add_argument_group(
+ "Minimum Possible Face Size (pixels)",
+ 'Specify the minimum possible face size. Faces smaller than that are ignored, defaults to "90 90".',
+ gooey_options={"show_border": True, "columns": 2},
+ )
- min_size = advanced.add_argument_group(
- "Minimum Possible Face Size (pixels)",
- 'Specify the minimum possible face size. Faces smaller than that are ignored, defaults to "90 90".',
- gooey_options={"show_border": True, "columns": 2},
- )
+ min_size.add_argument(
+ "--min_width",
+ type=int,
+ metavar="Minimum Width",
+ # help="Specify the minimum possible face width. Faces smaller than that are ignored, defaults to 90.",
+ default=default_min_width,
+ widget="IntegerField",
+ gooey_options={"initial_value": default_min_width, "min": 10, "max": 99999},
+ )
- min_size.add_argument(
- "--min_width",
- type=int,
- metavar="Minimum Width",
- # help="Specify the minimum possible face width. Faces smaller than that are ignored, defaults to 90.",
- default=default_min_width,
- widget="IntegerField",
- gooey_options={"initial_value": default_min_width, "min": 10, "max": 99999},
- )
+ min_size.add_argument(
+ "--min_height",
+ type=int,
+ metavar="Minimum Height",
+ # help="Specify the minimum possible face height. Faces smaller than that are ignored, defaults to 90.",
+ default=default_min_height,
+ widget="IntegerField",
+ gooey_options={"initial_value": default_min_height, "min": 10, "max": 99999},
+ )
- min_size.add_argument(
- "--min_height",
- type=int,
- metavar="Minimum Height",
- # help="Specify the minimum possible face height. Faces smaller than that are ignored, defaults to 90.",
- default=default_min_height,
- widget="IntegerField",
- gooey_options={"initial_value": default_min_height, "min": 10, "max": 99999},
+ kwargs = (
+ {"gooey_options": {"initial_value": 0.15, "min": 0.01, "max": 1.0}, "widget": "DecimalField"} if in_gui else {}
)
-
advanced.add_argument(
"--threshold",
type=float,
metavar="Minimum Possible Face Proportion",
help="Specify the minimum proportion of the skin area required to identify the face, defaults to 0.15.",
default=0.15,
- widget="DecimalField",
- gooey_options={"initial_value": 0.15, "min": 0.01, "max": 1.0},
+ **kwargs,
)
+
+ kwargs = {"gooey_options": {"initial_value": 0, "min": 0, "max": 99999}, "widget": "IntegerField"} if in_gui else {}
advanced.add_argument(
"--n_workers",
type=int,
@@ -499,28 +419,32 @@ def build_arguments():
help="Specify the number of workers to process the images.\n"
"0 means the total number of CPU cores in the system.",
default=0,
- widget="IntegerField",
- gooey_options={"initial_value": 0, "min": 0, "max": 99999},
+ **kwargs,
)
+ kwargs = dict(gooey_options={"visible": False}) if in_gui else {}
advanced.add_argument(
"-v",
"--version",
action="version",
version=f"%(prog)s {__version__}",
help="Show the version number and exit.",
- gooey_options={"visible": False},
+ **kwargs,
)
args = parser.parse_args()
images = args.images or []
- if args.image_dirs:
+ if getattr(args, "image_dirs", False):
images.extend(args.image_dirs)
- if args.image_files:
+ if getattr(args, "image_files", False):
images.extend(args.image_files)
- if args.image_urls:
+ if getattr(args, "image_urls", False):
images.extend(args.image_urls)
args.images = images
- if tuple(args.min_size) == (default_min_width, default_min_height):
+ if (
+ tuple(args.min_size) == (default_min_width, default_min_height)
+ and getattr(args, "min_width", False)
+ and getattr(args, "min_height", False)
+ ):
args.min_size = (args.min_width, args.min_height)
return args
@@ -598,7 +522,7 @@ def check_version():
print(
Fore.YELLOW + f"You are using an outdated version of {__package_name__} ({installed_version}).\n"
f"Please upgrade to the latest version ({latest_version}) with the following command:\n",
- Fore.GREEN + f"pip install {__package_name__} --upgrade\n" + Fore.RESET,
+ Fore.GREEN + f"pip install {__package_name__}[all] --upgrade\n" + Fore.RESET,
)
os.environ["STONE_UPGRADE_FLAG"] = "1"
except Exception:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 1c786d5..5c0ab55 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,13 +1,12 @@
import unittest
from pathlib import Path
-from unittest.mock import patch
from stone.utils import build_image_paths, resolve_labels
class TestUtils(unittest.TestCase):
def setUp(self):
- self.image_path = "./mock_data/images"
+ self.image_path = str(Path("./mock_data/images").resolve())
# Sorted image paths
self.expected_recursive_image_paths = [
f"{self.image_path}/fake_img_1.gif", # In default, sorted by the trailing number
@@ -60,7 +59,7 @@ def test_single_directory_non_recursive(self):
self.should_exclude_folder(image_paths, ["subfolder", "debug", "log"])
self.assertListEqual(
image_paths,
- [Path(p) for p in self.expected_non_recursive_image_paths],
+ [Path(p).resolve() for p in self.expected_non_recursive_image_paths],
)
def test_multiple_directories_recursive(self):
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..d59d625
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,52 @@
+# this file is *not* meant to cover or endorse the use of tox or pytest or
+# testing in general,
+#
+# It's meant to show the use of:
+#
+# - check-manifest
+# confirm items checked into vcs are in your sdist
+# - readme_renderer (when using a ReStructuredText README)
+# confirms your long_description will render correctly on PyPI.
+#
+# and also to help confirm pull requests to this project.
+
+[tox]
+envlist = py{39,310,311,312}
+
+# Define the minimal tox version required to run;
+# if the host tox is less than this the tool with create an environment and
+# provision it with a tox that satisfies it under provision_tox_env.
+# At least this version is needed for PEP 517/518 support.
+minversion = 3.3.0
+
+# Activate isolated build environment. tox will use a virtual environment
+# to build a source distribution from the source tree. For build tools and
+# arguments use the pyproject.toml file as specified in PEP-517 and PEP-518.
+isolated_build = true
+
+[testenv]
+deps =
+ check-manifest >= 0.42
+ # If your project uses README.rst, uncomment the following:
+ # readme_renderer
+ # flake8
+ # pytest
+ build
+ twine
+
+allowlist_externals =
+ cp
+
+commands =
+ check-manifest --ignore 'tox.ini,tests/**'
+ python -m build
+ python -m twine check dist/*
+ # flake8 .
+ # python -m unittest discover -v
+ cp -r tests/mock_data ./
+ python -m unittest discover -v tests
+ # py.test tests {posargs}
+
+# [flake8]
+# exclude = .tox,*.egg,build,data
+# select = E,W,F
\ No newline at end of file