├── nft
├── __init__.py
└── image.py
├── requirements.txt
├── pyproject.toml
├── tests
└── nft
│ ├── test_modules
│ ├── 2-top
│ │ └── neon-brown-teal.png
│ └── 1-bottom
│ │ └── maroon-orange-blue.png
│ └── test_image.py
├── Makefile
├── .github
└── workflows
│ └── lint.yml
├── setup.py
├── .circleci
└── config.yml
├── LICENSE
├── README.md
├── .gitignore
└── CODE_OF_CONDUCT.md
/nft/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Pillow==8.3.2
2 | simplejson==3.17.5
3 | pytest==6.2.3
4 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "setuptools>=42",
4 | "wheel"
5 | ]
6 | build-backend = "setuptools.build_meta"
7 |
--------------------------------------------------------------------------------
/tests/nft/test_modules/2-top/neon-brown-teal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanolaboratory/nft-toolkit/HEAD/tests/nft/test_modules/2-top/neon-brown-teal.png
--------------------------------------------------------------------------------
/tests/nft/test_modules/1-bottom/maroon-orange-blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nanolaboratory/nft-toolkit/HEAD/tests/nft/test_modules/1-bottom/maroon-orange-blue.png
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | clean_build: remove_dist build
2 |
3 | remove_dist:
4 | rm -rf ./dist
5 |
6 | build:
7 | python3 -m build
8 |
9 | release: clean_build release_to_pypi
10 |
11 | test_release: clean_build release_to_testpypi
12 |
13 | release_to_pypi:
14 | python3 -m twine upload --repository pypi dist/*
15 |
16 | release_to_testpypi:
17 | python3 -m twine upload --repository testpypi dist/*
18 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | # Trigger the workflow on push or pull request,
5 | # but only for the main branch
6 | push:
7 | branches:
8 | - main
9 | pull_request:
10 | branches:
11 | - main
12 |
13 | jobs:
14 | run-linters:
15 | name: Run linters
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - name: Check out Git repository
20 | uses: actions/checkout@v2
21 |
22 | - name: Set up Python
23 | uses: actions/setup-python@v1
24 | with:
25 | python-version: 3.6
26 |
27 | - name: Install Python dependencies
28 | run: pip install black
29 |
30 | - name: Run linters
31 | uses: wearerequired/lint-action@v1
32 | with:
33 | black: true
34 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 |
4 | setuptools.setup(
5 | name="nft-toolkit",
6 | version="0.1.1",
7 | author="Nano Labs LLC",
8 | author_email="hello@nanolabs.dev",
9 | description="A tool to generate a randomized 2D-image \
10 | NFT collection based on nft attribute layers",
11 | url="https://github.com/nanolaboratory/nft-toolkit",
12 | project_urls={
13 | "Bug Tracker": "https://github.com/nanolaboratory/nft-toolkit/issues",
14 | },
15 | classifiers=[
16 | "Programming Language :: Python :: 3",
17 | "License :: OSI Approved :: MIT License",
18 | "Operating System :: OS Independent",
19 | ],
20 | packages=["nft"],
21 | python_requires=">=3.6",
22 | install_requires=["Pillow==8.3.2", "simplejson"],
23 | )
24 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | python: circleci/python@1.2
5 |
6 | workflows:
7 | Circle CI:
8 | jobs:
9 | - build-and-test
10 | jobs:
11 | build-and-test:
12 | docker:
13 | - image: circleci/python:3.8.8
14 | auth:
15 | username: mydockerhub-user
16 | password: $DOCKERHUB_PASSWORD
17 | steps:
18 | - checkout
19 | - run:
20 | name: Upgrade pip
21 | command: python3 -m pip install --upgrade pip
22 | - run:
23 | name: Create venv
24 | command: python3 -m venv venv
25 | - run:
26 | name: Enter venv
27 | command: source venv/bin/activate
28 | - run:
29 | name: Run pip install
30 | command: pip install -r requirements.txt
31 | - run:
32 | name: Run tests
33 | command: python -m pytest
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Nano Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nft-toolkit
2 |
3 | [](https://circleci.com/gh/nanolaboratory/nft-toolkit/tree/main)
4 | [](https://badge.fury.io/py/nft-toolkit)
5 | [](https://github.com/psf/black)
6 | [](https://github.com/nanolaboratory/nft-toolkit/issues)
7 |
8 | A tool to generate a randomized 2D-image NFT collection based on nft attribute layers
9 |
10 | ## About
11 |
12 | `nft-toolkit` is a general purpose nft library to make generating thousands of nft's really easy
13 |
14 | ##### Made by Nano Labs in 2021
15 |
16 | ## Installation
17 |
18 | ### PIP
19 |
20 | ```bash
21 | pip install nft-toolkit
22 | ```
23 |
24 | ## Documentation
25 | To view documentation, after pip installation run the following commands in a python3 interactive shell.
26 |
27 | ```python
28 | from nft.image import RandomImageGenerator
29 | help(RandomImageGenerator)
30 | ```
31 |
32 | ## Examples
33 |
34 | ### Generate 100 Images:
35 |
36 | #### Steps
37 | 1. Create a directory of attributes that you would like to layer on top of each other. Each attribute must be of the same image size so that the overlays will line up properly.
38 |
39 | 2. Create a script similar to below. RandomImageGenerator uses 3 parameters: Number of permutations to generate, filepath to attributes, and filepath to place the images generated.
40 |
41 | ```python
42 | from nft.image import RandomImageGenerator
43 | nft_collection = RandomImageGenerator(100, "./my-nft-project/nft_images", "./my-nft-project/collection")
44 |
45 | nft_collection.generate_collection()
46 | ```
47 |
48 | 3. Your collection should be generated with each image's filename describing the attributes that are used.
49 |
50 | ## Contribute
51 | Issues and pull requests are welcome. If you add functionality, then please add unit tests to cover it. Continuous Integration is handled by CircleCI.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # MacOS
132 | .DS_Store
133 |
--------------------------------------------------------------------------------
/tests/nft/test_image.py:
--------------------------------------------------------------------------------
1 | from nft.image import RandomImageGenerator
2 | from PIL import Image
3 | from collections import OrderedDict
4 | from shutil import rmtree
5 |
6 | import os
7 | import pytest
8 |
9 |
10 | def get_image_data(image_path):
11 | with Image.open(image_path) as im:
12 | return list(im.getdata())
13 |
14 |
15 | @pytest.fixture()
16 | def random_image_generator():
17 | yield RandomImageGenerator(1, "./tests/nft/test_modules/", "./tests/nft/output")
18 |
19 | if os.path.isdir("./tests/nft/output/"):
20 | rmtree("./tests/nft/output")
21 |
22 |
23 | def test_image_dimensions():
24 | height, width = RandomImageGenerator.image_dimensions(
25 | None, "./tests/nft/test_modules/1-bottom/maroon-orange-blue.png"
26 | )
27 | assert height == 20
28 | assert width == 15
29 |
30 |
31 | def test_init_and_find_attributes(random_image_generator):
32 | # No exception raised means an attribute is found.
33 | assert random_image_generator is not None
34 |
35 |
36 | def test_file_is_generated(random_image_generator):
37 | random_image_generator.generate_collection()
38 | assert os.path.isdir("./tests/nft/output/")
39 | assert os.path.exists("./tests/nft/output/maroon-orange-blue_neon-brown-teal.png")
40 |
41 |
42 | def test_build_image_dict(random_image_generator):
43 | image_paths = random_image_generator.build_image_dict()
44 | assert image_paths == OrderedDict(
45 | [
46 | ("1-bottom", ["./tests/nft/test_modules//1-bottom/maroon-orange-blue.png"]),
47 | ("2-top", ["./tests/nft/test_modules//2-top/neon-brown-teal.png"]),
48 | ]
49 | )
50 |
51 |
52 | def test_input_image_bottom():
53 | upper_left = [(64, 0, 0, 255)] * 3
54 | upper_right = [(0, 0, 0, 0)] * 3
55 | lower_left = [(238, 99, 48, 255)] * 3
56 | lower_right = [(0, 88, 255, 255)] * 3
57 | bottom_offset = 15 * 17
58 |
59 | pixdata = get_image_data("./tests/nft/test_modules/1-bottom/maroon-orange-blue.png")
60 |
61 | for row_idx in range(3):
62 | pix_index = row_idx * 15
63 |
64 | assert pixdata[pix_index : pix_index + 3] == upper_left
65 | assert pixdata[pix_index + 12 : pix_index + 15] == upper_right
66 | assert (
67 | pixdata[pix_index + bottom_offset : pix_index + bottom_offset + 3]
68 | == lower_left
69 | )
70 | assert (
71 | pixdata[pix_index + bottom_offset + 12 : pix_index + bottom_offset + 15]
72 | == lower_right
73 | )
74 |
75 |
76 | def test_input_image_top():
77 | upper_left = [(204, 244, 15, 255)] * 3
78 | upper_right = [(68, 60, 20, 255)] * 3
79 | lower_left = [(0, 0, 0, 0)] * 3
80 | lower_right = [(48, 238, 114, 255)] * 3
81 | bottom_offset = 15 * 17
82 |
83 | pixdata = get_image_data("./tests/nft/test_modules/2-top/neon-brown-teal.png")
84 |
85 | for row_idx in range(3):
86 | pix_index = row_idx * 15
87 |
88 | assert pixdata[pix_index : pix_index + 3] == upper_left
89 | assert pixdata[pix_index + 12 : pix_index + 15] == upper_right
90 | assert (
91 | pixdata[pix_index + bottom_offset : pix_index + bottom_offset + 3]
92 | == lower_left
93 | )
94 | if row_idx < 2:
95 | assert (
96 | pixdata[pix_index + bottom_offset + 12 : pix_index + bottom_offset + 15]
97 | == lower_right
98 | )
99 |
100 |
101 | def test_overlay(random_image_generator):
102 | random_image_generator.generate_collection()
103 |
104 | upper_left = [(204, 244, 15, 255)] * 3
105 | upper_right = [(68, 60, 20, 255)] * 3
106 | lower_left = [(238, 99, 48, 255)] * 3
107 | top_lower_right = [(48, 238, 114, 255)] * 3
108 | bottom_lower_right = [(0, 88, 255, 255)] * 3
109 | bottom_offset = 15 * 17
110 |
111 | pixdata = get_image_data(
112 | "./tests/nft/output/maroon-orange-blue_neon-brown-teal.png"
113 | )
114 |
115 | for row_idx in range(3):
116 | pix_index = row_idx * 15
117 |
118 | assert pixdata[pix_index : pix_index + 3] == upper_left
119 | assert pixdata[pix_index + 12 : pix_index + 15] == upper_right
120 | assert (
121 | pixdata[pix_index + bottom_offset : pix_index + bottom_offset + 3]
122 | == lower_left
123 | )
124 |
125 | if row_idx != 2:
126 | assert (
127 | pixdata[pix_index + bottom_offset + 12 : pix_index + bottom_offset + 15]
128 | == top_lower_right
129 | )
130 | else:
131 | assert (
132 | pixdata[pix_index + bottom_offset + 12 : pix_index + bottom_offset + 15]
133 | == bottom_lower_right
134 | )
135 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | hello@nanolabs.dev.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/nft/image.py:
--------------------------------------------------------------------------------
1 | from random import randrange
2 | from PIL import Image
3 | from collections import OrderedDict
4 |
5 | import os
6 |
7 |
8 | class RandomImageGenerator(object):
9 | """
10 | A class to generate a images based on modules specified by the user
11 |
12 | ...
13 | Attributes
14 | _________
15 | permutations : int
16 | number of permutations to generate
17 | modules_path : str
18 | file directory of where the parts are located
19 | modules_list : str[]
20 | list of sorted part directories
21 |
22 | Methods
23 | _______
24 | build_images_paths()
25 | goes through all the images and stores the infomrmation in memory
26 | image_name(image)
27 | returns the image name in a string
28 | layer_attributes(images_paths)
29 | layers the parts to create the image
30 | generate_collection()
31 | generates the images
32 |
33 | """
34 |
35 | def __init__(self, permutations, modules_path, collection_output_path):
36 | """
37 | Parameters
38 | __________
39 | permutations : int
40 | Number of permutations to generate
41 | modules_path : str
42 | Path of the directory that contain the input modules
43 | collection_output_path : str
44 | Path of where to store the output images
45 | """
46 | self.permutations = permutations
47 | self.collection_output_path = collection_output_path
48 |
49 | self.modules_path = modules_path
50 | self.modules_list = sorted(os.listdir(self.modules_path))
51 | self.modules_list = [
52 | module_title
53 | for module_title in self.modules_list
54 | if not module_title.startswith(".")
55 | ]
56 |
57 | if len(self.modules_list) == 0:
58 | raise AssertionError("No modules")
59 |
60 | # Find first image to determine dimensions
61 | first_attribute_path = self.find_first_attribute()
62 | self.height, self.width = self.image_dimensions(first_attribute_path)
63 |
64 | def find_first_attribute(self):
65 | """Finds the first attribute (image) in the directory containing the modules
66 |
67 | Raises
68 | __________
69 | Exception
70 | If no attributes/images are found
71 |
72 | """
73 | for module in self.modules_list:
74 | attribute_list = os.listdir("{}/{}".format(self.modules_path, module))
75 |
76 | for attribute in attribute_list:
77 | if not attribute.startswith("."):
78 | attribute_path = "{}/{}/{}".format(
79 | self.modules_path, self.modules_list[0], attribute
80 | )
81 | return attribute_path
82 |
83 | raise Exception("No attributes found.")
84 |
85 | def image_dimensions(self, image_path):
86 | """Returns the image dimensions
87 |
88 | Parameters
89 | __________
90 | image_path : str
91 | File location of the image
92 |
93 | Returns
94 | _______
95 | tuple
96 | (int,int) tuple of (height, width)
97 | """
98 | with Image.open(image_path) as im:
99 | return (im.height, im.width)
100 |
101 | def build_image_dict(self):
102 | """Builds a dictionary of image paths and organizes the dictionary by collection
103 |
104 | Each entry in the dictionary is categorized by the module. Each module is a list of attribute (image) paths
105 |
106 | Returns
107 | _______
108 | OrderedDict
109 | Dictionary of image paths
110 | """
111 | images_paths = OrderedDict()
112 | for module in self.modules_list:
113 | images_paths.setdefault(module, [])
114 |
115 | module_path = "{}/{}".format(self.modules_path, module)
116 | module_list = sorted(os.listdir(module_path))
117 | module_list = [
118 | module_title
119 | for module_title in module_list
120 | if not module_title.startswith(".")
121 | ]
122 |
123 | for attribute in module_list:
124 | attribute_image_path = "{}/{}".format(module_path, attribute)
125 | images_paths[module].append(attribute_image_path)
126 |
127 | return images_paths
128 |
129 | def image_name(self, image):
130 | """Finds the filename of the image that is opened
131 |
132 | Parameters
133 | __________
134 | PIL.Image
135 | Handle on the image that is opened
136 |
137 | Returns
138 | _______
139 | str
140 | Image filename as a string
141 | """
142 |
143 | return image.filename.split("/")[-1].split(".", 1)[0]
144 |
145 | def layer_attributes(self, images_paths):
146 | """Goes through the number of permutations specified and creates images by layering the modules on top of each other
147 |
148 | Each attribute is picked at random to use
149 |
150 | Parameters
151 | __________
152 | OrderedDict
153 | Dictionary of all the image paths
154 |
155 | Raises
156 | ______
157 | AssertionError
158 | If the height and/or width are not equal to the other images
159 | """
160 | for _ in range(self.permutations):
161 | canvas = (self.width, self.height)
162 | nft_image = Image.new("RGBA", canvas, (0, 0, 0, 0))
163 |
164 | attribute_name = ""
165 | for _, attributes in images_paths.items():
166 | attribute_rng = randrange(len(attributes))
167 | with Image.open(attributes[attribute_rng]) as attribute_image:
168 | attribute_name += "{}_".format(self.image_name(attribute_image))
169 |
170 | attribute_image_h = attribute_image.height
171 | attribute_image_w = attribute_image.width
172 |
173 | if (
174 | attribute_image_h != self.height
175 | and attribute_image_w != self.width
176 | ):
177 | raise AssertionError(
178 | "Height and/or width does not match \
179 | {0} != {1} and/or {2} != {3}.".format(
180 | attribute_image_h,
181 | self.height,
182 | attribute_image_w,
183 | self.width,
184 | )
185 | )
186 |
187 | converted_image = attribute_image.convert("RGBA")
188 | nft_image.paste(converted_image, (0, 0), mask=converted_image)
189 | nft_image_path = "{}/{}.png".format(
190 | self.collection_output_path, attribute_name[:-1]
191 | )
192 | nft_image.save(nft_image_path)
193 |
194 | def generate_collection(self):
195 | """Generates the collection of images
196 |
197 | First builds the dictionary of image paths and then layers the attributes to create the images for the number of permutations specified in the init
198 | """
199 | images_paths = self.build_image_dict()
200 |
201 | if not os.path.exists(self.collection_output_path):
202 | os.makedirs(self.collection_output_path)
203 |
204 | self.layer_attributes(images_paths)
205 |
--------------------------------------------------------------------------------