Skip to content

Instantly share code, notes, and snippets.

@flxai
Created February 3, 2023 19:30
Show Gist options
  • Save flxai/497ce622ad6c28cc08da007fdd91ba6c to your computer and use it in GitHub Desktop.
Save flxai/497ce622ad6c28cc08da007fdd91ba6c to your computer and use it in GitHub Desktop.
Recreate Salavon's piece 'Every Playboy Centerfold, The 1970s (normalized)' from 2002
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "90f14a7f-5f5a-4c92-bc5d-2e779aecd7bf",
"metadata": {},
"source": [
"# Recreate Salavon's piece 'Every Playboy Centerfold, The 1970s (normalized)' from 2002\n",
"This notebook recreates [Salavon's piece](http://salavon.com/work/everyplayboycenterfolddecades/image/216/), although not faithfully.\n",
"Please also see the slides of the talk that goes along with it:\n",
"\n",
"[https://s.flx.ai/2022/digital-art-forgery/](https://s.flx.ai/2022/digital-art-forgery/)"
]
},
{
"cell_type": "markdown",
"id": "b69f4596-fb6b-4a7c-a329-7dac1ab26b3f",
"metadata": {},
"source": [
"## Imports"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4799c2da-4c24-4cb1-abe9-2e2271d1dcf4",
"metadata": {},
"outputs": [],
"source": [
"import regex as re\n",
"import pylab as plt\n",
"import pandas as pd\n",
"\n",
"from glob import glob\n",
"from IPython import display\n",
"from pathlib import Path\n",
"from PIL import Image\n",
"\n",
"from tqdm.auto import tqdm"
]
},
{
"cell_type": "markdown",
"id": "6758c2c0-187e-4105-847e-4a0d79d2952e",
"metadata": {},
"source": [
"## Settings"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f9bdd68-ae9f-4cd3-90af-bbf9a400f1c8",
"metadata": {},
"outputs": [],
"source": [
"# Download centerfolds first, e.g. from:\n",
"# https://www.1377x.to/torrent/2758469/Playboy-Centerfolds-Ultra-High-Quality-The-Full-1953-2015-Year/"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f9bdd68-ae9f-4cd3-90af-bbf9a400f1c8",
"metadata": {},
"outputs": [],
"source": [
"data_dir = 'data/centerfolds'"
]
},
{
"cell_type": "markdown",
"id": "4ad567f9-f4d7-4006-818a-acb169c76028",
"metadata": {},
"source": [
"## Extract metadata"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0914be0a-d041-43d4-bc0f-dacecc1a7b6d",
"metadata": {},
"outputs": [],
"source": [
"# img_files = sorted(glob(f'{data_dir}/**/*.jpg'))\n",
"img_files = sorted(glob(f'{data_dir}/*197?*/*.jpg'))\n",
"# img_files = sorted(glob(f'{data_dir}/*198?*/*.jpg'))\n",
"albums = list(set([\n",
" Path(c).parent.stem\n",
" for c in img_files\n",
"]))\n",
"print(f\"Found {len(img_files)} number of images. This relates to {len(albums)} albums.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f598997-725a-494a-8eee-23a41993fae2",
"metadata": {},
"outputs": [],
"source": [
"images = []\n",
"widths = []\n",
"heights = []\n",
"for img_file in tqdm(img_files):\n",
" with Image.open(img_file) as im:\n",
" width, height = im.size\n",
" widths.append(width)\n",
" heights.append(height)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1ad6f340-e69f-4886-a9d9-f0bce3144350",
"metadata": {},
"outputs": [],
"source": [
"regex_year = re.compile(r'([0-9]{4})')\n",
"regex_month = re.compile(r'([0-9]{2})_')\n",
"data = {\n",
" 'file_name': [Path(f).stem for f in img_files],\n",
" 'file_path': [Path(f) for f in img_files],\n",
" 'width': widths,\n",
" 'height': heights,\n",
" # 'album': [Path(f).parent.stem for f in img_files],\n",
" 'year': [re.findall(regex_year, Path(f).parent.stem)[0] for f in img_files],\n",
" 'month': [re.findall(regex_month, Path(f).stem)[0] for f in img_files],\n",
"}\n",
"df = pd.DataFrame(columns=data.keys(), data=data)\n",
"model_names = df['file_name'].str.extract(r'([A-Z][a-z]+)[ _]?([A-Z][a-z]+)(?:_CF)?$')\n",
"df['model_name'] = model_names[0]\n",
"df['model_surname'] = model_names[1]\n",
"df = df.drop('file_name', axis=1)\n",
"# Select only portrait formats\n",
"len_df = len(df)\n",
"df = df[df.width < df.height]\n",
"print(f'Removed {len_df - len(df)} images in landscape format.')\n",
"# Reindex\n",
"df = df.reset_index()\n",
"# Sort columns \n",
"df = df[['year', 'month', 'model_name', 'model_surname', 'width', 'height', 'file_path']]\n",
"df"
]
},
{
"cell_type": "markdown",
"id": "4691bf09-06f7-4d3d-beff-e30c94c86e52",
"metadata": {},
"source": [
"## Select images by resolution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1dc4246-675b-477c-887f-89cb40eedf62",
"metadata": {},
"outputs": [],
"source": [
"import tabulate\n",
"\n",
"from IPython.display import display, HTML, Markdown"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4845fda5-adb7-4085-9672-35481c91d4e7",
"metadata": {},
"outputs": [],
"source": [
"min_w = df.width.min()\n",
"min_h = df.height.min()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bcb79942-6fc1-4e3b-af4f-604023a86d2f",
"metadata": {},
"outputs": [],
"source": [
"bin_count = 50\n",
"mode_w = df.width.mode()[0]\n",
"mode_h = df.height.mode()[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "96f4e9ab-bc59-4e39-b447-4da419b68fee",
"metadata": {},
"outputs": [],
"source": [
"len_min_or = len(df[(df.height >= min_h) | (df.width >= min_w)])\n",
"len_min_and = len(df[(df.height >= min_h) & (df.width >= min_w)])\n",
"len_mode_or = len(df[(df.height >= mode_h) | (df.width >= mode_w)])\n",
"len_mode_and = len(df[(df.height >= mode_h) & (df.width >= mode_w)])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e89c9944-7bc6-461b-addc-56708f1dd521",
"metadata": {},
"outputs": [],
"source": [
"table_headers = [\"Selector\", \"Gate\", \"Width\", \"Height\", \"Selected\", \"Total\"]\n",
"table_data = [\n",
" [\"Min\", \"OR\", f\"{min_w}\", f\"{min_h}\", f\"{len_min_or}\", f\"{len(df)}\"],\n",
" [\"Min\", \"AND\", f\"{min_w}\", f\"{min_h}\", f\"{len_min_and}\", f\"{len(df)}\"],\n",
" [\"Mode\", \"OR\", f\"{mode_w}\", f\"{mode_h}\", f\"{len_mode_or}\", f\"{len(df)}\"],\n",
" [\"Mode\", \"AND\", f\"{mode_w}\", f\"{mode_h}\", f\"{len_mode_and}\", f\"{len(df)}\"],\n",
"]\n",
"table = tabulate.tabulate(table_data, headers=table_headers, tablefmt='html')\n",
"table"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a648d767-f109-44e6-8d05-3258b13bee12",
"metadata": {},
"outputs": [],
"source": [
"fig, axs = plt.subplots(1, 2)\n",
"axs[0].hist(df.width, bins=bin_count)\n",
"axs[0].set_title(\"Width\")\n",
"axs[0].axvline(mode_w, label=f\"Mode @ {mode_w}\", color='C1')\n",
"axs[0].axvline(min_w, label=f\"Min @ {min_w}\", color='C2')\n",
"axs[0].legend()\n",
"axs[1].hist(df.height, bins=bin_count)\n",
"axs[1].set_title(\"Height\")\n",
"axs[1].axvline(mode_h, label=f\"Mode @ {mode_h}\", color='C1')\n",
"axs[1].axvline(min_h, label=f\"Min @ {min_h}\", color='C2')\n",
"axs[1].legend()\n",
"plt.suptitle(\"Occurences of image sizes per axis\")\n",
"\n",
"display(HTML(table))\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "5e9a62eb-4a25-4f88-a5cd-68b707e79e73",
"metadata": {},
"source": [
"### Get common image ratio"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a0fdbd19-0889-42b7-9f68-1965ad1c5ca9",
"metadata": {},
"outputs": [],
"source": [
"from fractions import Fraction"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24ebb133-2c46-48a2-a983-0b842153242c",
"metadata": {},
"outputs": [],
"source": [
"mode_ratio = Fraction(mode_w, mode_h)\n",
"ratio_w, ratio_h = mode_ratio.numerator, mode_ratio.denominator"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e9902fbe-1064-485b-89e4-08bf5dba7ab3",
"metadata": {},
"outputs": [],
"source": [
"display(Markdown(f\"(ℹ) Images are commonly shot in {ratio_w}:{ratio_h} format.\"))"
]
},
{
"cell_type": "markdown",
"id": "6e298daa-59f9-4c8f-b25a-866bc01328dd",
"metadata": {},
"source": [
"## Pre-process images"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7219bbc-e3a3-4d2c-a146-252e03be2782",
"metadata": {},
"outputs": [],
"source": [
"def load_image_size(row, mode_w, mode_h, ratio_w, ratio_h):\n",
" ratio = ratio_w / ratio_h\n",
" fn = Path(row.file_path).stem\n",
" im = Image.open(row.file_path)\n",
" w, h = im.size\n",
" r = w / h\n",
" \n",
" # Rescale if size does not fit\n",
" if w != mode_w or h != mode_h:\n",
" assert ratio != r\n",
" # Select side depending on ratio\n",
" if ratio > r:\n",
" # Image taller, scale width\n",
" new_h = int(mode_w / r)\n",
" im = im.resize((mode_w, new_h))\n",
" w, h = im.size\n",
" # Crop height\n",
" crop_h = h - mode_h\n",
" crop_top = int(crop_h // 2)\n",
" crop_bottom = crop_top if crop_h % 2 == 0 else crop_top + 1\n",
" im = im.crop((0, crop_top, w, h - crop_bottom))\n",
" elif ratio < r:\n",
" # Image wider, scale height\n",
" new_w = int(mode_h * r)\n",
" im = im.resize((new_w, mode_h))\n",
" w, h = im.size\n",
" # Crop width\n",
" crop_w = w - mode_w\n",
" crop_left = int(crop_w // 2)\n",
" crop_right = crop_left if crop_w % 2 == 0 else crop_left + 1\n",
" im = im.crop((crop_left, 0, w - crop_right, h))\n",
" else:\n",
" print(f\"Rescaling using same ratio... yet untested\") \n",
" im = im.resize((mode_w, mode_h))\n",
" return im"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d113435-bfcf-4c80-90f0-0f5c1876008f",
"metadata": {},
"outputs": [],
"source": [
"ims = {}\n",
"for idx, row in tqdm(df.iterrows(), total=len(df)):\n",
" im = load_image_size(row, mode_w, mode_h, ratio_w, ratio_h)\n",
" ims[row.file_path] = im"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4361b5a2-4608-418a-8b2c-ca057bf3c830",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "daab86a1-b8ca-4c62-9bbe-5e707853a3dd",
"metadata": {},
"outputs": [],
"source": [
"mode_w, min_w"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b060b332-15dd-424e-bc75-8bb226215356",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "4a6c9a16-726e-4cae-b8b4-63c7e6b0b393",
"metadata": {},
"outputs": [],
"source": [
"new_width, new_height = (w / 3, h / 2)\n",
"\n",
"left = (width - new_width) / 2\n",
"top = (height - new_height) / 2\n",
"right = (width + new_width) / 2\n",
"bottom = (height + new_height) / 2\n",
"\n",
"# Crop the center of the image\n",
"im = im.crop((left, top, right, bottom))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d283fee3-06ca-4b37-91e5-f80be2f5b023",
"metadata": {},
"outputs": [],
"source": [
"im.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34462965-948d-4acf-ac98-da4f67502b6e",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment