Skip to content

Commit

Permalink
types + another artwork + palette + readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Mirtia committed Jul 12, 2023
1 parent f62d826 commit 05caa30
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 31 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ cx
weights
downloaded
styles
*.zip
*.zip
models
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
# Artstyle Detector

To make an artstyle detector, I first had to find enough images for the training.
The following are the list of the sources I *used* or at least tried to use to gather a bunch of images.

## Google Images

The first approach may not always be the best (it's never). To crawl Google Images I had to use [Google API](https://console.cloud.google.com/apis/library), make a custom [Programmable Search engine](https://developers.google.com/custom-search) and also be under a limit of requests. I didn't look at it further but made a test run.

## Wikimedia

Example of wikimedia categories *Impressionist_paintings*. The way I crawled this was pretty stupid but honest (bs4s, asyncio classic). Why? I could try an approach using SPARQL queries at [Wikidata Query Service](https://query.wikidata.org/). I may implement it in the future.

## Wikiart

## Google Images
As the wikimedia images were not enough ?!, I tried another approach. I found Wikiart which seemed to have an adequate amount of images at first glance. Luckily, there was a [repository](https://github.com/asahi417/wikiart-image-dataset) working on this, so I downloaded the datasets by the links provided.

## Detecting style

To train the model I used [ImageAI](https://github.com/OlafenwaMoses/ImageAI/tree/master). After writing some functionalities to construct the directory structure, training was pretty straightforward.

I was not satisfied with the accuracy it achieved but it was probably because of the dataset similarities. I'll try to use more categories in the future.

Truth is, a painting can have multiple arstyles. I tested some pictures, as welll as some of my own, and the results were okayish, but with not high confidence most of the times.

## Objects prevalent in each style

Not yet implemented

## Palette extractor

I used [color-thief](https://github.com/fengsp/color-thief-py) to extract paletters and dominant colors from the images. I am still not sure what to do with this information but it was cool. I thought about organizing the images to clusters according to their palette range.
34 changes: 18 additions & 16 deletions src/crawler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@
# Eventually, I will put more functionalities in this class, this is just not a class.
class Crawler:

def __init__(self, output_dir, input_file, prefix):
def __init__(self, output_dir: str, input_file: str, prefix: str):
self.output_dir = self.dir_exists(output_dir, prefix)
self.input_file = self.file_exists(input_file)
self.prefix = prefix


@staticmethod
def dir_exists(dir, prefix=""):
def dir_exists(dir: str, prefix: str="") -> str:
"""
This is a static method in Python that checks if a directory exists and creates it if it
doesn't.
The `dir_exists` function checks if a directory exists and creates it if it doesn't, then returns
the directory path.
:param dir: The directory path where the new directory will be created
:param prefix: The prefix parameter is an optional string that can be added to the directory
path. It is used to create a subdirectory within the main directory specified by the dir
parameter. If no prefix is provided, the function will simply create the directory specified by
the dir parameter
:return: the directory path that was created or already existed.
:param dir: The "dir" parameter is a string that represents the directory path where you want to
check if a directory exists or create a new directory
:type dir: str
:param prefix: The "prefix" parameter is a string that is added to the directory path before
checking if it exists. It is optional and its default value is an empty string
:type prefix: str
:return: the directory path.
"""
dir_path = os.path.join(dir, prefix)
if not os.path.exists(dir_path):
Expand All @@ -30,14 +32,14 @@ def dir_exists(dir, prefix=""):


@staticmethod
def file_exists(file):
def file_exists(file: str) -> str:
"""
This is a static method in Python that checks if a file exists and raises an error if it
doesn't.
The `file_exists` function checks if a file exists and returns the file name if it does,
otherwise it raises a `FileNotFoundError` with an error message.
:param file: The parameter "file" is a string representing the file path of the file being
checked for existence
:return: the input file if it exists.
:param file: The `file` parameter is a string that represents the file path or file name
:type file: str
:return: The file path is being returned.
"""
if not os.path.isfile(file):
raise FileNotFoundError("Error: Input file does not exist. Please provide an existing file.")
Expand Down
4 changes: 2 additions & 2 deletions src/google_crawler.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ def read_input_file(self):
with open(self.input_file, mode="r", encoding="utf-8") as f:
self.queries = f.read().split("\n")

def read_search_parameters(self, parameters_file):
def read_search_parameters(self, parameters_file: str):
"""
This function reads search parameters from a JSON file and stores them in an object attribute.
:param parameters_file: The file path of the JSON file containing the search parameters
"""
with open(parameters_file, mode="r", encoding="utf-8") as f:
self.search_parameters = json.load(f)
return json.load(f)
4 changes: 2 additions & 2 deletions src/image_upscaler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
# given directory. Not sure if this will be needed, it was just cool to try out.
class ImageUpscaler:

def __init__(self, input_dir):
def __init__(self, input_dir: str):
self.input_dir = input_dir
self.model = self.initialize_model()

def initialize_model(self):
def initialize_model(self) -> RealESRGAN:
"""
The function initializes a RealESRGAN model and loads pre-trained weights.
:return: an instance of the RealESRGAN model that has been initialized and loaded with
Expand Down
16 changes: 9 additions & 7 deletions src/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class StyleModel:
TRAIN_SET_SIZE = 1000
TEST_SET_SIZE = 500

def __init__(self, input_dir, output_dir, skip=True):
def __init__(self, input_dir: str, output_dir: str, skip: bool=True):
self.input_dir = input_dir
self.output_dir = output_dir
if not skip:
Expand Down Expand Up @@ -61,13 +61,15 @@ def train(self):
self.model_trainer.setDataDirectory(self.output_dir)
self.model_trainer.trainModel(num_experiments=100, batch_size=32)

def __get_paths(self, path):
def __get_paths(self, path: str) -> tuple:
"""
The function "__get_paths" takes a path as input and returns the paths of a model file and a JSON
file within that path.
The function `__get_paths` takes a directory path as input and returns the paths of a model file
and a JSON file within that directory.
:param path: The `path` parameter is the directory path where the files are located
:return: two variables: model_path and json_path.
:param path: The `path` parameter is a string that represents the directory path where the files
are located
:type path: str
:return: a tuple containing the paths of the model file and the JSON file.
"""
model_path, json_path = None, None
if os.path.isdir(path):
Expand All @@ -81,7 +83,7 @@ def __get_paths(self, path):
return model_path, json_path


def classify(self, input_img):
def classify(self, input_img: str):
"""
The `classify` function uses a pre-trained ResNet50 model to classify an input image and prints
the top 10 predictions along with their probabilities.
Expand Down
15 changes: 15 additions & 0 deletions src/object_detector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from imageai.Detection import ObjectDetection

class ObjectDetector:

def __init__(self, input):
self.input = input
self.detector = ObjectDetection()
self.detector.setModelTypeAsYOLOv3()
self.detector.setModelPath()
# TODO: ...

def test(self):
pass


95 changes: 95 additions & 0 deletions src/palette_extractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import os

import matplotlib.pyplot
from colorthief import ColorThief

def rgb_to_hex(rgb: tuple) -> str:
"""
The function `rgb_to_hex` takes a tuple representing an RGB color and returns the corresponding
hexadecimal color code.
:param rgb: The `rgb` parameter is a tuple containing three integers representing the red, green,
and blue values of a color
:type rgb: tuple
:return: The function `rgb_to_hex` returns a string representation of the RGB color in hexadecimal
format.
"""
return "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2])

def hex_to_rgb(hex: str) -> tuple:
"""
The function `hex_to_rgb` takes a hexadecimal color code as input and returns the corresponding RGB
values as a tuple.
:param hex: A string representing a hexadecimal color code
:type hex: str
:return: The function `hex_to_rgb` returns a tuple containing the RGB values corresponding to the
given hexadecimal color code.
"""
hex = hex.lstrip("#")
return tuple([int(hex[i : i + 2], 16) for i in range(0, len(hex), 2)])

def get_color_console(rgb: tuple) -> str:
"""
The function `get_color_console` returns a string that represents a console color based on the given
RGB values.
:param rgb: The `rgb` parameter is a tuple that represents the RGB values of a color. It should
contain three integers, where the first integer represents the red value, the second integer
represents the green value, and the third integer represents the blue value
:type rgb: tuple
:return: a string that represents a console color using the RGB values provided.
"""
return f"\033[48:2::{rgb[0]}:{rgb[1]}:{rgb[2]}m \033[49m"

def print_colors_to_console(hex_list: list) -> str:
"""
The function takes a list of hexadecimal color codes and returns a string with each color code
printed on a new line.
:param hex_list: A list of hexadecimal color codes
:type hex_list: list
:return: a string that contains all the elements of the input list, separated by newlines.
"""
return "\n".join(hex_list)

def plot_colors(hex_list: list, dominant_color: tuple, side: int, output:str):
"""
The function `plot_colors` takes a list of hexadecimal color codes, a dominant color as a tuple, a
side length for the color patches, and an output directory path, and plots the colors as rectangles
in a palette, saves the plot as an image file with the dominant color as the filename, and displays
the plot.
:param hex_list: The `hex_list` parameter is a list of hexadecimal color codes. These codes
represent the colors that will be plotted in the palette
:type hex_list: list
:param dominant_color: The `dominant_color` parameter is a tuple representing the RGB values of the
dominant color in the `hex_list`
:type dominant_color: tuple
:param side: The parameter "side" represents the length of each side of the rectangle that
represents a color in the plot. It is used to determine the size of each color patch in the plot
:type side: int
:param output: The `output` parameter is a string that specifies the directory where the output
image file will be saved
:type output: str
"""
figure = matplotlib.pyplot.figure()
palette = figure.add_subplot()
for i in range(len(hex_list)):
palette.add_patch(matplotlib.patches.Rectangle((side * i, 0), side, side, color=hex_list[i]))
matplotlib.pyplot.xlim([0, len(hex_list) * side])
matplotlib.pyplot.ylim([0, side])
matplotlib.pyplot.axis("off")
matplotlib.pyplot.tight_layout()

os.makedirs(output, exist_ok=True)
matplotlib.pyplot.savefig(os.path.join(output, rgb_to_hex(dominant_color) + ".jpg" ))
matplotlib.pyplot.show()

# Still not sure how to use this code, may `sort` images by their color scheme
# Example usage:
# color_thief = ColorThief("tests/monet_impressionism.jpg")
# dominant_color = color_thief.get_color(quality=1)
# palette = color_thief.get_palette(color_count=6)
# hex_palette = [rgb_to_hex(color) for color in palette]
# plot_colors(hex_palette, dominant_color, side=50, output="output/palette")
4 changes: 2 additions & 2 deletions src/wikimedia_crawler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class WikimediaCrawler(Crawler):

url = "https://commons.wikimedia.org/w/api.php"

def __init__(self, input_file, output_dir, prefix):
def __init__(self, input_file: str, output_dir: str, prefix: str):
super().__init__(input_file, output_dir, prefix)
self.session = requests.Session()
self.read_input_file()
Expand Down Expand Up @@ -62,7 +62,7 @@ def read_input_file(self):
with open(self.input_file, mode="r", encoding="utf-8") as f:
self.categories = f.read().split("\n")[:-1]

async def download_image(self, url, title, output_dir):
async def download_image(self, url: str, title: str, output_dir: str):
"""
This is an async function that downloads an image from a given URL and saves it to a specified
output directory.
Expand Down
Binary file added tests/my_artwork_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 05caa30

Please sign in to comment.