diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f77d5..9fc575f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.7] - 2022-05-04 +### Added +- `fill_data` function +- `random_hex_color_gen` function +- `color`,`bgcolor` and `projection` parameters random mode +### Changed +- Calculation warning added to `generate` method +- Hex color support for `color` and `bgcolor` parameters +- Test system modified +- Random mode modified +- `filter_color` function modified +- `filter_projection` function modified +- `is_same_data` function modified +- `README.md` updated ## [0.6] - 2022-04-13 ### Added - `save_params_filter` function @@ -95,7 +109,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - `generate` method - `nft_storage` method -[Unreleased]: https://github.com/sepandhaghighi/samila/compare/v0.6...dev +[Unreleased]: https://github.com/sepandhaghighi/samila/compare/v0.7...dev +[0.7]: https://github.com/sepandhaghighi/samila/compare/v0.6...v0.7 [0.6]: https://github.com/sepandhaghighi/samila/compare/v0.5...v0.6 [0.5]: https://github.com/sepandhaghighi/samila/compare/v0.4...v0.5 [0.4]: https://github.com/sepandhaghighi/samila/compare/v0.3...v0.4 diff --git a/README.md b/README.md index 4506fa9..78a6ac4 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ * [Social Media](https://github.com/sepandhaghighi/samila#social-media) * [Contribution](https://github.com/sepandhaghighi/samila/blob/master/.github/CONTRIBUTING.md) * [References](https://github.com/sepandhaghighi/samila#references) + * [Acknowledgments](https://github.com/sepandhaghighi/samila#acknowledgments) * [Authors](https://github.com/sepandhaghighi/samila/blob/master/AUTHORS.md) * [License](https://github.com/sepandhaghighi/samila/blob/master/LICENSE) * [Show Your Support](https://github.com/sepandhaghighi/samila#show-your-support) @@ -87,7 +88,7 @@ Samila is a generative art generator written in Python, Samila let's you create ### Source code -- Download [Version 0.6](https://github.com/sepandhaghighi/samila/archive/v0.6.zip) or [Latest Source ](https://github.com/sepandhaghighi/samila/archive/dev.zip) +- Download [Version 0.7](https://github.com/sepandhaghighi/samila/archive/v0.7.zip) or [Latest Source ](https://github.com/sepandhaghighi/samila/archive/dev.zip) - Run `pip install -r requirements.txt` or `pip3 install -r requirements.txt` (Need root access) - Run `python3 setup.py install` or `python setup.py install` (Need root access) @@ -95,7 +96,7 @@ Samila is a generative art generator written in Python, Samila let's you create - Check [Python Packaging User Guide](https://packaging.python.org/installing/) -- Run `pip install samila==0.6` or `pip3 install samila==0.6` (Need root access) +- Run `pip install samila==0.7` or `pip3 install samila==0.7` (Need root access) ### Easy install @@ -146,7 +147,7 @@ Samila is a generative art generator written in Python, Samila let's you create ``` -* Supported projections : `RECTILINEAR`, `POLAR`, `AITOFF`, `HAMMER`, `LAMBERT` and `MOLLWEIDE` +* Supported projections : `RECTILINEAR`, `POLAR`, `AITOFF`, `HAMMER`, `LAMBERT`, `MOLLWEIDE` and `RANDOM` * Default projection is `RECTILINEAR` ### Range @@ -172,7 +173,12 @@ Samila is a generative art generator written in Python, Samila let's you create * Supported colors are available in `VALID_COLORS` list -* `color` and `bgcolor` parameters support color name and RGB/RGBA formats +* `color` and `bgcolor` parameters supported formats: + + 1. Color name (example: `yellow`) + 2. RGB/RGBA (example: `(0.1,0.1,0.1)`, `(0.1,0.1,0.1,0.1)`) + 3. Hex (example: `#eeefff`) + 4. Random (example: `random`) ### Regeneration ```pycon @@ -362,7 +368,10 @@ You can also join our discord server
2- Create Generative Art with R
3- NFT.storage : Free decentralized storage and bandwidth for NFTs
- + +## Acknowledgments + +This project was funded through the **Next Step Microgrant**, a program established by [Protocol Labs](https://protocol.ai/). ## Show your support diff --git a/dev-requirements.txt b/dev-requirements.txt index 5e8e837..b8b9778 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1,5 @@ matplotlib==3.5.1 -art==5.5 +art==5.6 vulture>=1.0 bandit>=1.5.1 pydocstyle>=3.0.0 diff --git a/examples/demo.ipynb b/examples/demo.ipynb index 353bbff..9ab48e4 100644 --- a/examples/demo.ipynb +++ b/examples/demo.ipynb @@ -88,7 +88,7 @@ "## Projection\n", "We can use the `projection` attribute to define the coordinate system to transform our functions\n", "\n", - "The avaliable projections are `RECTILINEAR`, `POLAR`, `AITOFF`, `HAMMER`, `LAMBERT` and `MOLLWEIDE`" + "The avaliable projections are `RECTILINEAR`, `POLAR`, `AITOFF`, `HAMMER`, `LAMBERT`, `MOLLWEIDE` and `RANDOM`" ] }, { @@ -151,6 +151,18 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* `color` and `bgcolor` parameters supported formats:\n", + "\n", + " 1. Color name (example: `yellow`)\n", + " 2. RGB/RGBA (example: `(0.1,0.1,0.1)`, `(0.1,0.1,0.1,0.1)`)\n", + " 3. Hex (example: `#eeefff`)\n", + " 4. Random (example: `random`)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/otherfiles/version_check.py b/otherfiles/version_check.py index dbe1513..6637947 100644 --- a/otherfiles/version_check.py +++ b/otherfiles/version_check.py @@ -4,7 +4,7 @@ import os import sys import codecs Failed = 0 -SAMILA_VERSION = "0.6" +SAMILA_VERSION = "0.7" SETUP_ITEMS = [ diff --git a/samila/functions.py b/samila/functions.py index 0703275..c4ef5cc 100644 --- a/samila/functions.py +++ b/samila/functions.py @@ -4,16 +4,17 @@ import requests import io import os +import re import json import random import matplotlib from .params import DEFAULT_START, DEFAULT_STOP, DEFAULT_STEP, DEFAULT_COLOR, DEFAULT_IMAGE_SIZE, DEFAULT_DEPTH from .params import DEFAULT_BACKGROUND_COLOR, DEFAULT_SPOT_SIZE, DEFAULT_PROJECTION, DEFAULT_ALPHA, DEFAULT_LINEWIDTH -from .params import Projection, VALID_COLORS, NFT_STORAGE_API, NFT_STORAGE_LINK, OVERVIEW +from .params import Projection, VALID_COLORS, HEX_COLOR_PATTERN, NFT_STORAGE_API, NFT_STORAGE_LINK, OVERVIEW from .params import DATA_TYPE_ERROR, CONFIG_TYPE_ERROR, PLOT_DATA_ERROR, CONFIG_NO_STR_FUNCTION_ERROR from .params import NO_FIG_ERROR_MESSAGE, FIG_SAVE_SUCCESS_MESSAGE, NFT_STORAGE_SUCCESS_MESSAGE, SAVE_NO_DATA_ERROR from .params import DATA_SAVE_SUCCESS_MESSAGE, SEED_LOWER_BOUND, SEED_UPPER_BOUND -from .params import ELEMENTS_LIST, ARGUMENTS_LIST, OPERATORS_LIST +from .params import ELEMENTS_LIST, ARGUMENTS_LIST, OPERATORS_LIST, RANDOM_COEF_LIST from .errors import samilaDataError, samilaPlotError, samilaConfigError @@ -26,7 +27,7 @@ def random_equation_gen(): num_elements = random.randint(2, len(ELEMENTS_LIST) + 3) result = "" index = 1 - random_coef = "random.uniform(-1,1)" + random_coef = random.choice(RANDOM_COEF_LIST) while(index <= num_elements): argument = random.choice(ARGUMENTS_LIST) result = result + \ @@ -37,6 +38,16 @@ def random_equation_gen(): return result +def random_hex_color_gen(): + """ + Generate random hex color code. + + :return: color code as str + """ + random_color = "#%06x" % random.randint(0, 0xFFFFFF) + return random_color + + def float_range(start, stop, step): """ Generate float range. @@ -95,6 +106,10 @@ def filter_color(color): if isinstance(color, tuple): return color if isinstance(color, str): + if color.upper() == "RANDOM": + return random_hex_color_gen() + if re.match(HEX_COLOR_PATTERN, color): + return color distance_list = list(map(lambda x: distance_calc(color, x), VALID_COLORS)) min_distance = min(distance_list) @@ -111,7 +126,12 @@ def filter_projection(projection): :return: filtered version of projection """ if isinstance(projection, Projection): - return projection.value + projection_value = projection.value + if projection_value == "random": + projection_list = list(Projection) + projection_list.remove(Projection.RANDOM) + projection_value = random.choice(projection_list).value + return projection_value return None @@ -234,6 +254,27 @@ def generate_params_filter( g.seed, g.start, g.step, g.stop = seed, start, step, stop +def fill_data(g, point): + """ + Fill data with functions in given points. + + :param g: generative image instance + :type g: GenerativeImage + :param point: given point + :type point: tuple + :return: false if some exception occurred + """ + random.seed(g.seed) + try: + data1_ = g.function1(point[0], point[1]).real + data2_ = g.function2(point[0], point[1]).real + except Exception: + return False + g.data1.append(data1_) + g.data2.append(data2_) + return True + + def save_params_filter(g, depth=None): """ Filter save_image method parameters. @@ -475,6 +516,8 @@ def is_same_data(data1, data2, precision=10**-5): :type precision: float :return: True if they are the same """ + if len(data1) != len(data2): + return False is_same = map(lambda x, y: abs(x - y) < precision, data1, data2) return all(is_same) diff --git a/samila/genimage.py b/samila/genimage.py index 3fc057c..20d870f 100644 --- a/samila/genimage.py +++ b/samila/genimage.py @@ -7,7 +7,7 @@ import matplotlib import matplotlib.pyplot as plt from .functions import _GI_initializer, plot_params_filter, generate_params_filter, save_params_filter from .functions import float_range, save_data_file, save_fig_file, save_fig_buf, save_config_file -from .functions import load_data, load_config, random_equation_gen, nft_storage_upload +from .functions import load_data, load_config, random_equation_gen, nft_storage_upload, fill_data from .params import * from warnings import warn @@ -80,10 +80,12 @@ class GenerativeImage: range1 = list(float_range(self.start, self.stop, self.step)) range2 = list(float_range(self.start, self.stop, self.step)) range_prod = list(itertools.product(range1, range2)) - for item in range_prod: - random.seed(self.seed) - self.data1.append(self.function1(item[0], item[1]).real) - self.data2.append(self.function2(item[0], item[1]).real) + calc_exception = False + for point in range_prod: + if not fill_data(self, point): + calc_exception = True + if calc_exception: + warn(CALCULATION_EXCEPTION_WARNING, RuntimeWarning) def plot( self, diff --git a/samila/params.py b/samila/params.py index fcfc5c9..4075ac8 100644 --- a/samila/params.py +++ b/samila/params.py @@ -4,7 +4,7 @@ import math from enum import Enum from matplotlib import colors as mcolors -SAMILA_VERSION = "0.6" # pragma: no cover +SAMILA_VERSION = "0.7" # pragma: no cover OVERVIEW = ''' Samila is a generative art generator written in Python, Samila let's you @@ -27,6 +27,7 @@ DEFAULT_PROJECTION = "rectilinear" SEED_LOWER_BOUND = 0 SEED_UPPER_BOUND = 2**20 VALID_COLORS = list(dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS).keys()) +HEX_COLOR_PATTERN = r'^#(?:[0-9a-fA-F]{3}){1,2}$' NFT_STORAGE_API = "https://api.nft.storage/upload" NFT_STORAGE_LINK = "https://ipfs.io/ipfs/{}" NFT_STORAGE_SUCCESS_MESSAGE = "Everything seems good." @@ -39,6 +40,7 @@ CONFIG_NO_STR_FUNCTION_ERROR = "Config file can't be saved. At least one of the PLOT_DATA_ERROR = "Plotting process can't be Done because data{0} is empty. Use generate method first." SAVE_NO_DATA_ERROR = "Data file can't be saved. At least one of the data1 or data2 is None." MATPLOTLIB_VERSION_WARNING = "Source matplotlib version({0}) is different from yours, plots may be different." +CALCULATION_EXCEPTION_WARNING = "The given functions are undefined at some points. Your plot may not be complete." class Projection(Enum): @@ -55,9 +57,20 @@ class Projection(Enum): LAMBERT = "lambert" MOLLWEIDE = "mollweide" RECTILINEAR = "rectilinear" + RANDOM = "random" +RANDOM_COEF_LIST = [ + "random.uniform(-1,1)", + "random.gauss(0,1)", + "random.betavariate(1,1)", + "random.gammavariate(1,1)", + "random.lognormvariate(0,1)"] + ELEMENTS_LIST = [ + "{0}*math.asinh({1})", + "{0}*math.acosh(abs({1})+1)", + "{0}*math.erf({1})", "{0}*math.sqrt(abs({1}))", "{0}*math.log(abs({1})+1)", "{0}*math.tanh({1})", diff --git a/setup.py b/setup.py index 0485b56..8e5e416 100644 --- a/setup.py +++ b/setup.py @@ -32,14 +32,14 @@ def read_description(): setup( name='samila', packages=['samila'], - version='0.6', + version='0.7', description='Generative ART', long_description=read_description(), long_description_content_type='text/markdown', - author='Sepand Haghighi', + author='Samila Development Team', author_email='info@4r7.ir', url='https://github.com/sepandhaghighi/samila', - download_url='https://github.com/sepandhaghighi/samila/tarball/v0.6', + download_url='https://github.com/sepandhaghighi/samila/tarball/v0.7', keywords="generative-art art nft file nft-storage", project_urls={ 'Source': 'https://github.com/sepandhaghighi/samila', diff --git a/test/function_test.py b/test/function_test.py index 0743fa1..48ba874 100644 --- a/test/function_test.py +++ b/test/function_test.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """ +>>> import random >>> from samila.functions import * >>> s = list(float_range(1,1.5,0.1)) >>> s @@ -8,16 +9,47 @@ True >>> is_same_data([1,1.1,1.2,1.3,1.4],[1,1.11,1.3,1.4,1.5]) False +>>> is_same_data(s,[1,1.1,1.2,1.3,1.4,1.5,1.6]) +False +>>> is_same_data(s,[]) +False >>> filter_color("yellow") 'yellow' >>> filter_color((0.2,0.3,0.4)) (0.2, 0.3, 0.4) +>>> filter_color("#FFFFFF") +'#FFFFFF' +>>> random.seed(2) +>>> color1 = filter_color("random") +>>> random.seed(3) +>>> color2 = filter_color("RANDOM") +>>> color1 == color2 +False +>>> random.seed(2) +>>> color1 = random_hex_color_gen() +>>> random.seed(3) +>>> color2 = random_hex_color_gen() +>>> color1 == color2 +False +>>> len(color1) +7 +>>> len(color2) +7 >>> filter_color(2) >>> filter_color(4) >>> filter_size(2) >>> filter_size((2, 'test')) >>> filter_size((2, 3.5)) (2, 3.5) +>>> filter_projection(2) +>>> filter_projection(Projection.POLAR) +'polar' +>>> random.seed(2) +>>> projection1 = filter_projection(Projection.RANDOM) +>>> random.seed(3) +>>> projection2 = filter_projection(Projection.RANDOM) +>>> projection1 == projection2 +False >>> distance_calc("test","test1") 1 >>> distance_calc("te1st","test") diff --git a/test/nft_upload_test.py b/test/nft_upload_test.py index 203580f..b97ba4f 100644 --- a/test/nft_upload_test.py +++ b/test/nft_upload_test.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- """ >>> import os ->>> import math ->>> import random >>> import time >>> from samila import GenerativeImage, Projection >>> from samila.params import VALID_COLORS @@ -11,10 +9,7 @@ >>> g.plot() >>> NFT_STORAGE_API_KEY = os.environ["NFT_STORAGE_API_KEY"] >>> g.generate() ->>> random_projection = random.choice(list(Projection)) ->>> random_color = random.choice(VALID_COLORS) ->>> random_bgcolor = random.choice(VALID_COLORS) ->>> g.plot(projection=random_projection,color=random_color,bgcolor=random_bgcolor) +>>> g.plot(projection=Projection.RANDOM, color="random", bgcolor="random") >>> counter = 0 >>> try_limit = 10 >>> status = False diff --git a/test/overall_test.py b/test/overall_test.py index 704fe0f..1da3fc9 100644 --- a/test/overall_test.py +++ b/test/overall_test.py @@ -54,9 +54,46 @@ True True >>> from samila import GenerativeImage, Projection >>> g.plot(projection=Projection.POLAR, color='red', bgcolor='black') +>>> g.color +'red' +>>> g.bgcolor +'black' +>>> g.plot(projection=Projection.POLAR, color='rod', bgcolor='blacc') +>>> g.color +'red' +>>> g.bgcolor +'black' +>>> g.plot(projection=Projection.POLAR, color="#EEE245", bgcolor="#000000") +>>> g.projection +'polar' +>>> g.color +'#EEE245' +>>> g.bgcolor +'#000000' >>> g.plot(projection=Projection.POLAR, color=(.1, .2, .8)) +>>> g.color +(0.1, 0.2, 0.8) >>> g.plot(bgcolor=(.1, .2, .8), spot_size=0.1) >>> g.plot(size=(20, 20)) +>>> g.size +(20, 20) +>>> g.plot(alpha=0.5, linewidth=2.2) +>>> g.alpha +0.5 +>>> g.linewidth +2.2 +>>> random.seed(2) +>>> g.plot(color="random", bgcolor="random", projection=Projection.RANDOM) +>>> color1, bgcolor1, projection1 = g.color, g.bgcolor, g.projection +>>> random.seed(3) +>>> g.plot(color="random", bgcolor="random", projection=Projection.RANDOM) +>>> color2, bgcolor2, projection2 = g.color, g.bgcolor, g.projection +>>> color1 == color2 +False +>>> bgcolor1 == bgcolor2 +False +>>> projection1 == projection2 +False >>> result = g.nft_storage(api_key="") >>> result['status'] False @@ -169,7 +206,24 @@ False [1] >>> g_ = GenerativeImage() >>> del(g) +>>> del g_.data1 >>> del(g_) +>>> g1 = GenerativeImage() +>>> function1 = eval("lambda x, y:" + g1.function1_str) +>>> function2 = eval("lambda x, y:" + g1.function2_str) +>>> g2 = GenerativeImage(function1=function1, function2=function2) +>>> g1.generate(seed=22) +>>> g2.generate(seed=22) +>>> is_same_data(g1.data1, g2.data1) +True +>>> is_same_data(g1.data2, g2.data2) +True +>>> len(g1.data1) > 0 +True +>>> len(g1.data2) > 0 +True +>>> del(g1) +>>> del(g2) >>> os.remove("test.png") >>> os.remove("test2.png") >>> os.remove("data.json") diff --git a/test/warning_test.py b/test/warning_test.py index 988766f..99fea9a 100644 --- a/test/warning_test.py +++ b/test/warning_test.py @@ -20,6 +20,9 @@ True ... json.dump({'f1': 'x', 'f2': 'y', 'matplotlib_version': '0'}, fp) >>> with warns(RuntimeWarning, match=r"Source matplotlib version(.*) is different from yours, plots may be different."): ... g = GenerativeImage(config=open('config.json', 'r')) +>>> g = GenerativeImage(lambda x, y: 1 / x, lambda x, y: 1 / (y - 1)) +>>> with warns(RuntimeWarning, match=r"The given functions are undefined at some points. Your plot may not be complete."): +... g.generate(start=0, stop=2, step=0.1) >>> os.remove('data.json') >>> os.remove('config.json') """