Merge remote-tracking branch 'origin/main' into main
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +3 -0
- README.md +28 -19
- gitlab-ci.yml +11 -0
- {scripts → jupyter_notebooks}/0_Scraping_image_metadata.ipynb +42 -8
- jupyter_notebooks/0_Scraping_model_metadata.ipynb +643 -0
- scripts/Section_2_Image_grid.ipynb → jupyter_notebooks/Section_1_Figure_1_image_grid.ipynb +2 -2
- scripts/Section_6-1_Histograms.ipynb → jupyter_notebooks/Section_2-2-2_Figure_3_histogram_monthly_images_nsfw_levels.ipynb +2 -2
- scripts/Section_6-2_Demographics.ipynb → jupyter_notebooks/Section_2-2-2_Figure_4_Demographic_patterns_in_gen_images.ipynb +16 -0
- scripts/Section_6-3_Models.ipynb → jupyter_notebooks/Section_2-3-2_top_10_most_popular_checkpoints.ipynb +0 -0
- scripts/Section_6-4_Top_models.ipynb → jupyter_notebooks/Section_2-3-3_Figure_7_top_30_adapters.ipynb +0 -0
- jupyter_notebooks/Section_2-3-4__Figure_8_Deepfake_victims.ipynb +668 -0
- jupyter_notebooks/Section_2-3-4__Figure_8a_sunburst_gender.ipynb +127 -0
- jupyter_notebooks/Section_2-3-4__Figure_8b_sunburst_profession.ipynb +123 -0
- jupyter_notebooks/Section_2-3_Figure_5_co-occurence_promotional_tags.ipynb +314 -0
- jupyter_notebooks/Section_2-4_Figure_9_Training_tags_Sankey.ipynb +203 -0
- jupyter_notebooks/Section_2-4_Figure_9_ectract_LoRA_metadata_v2.ipynb +400 -0
- jupyter_notebooks/Section_2-4_Figure_9_extract_LoRA_metadata.ipynb +557 -0
- {scripts → jupyter_notebooks}/Section_3-3-4_deepfakes.ipynb +0 -0
- jupyter_notebooks/SuppM_Figure_13_Danbooru_categories.ipynb +141 -0
- jupyter_notebooks/SuppM_Figure_S12_asset_types.ipynb +129 -0
- jupyter_notebooks/SuppM_Figure_S13_Danbooru_Taxonomy.ipynb +1848 -0
- jupyter_notebooks/SuppM_Figure_S14_co-occurence_training_data.ipynb +152 -0
- misc/api_keys_example.txt +0 -2
- misc/danbooru/tag_info_test/wombat_tag_info.json +27 -0
- misc/deepseek_query_index.txt +1 -0
- misc/deletemememe.txt +0 -0
- misc/lists/countries.csv +250 -0
- misc/lists/danbooru.json +0 -0
- misc/lists/mapped_professions.csv +1074 -0
- misc/lists/professions.csv +36 -0
- plots/2024-06_image_contributions.svg +0 -0
- plots/Figure_12.svg +1936 -0
- plots/Sec-6-5_lora_tags_categories.svg +0 -2350
- plots/Sec_6-5_taggers.svg +0 -777
- plots/Section_6-7_model_contributions_sankey.svg +0 -1
- plots/models_nsfw_monthly_histogram_with_quarters.svg +0 -2108
- public/Figure_13.html +166 -0
- public/Figure_8a_sunburst.html +223 -0
- public/Figure_8b_sunburst.html +254 -0
- public/Figure_9.html +226 -0
- public/Figure_9_old.html +213 -0
- public/csv/sankey_data.csv +22 -0
- public/figure_15.html +325 -0
- public/figure_5.html +442 -0
- public/img/booru_tags.svg +0 -0
- public/img/japan.svg +1776 -0
- plots/grid10x120.png → public/img/sankey.png +2 -2
- public/img/tag_clusters.png +3 -0
- public/img/training_tags.png +3 -0
- public/index.html +62 -0
.gitignore
CHANGED
|
@@ -3,5 +3,8 @@ ext
|
|
| 3 |
ARCHIVE
|
| 4 |
misc/api_keys.txt
|
| 5 |
misc/training_tags_categories
|
|
|
|
|
|
|
| 6 |
scripts/ARCHIVE
|
| 7 |
scripts/CEMETARY
|
|
|
|
|
|
| 3 |
ARCHIVE
|
| 4 |
misc/api_keys.txt
|
| 5 |
misc/training_tags_categories
|
| 6 |
+
misc/credentials/*
|
| 7 |
+
misc/credentials
|
| 8 |
scripts/ARCHIVE
|
| 9 |
scripts/CEMETARY
|
| 10 |
+
cemetary
|
README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# Code for the Paper titled "Perpetual Misogyny: How Gendered Tropes Shape Text-To-Image AI Personalization"
|
| 2 |
|
| 3 |
|
| 4 |
|
|
@@ -7,28 +7,37 @@
|
|
| 7 |
|
| 8 |
```
|
| 9 |
CIVITAI_VISUALIZATIONS/
|
| 10 |
-
├── .virtual_documents/
|
| 11 |
-
├── data/
|
| 12 |
-
│ ├── subset1/
|
| 13 |
│ ├── subset2/
|
| 14 |
│ └── ...
|
| 15 |
-
├── misc/
|
| 16 |
-
|
| 17 |
-
├──
|
| 18 |
-
|
| 19 |
-
│ ├──
|
| 20 |
-
│ ├──
|
| 21 |
-
│ ├──
|
| 22 |
-
│ ├──
|
| 23 |
-
│ ├──
|
| 24 |
-
│ ├──
|
| 25 |
-
│
|
| 26 |
-
│ ├──
|
| 27 |
-
│ ├──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
├── .gitignore
|
| 29 |
├── .gitmodules
|
| 30 |
-
├── README.md
|
| 31 |
-
└── requirements.txt
|
| 32 |
```
|
| 33 |
|
| 34 |
# Project Setup
|
|
|
|
| 1 |
+
# Code for the Paper titled ["Perpetual Misogyny: How Gendered Tropes Shape Text-To-Image AI Personalization"](http://arxiv.org/pdf/2505.04600)
|
| 2 |
|
| 3 |
|
| 4 |
|
|
|
|
| 7 |
|
| 8 |
```
|
| 9 |
CIVITAI_VISUALIZATIONS/
|
| 10 |
+
├── .virtual_documents/ # Temporary files from Jupyter
|
| 11 |
+
├── data/ # Final curated datasets
|
| 12 |
+
│ ├── subset1/ # Specific data splits or versions
|
| 13 |
│ ├── subset2/
|
| 14 |
│ └── ...
|
| 15 |
+
├── misc/
|
| 16 |
+
│ └── credentials/ # API keys and sensitive config (excluded from versioning)
|
| 17 |
+
├── plots/ # Output plots and figures used in the paper
|
| 18 |
+
├── ├── jupyter_notebooks/ # Main analysis notebooks
|
| 19 |
+
│ ├── 0_Scraping_image_metadata.ipynb # Scrapes CivitAI metadata via API
|
| 20 |
+
│ ├── Section_1_Figure_1_image_grid.ipynb # Grid of sample images
|
| 21 |
+
│ ├── Section_3-2-1_Figure_3_histogram.ipynb # Histogram of upload trends
|
| 22 |
+
│ ├── Section_3-2-1_Figure_4_Mivolo.ipynb # Model activity plot (Mivolo-focused)
|
| 23 |
+
│ ├── Section_3-3-1_Figure_5_tags.ipynb # Tag frequency and usage visualizations
|
| 24 |
+
│ ├── Section_3-3-3_download_popular_models.ipynb # Download models for analysis
|
| 25 |
+
│ ├── Section_3-3-3_Figure_6.ipynb # Promotional tag usage patterns
|
| 26 |
+
│ ├── Section_3-3-4_Figure_8a.ipynb # Ranking of popular models
|
| 27 |
+
│ ├── Section_3-3-4_Figure_8b.ipynb # Continuation of model rankings
|
| 28 |
+
│ ├── Section_3-3-4_Figure_9_Sankey.ipynb # Sankey diagram: user-model contributions
|
| 29 |
+
│ ├── Section_3-3-4_LLM_annotation.ipynb # Annotations using large language models
|
| 30 |
+
│ ├── Section_3-4_extract_LoRA_metadata.ipynb # LoRA metadata extraction
|
| 31 |
+
│ ├── SuppM_Figure_12_Danbooru_Taxonomy.ipynb # Danbooru tag taxonomy: visualization
|
| 32 |
+
│ ├── SuppM_Figure_13_Danbooru_taxonomy.ipynb # Tag grouping and structure
|
| 33 |
+
│ └── SuppM_Figure_13.ipynb # Supplementary figure generation
|
| 34 |
+
├── misc/ # Utility scripts and API credentials (excluded)
|
| 35 |
+
├── plots/ # Output plots and visualizations
|
| 36 |
+
├── public/ # Optional public-facing files
|
| 37 |
├── .gitignore
|
| 38 |
├── .gitmodules
|
| 39 |
+
├── README.md # This file
|
| 40 |
+
└── requirements.txt # Python dependencies
|
| 41 |
```
|
| 42 |
|
| 43 |
# Project Setup
|
gitlab-ci.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# .gitlab-ci.yml
|
| 3 |
+
pages:
|
| 4 |
+
stage: deploy
|
| 5 |
+
script:
|
| 6 |
+
- echo "Nothing to build, serving public/"
|
| 7 |
+
artifacts:
|
| 8 |
+
paths:
|
| 9 |
+
- public
|
| 10 |
+
only:
|
| 11 |
+
- main
|
{scripts → jupyter_notebooks}/0_Scraping_image_metadata.ipynb
RENAMED
|
@@ -79,7 +79,7 @@
|
|
| 79 |
},
|
| 80 |
{
|
| 81 |
"cell_type": "code",
|
| 82 |
-
"execution_count":
|
| 83 |
"id": "f8decb63-43f5-4731-823d-94632eee7618",
|
| 84 |
"metadata": {
|
| 85 |
"execution": {
|
|
@@ -108,7 +108,7 @@
|
|
| 108 |
},
|
| 109 |
{
|
| 110 |
"cell_type": "code",
|
| 111 |
-
"execution_count":
|
| 112 |
"id": "4b2426c3-96a0-468e-b6dc-78dea9c3e92b",
|
| 113 |
"metadata": {
|
| 114 |
"execution": {
|
|
@@ -140,7 +140,7 @@
|
|
| 140 |
"outputs": [],
|
| 141 |
"source": [
|
| 142 |
"# Define the input timestamp in ISO 8601 format\n",
|
| 143 |
-
"input_timestamp = \"2025-
|
| 144 |
"\n",
|
| 145 |
"# Function to convert an ISO 8601 date string to a Unix timestamp in milliseconds with a 2-hour offset \n",
|
| 146 |
"def iso_to_timestamp(iso_str):\n",
|
|
@@ -304,7 +304,7 @@
|
|
| 304 |
},
|
| 305 |
{
|
| 306 |
"cell_type": "code",
|
| 307 |
-
"execution_count":
|
| 308 |
"id": "7c89cb68-983b-47df-a028-e02d7ca0829d",
|
| 309 |
"metadata": {
|
| 310 |
"execution": {
|
|
@@ -317,7 +317,7 @@
|
|
| 317 |
},
|
| 318 |
"outputs": [],
|
| 319 |
"source": [
|
| 320 |
-
"
|
| 321 |
]
|
| 322 |
},
|
| 323 |
{
|
|
@@ -1284,12 +1284,46 @@
|
|
| 1284 |
"id": "49e35088-8b2e-4189-83e1-3098d55dcad2",
|
| 1285 |
"metadata": {},
|
| 1286 |
"outputs": [],
|
| 1287 |
-
"source": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1288 |
}
|
| 1289 |
],
|
| 1290 |
"metadata": {
|
| 1291 |
"kernelspec": {
|
| 1292 |
-
"display_name": "
|
| 1293 |
"language": "python",
|
| 1294 |
"name": "python3"
|
| 1295 |
},
|
|
@@ -1303,7 +1337,7 @@
|
|
| 1303 |
"name": "python",
|
| 1304 |
"nbconvert_exporter": "python",
|
| 1305 |
"pygments_lexer": "ipython3",
|
| 1306 |
-
"version": "3.
|
| 1307 |
}
|
| 1308 |
},
|
| 1309 |
"nbformat": 4,
|
|
|
|
| 79 |
},
|
| 80 |
{
|
| 81 |
"cell_type": "code",
|
| 82 |
+
"execution_count": 1,
|
| 83 |
"id": "f8decb63-43f5-4731-823d-94632eee7618",
|
| 84 |
"metadata": {
|
| 85 |
"execution": {
|
|
|
|
| 108 |
},
|
| 109 |
{
|
| 110 |
"cell_type": "code",
|
| 111 |
+
"execution_count": 2,
|
| 112 |
"id": "4b2426c3-96a0-468e-b6dc-78dea9c3e92b",
|
| 113 |
"metadata": {
|
| 114 |
"execution": {
|
|
|
|
| 140 |
"outputs": [],
|
| 141 |
"source": [
|
| 142 |
"# Define the input timestamp in ISO 8601 format\n",
|
| 143 |
+
"input_timestamp = \"2025-03-24T12:59:03.335Z\" # point in time from when you want to obtain metadata (you can copy the timestamp from the last *.json batch obtained to get the data of longer timespans)\n",
|
| 144 |
"\n",
|
| 145 |
"# Function to convert an ISO 8601 date string to a Unix timestamp in milliseconds with a 2-hour offset \n",
|
| 146 |
"def iso_to_timestamp(iso_str):\n",
|
|
|
|
| 304 |
},
|
| 305 |
{
|
| 306 |
"cell_type": "code",
|
| 307 |
+
"execution_count": null,
|
| 308 |
"id": "7c89cb68-983b-47df-a028-e02d7ca0829d",
|
| 309 |
"metadata": {
|
| 310 |
"execution": {
|
|
|
|
| 317 |
},
|
| 318 |
"outputs": [],
|
| 319 |
"source": [
|
| 320 |
+
"get_image_metadata()"
|
| 321 |
]
|
| 322 |
},
|
| 323 |
{
|
|
|
|
| 1284 |
"id": "49e35088-8b2e-4189-83e1-3098d55dcad2",
|
| 1285 |
"metadata": {},
|
| 1286 |
"outputs": [],
|
| 1287 |
+
"source": [
|
| 1288 |
+
"import pandas as pd\n",
|
| 1289 |
+
"import os\n",
|
| 1290 |
+
"\n",
|
| 1291 |
+
"# Load the dataset\n",
|
| 1292 |
+
"df = pd.read_csv('data/all_models_with_tags.csv')\n",
|
| 1293 |
+
"\n",
|
| 1294 |
+
"# Filter for rows where poi is True\n",
|
| 1295 |
+
"filtered_df = df[df['poi'] == True]\n",
|
| 1296 |
+
"os.makedirs('data/model_subsets', exist_ok=True)\n",
|
| 1297 |
+
"\n",
|
| 1298 |
+
"# Save the filtered DataFrame to a new CSV file\n",
|
| 1299 |
+
"filtered_df.to_csv('data/model_subsets/all_models_poi.csv', index=False)\n"
|
| 1300 |
+
]
|
| 1301 |
+
},
|
| 1302 |
+
{
|
| 1303 |
+
"cell_type": "code",
|
| 1304 |
+
"execution_count": null,
|
| 1305 |
+
"id": "06c15f2c",
|
| 1306 |
+
"metadata": {},
|
| 1307 |
+
"outputs": [],
|
| 1308 |
+
"source": [
|
| 1309 |
+
"import pandas as pd\n",
|
| 1310 |
+
"import os\n",
|
| 1311 |
+
"\n",
|
| 1312 |
+
"# Load the dataset\n",
|
| 1313 |
+
"df = pd.read_csv('data/all_models_with_tags.csv')\n",
|
| 1314 |
+
"\n",
|
| 1315 |
+
"# Filter for rows where poi is True\n",
|
| 1316 |
+
"filtered_df = df[df['poi'] == False]\n",
|
| 1317 |
+
"os.makedirs('data/model_subsets', exist_ok=True)\n",
|
| 1318 |
+
"\n",
|
| 1319 |
+
"# Save the filtered DataFrame to a new CSV file\n",
|
| 1320 |
+
"filtered_df.to_csv('data/model_subsets/all_models_poi_false.csv', index=False)\n"
|
| 1321 |
+
]
|
| 1322 |
}
|
| 1323 |
],
|
| 1324 |
"metadata": {
|
| 1325 |
"kernelspec": {
|
| 1326 |
+
"display_name": "latm",
|
| 1327 |
"language": "python",
|
| 1328 |
"name": "python3"
|
| 1329 |
},
|
|
|
|
| 1337 |
"name": "python",
|
| 1338 |
"nbconvert_exporter": "python",
|
| 1339 |
"pygments_lexer": "ipython3",
|
| 1340 |
+
"version": "3.10.15"
|
| 1341 |
}
|
| 1342 |
},
|
| 1343 |
"nbformat": 4,
|
jupyter_notebooks/0_Scraping_model_metadata.ipynb
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "1111ea95-d385-49b9-a4d9-ef886ace5c7a",
|
| 6 |
+
"metadata": {
|
| 7 |
+
"execution": {
|
| 8 |
+
"iopub.execute_input": "2025-02-06T11:24:25.566747Z",
|
| 9 |
+
"iopub.status.busy": "2025-02-06T11:24:25.566066Z",
|
| 10 |
+
"iopub.status.idle": "2025-02-06T11:24:25.571748Z",
|
| 11 |
+
"shell.execute_reply": "2025-02-06T11:24:25.571305Z",
|
| 12 |
+
"shell.execute_reply.started": "2025-02-06T11:24:25.566705Z"
|
| 13 |
+
}
|
| 14 |
+
},
|
| 15 |
+
"source": [
|
| 16 |
+
"# 0 Scraping Metadata and Dataset consolidation\n"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "markdown",
|
| 21 |
+
"id": "6632505a-e7ca-4463-9ffc-e36fad42235f",
|
| 22 |
+
"metadata": {},
|
| 23 |
+
"source": [
|
| 24 |
+
"## IMAGES\n",
|
| 25 |
+
"---"
|
| 26 |
+
]
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"cell_type": "markdown",
|
| 30 |
+
"id": "e3388bac-bb71-40bc-a693-9ac7a2d5f32c",
|
| 31 |
+
"metadata": {
|
| 32 |
+
"execution": {
|
| 33 |
+
"iopub.execute_input": "2025-02-06T10:08:22.229784Z",
|
| 34 |
+
"iopub.status.busy": "2025-02-06T10:08:22.229287Z",
|
| 35 |
+
"iopub.status.idle": "2025-02-06T10:08:22.232210Z",
|
| 36 |
+
"shell.execute_reply": "2025-02-06T10:08:22.231793Z",
|
| 37 |
+
"shell.execute_reply.started": "2025-02-06T10:08:22.229766Z"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"source": [
|
| 41 |
+
"### Step 1: Image metadata scraping, sorting and CSV consolidation"
|
| 42 |
+
]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"cell_type": "code",
|
| 46 |
+
"execution_count": 1,
|
| 47 |
+
"id": "f8decb63-43f5-4731-823d-94632eee7618",
|
| 48 |
+
"metadata": {
|
| 49 |
+
"execution": {
|
| 50 |
+
"iopub.execute_input": "2025-02-08T19:37:51.115763Z",
|
| 51 |
+
"iopub.status.busy": "2025-02-08T19:37:51.114573Z",
|
| 52 |
+
"iopub.status.idle": "2025-02-08T19:37:51.170027Z",
|
| 53 |
+
"shell.execute_reply": "2025-02-08T19:37:51.169401Z",
|
| 54 |
+
"shell.execute_reply.started": "2025-02-08T19:37:51.115738Z"
|
| 55 |
+
}
|
| 56 |
+
},
|
| 57 |
+
"outputs": [],
|
| 58 |
+
"source": [
|
| 59 |
+
"import os\n",
|
| 60 |
+
"import json\n",
|
| 61 |
+
"import csv\n",
|
| 62 |
+
"import requests\n",
|
| 63 |
+
"from datetime import datetime\n",
|
| 64 |
+
"import time\n",
|
| 65 |
+
"from pathlib import Path\n",
|
| 66 |
+
"import hashlib\n",
|
| 67 |
+
"import pandas as pd\n",
|
| 68 |
+
"import sys\n",
|
| 69 |
+
"from datetime import datetime, timedelta\n",
|
| 70 |
+
"import shutil"
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"cell_type": "code",
|
| 75 |
+
"execution_count": 2,
|
| 76 |
+
"id": "4b2426c3-96a0-468e-b6dc-78dea9c3e92b",
|
| 77 |
+
"metadata": {
|
| 78 |
+
"execution": {
|
| 79 |
+
"iopub.execute_input": "2025-02-08T19:37:51.809027Z",
|
| 80 |
+
"iopub.status.busy": "2025-02-08T19:37:51.808835Z",
|
| 81 |
+
"iopub.status.idle": "2025-02-08T19:37:51.812922Z",
|
| 82 |
+
"shell.execute_reply": "2025-02-08T19:37:51.812429Z",
|
| 83 |
+
"shell.execute_reply.started": "2025-02-08T19:37:51.809009Z"
|
| 84 |
+
}
|
| 85 |
+
},
|
| 86 |
+
"outputs": [],
|
| 87 |
+
"source": [
|
| 88 |
+
"current_dir = Path.cwd()"
|
| 89 |
+
]
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"cell_type": "markdown",
|
| 93 |
+
"id": "11647bb7-5ce9-414a-8486-5bdce8d9cfea",
|
| 94 |
+
"metadata": {
|
| 95 |
+
"execution": {
|
| 96 |
+
"iopub.execute_input": "2025-02-06T12:49:43.126762Z",
|
| 97 |
+
"iopub.status.busy": "2025-02-06T12:49:43.125797Z",
|
| 98 |
+
"iopub.status.idle": "2025-02-06T12:49:43.129759Z",
|
| 99 |
+
"shell.execute_reply": "2025-02-06T12:49:43.129176Z",
|
| 100 |
+
"shell.execute_reply.started": "2025-02-06T12:49:43.126736Z"
|
| 101 |
+
}
|
| 102 |
+
},
|
| 103 |
+
"source": [
|
| 104 |
+
"## MODELS"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"cell_type": "markdown",
|
| 109 |
+
"id": "5c9cb5e3-7cea-4574-9319-f3cd89354b1f",
|
| 110 |
+
"metadata": {
|
| 111 |
+
"execution": {
|
| 112 |
+
"iopub.execute_input": "2025-02-06T13:23:38.017078Z",
|
| 113 |
+
"iopub.status.busy": "2025-02-06T13:23:38.016639Z",
|
| 114 |
+
"iopub.status.idle": "2025-02-06T13:23:38.019993Z",
|
| 115 |
+
"shell.execute_reply": "2025-02-06T13:23:38.019549Z",
|
| 116 |
+
"shell.execute_reply.started": "2025-02-06T13:23:38.017053Z"
|
| 117 |
+
}
|
| 118 |
+
},
|
| 119 |
+
"source": [
|
| 120 |
+
"### Step 1: Scrape model metadata"
|
| 121 |
+
]
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
"cell_type": "markdown",
|
| 125 |
+
"id": "83e12b9f-5ae1-407d-9754-5979d837f787",
|
| 126 |
+
"metadata": {
|
| 127 |
+
"execution": {
|
| 128 |
+
"iopub.execute_input": "2025-02-06T14:06:04.857874Z",
|
| 129 |
+
"iopub.status.busy": "2025-02-06T14:06:04.857500Z",
|
| 130 |
+
"iopub.status.idle": "2025-02-06T14:06:04.860438Z",
|
| 131 |
+
"shell.execute_reply": "2025-02-06T14:06:04.860030Z",
|
| 132 |
+
"shell.execute_reply.started": "2025-02-06T14:06:04.857856Z"
|
| 133 |
+
}
|
| 134 |
+
},
|
| 135 |
+
"source": [
|
| 136 |
+
"#### the resulting files will appear in data/raw/model_metadata as *.json"
|
| 137 |
+
]
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"cell_type": "code",
|
| 141 |
+
"execution_count": 12,
|
| 142 |
+
"id": "5db9c00e",
|
| 143 |
+
"metadata": {},
|
| 144 |
+
"outputs": [],
|
| 145 |
+
"source": [
|
| 146 |
+
"key_karussell = current_dir.parent / 'misc/credentials/civitai_api_keys.txt'\n",
|
| 147 |
+
"directory_path = current_dir.parent / 'data/raw/model_metadata/'"
|
| 148 |
+
]
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"cell_type": "code",
|
| 152 |
+
"execution_count": 13,
|
| 153 |
+
"id": "41ee14e0-fb78-4f91-aba1-13faf05af7d8",
|
| 154 |
+
"metadata": {
|
| 155 |
+
"execution": {
|
| 156 |
+
"iopub.execute_input": "2025-02-08T19:37:56.687832Z",
|
| 157 |
+
"iopub.status.busy": "2025-02-08T19:37:56.687251Z",
|
| 158 |
+
"iopub.status.idle": "2025-02-08T19:37:56.696572Z",
|
| 159 |
+
"shell.execute_reply": "2025-02-08T19:37:56.696059Z",
|
| 160 |
+
"shell.execute_reply.started": "2025-02-08T19:37:56.687809Z"
|
| 161 |
+
}
|
| 162 |
+
},
|
| 163 |
+
"outputs": [],
|
| 164 |
+
"source": [
|
| 165 |
+
"import datetime\n",
|
| 166 |
+
"\n",
|
| 167 |
+
"def load_api_keys():\n",
|
| 168 |
+
" \"\"\"Load API keys from a text file, one per line.\"\"\"\n",
|
| 169 |
+
" if not os.path.exists(key_karussell):\n",
|
| 170 |
+
" raise FileNotFoundError(f\"API key file '{API_KEYS_FILE}' not found!\")\n",
|
| 171 |
+
" \n",
|
| 172 |
+
" with open(key_karussell, 'r') as file:\n",
|
| 173 |
+
" keys = [line.strip() for line in file if line.strip()]\n",
|
| 174 |
+
" \n",
|
| 175 |
+
" if not keys:\n",
|
| 176 |
+
" raise ValueError(\"No API keys found in the file!\")\n",
|
| 177 |
+
" \n",
|
| 178 |
+
" return keys\n",
|
| 179 |
+
"\n",
|
| 180 |
+
"def get_model_metadata():\n",
|
| 181 |
+
" base_url = \"https://civitai.com/api/v1/models\"\n",
|
| 182 |
+
" params = {\"sort\": \"Newest\", \"nsfw\": True}\n",
|
| 183 |
+
"\n",
|
| 184 |
+
" # Load API keys\n",
|
| 185 |
+
" api_keys = load_api_keys()\n",
|
| 186 |
+
" key_index = 0 # Start with the first key\n",
|
| 187 |
+
"\n",
|
| 188 |
+
" page_counter = 0\n",
|
| 189 |
+
" max_pages = 300000000 # Adjust as needed\n",
|
| 190 |
+
" os.makedirs(directory_path, exist_ok=True)\n",
|
| 191 |
+
"\n",
|
| 192 |
+
" while True:\n",
|
| 193 |
+
" if page_counter >= max_pages:\n",
|
| 194 |
+
" print(f\"Reached the limit of {max_pages} pages.\")\n",
|
| 195 |
+
" break\n",
|
| 196 |
+
"\n",
|
| 197 |
+
" headers = {\n",
|
| 198 |
+
" \"Accept\": \"application/json\",\n",
|
| 199 |
+
" \"Authorization\": f\"Bearer {api_keys[key_index]}\"\n",
|
| 200 |
+
" }\n",
|
| 201 |
+
"\n",
|
| 202 |
+
" response = requests.get(base_url, headers=headers, params=params)\n",
|
| 203 |
+
"\n",
|
| 204 |
+
" if response.status_code == 200:\n",
|
| 205 |
+
" data = response.json()\n",
|
| 206 |
+
" page_counter += 1\n",
|
| 207 |
+
"\n",
|
| 208 |
+
" # Add timestamp\n",
|
| 209 |
+
" formatted_timestamp = datetime.datetime.now().strftime(\"data obtained on the %d.%m.%Y at %H:%M CEST\")\n",
|
| 210 |
+
"\n",
|
| 211 |
+
" data['timestamp'] = formatted_timestamp\n",
|
| 212 |
+
"\n",
|
| 213 |
+
" # Save data to file\n",
|
| 214 |
+
" file_path = os.path.join(directory_path, f'newest_models_{page_counter}.json')\n",
|
| 215 |
+
" with open(file_path, 'w', encoding='utf-8') as file:\n",
|
| 216 |
+
" json.dump(data, file, indent=4)\n",
|
| 217 |
+
"\n",
|
| 218 |
+
" # Check for nextCursor\n",
|
| 219 |
+
" next_cursor = data.get('metadata', {}).get('nextCursor')\n",
|
| 220 |
+
" if not next_cursor:\n",
|
| 221 |
+
" print(\"No more data available.\")\n",
|
| 222 |
+
" break\n",
|
| 223 |
+
" else:\n",
|
| 224 |
+
" params['cursor'] = next_cursor\n",
|
| 225 |
+
" \n",
|
| 226 |
+
" elif response.status_code in (401, 403): # Unauthorized or Forbidden\n",
|
| 227 |
+
" print(f\"API Key {key_index + 1} failed with status {response.status_code}. Trying next key...\")\n",
|
| 228 |
+
" key_index += 1\n",
|
| 229 |
+
"\n",
|
| 230 |
+
" if key_index >= len(api_keys):\n",
|
| 231 |
+
" print(\"All API keys failed. Exiting.\")\n",
|
| 232 |
+
" break # Stop if all keys fail\n",
|
| 233 |
+
" \n",
|
| 234 |
+
" else:\n",
|
| 235 |
+
" print(f\"Failed to fetch data: HTTP {response.status_code}\")\n",
|
| 236 |
+
" break # Stop on other errors\n"
|
| 237 |
+
]
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"cell_type": "markdown",
|
| 241 |
+
"id": "8f43ce23-5986-4b67-be2d-453831af9a6e",
|
| 242 |
+
"metadata": {},
|
| 243 |
+
"source": [
|
| 244 |
+
"uncomment this to get model metadata"
|
| 245 |
+
]
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"cell_type": "code",
|
| 249 |
+
"execution_count": 14,
|
| 250 |
+
"id": "3f95b4ba-5742-4268-b2e8-de9145faf495",
|
| 251 |
+
"metadata": {
|
| 252 |
+
"execution": {
|
| 253 |
+
"iopub.execute_input": "2025-02-08T19:37:58.117386Z",
|
| 254 |
+
"iopub.status.busy": "2025-02-08T19:37:58.117158Z",
|
| 255 |
+
"iopub.status.idle": "2025-02-08T19:37:58.121162Z",
|
| 256 |
+
"shell.execute_reply": "2025-02-08T19:37:58.120580Z",
|
| 257 |
+
"shell.execute_reply.started": "2025-02-08T19:37:58.117369Z"
|
| 258 |
+
}
|
| 259 |
+
},
|
| 260 |
+
"outputs": [
|
| 261 |
+
{
|
| 262 |
+
"name": "stdout",
|
| 263 |
+
"output_type": "stream",
|
| 264 |
+
"text": [
|
| 265 |
+
"Failed to fetch data: HTTP 500\n"
|
| 266 |
+
]
|
| 267 |
+
}
|
| 268 |
+
],
|
| 269 |
+
"source": [
|
| 270 |
+
"get_model_metadata()"
|
| 271 |
+
]
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"cell_type": "markdown",
|
| 275 |
+
"id": "1ed5f44e-612e-4b6d-8974-904fb3e058d6",
|
| 276 |
+
"metadata": {
|
| 277 |
+
"execution": {
|
| 278 |
+
"iopub.execute_input": "2025-02-06T12:52:40.162173Z",
|
| 279 |
+
"iopub.status.busy": "2025-02-06T12:52:40.159989Z",
|
| 280 |
+
"iopub.status.idle": "2025-02-06T12:52:40.170634Z",
|
| 281 |
+
"shell.execute_reply": "2025-02-06T12:52:40.169945Z",
|
| 282 |
+
"shell.execute_reply.started": "2025-02-06T12:52:40.162124Z"
|
| 283 |
+
}
|
| 284 |
+
},
|
| 285 |
+
"source": [
|
| 286 |
+
"### Step 2 Consolidate Model-dataset CSV"
|
| 287 |
+
]
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"cell_type": "code",
|
| 291 |
+
"execution_count": 8,
|
| 292 |
+
"id": "464d82f5-c24e-4b53-9b50-682fa4bf3430",
|
| 293 |
+
"metadata": {
|
| 294 |
+
"execution": {
|
| 295 |
+
"iopub.execute_input": "2025-02-08T19:37:59.052245Z",
|
| 296 |
+
"iopub.status.busy": "2025-02-08T19:37:59.051645Z",
|
| 297 |
+
"iopub.status.idle": "2025-02-08T19:37:59.056017Z",
|
| 298 |
+
"shell.execute_reply": "2025-02-08T19:37:59.055598Z",
|
| 299 |
+
"shell.execute_reply.started": "2025-02-08T19:37:59.052224Z"
|
| 300 |
+
}
|
| 301 |
+
},
|
| 302 |
+
"outputs": [],
|
| 303 |
+
"source": [
|
| 304 |
+
"## path thingy\n",
|
| 305 |
+
"try: #scripts\n",
|
| 306 |
+
" current_dir = Path(__file__).resolve().parent\n",
|
| 307 |
+
"except NameError:\n",
|
| 308 |
+
" # jupyter\n",
|
| 309 |
+
" current_dir = Path.cwd()"
|
| 310 |
+
]
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"cell_type": "code",
|
| 314 |
+
"execution_count": null,
|
| 315 |
+
"id": "14f3db4d-ef93-4629-8a27-971adb49248b",
|
| 316 |
+
"metadata": {
|
| 317 |
+
"execution": {
|
| 318 |
+
"iopub.execute_input": "2025-02-08T19:37:59.443457Z",
|
| 319 |
+
"iopub.status.busy": "2025-02-08T19:37:59.443236Z",
|
| 320 |
+
"iopub.status.idle": "2025-02-08T19:37:59.890103Z",
|
| 321 |
+
"shell.execute_reply": "2025-02-08T19:37:59.889571Z",
|
| 322 |
+
"shell.execute_reply.started": "2025-02-08T19:37:59.443439Z"
|
| 323 |
+
}
|
| 324 |
+
},
|
| 325 |
+
"outputs": [
|
| 326 |
+
{
|
| 327 |
+
"name": "stdout",
|
| 328 |
+
"output_type": "stream",
|
| 329 |
+
"text": [
|
| 330 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_2.json\n",
|
| 331 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_1.json\n",
|
| 332 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_4.json\n",
|
| 333 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_6.json\n",
|
| 334 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_7.json\n",
|
| 335 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_5.json\n",
|
| 336 |
+
"Processing file: /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/model_metadata/newest_models_3.json\n"
|
| 337 |
+
]
|
| 338 |
+
}
|
| 339 |
+
],
|
| 340 |
+
"source": [
|
| 341 |
+
"import os\n",
|
| 342 |
+
"import json\n",
|
| 343 |
+
"import pandas as pd\n",
|
| 344 |
+
"import hashlib\n",
|
| 345 |
+
"from pathlib import Path\n",
|
| 346 |
+
"from datetime import datetime, timezone\n",
|
| 347 |
+
"\n",
|
| 348 |
+
"\n",
|
| 349 |
+
"\n",
|
| 350 |
+
"\n",
|
| 351 |
+
"def hash_username(username):\n",
|
| 352 |
+
" return hashlib.sha256(username.encode('utf-8')).hexdigest()[:16]\n",
|
| 353 |
+
"\n",
|
| 354 |
+
"def parse_date(date_str):\n",
|
| 355 |
+
" try:\n",
|
| 356 |
+
" return datetime.fromisoformat(date_str.replace('Z', '+00:00'))\n",
|
| 357 |
+
" except Exception:\n",
|
| 358 |
+
" return datetime.min.replace(tzinfo=timezone.utc) # Make it timezone-aware\n",
|
| 359 |
+
"\n",
|
| 360 |
+
"def get_latest_model_version(model_versions):\n",
|
| 361 |
+
" return max(model_versions, key=lambda mv: parse_date(mv.get('publishedAt', '')))\n",
|
| 362 |
+
"\n",
|
| 363 |
+
"def process_directory_recursively(root_dir):\n",
|
| 364 |
+
" root_path = Path(root_dir)\n",
|
| 365 |
+
" seen = {} # id -> (publishedAt, record)\n",
|
| 366 |
+
" data_records = []\n",
|
| 367 |
+
"\n",
|
| 368 |
+
" for json_file in root_path.rglob('*.json'):\n",
|
| 369 |
+
" if not json_file.is_file():\n",
|
| 370 |
+
" continue\n",
|
| 371 |
+
"\n",
|
| 372 |
+
" #print(f\"Processing file: {json_file}\")\n",
|
| 373 |
+
" try:\n",
|
| 374 |
+
" with open(json_file, 'r', encoding='utf-8') as f:\n",
|
| 375 |
+
" data = json.load(f)\n",
|
| 376 |
+
" except Exception as e:\n",
|
| 377 |
+
" print(f\"Failed to load {json_file}: {e}\")\n",
|
| 378 |
+
" continue\n",
|
| 379 |
+
"\n",
|
| 380 |
+
" items = data.get('items') or data.get('data') or []\n",
|
| 381 |
+
" for item in items:\n",
|
| 382 |
+
" if not isinstance(item, dict):\n",
|
| 383 |
+
" continue\n",
|
| 384 |
+
"\n",
|
| 385 |
+
" model_id = item.get('id')\n",
|
| 386 |
+
" model_versions = item.get('modelVersions', [])\n",
|
| 387 |
+
" if not model_versions:\n",
|
| 388 |
+
" continue\n",
|
| 389 |
+
"\n",
|
| 390 |
+
" latest_version = get_latest_model_version(model_versions)\n",
|
| 391 |
+
" published_at = latest_version.get('publishedAt', '')\n",
|
| 392 |
+
" current_dt = parse_date(published_at)\n",
|
| 393 |
+
"\n",
|
| 394 |
+
" if model_id in seen and current_dt <= seen[model_id][0]:\n",
|
| 395 |
+
" continue\n",
|
| 396 |
+
" seen[model_id] = (current_dt, item)\n",
|
| 397 |
+
"\n",
|
| 398 |
+
" for model_id, (_, item) in seen.items():\n",
|
| 399 |
+
" model_versions = item.get('modelVersions', [])\n",
|
| 400 |
+
" latest_version = get_latest_model_version(model_versions)\n",
|
| 401 |
+
" version_ids = [mv.get('id', '') for mv in model_versions[:20]]\n",
|
| 402 |
+
"\n",
|
| 403 |
+
" files = latest_version.get('files', [])\n",
|
| 404 |
+
" auto_hashes = files[0].get('hashes', {}) if files else {}\n",
|
| 405 |
+
" images = latest_version.get('images', [])\n",
|
| 406 |
+
" first_image_url = images[0]['url'] if images else ''\n",
|
| 407 |
+
" latest_image_url = images[-1]['url'] if images else ''\n",
|
| 408 |
+
"\n",
|
| 409 |
+
" username = item.get('creator', {}).get('username', '')\n",
|
| 410 |
+
" record = {\n",
|
| 411 |
+
" 'id': item.get('id', ''),\n",
|
| 412 |
+
" 'name': item.get('name', ''),\n",
|
| 413 |
+
" 'type': item.get('type', ''),\n",
|
| 414 |
+
" 'baseModel': latest_version.get('baseModel', ''),\n",
|
| 415 |
+
" 'downloadCount': item.get('stats', {}).get('downloadCount', 0),\n",
|
| 416 |
+
" 'nsfwLevel': item.get('nsfwLevel', 0),\n",
|
| 417 |
+
" 'modelVersions': len(model_versions),\n",
|
| 418 |
+
" 'publishedAt': latest_version.get('publishedAt', ''),\n",
|
| 419 |
+
" 'usernameHash': hash_username(username) if username else '',\n",
|
| 420 |
+
" 'downloadUrl': latest_version.get('downloadUrl', ''),\n",
|
| 421 |
+
" 'firstImageUrl': first_image_url,\n",
|
| 422 |
+
" 'latestImageUrl': latest_image_url,\n",
|
| 423 |
+
" 'poi': item.get('poi', False),\n",
|
| 424 |
+
" 'AutoV1': auto_hashes.get('AutoV1', ''),\n",
|
| 425 |
+
" 'AutoV2': auto_hashes.get('AutoV2', ''),\n",
|
| 426 |
+
" 'AutoV3': auto_hashes.get('AutoV3', ''),\n",
|
| 427 |
+
" 'SHA256': auto_hashes.get('SHA256', ''),\n",
|
| 428 |
+
" 'CRC32': auto_hashes.get('CRC32', ''),\n",
|
| 429 |
+
" 'BLAKE3': auto_hashes.get('BLAKE3', ''),\n",
|
| 430 |
+
" 'previewImage': latest_image_url\n",
|
| 431 |
+
" }\n",
|
| 432 |
+
"\n",
|
| 433 |
+
" for i in range(20):\n",
|
| 434 |
+
" record[f'version_id_{i+1}'] = version_ids[i] if i < len(version_ids) else ''\n",
|
| 435 |
+
"\n",
|
| 436 |
+
" tags = item.get('tags', [])\n",
|
| 437 |
+
" for i in range(7):\n",
|
| 438 |
+
" record[f'tag_{i+1}'] = tags[i] if i < len(tags) else ''\n",
|
| 439 |
+
"\n",
|
| 440 |
+
" data_records.append(record)\n",
|
| 441 |
+
"\n",
|
| 442 |
+
" return pd.DataFrame(data_records)\n",
|
| 443 |
+
"\n",
|
| 444 |
+
"# Usage Example\n",
|
| 445 |
+
"root_directory = current_dir.parent / 'data/raw/model_metadata/'\n",
|
| 446 |
+
"df = process_directory_recursively(root_directory)\n",
|
| 447 |
+
"df_sorted = df.sort_values(by='downloadCount', ascending=False)\n",
|
| 448 |
+
"\n",
|
| 449 |
+
"# Optionally save\n",
|
| 450 |
+
"# df_sorted.to_csv('combined_metadata.csv', index=False)\n"
|
| 451 |
+
]
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"cell_type": "markdown",
|
| 455 |
+
"id": "2d6f822f-754f-4feb-9ed6-6f868421c454",
|
| 456 |
+
"metadata": {},
|
| 457 |
+
"source": [
|
| 458 |
+
"### Save model-data to CSV"
|
| 459 |
+
]
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"cell_type": "code",
|
| 463 |
+
"execution_count": null,
|
| 464 |
+
"id": "f861cf7b-5eb3-46ad-ab2d-9e2fdf2169b4",
|
| 465 |
+
"metadata": {
|
| 466 |
+
"execution": {
|
| 467 |
+
"iopub.execute_input": "2025-02-08T19:38:02.047421Z",
|
| 468 |
+
"iopub.status.busy": "2025-02-08T19:38:02.046761Z",
|
| 469 |
+
"iopub.status.idle": "2025-02-08T19:38:02.193381Z",
|
| 470 |
+
"shell.execute_reply": "2025-02-08T19:38:02.192886Z",
|
| 471 |
+
"shell.execute_reply.started": "2025-02-08T19:38:02.047397Z"
|
| 472 |
+
}
|
| 473 |
+
},
|
| 474 |
+
"outputs": [
|
| 475 |
+
{
|
| 476 |
+
"name": "stdout",
|
| 477 |
+
"output_type": "stream",
|
| 478 |
+
"text": [
|
| 479 |
+
"Data has been saved to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/CSV/Civiverse-Models.csv\n"
|
| 480 |
+
]
|
| 481 |
+
}
|
| 482 |
+
],
|
| 483 |
+
"source": [
|
| 484 |
+
"output_csv = current_dir.parent / 'data/CSV/Civiverse-Models_2025.csv'\n",
|
| 485 |
+
"output_csv.parent.mkdir(parents=True, exist_ok=True)\n",
|
| 486 |
+
"df_sorted.to_csv(output_csv, index=False)\n",
|
| 487 |
+
"print(f\"Data has been saved to {output_csv}\")"
|
| 488 |
+
]
|
| 489 |
+
},
|
| 490 |
+
{
|
| 491 |
+
"cell_type": "markdown",
|
| 492 |
+
"id": "9b948933-2a2f-42b0-9083-2d81586ae3f2",
|
| 493 |
+
"metadata": {
|
| 494 |
+
"execution": {
|
| 495 |
+
"iopub.execute_input": "2025-02-06T13:42:32.447860Z",
|
| 496 |
+
"iopub.status.busy": "2025-02-06T13:42:32.446832Z",
|
| 497 |
+
"iopub.status.idle": "2025-02-06T13:42:32.453827Z",
|
| 498 |
+
"shell.execute_reply": "2025-02-06T13:42:32.453271Z",
|
| 499 |
+
"shell.execute_reply.started": "2025-02-06T13:42:32.447821Z"
|
| 500 |
+
}
|
| 501 |
+
},
|
| 502 |
+
"source": [
|
| 503 |
+
"### Step 3 Create Subsets: Checkpoint only, POI True, POI False"
|
| 504 |
+
]
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"cell_type": "code",
|
| 508 |
+
"execution_count": 11,
|
| 509 |
+
"id": "ae894e26-4984-40e6-80a6-54fb6b61c873",
|
| 510 |
+
"metadata": {
|
| 511 |
+
"execution": {
|
| 512 |
+
"iopub.execute_input": "2025-02-08T19:38:03.089160Z",
|
| 513 |
+
"iopub.status.busy": "2025-02-08T19:38:03.088490Z",
|
| 514 |
+
"iopub.status.idle": "2025-02-08T19:38:03.093067Z",
|
| 515 |
+
"shell.execute_reply": "2025-02-08T19:38:03.092634Z",
|
| 516 |
+
"shell.execute_reply.started": "2025-02-08T19:38:03.089138Z"
|
| 517 |
+
}
|
| 518 |
+
},
|
| 519 |
+
"outputs": [],
|
| 520 |
+
"source": [
|
| 521 |
+
"file_path = current_dir.parent / 'data/CSV/Civiverse-Models.csv' # Update this with your actual file path\n",
|
| 522 |
+
"(current_dir.parent / 'data/CSV/model_subsets').mkdir(parents=True, exist_ok=True)\n"
|
| 523 |
+
]
|
| 524 |
+
},
|
| 525 |
+
{
|
| 526 |
+
"cell_type": "code",
|
| 527 |
+
"execution_count": 13,
|
| 528 |
+
"id": "88438f88-c723-423e-a4e5-e07f59096b72",
|
| 529 |
+
"metadata": {
|
| 530 |
+
"execution": {
|
| 531 |
+
"iopub.execute_input": "2025-02-08T19:41:39.279495Z",
|
| 532 |
+
"iopub.status.busy": "2025-02-08T19:41:39.279039Z",
|
| 533 |
+
"iopub.status.idle": "2025-02-08T19:41:39.674445Z",
|
| 534 |
+
"shell.execute_reply": "2025-02-08T19:41:39.673981Z",
|
| 535 |
+
"shell.execute_reply.started": "2025-02-08T19:41:39.279476Z"
|
| 536 |
+
}
|
| 537 |
+
},
|
| 538 |
+
"outputs": [
|
| 539 |
+
{
|
| 540 |
+
"name": "stdout",
|
| 541 |
+
"output_type": "stream",
|
| 542 |
+
"text": [
|
| 543 |
+
"Files saved successfully!\n"
|
| 544 |
+
]
|
| 545 |
+
}
|
| 546 |
+
],
|
| 547 |
+
"source": [
|
| 548 |
+
"import pandas as pd\n",
|
| 549 |
+
"\n",
|
| 550 |
+
"# Load the dataset\n",
|
| 551 |
+
"\n",
|
| 552 |
+
"data = pd.read_csv(file_path)\n",
|
| 553 |
+
"\n",
|
| 554 |
+
"# Version 1: Only 'poi' true models\n",
|
| 555 |
+
"poi_true_models = data[data['poi'] == True]\n",
|
| 556 |
+
"\n",
|
| 557 |
+
"# Version 2: Only types lora, dora, locon, textual inversion\n",
|
| 558 |
+
"specific_types = ['LORA', 'DORA', 'LOCON', 'textualInversion']\n",
|
| 559 |
+
"adapters = data[data['type'].isin(specific_types)]\n",
|
| 560 |
+
"\n",
|
| 561 |
+
"# Version 3: Only type checkpoint\n",
|
| 562 |
+
"checkpoint_models = data[data['type'] == 'Checkpoint']\n",
|
| 563 |
+
"\n",
|
| 564 |
+
"# Version 4: All models apart from 'poi' true\n",
|
| 565 |
+
"non_poi_models = data[data['poi'] != True]\n",
|
| 566 |
+
"\n",
|
| 567 |
+
"# Version 5: All models apart from 'poi' true and with nsfwLevel below 13\n",
|
| 568 |
+
"non_poi_low_nsfw_models = data[(data['poi'] != True) & (data['nsfwLevel'] < 13)]\n",
|
| 569 |
+
"\n",
|
| 570 |
+
"# Save the versions as separate CSV files\n",
|
| 571 |
+
"poi_true_models.to_csv(current_dir.parent / 'data/CSV/model_subsets/Civiverse_adapters_poi_true.csv', index=False)\n",
|
| 572 |
+
"adapters.to_csv(current_dir.parent / 'data/CSV/adapters.csv', index=False)\n",
|
| 573 |
+
"checkpoint_models.to_csv(current_dir.parent / 'data/CSV/model_subsets/Civiverse_checkpoint_only.csv', index=False)\n",
|
| 574 |
+
"non_poi_models.to_csv(current_dir.parent / 'data/CSV/model_subsets/Civiverse_adapters_poi_false.csv', index=False)\n",
|
| 575 |
+
"\n",
|
| 576 |
+
"print(\"Files saved successfully!\")\n"
|
| 577 |
+
]
|
| 578 |
+
},
|
| 579 |
+
{
|
| 580 |
+
"cell_type": "code",
|
| 581 |
+
"execution_count": null,
|
| 582 |
+
"id": "49e35088-8b2e-4189-83e1-3098d55dcad2",
|
| 583 |
+
"metadata": {},
|
| 584 |
+
"outputs": [],
|
| 585 |
+
"source": [
|
| 586 |
+
"import pandas as pd\n",
|
| 587 |
+
"import os\n",
|
| 588 |
+
"\n",
|
| 589 |
+
"# Load the dataset\n",
|
| 590 |
+
"df = pd.read_csv('data/all_models_with_tags.csv')\n",
|
| 591 |
+
"\n",
|
| 592 |
+
"# Filter for rows where poi is True\n",
|
| 593 |
+
"filtered_df = df[df['poi'] == True]\n",
|
| 594 |
+
"os.makedirs('data/model_subsets', exist_ok=True)\n",
|
| 595 |
+
"\n",
|
| 596 |
+
"# Save the filtered DataFrame to a new CSV file\n",
|
| 597 |
+
"filtered_df.to_csv('data/model_subsets/all_models_poi.csv', index=False)\n"
|
| 598 |
+
]
|
| 599 |
+
},
|
| 600 |
+
{
|
| 601 |
+
"cell_type": "code",
|
| 602 |
+
"execution_count": null,
|
| 603 |
+
"id": "06c15f2c",
|
| 604 |
+
"metadata": {},
|
| 605 |
+
"outputs": [],
|
| 606 |
+
"source": [
|
| 607 |
+
"import pandas as pd\n",
|
| 608 |
+
"import os\n",
|
| 609 |
+
"\n",
|
| 610 |
+
"# Load the dataset\n",
|
| 611 |
+
"df = pd.read_csv('data/all_models_with_tags.csv')\n",
|
| 612 |
+
"\n",
|
| 613 |
+
"# Filter for rows where poi is True\n",
|
| 614 |
+
"filtered_df = df[df['poi'] == False]\n",
|
| 615 |
+
"os.makedirs('data/model_subsets', exist_ok=True)\n",
|
| 616 |
+
"\n",
|
| 617 |
+
"# Save the filtered DataFrame to a new CSV file\n",
|
| 618 |
+
"filtered_df.to_csv('data/model_subsets/all_models_poi_false.csv', index=False)\n"
|
| 619 |
+
]
|
| 620 |
+
}
|
| 621 |
+
],
|
| 622 |
+
"metadata": {
|
| 623 |
+
"kernelspec": {
|
| 624 |
+
"display_name": "latm",
|
| 625 |
+
"language": "python",
|
| 626 |
+
"name": "python3"
|
| 627 |
+
},
|
| 628 |
+
"language_info": {
|
| 629 |
+
"codemirror_mode": {
|
| 630 |
+
"name": "ipython",
|
| 631 |
+
"version": 3
|
| 632 |
+
},
|
| 633 |
+
"file_extension": ".py",
|
| 634 |
+
"mimetype": "text/x-python",
|
| 635 |
+
"name": "python",
|
| 636 |
+
"nbconvert_exporter": "python",
|
| 637 |
+
"pygments_lexer": "ipython3",
|
| 638 |
+
"version": "3.10.15"
|
| 639 |
+
}
|
| 640 |
+
},
|
| 641 |
+
"nbformat": 4,
|
| 642 |
+
"nbformat_minor": 5
|
| 643 |
+
}
|
scripts/Section_2_Image_grid.ipynb → jupyter_notebooks/Section_1_Figure_1_image_grid.ipynb
RENAMED
|
@@ -367,7 +367,7 @@
|
|
| 367 |
],
|
| 368 |
"metadata": {
|
| 369 |
"kernelspec": {
|
| 370 |
-
"display_name": "
|
| 371 |
"language": "python",
|
| 372 |
"name": "python3"
|
| 373 |
},
|
|
@@ -381,7 +381,7 @@
|
|
| 381 |
"name": "python",
|
| 382 |
"nbconvert_exporter": "python",
|
| 383 |
"pygments_lexer": "ipython3",
|
| 384 |
-
"version": "3.
|
| 385 |
}
|
| 386 |
},
|
| 387 |
"nbformat": 4,
|
|
|
|
| 367 |
],
|
| 368 |
"metadata": {
|
| 369 |
"kernelspec": {
|
| 370 |
+
"display_name": "latm",
|
| 371 |
"language": "python",
|
| 372 |
"name": "python3"
|
| 373 |
},
|
|
|
|
| 381 |
"name": "python",
|
| 382 |
"nbconvert_exporter": "python",
|
| 383 |
"pygments_lexer": "ipython3",
|
| 384 |
+
"version": "3.10.15"
|
| 385 |
}
|
| 386 |
},
|
| 387 |
"nbformat": 4,
|
scripts/Section_6-1_Histograms.ipynb → jupyter_notebooks/Section_2-2-2_Figure_3_histogram_monthly_images_nsfw_levels.ipynb
RENAMED
|
@@ -454,7 +454,7 @@
|
|
| 454 |
],
|
| 455 |
"metadata": {
|
| 456 |
"kernelspec": {
|
| 457 |
-
"display_name": "
|
| 458 |
"language": "python",
|
| 459 |
"name": "python3"
|
| 460 |
},
|
|
@@ -468,7 +468,7 @@
|
|
| 468 |
"name": "python",
|
| 469 |
"nbconvert_exporter": "python",
|
| 470 |
"pygments_lexer": "ipython3",
|
| 471 |
-
"version": "3.
|
| 472 |
}
|
| 473 |
},
|
| 474 |
"nbformat": 4,
|
|
|
|
| 454 |
],
|
| 455 |
"metadata": {
|
| 456 |
"kernelspec": {
|
| 457 |
+
"display_name": "latm",
|
| 458 |
"language": "python",
|
| 459 |
"name": "python3"
|
| 460 |
},
|
|
|
|
| 468 |
"name": "python",
|
| 469 |
"nbconvert_exporter": "python",
|
| 470 |
"pygments_lexer": "ipython3",
|
| 471 |
+
"version": "3.10.15"
|
| 472 |
}
|
| 473 |
},
|
| 474 |
"nbformat": 4,
|
scripts/Section_6-2_Demographics.ipynb → jupyter_notebooks/Section_2-2-2_Figure_4_Demographic_patterns_in_gen_images.ipynb
RENAMED
|
@@ -189,6 +189,14 @@
|
|
| 189 |
"mivolo_out.mkdir(parents=True, exist_ok=True) # Create the output directory if it doesn't exist"
|
| 190 |
]
|
| 191 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
{
|
| 193 |
"cell_type": "code",
|
| 194 |
"execution_count": null,
|
|
@@ -1470,6 +1478,14 @@
|
|
| 1470 |
" print(f\"File not found: {input_file_path}\")"
|
| 1471 |
]
|
| 1472 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1473 |
{
|
| 1474 |
"cell_type": "code",
|
| 1475 |
"execution_count": null,
|
|
|
|
| 189 |
"mivolo_out.mkdir(parents=True, exist_ok=True) # Create the output directory if it doesn't exist"
|
| 190 |
]
|
| 191 |
},
|
| 192 |
+
{
|
| 193 |
+
"cell_type": "markdown",
|
| 194 |
+
"id": "ffb7dd23",
|
| 195 |
+
"metadata": {},
|
| 196 |
+
"source": [
|
| 197 |
+
"## MiVOLO gender and age inference"
|
| 198 |
+
]
|
| 199 |
+
},
|
| 200 |
{
|
| 201 |
"cell_type": "code",
|
| 202 |
"execution_count": null,
|
|
|
|
| 1478 |
" print(f\"File not found: {input_file_path}\")"
|
| 1479 |
]
|
| 1480 |
},
|
| 1481 |
+
{
|
| 1482 |
+
"cell_type": "markdown",
|
| 1483 |
+
"id": "26aeeef7",
|
| 1484 |
+
"metadata": {},
|
| 1485 |
+
"source": [
|
| 1486 |
+
"## Visualization code"
|
| 1487 |
+
]
|
| 1488 |
+
},
|
| 1489 |
{
|
| 1490 |
"cell_type": "code",
|
| 1491 |
"execution_count": null,
|
scripts/Section_6-3_Models.ipynb → jupyter_notebooks/Section_2-3-2_top_10_most_popular_checkpoints.ipynb
RENAMED
|
File without changes
|
scripts/Section_6-4_Top_models.ipynb → jupyter_notebooks/Section_2-3-3_Figure_7_top_30_adapters.ipynb
RENAMED
|
File without changes
|
jupyter_notebooks/Section_2-3-4__Figure_8_Deepfake_victims.ipynb
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "06763fde",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# LLM annotation of Deepfake adapters"
|
| 9 |
+
]
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
"cell_type": "markdown",
|
| 13 |
+
"id": "b773045a",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"## Step 01 Data cleaning NER using spaCy"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "markdown",
|
| 21 |
+
"id": "234a55e5",
|
| 22 |
+
"metadata": {},
|
| 23 |
+
"source": [
|
| 24 |
+
"#### Here we clean leetspeak and architectre specifiers from the names as a preprossesing step for the named entity recognition NER below"
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"cell_type": "code",
|
| 29 |
+
"execution_count": 1,
|
| 30 |
+
"id": "f177df11",
|
| 31 |
+
"metadata": {},
|
| 32 |
+
"outputs": [],
|
| 33 |
+
"source": [
|
| 34 |
+
"import pandas as pd\n",
|
| 35 |
+
"import spacy\n",
|
| 36 |
+
"import re\n",
|
| 37 |
+
"import torch\n",
|
| 38 |
+
"from pathlib import Path\n",
|
| 39 |
+
"import unicodedata\n"
|
| 40 |
+
]
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"cell_type": "code",
|
| 44 |
+
"execution_count": 2,
|
| 45 |
+
"id": "a2383f32",
|
| 46 |
+
"metadata": {},
|
| 47 |
+
"outputs": [],
|
| 48 |
+
"source": [
|
| 49 |
+
"current_dir = Path.cwd()\n",
|
| 50 |
+
"poi_models_dir = current_dir.parent / \"data/CSV/model_subsets/POI_models.csv\" ### POI models dataset\n",
|
| 51 |
+
"output = current_dir.parent / \"data/CSV/model_subsets/NER_poi_step_01.csv\" ### Output file"
|
| 52 |
+
]
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"cell_type": "code",
|
| 56 |
+
"execution_count": 3,
|
| 57 |
+
"id": "66fc691f",
|
| 58 |
+
"metadata": {},
|
| 59 |
+
"outputs": [
|
| 60 |
+
{
|
| 61 |
+
"name": "stderr",
|
| 62 |
+
"output_type": "stream",
|
| 63 |
+
"text": [
|
| 64 |
+
"/home/lauhp/anaconda3/envs/latm/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
| 65 |
+
" from .autonotebook import tqdm as notebook_tqdm\n"
|
| 66 |
+
]
|
| 67 |
+
}
|
| 68 |
+
],
|
| 69 |
+
"source": [
|
| 70 |
+
"nlp = spacy.load(\"en_core_web_sm\") # or another model of your choice"
|
| 71 |
+
]
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"cell_type": "code",
|
| 75 |
+
"execution_count": 4,
|
| 76 |
+
"id": "8348c4a4",
|
| 77 |
+
"metadata": {},
|
| 78 |
+
"outputs": [
|
| 79 |
+
{
|
| 80 |
+
"name": "stdout",
|
| 81 |
+
"output_type": "stream",
|
| 82 |
+
"text": [
|
| 83 |
+
"Done! Saved to NER_poi_step_01.csv\n"
|
| 84 |
+
]
|
| 85 |
+
}
|
| 86 |
+
],
|
| 87 |
+
"source": [
|
| 88 |
+
"def preprocess_name(name):\n",
|
| 89 |
+
" name = str(name)\n",
|
| 90 |
+
"\n",
|
| 91 |
+
" # Normalize unicode characters (e.g., fancy fonts)\n",
|
| 92 |
+
" name = unicodedata.normalize(\"NFKD\", name)\n",
|
| 93 |
+
"\n",
|
| 94 |
+
" # Lowercase everything\n",
|
| 95 |
+
" name = name.lower()\n",
|
| 96 |
+
"\n",
|
| 97 |
+
" # Remove special keywords and patterns\n",
|
| 98 |
+
" junk_words = [\n",
|
| 99 |
+
" 'jav', 'jp', 'lora', 'locon', 'lycoris', 'requested', 'japanese', 'model',\n",
|
| 100 |
+
" 'flux', 'flux1.d', 'pony', 'realistic'\n",
|
| 101 |
+
" ]\n",
|
| 102 |
+
" for word in junk_words:\n",
|
| 103 |
+
" name = re.sub(rf'\\b{re.escape(word)}\\b', '', name, flags=re.IGNORECASE)\n",
|
| 104 |
+
"\n",
|
| 105 |
+
" # Remove versions like v1, v2.0, etc.\n",
|
| 106 |
+
" name = re.sub(r'v\\.?\\d+(\\.\\d+)?', '', name)\n",
|
| 107 |
+
"\n",
|
| 108 |
+
" # Remove 'not' followed by a word\n",
|
| 109 |
+
" name = re.sub(r'\\bnot\\s+\\w+', '', name)\n",
|
| 110 |
+
"\n",
|
| 111 |
+
" # Replace underscores and pipes with spaces\n",
|
| 112 |
+
" name = re.sub(r'[_|]', ' ', name)\n",
|
| 113 |
+
"\n",
|
| 114 |
+
" # Remove parentheses and content within\n",
|
| 115 |
+
" name = re.sub(r'\\(.*?\\)', '', name)\n",
|
| 116 |
+
"\n",
|
| 117 |
+
" # Remove excess whitespace\n",
|
| 118 |
+
" name = re.sub(r'\\s+', ' ', name).strip()\n",
|
| 119 |
+
"\n",
|
| 120 |
+
" return name\n",
|
| 121 |
+
"\n",
|
| 122 |
+
"# -------------------------\n",
|
| 123 |
+
"# Fallback Extractor\n",
|
| 124 |
+
"# -------------------------\n",
|
| 125 |
+
"def fallback_extract(text):\n",
|
| 126 |
+
" words = text.split()\n",
|
| 127 |
+
" capitalized = [w for w in words if w and w[0].isalpha()]\n",
|
| 128 |
+
" if len(capitalized) >= 2:\n",
|
| 129 |
+
" return \" \".join(capitalized[:2])\n",
|
| 130 |
+
" elif capitalized:\n",
|
| 131 |
+
" return capitalized[0]\n",
|
| 132 |
+
" return None\n",
|
| 133 |
+
"\n",
|
| 134 |
+
"# -------------------------\n",
|
| 135 |
+
"# Full Extraction Logic\n",
|
| 136 |
+
"# -------------------------\n",
|
| 137 |
+
"def extract_real_name(raw_name):\n",
|
| 138 |
+
" cleaned = preprocess_name(raw_name)\n",
|
| 139 |
+
" doc = nlp(cleaned)\n",
|
| 140 |
+
" persons = [ent.text for ent in doc.ents if ent.label_ == \"PERSON\"]\n",
|
| 141 |
+
" if persons:\n",
|
| 142 |
+
" return persons[0]\n",
|
| 143 |
+
" return fallback_extract(cleaned)\n",
|
| 144 |
+
"\n",
|
| 145 |
+
"# -------------------------\n",
|
| 146 |
+
"# Load Data and Apply\n",
|
| 147 |
+
"# -------------------------\n",
|
| 148 |
+
"df = pd.read_csv(poi_models_dir) # Or your own path\n",
|
| 149 |
+
"\n",
|
| 150 |
+
"# Apply the full extractor\n",
|
| 151 |
+
"texts = df['name'].astype(str).tolist()\n",
|
| 152 |
+
"docs = list(nlp.pipe([preprocess_name(t) for t in texts], batch_size=32))\n",
|
| 153 |
+
"\n",
|
| 154 |
+
"def extract_from_doc(doc, raw_text):\n",
|
| 155 |
+
" persons = [ent.text for ent in doc.ents if ent.label_ == \"PERSON\"]\n",
|
| 156 |
+
" if persons:\n",
|
| 157 |
+
" return persons[0]\n",
|
| 158 |
+
" return fallback_extract(preprocess_name(raw_text))\n",
|
| 159 |
+
"\n",
|
| 160 |
+
"df['real_name'] = [extract_from_doc(doc, raw_text) for doc, raw_text in zip(docs, texts)]\n",
|
| 161 |
+
"\n",
|
| 162 |
+
"# Save the result\n",
|
| 163 |
+
"df.to_csv(output, index=False)\n",
|
| 164 |
+
"print(\"Done! Saved to NER_poi_step_01.csv\")\n"
|
| 165 |
+
]
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"cell_type": "markdown",
|
| 169 |
+
"id": "a40b17fe",
|
| 170 |
+
"metadata": {},
|
| 171 |
+
"source": [
|
| 172 |
+
"## Step 2: compare country and profession with lists"
|
| 173 |
+
]
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"cell_type": "code",
|
| 177 |
+
"execution_count": 5,
|
| 178 |
+
"id": "414954ed",
|
| 179 |
+
"metadata": {},
|
| 180 |
+
"outputs": [
|
| 181 |
+
{
|
| 182 |
+
"name": "stdout",
|
| 183 |
+
"output_type": "stream",
|
| 184 |
+
"text": [
|
| 185 |
+
" real_name likely_country likely_nationality likely_profession\n",
|
| 186 |
+
"0 ronnie alonte model\n",
|
| 187 |
+
"1 zh elena kamperi twitch streamer\n",
|
| 188 |
+
"2 sofia vergara model\n",
|
| 189 |
+
"3 安然 anran celebrity\n",
|
| 190 |
+
"4 zoe kravitz celebrity\n"
|
| 191 |
+
]
|
| 192 |
+
}
|
| 193 |
+
],
|
| 194 |
+
"source": [
|
| 195 |
+
"import pandas as pd\n",
|
| 196 |
+
"from pathlib import Path\n",
|
| 197 |
+
"\n",
|
| 198 |
+
"# Set up paths\n",
|
| 199 |
+
"current_dir = Path.cwd()\n",
|
| 200 |
+
"countries = current_dir.parent / \"misc/lists/countries.csv\"\n",
|
| 201 |
+
"professions = current_dir.parent / \"misc/lists/professions.csv\"\n",
|
| 202 |
+
"inputNER = current_dir.parent / \"data/CSV/model_subsets/NER_poi_step_01.csv\"\n",
|
| 203 |
+
"outfile = current_dir.parent / \"data/CSV/model_subsets/NER_poi_step_02.csv\"\n",
|
| 204 |
+
"\n",
|
| 205 |
+
"# Load datasets\n",
|
| 206 |
+
"poi_df = pd.read_csv(inputNER)\n",
|
| 207 |
+
"countries_df = pd.read_csv(countries)\n",
|
| 208 |
+
"professions_df = pd.read_csv(professions)\n",
|
| 209 |
+
"\n",
|
| 210 |
+
"# Step 1: Combine tags into one lowercase list\n",
|
| 211 |
+
"def combine_tags(row):\n",
|
| 212 |
+
" return [str(row[f\"tag_{i}\"]).strip().lower() for i in range(1, 8) if pd.notna(row.get(f\"tag_{i}\"))]\n",
|
| 213 |
+
"\n",
|
| 214 |
+
"poi_df[\"tags\"] = poi_df.apply(combine_tags, axis=1)\n",
|
| 215 |
+
"\n",
|
| 216 |
+
"# Step 2: Build tag → (country, nationality) mapping\n",
|
| 217 |
+
"tag_to_country_nationality = {}\n",
|
| 218 |
+
"\n",
|
| 219 |
+
"for _, row in countries_df.iterrows():\n",
|
| 220 |
+
" country = str(row[\"en_short_name\"]).strip()\n",
|
| 221 |
+
" nationality = str(row[\"nationality\"]).strip()\n",
|
| 222 |
+
"\n",
|
| 223 |
+
" country_lc = country.lower()\n",
|
| 224 |
+
" nationality_lc = nationality.lower()\n",
|
| 225 |
+
"\n",
|
| 226 |
+
" # Add variations to the mapping\n",
|
| 227 |
+
" tag_to_country_nationality[country_lc] = (country, \"\")\n",
|
| 228 |
+
" tag_to_country_nationality[nationality_lc] = (\"\", nationality)\n",
|
| 229 |
+
" tag_to_country_nationality[country_lc.replace(\" \", \"\")] = (country, \"\")\n",
|
| 230 |
+
" tag_to_country_nationality[nationality_lc.replace(\" \", \"\")] = (\"\", nationality)\n",
|
| 231 |
+
"\n",
|
| 232 |
+
" for part in country_lc.split():\n",
|
| 233 |
+
" tag_to_country_nationality[part] = (country, \"\")\n",
|
| 234 |
+
" for part in nationality_lc.split():\n",
|
| 235 |
+
" tag_to_country_nationality[part] = (\"\", nationality)\n",
|
| 236 |
+
"\n",
|
| 237 |
+
"# Step 3: Infer likely_country and likely_nationality\n",
|
| 238 |
+
"# Step 3: Infer likely_country and likely_nationality\n",
|
| 239 |
+
"def infer_country_and_nationality(tags):\n",
|
| 240 |
+
" for tag in tags:\n",
|
| 241 |
+
" cleaned = tag.replace(\" \", \"\").lower()\n",
|
| 242 |
+
" if cleaned in tag_to_country_nationality:\n",
|
| 243 |
+
" country, nationality = tag_to_country_nationality[cleaned]\n",
|
| 244 |
+
" # Special case: skip if country is \"Isle of Man\"\n",
|
| 245 |
+
" if country == \"Isle of Man\":\n",
|
| 246 |
+
" country = \"\"\n",
|
| 247 |
+
" return pd.Series([country, nationality])\n",
|
| 248 |
+
" return pd.Series([\"\", \"\"])\n",
|
| 249 |
+
"\n",
|
| 250 |
+
"\n",
|
| 251 |
+
"poi_df[[\"likely_country\", \"likely_nationality\"]] = poi_df[\"tags\"].apply(infer_country_and_nationality)\n",
|
| 252 |
+
"\n",
|
| 253 |
+
"# Step 4: Build tag → profession mapping\n",
|
| 254 |
+
"profession_alias_map = {}\n",
|
| 255 |
+
"\n",
|
| 256 |
+
"for _, row in professions_df.iterrows():\n",
|
| 257 |
+
" canonical = str(row['profession']).strip().lower()\n",
|
| 258 |
+
" profession_alias_map[canonical] = canonical\n",
|
| 259 |
+
" for alias_col in ['alias_1', 'alias_2', 'alias_3']:\n",
|
| 260 |
+
" alias = row.get(alias_col)\n",
|
| 261 |
+
" if pd.notna(alias):\n",
|
| 262 |
+
" profession_alias_map[str(alias).strip().lower()] = canonical\n",
|
| 263 |
+
"\n",
|
| 264 |
+
"# Step 5: Infer likely profession from tags\n",
|
| 265 |
+
"def infer_profession_from_tags(tags):\n",
|
| 266 |
+
" matched = []\n",
|
| 267 |
+
" for tag in tags:\n",
|
| 268 |
+
" cleaned = tag.strip().lower()\n",
|
| 269 |
+
" if cleaned in profession_alias_map:\n",
|
| 270 |
+
" matched.append(profession_alias_map[cleaned])\n",
|
| 271 |
+
"\n",
|
| 272 |
+
" if not matched:\n",
|
| 273 |
+
" return \"\"\n",
|
| 274 |
+
" if \"celebrity\" in matched and len(set(matched)) > 1:\n",
|
| 275 |
+
" # Drop 'celebrity' if other professions are present\n",
|
| 276 |
+
" matched = [m for m in matched if m != \"celebrity\"]\n",
|
| 277 |
+
"\n",
|
| 278 |
+
" return matched[0] # Return the first specific match\n",
|
| 279 |
+
"\n",
|
| 280 |
+
"\n",
|
| 281 |
+
"poi_df[\"likely_profession\"] = poi_df[\"tags\"].apply(infer_profession_from_tags)\n",
|
| 282 |
+
"\n",
|
| 283 |
+
"# Step 6: Save enriched dataset\n",
|
| 284 |
+
"poi_df.to_csv(outfile, index=False)\n",
|
| 285 |
+
"\n",
|
| 286 |
+
"# Optional: Preview\n",
|
| 287 |
+
"print(poi_df[[\"real_name\", \"likely_country\", \"likely_nationality\", \"likely_profession\"]].head())\n"
|
| 288 |
+
]
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"cell_type": "code",
|
| 292 |
+
"execution_count": 6,
|
| 293 |
+
"id": "054f230b",
|
| 294 |
+
"metadata": {},
|
| 295 |
+
"outputs": [],
|
| 296 |
+
"source": [
|
| 297 |
+
"#!pip install transformers torch\n",
|
| 298 |
+
"#!python -m spacy download en_core_web_trf\n",
|
| 299 |
+
"#pip install openai"
|
| 300 |
+
]
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"cell_type": "markdown",
|
| 304 |
+
"id": "59185461",
|
| 305 |
+
"metadata": {},
|
| 306 |
+
"source": [
|
| 307 |
+
"## Step 3: Query Deepseek-v3 with NAME and HINTS"
|
| 308 |
+
]
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"cell_type": "code",
|
| 312 |
+
"execution_count": 7,
|
| 313 |
+
"id": "504b970f",
|
| 314 |
+
"metadata": {},
|
| 315 |
+
"outputs": [],
|
| 316 |
+
"source": [
|
| 317 |
+
"#!pip install openpyxl"
|
| 318 |
+
]
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
"cell_type": "code",
|
| 322 |
+
"execution_count": 10,
|
| 323 |
+
"id": "7c209115",
|
| 324 |
+
"metadata": {},
|
| 325 |
+
"outputs": [
|
| 326 |
+
{
|
| 327 |
+
"name": "stdout",
|
| 328 |
+
"output_type": "stream",
|
| 329 |
+
"text": [
|
| 330 |
+
"Row 1/4...\n",
|
| 331 |
+
"Saved up to row 1\n",
|
| 332 |
+
"Row 2/4...\n",
|
| 333 |
+
"Saved up to row 2\n",
|
| 334 |
+
"Row 3/4...\n",
|
| 335 |
+
"Saved up to row 3\n",
|
| 336 |
+
"Row 4/4...\n",
|
| 337 |
+
"Saved up to row 4\n",
|
| 338 |
+
"All done! Files: /home/lauhp/000_PHD/000_010_PUBLICATION/2025_SAGE/CODE/pm-paper_uzh_gitlab/pm-paper/data/CSV/Deepseek_annotated_POI.csv /home/lauhp/000_PHD/000_010_PUBLICATION/2025_SAGE/CODE/pm-paper_uzh_gitlab/pm-paper/data/CSV/Deepseek_annotated_POI.xlsx\n"
|
| 339 |
+
]
|
| 340 |
+
}
|
| 341 |
+
],
|
| 342 |
+
"source": [
|
| 343 |
+
"import pandas as pd\n",
|
| 344 |
+
"import openai\n",
|
| 345 |
+
"import time\n",
|
| 346 |
+
"import os\n",
|
| 347 |
+
"from pathlib import Path\n",
|
| 348 |
+
"from openai import OpenAI # Add this import\n",
|
| 349 |
+
"\n",
|
| 350 |
+
"# === PATHS & CONFIG ===\n",
|
| 351 |
+
"current_dir = Path.cwd()\n",
|
| 352 |
+
"inputCSV = current_dir.parent / \"data/CSV/model_subsets/NER_poi_step_02.csv\"\n",
|
| 353 |
+
"api_key_file = current_dir.parent / \"misc/credentials/deepseek_api_key.txt\" #store your API key under misc/credentials/deepseek_api_key.txt\n",
|
| 354 |
+
"\n",
|
| 355 |
+
"# Output both CSV and Excel for compatibility\n",
|
| 356 |
+
"OUTPUT_CSV = current_dir.parent / \"data/CSV/Deepseek_annotated_POI.csv\"\n",
|
| 357 |
+
"OUTPUT_XLSX = current_dir.parent / \"data/CSV/Deepseek_annotated_POI.xlsx\"\n",
|
| 358 |
+
"INDEX_FILE = current_dir.parent / \"misc/deepseek_query_index.txt\"\n",
|
| 359 |
+
"SAVE_INTERVAL = 1 # Save every N rows\n",
|
| 360 |
+
"START_ROW = 1 # Row index to start from (0-based)\n",
|
| 361 |
+
"END_ROW = 5 # Row index to end (exclusive)\n",
|
| 362 |
+
"\n",
|
| 363 |
+
"# === LOAD API KEY & CLIENT ===\n",
|
| 364 |
+
"with open(api_key_file) as f:\n",
|
| 365 |
+
" api_key = f.read().strip()\n",
|
| 366 |
+
"\n",
|
| 367 |
+
"client = OpenAI(\n",
|
| 368 |
+
" api_key=api_key,\n",
|
| 369 |
+
" base_url=\"https://api.deepseek.com/v1\"\n",
|
| 370 |
+
")\n",
|
| 371 |
+
"\n",
|
| 372 |
+
"# === LOAD DATA ===\n",
|
| 373 |
+
"df = pd.read_csv(inputCSV)\n",
|
| 374 |
+
"df = df.iloc[START_ROW:END_ROW].reset_index(drop=True)\n",
|
| 375 |
+
"\n",
|
| 376 |
+
"# === PREPARE PROMPTS ===\n",
|
| 377 |
+
"def create_prompt(row):\n",
|
| 378 |
+
" name = row['real_name'] if pd.notna(row['real_name']) else row['name']\n",
|
| 379 |
+
" hints = []\n",
|
| 380 |
+
" for col in ('likely_profession','likely_nationality','likely_country'):\n",
|
| 381 |
+
" if pd.notna(row.get(col, None)):\n",
|
| 382 |
+
" hints.append(row[col])\n",
|
| 383 |
+
" if not hints:\n",
|
| 384 |
+
" tags = [row[f'tag_{i}'] for i in range(1,8) if pd.notna(row.get(f'tag_{i}', None))]\n",
|
| 385 |
+
" hints.extend(tags[:3])\n",
|
| 386 |
+
" hint_text = \", \".join(hints[:5])\n",
|
| 387 |
+
" return (\n",
|
| 388 |
+
" f\"Given '{name}' ({hint_text}), provide:\\n\"\n",
|
| 389 |
+
" \"1. Full legal name (Western order if non-latin script)\\n\"\n",
|
| 390 |
+
" \"2. Any stage names/aliases (comma separated)\\n\"\n",
|
| 391 |
+
" \"3. Gender\\n\"\n",
|
| 392 |
+
" \"4. Top 3 most specific, factual professions (use industry-standard terms, no euphemisms)\\n\"\n",
|
| 393 |
+
" \"5. Primary country associated\\n\"\n",
|
| 394 |
+
" \"Use 'Unknown' when uncertain or you encounter a fictional character or place. \"\n",
|
| 395 |
+
" \"For entertainment fields, specify sub-genres when known (kpop, adult industry, etc.).\"\n",
|
| 396 |
+
" )\n",
|
| 397 |
+
"\n",
|
| 398 |
+
"prompts = df.apply(create_prompt, axis=1).tolist()\n",
|
| 399 |
+
"\n",
|
| 400 |
+
"# === CHECK FOR EXISTING OUTPUT ===\n",
|
| 401 |
+
"if os.path.exists(INDEX_FILE):\n",
|
| 402 |
+
" with open(INDEX_FILE, 'r') as f:\n",
|
| 403 |
+
" current_index = int(f.read().strip())\n",
|
| 404 |
+
"else:\n",
|
| 405 |
+
" current_index = 0\n",
|
| 406 |
+
"\n",
|
| 407 |
+
"results = []\n",
|
| 408 |
+
"if os.path.exists(OUTPUT_XLSX):\n",
|
| 409 |
+
" existing = pd.read_excel(OUTPUT_XLSX)\n",
|
| 410 |
+
" if {'full_name','aliases','gender','profession_llm','country'}.issubset(existing.columns):\n",
|
| 411 |
+
" results = existing[['full_name','aliases','gender','profession_llm','country']].values.tolist()\n",
|
| 412 |
+
"\n",
|
| 413 |
+
"# === QUERY & PARSE ===\n",
|
| 414 |
+
"def query_deepseek(prompt):\n",
|
| 415 |
+
" try:\n",
|
| 416 |
+
" resp = client.chat.completions.create(\n",
|
| 417 |
+
" model=\"deepseek-chat\",\n",
|
| 418 |
+
" messages=[\n",
|
| 419 |
+
" {\"role\":\"system\",\"content\":\"You extract key data on a person; respond with exactly 5 numbered lines.\"},\n",
|
| 420 |
+
" {\"role\":\"user\",\"content\":prompt}\n",
|
| 421 |
+
" ],\n",
|
| 422 |
+
" temperature=0.05, top_p=0.8\n",
|
| 423 |
+
" )\n",
|
| 424 |
+
" return resp.choices[0].message.content.strip()\n",
|
| 425 |
+
" except Exception as e:\n",
|
| 426 |
+
" print(\"API error:\", e)\n",
|
| 427 |
+
" return \"\"\n",
|
| 428 |
+
"\n",
|
| 429 |
+
"def parse_response(resp):\n",
|
| 430 |
+
" lines = [l.strip() for l in resp.split('\\n') if l.strip()]\n",
|
| 431 |
+
" out = [\"Unknown\"]*5\n",
|
| 432 |
+
" for l in lines:\n",
|
| 433 |
+
" if l.startswith('1.'): out[0] = l[2:].strip()\n",
|
| 434 |
+
" elif l.startswith('2.'): out[1] = l[2:].strip()\n",
|
| 435 |
+
" elif l.startswith('3.'): out[2] = l[2:].strip()\n",
|
| 436 |
+
" elif l.startswith('4.'): out[3] = l[2:].strip()\n",
|
| 437 |
+
" elif l.startswith('5.'): out[4] = l[2:].strip()\n",
|
| 438 |
+
" return out\n",
|
| 439 |
+
"\n",
|
| 440 |
+
"# === PROCESS & SAVE ===\n",
|
| 441 |
+
"for i in range(current_index, len(df)):\n",
|
| 442 |
+
" print(f\"Row {i+1}/{len(df)}...\")\n",
|
| 443 |
+
" data = parse_response(query_deepseek(prompts[i]))\n",
|
| 444 |
+
" if i < len(results): results[i] = data\n",
|
| 445 |
+
" else: results.append(data)\n",
|
| 446 |
+
" current_index = i+1\n",
|
| 447 |
+
"\n",
|
| 448 |
+
" if current_index % SAVE_INTERVAL == 0 or current_index == len(df):\n",
|
| 449 |
+
" out_df = df.iloc[:current_index].copy()\n",
|
| 450 |
+
" out_df[['full_name','aliases','gender','profession_llm','country']] = pd.DataFrame(results[:current_index])\n",
|
| 451 |
+
" # CSV\n",
|
| 452 |
+
" out_df.to_csv(OUTPUT_CSV, index=False)\n",
|
| 453 |
+
" # Excel\n",
|
| 454 |
+
" out_df.to_excel(OUTPUT_XLSX, index=False)\n",
|
| 455 |
+
" with open(INDEX_FILE, 'w') as f:\n",
|
| 456 |
+
" f.write(str(current_index))\n",
|
| 457 |
+
" print(\"Saved up to row\", current_index)\n",
|
| 458 |
+
" time.sleep(1)\n",
|
| 459 |
+
"\n",
|
| 460 |
+
"print(\"All done! Files:\", OUTPUT_CSV, OUTPUT_XLSX)\n"
|
| 461 |
+
]
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"cell_type": "markdown",
|
| 465 |
+
"id": "7910e574",
|
| 466 |
+
"metadata": {},
|
| 467 |
+
"source": []
|
| 468 |
+
},
|
| 469 |
+
{
|
| 470 |
+
"cell_type": "markdown",
|
| 471 |
+
"id": "9d377005",
|
| 472 |
+
"metadata": {},
|
| 473 |
+
"source": [
|
| 474 |
+
"# Aggregate by individual names"
|
| 475 |
+
]
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"cell_type": "markdown",
|
| 479 |
+
"id": "d2e75354",
|
| 480 |
+
"metadata": {},
|
| 481 |
+
"source": [
|
| 482 |
+
" ##### e.g. Emma Watson [model1, model2, model3] etc."
|
| 483 |
+
]
|
| 484 |
+
},
|
| 485 |
+
{
|
| 486 |
+
"cell_type": "code",
|
| 487 |
+
"execution_count": 11,
|
| 488 |
+
"id": "747c3a2f",
|
| 489 |
+
"metadata": {},
|
| 490 |
+
"outputs": [],
|
| 491 |
+
"source": [
|
| 492 |
+
"import pandas as pd\n",
|
| 493 |
+
"import re\n",
|
| 494 |
+
"from pathlib import Path\n",
|
| 495 |
+
"current_dir = Path.cwd()\n",
|
| 496 |
+
"\n",
|
| 497 |
+
"profession_map = current_dir.parent / \"misc/lists/mapped_professions.csv\"\n",
|
| 498 |
+
"\n",
|
| 499 |
+
"poi_df = current_dir.parent / \"data/CSV/Deepseek_annotated_POI.csv\"\n",
|
| 500 |
+
"\n",
|
| 501 |
+
"output = current_dir.parent / \"data/CSV/Deepseek_annotated_POI_aggregated.csv\"\n",
|
| 502 |
+
"\n",
|
| 503 |
+
"countries_csv = current_dir.parent / \"misc/lists/countries.csv\"\n",
|
| 504 |
+
"countries_df = pd.read_csv(countries_csv)\n",
|
| 505 |
+
"\n",
|
| 506 |
+
"# Extract valid country names (strip whitespace)\n",
|
| 507 |
+
"valid_countries = set(countries_df['en_short_name'].str.strip())\n",
|
| 508 |
+
"\n",
|
| 509 |
+
"\n",
|
| 510 |
+
"# Load the dataset\n",
|
| 511 |
+
"df = pd.read_csv(poi_df) # Update path if needed\n",
|
| 512 |
+
"\n",
|
| 513 |
+
"# Step 1: Group by 'full_name' and aggregate required information\n",
|
| 514 |
+
"grouped_df = df.groupby('full_name').agg(\n",
|
| 515 |
+
" No_of_models=('id', 'count'),\n",
|
| 516 |
+
" modelIDs=('id', lambda x: list(x)),\n",
|
| 517 |
+
" combinedDownloadCount=('downloadCount', 'sum')\n",
|
| 518 |
+
").reset_index()\n",
|
| 519 |
+
"\n",
|
| 520 |
+
"# Step 2: Keep representative info for each person\n",
|
| 521 |
+
"# Keep representative info (including aliases)\n",
|
| 522 |
+
"additional_columns = df.groupby('full_name').agg(\n",
|
| 523 |
+
" country=('country', 'first'),\n",
|
| 524 |
+
" profession_llm=('profession_llm', 'first'),\n",
|
| 525 |
+
" gender=('gender', 'first'),\n",
|
| 526 |
+
" aliases=('aliases', 'first') # ✅ Add this line\n",
|
| 527 |
+
").reset_index()\n",
|
| 528 |
+
"\n",
|
| 529 |
+
"\n",
|
| 530 |
+
"\n",
|
| 531 |
+
"def standardize_country(country):\n",
|
| 532 |
+
" if not isinstance(country, str):\n",
|
| 533 |
+
" return \"Unknown\"\n",
|
| 534 |
+
"\n",
|
| 535 |
+
" country_clean = country.strip()\n",
|
| 536 |
+
" lowered = country_clean.lower()\n",
|
| 537 |
+
"\n",
|
| 538 |
+
" # Handle fictional or fantasy countries\n",
|
| 539 |
+
" fictional_keywords = [\"fictional\", \"westeros\", \"asgard\", \"middle-earth\", \"naboo\", \"middle earth\", \"latveria\"]\n",
|
| 540 |
+
" if any(keyword in lowered for keyword in fictional_keywords):\n",
|
| 541 |
+
" return \"Unknown\"\n",
|
| 542 |
+
"\n",
|
| 543 |
+
" # Handle known region-based adjustments\n",
|
| 544 |
+
" if \"macau\" in lowered:\n",
|
| 545 |
+
" return \"Macau\"\n",
|
| 546 |
+
" elif \"hong kong\" in lowered:\n",
|
| 547 |
+
" return \"Hong Kong\"\n",
|
| 548 |
+
" elif \"taiwan\" in lowered:\n",
|
| 549 |
+
" return \"Taiwan\"\n",
|
| 550 |
+
"\n",
|
| 551 |
+
" # Normalize complex or alternate country names\n",
|
| 552 |
+
" lowered = lowered.replace(\"United Kingdom of Great Britain and Northern Ireland\", \"united kingdom\")\n",
|
| 553 |
+
" lowered = lowered.replace(\"england\", \"united kingdom\")\n",
|
| 554 |
+
" lowered = lowered.replace(\"united states of america\", \"united states\")\n",
|
| 555 |
+
"\n",
|
| 556 |
+
" # Remove anything in brackets and after commas\n",
|
| 557 |
+
" country_clean = re.sub(r\"\\(.*?\\)\", \"\", country_clean)\n",
|
| 558 |
+
" country_clean = country_clean.split(',')[0].strip().lower()\n",
|
| 559 |
+
"\n",
|
| 560 |
+
" # Manual overrides\n",
|
| 561 |
+
" replacements = {\n",
|
| 562 |
+
" \"united kingdom\": \"UK\",\n",
|
| 563 |
+
" \"united kingdom of great britain and northern ireland\": \"UK\",\n",
|
| 564 |
+
" \"french southern territories\": \"Other\",\n",
|
| 565 |
+
" \"united states\": \"US\",\n",
|
| 566 |
+
" \"united states of america\": \"US\",\n",
|
| 567 |
+
" \"turkey\": \"Türkiye\",\n",
|
| 568 |
+
" \"czech republic\": \"Czechia\"\n",
|
| 569 |
+
" }\n",
|
| 570 |
+
"\n",
|
| 571 |
+
" if country_clean in replacements:\n",
|
| 572 |
+
" return replacements[country_clean]\n",
|
| 573 |
+
"\n",
|
| 574 |
+
" # Final check against valid country list (case-insensitive)\n",
|
| 575 |
+
" for valid in valid_countries:\n",
|
| 576 |
+
" if country_clean == valid.lower():\n",
|
| 577 |
+
" return valid\n",
|
| 578 |
+
"\n",
|
| 579 |
+
" return \"Unknown\"\n",
|
| 580 |
+
"\n",
|
| 581 |
+
"\n",
|
| 582 |
+
"# Updated function to fully remove anything in brackets (complete or not)\n",
|
| 583 |
+
"def get_profession_short(profession):\n",
|
| 584 |
+
" if isinstance(profession, str):\n",
|
| 585 |
+
" # Get first part before comma\n",
|
| 586 |
+
" first_prof = profession.split(',')[0].strip()\n",
|
| 587 |
+
" # Remove all bracketed content, even malformed\n",
|
| 588 |
+
" first_prof = re.sub(r\"[\\[].∗?[\\[].*?[\\]]\", \"\", first_prof) # removes properly closed\n",
|
| 589 |
+
" first_prof = re.sub(r\"[\\(\\[].*\", \"\", first_prof) # removes malformed\n",
|
| 590 |
+
" cleaned = first_prof.strip()\n",
|
| 591 |
+
" # Normalize 'Actress' to 'Actor'\n",
|
| 592 |
+
" if cleaned.lower() == \"actress\":\n",
|
| 593 |
+
" return \"Actor\"\n",
|
| 594 |
+
" return cleaned\n",
|
| 595 |
+
" return None\n",
|
| 596 |
+
"\n",
|
| 597 |
+
"# Load your mapping file\n",
|
| 598 |
+
"mapping_df = pd.read_csv(profession_map, on_bad_lines='skip') # or 'warn'\n",
|
| 599 |
+
"\n",
|
| 600 |
+
"\n",
|
| 601 |
+
"# Ensure the mapping columns are named correctly\n",
|
| 602 |
+
"# (Assuming columns are: 'profession_llm' or 'profession_short', and 'category' or 'mapped_profession')\n",
|
| 603 |
+
"# Adjust these as needed\n",
|
| 604 |
+
"mapping_df.columns = [col.strip().lower() for col in mapping_df.columns]\n",
|
| 605 |
+
"\n",
|
| 606 |
+
"# Rename for clarity and consistency\n",
|
| 607 |
+
"if 'profession_llm' in mapping_df.columns:\n",
|
| 608 |
+
" mapping_df = mapping_df.rename(columns={'profession_llm': 'profession_short'})\n",
|
| 609 |
+
"if 'category' in mapping_df.columns:\n",
|
| 610 |
+
" mapping_df = mapping_df.rename(columns={'category': 'mapped_profession'})\n",
|
| 611 |
+
"\n",
|
| 612 |
+
"# Merge the mapped profession into final_df\n",
|
| 613 |
+
"#final_df = final_df.merge(mapping_df[['profession_short', 'mapped_profession']], on='profession_short', how='left')\n",
|
| 614 |
+
"\n",
|
| 615 |
+
"\n",
|
| 616 |
+
"additional_columns = df.groupby('full_name').agg(\n",
|
| 617 |
+
" country=('country', 'first'),\n",
|
| 618 |
+
" profession_llm=('profession_llm', 'first'),\n",
|
| 619 |
+
" gender=('gender', 'first'),\n",
|
| 620 |
+
" aliases=('aliases', 'first') # <-- Added this line\n",
|
| 621 |
+
").reset_index()\n",
|
| 622 |
+
"\n",
|
| 623 |
+
"\n",
|
| 624 |
+
"# Step 3: Merge the aggregated info with the representative info\n",
|
| 625 |
+
"final_df = pd.merge(grouped_df, additional_columns, on='full_name', how='left')\n",
|
| 626 |
+
"\n",
|
| 627 |
+
"# Step 4: Clean and transform columns\n",
|
| 628 |
+
"final_df['profession_short'] = final_df['profession_llm'].apply(get_profession_short)\n",
|
| 629 |
+
"final_df['country'] = final_df['country'].apply(standardize_country)\n",
|
| 630 |
+
"\n",
|
| 631 |
+
"# Step 5: Merge with profession mapping\n",
|
| 632 |
+
"final_df = final_df.merge(mapping_df[['profession_short', 'mapped_profession']], on='profession_short', how='left')\n",
|
| 633 |
+
"\n",
|
| 634 |
+
"# Optional: Save the result to a CSV file\n",
|
| 635 |
+
"final_df.to_csv(output, index=False)\n"
|
| 636 |
+
]
|
| 637 |
+
},
|
| 638 |
+
{
|
| 639 |
+
"cell_type": "code",
|
| 640 |
+
"execution_count": null,
|
| 641 |
+
"id": "704e5246",
|
| 642 |
+
"metadata": {},
|
| 643 |
+
"outputs": [],
|
| 644 |
+
"source": []
|
| 645 |
+
}
|
| 646 |
+
],
|
| 647 |
+
"metadata": {
|
| 648 |
+
"kernelspec": {
|
| 649 |
+
"display_name": "latm",
|
| 650 |
+
"language": "python",
|
| 651 |
+
"name": "python3"
|
| 652 |
+
},
|
| 653 |
+
"language_info": {
|
| 654 |
+
"codemirror_mode": {
|
| 655 |
+
"name": "ipython",
|
| 656 |
+
"version": 3
|
| 657 |
+
},
|
| 658 |
+
"file_extension": ".py",
|
| 659 |
+
"mimetype": "text/x-python",
|
| 660 |
+
"name": "python",
|
| 661 |
+
"nbconvert_exporter": "python",
|
| 662 |
+
"pygments_lexer": "ipython3",
|
| 663 |
+
"version": "3.10.15"
|
| 664 |
+
}
|
| 665 |
+
},
|
| 666 |
+
"nbformat": 4,
|
| 667 |
+
"nbformat_minor": 5
|
| 668 |
+
}
|
jupyter_notebooks/Section_2-3-4__Figure_8a_sunburst_gender.ipynb
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Prepare *.json for Figure 8a"
|
| 8 |
+
]
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"cell_type": "code",
|
| 12 |
+
"execution_count": 8,
|
| 13 |
+
"metadata": {},
|
| 14 |
+
"outputs": [
|
| 15 |
+
{
|
| 16 |
+
"name": "stdout",
|
| 17 |
+
"output_type": "stream",
|
| 18 |
+
"text": [
|
| 19 |
+
"8a.json\n"
|
| 20 |
+
]
|
| 21 |
+
}
|
| 22 |
+
],
|
| 23 |
+
"source": [
|
| 24 |
+
"import pandas as pd\n",
|
| 25 |
+
"from collections import defaultdict\n",
|
| 26 |
+
"import json\n",
|
| 27 |
+
"from pathlib import Path \n",
|
| 28 |
+
"\n",
|
| 29 |
+
"current_dir = Path.cwd()\n",
|
| 30 |
+
"sunburst_json = current_dir.parent / \"public/json/8a.json\"\n",
|
| 31 |
+
"\n",
|
| 32 |
+
"\n",
|
| 33 |
+
"aggregated_poi = current_dir.parent / \"data/CSV/Deepseek_annotated_POI_aggregated.csv\"\n",
|
| 34 |
+
"df = pd.read_csv(aggregated_poi)\n",
|
| 35 |
+
"# ---- Normalize Gender (group Non-binary and Unknown into 'Other') ----\n",
|
| 36 |
+
"def normalize_gender(g):\n",
|
| 37 |
+
" g = str(g).strip().lower()\n",
|
| 38 |
+
" if g in [\"female\", \"woman\", \"female (group)\", \"female (transgender)\", \"female (virtual persona)\", \"female (group members)\"]:\n",
|
| 39 |
+
" return \"Female\"\n",
|
| 40 |
+
" elif g in [\"male\", \"male (android)\", \"male (character)\"]:\n",
|
| 41 |
+
" return \"Male\"\n",
|
| 42 |
+
" else:\n",
|
| 43 |
+
" return \"Other\"\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"df['gender_normalized'] = df['gender'].apply(normalize_gender)\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"# ---- Step 1: Limit to top 10 countries ----\n",
|
| 48 |
+
"df['country_cleaned'] = df['country'].apply(lambda x: x if x not in ['Unknown', '', None] else 'Other')\n",
|
| 49 |
+
"top_countries = df['country_cleaned'].value_counts().nlargest(12).index.tolist()\n",
|
| 50 |
+
"df['country_limited'] = df['country_cleaned'].apply(lambda x: x if x in top_countries else 'Other')\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"# ---- Step 2: Normalize and limit professions ----\n",
|
| 53 |
+
"valid_categories = [\n",
|
| 54 |
+
" \"Actor\", \"Adult Performer\", \"Singer, Musician\", \"Model\",\n",
|
| 55 |
+
" \"Online Personality\", \"TV Personality\", \"Voice Actor\",\"Public Figure\", \"Sports Professional\"\n",
|
| 56 |
+
"]\n",
|
| 57 |
+
"\n",
|
| 58 |
+
"def remap_profession(profession):\n",
|
| 59 |
+
" if profession == 'Unknown' or profession not in valid_categories:\n",
|
| 60 |
+
" return 'Other'\n",
|
| 61 |
+
" elif profession == 'Fictional Character':\n",
|
| 62 |
+
" return 'Actor'\n",
|
| 63 |
+
" elif profession == 'Voice actor':\n",
|
| 64 |
+
" return 'Voice Actor'\n",
|
| 65 |
+
" return profession\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"df['profession_limited'] = df['mapped_profession'].apply(remap_profession)\n",
|
| 68 |
+
"\n",
|
| 69 |
+
"# ---- Step 3: Group by gender and profession ----\n",
|
| 70 |
+
"sunburst_data = df.groupby(['gender_normalized', 'profession_limited']).size().reset_index(name='count')\n",
|
| 71 |
+
"\n",
|
| 72 |
+
"# ---- Step 4: Create nested structure for D3.js ----\n",
|
| 73 |
+
"sunburst_dict = {\"name\": \"root\", \"children\": []}\n",
|
| 74 |
+
"gender_map = defaultdict(list)\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"for _, row in sunburst_data.iterrows():\n",
|
| 77 |
+
" gender = row['gender_normalized']\n",
|
| 78 |
+
" profession = row['profession_limited']\n",
|
| 79 |
+
" count = int(row['count'])\n",
|
| 80 |
+
" gender_map[gender].append({\"name\": profession, \"value\": count})\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"# Sort 'Other' professions to appear last\n",
|
| 83 |
+
"for gender, professions in gender_map.items():\n",
|
| 84 |
+
" professions_sorted = sorted(professions, key=lambda d: (d[\"name\"] == \"Other\", d[\"name\"]))\n",
|
| 85 |
+
" gender_map[gender] = professions_sorted\n",
|
| 86 |
+
"\n",
|
| 87 |
+
"# Build the final nested structure\n",
|
| 88 |
+
"for gender, professions in gender_map.items():\n",
|
| 89 |
+
" sunburst_dict[\"children\"].append({\"name\": gender, \"children\": professions})\n",
|
| 90 |
+
"\n",
|
| 91 |
+
"# ---- Step 5: Save to a JSON file ----\n",
|
| 92 |
+
"with open(sunburst_json, \"w\", encoding='utf-8') as f:\n",
|
| 93 |
+
" json.dump(sunburst_dict, f, ensure_ascii=False, indent=2)\n",
|
| 94 |
+
"\n",
|
| 95 |
+
"print(\"8a.json\")\n"
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"cell_type": "code",
|
| 100 |
+
"execution_count": null,
|
| 101 |
+
"metadata": {},
|
| 102 |
+
"outputs": [],
|
| 103 |
+
"source": []
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"metadata": {
|
| 107 |
+
"kernelspec": {
|
| 108 |
+
"display_name": "latm",
|
| 109 |
+
"language": "python",
|
| 110 |
+
"name": "python3"
|
| 111 |
+
},
|
| 112 |
+
"language_info": {
|
| 113 |
+
"codemirror_mode": {
|
| 114 |
+
"name": "ipython",
|
| 115 |
+
"version": 3
|
| 116 |
+
},
|
| 117 |
+
"file_extension": ".py",
|
| 118 |
+
"mimetype": "text/x-python",
|
| 119 |
+
"name": "python",
|
| 120 |
+
"nbconvert_exporter": "python",
|
| 121 |
+
"pygments_lexer": "ipython3",
|
| 122 |
+
"version": "3.10.15"
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"nbformat": 4,
|
| 126 |
+
"nbformat_minor": 2
|
| 127 |
+
}
|
jupyter_notebooks/Section_2-3-4__Figure_8b_sunburst_profession.ipynb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Prepare *.json for Figure 8b"
|
| 8 |
+
]
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"cell_type": "code",
|
| 12 |
+
"execution_count": 5,
|
| 13 |
+
"metadata": {},
|
| 14 |
+
"outputs": [
|
| 15 |
+
{
|
| 16 |
+
"name": "stdout",
|
| 17 |
+
"output_type": "stream",
|
| 18 |
+
"text": [
|
| 19 |
+
"✅ Sunburst data saved to sunburst_data.json\n"
|
| 20 |
+
]
|
| 21 |
+
}
|
| 22 |
+
],
|
| 23 |
+
"source": [
|
| 24 |
+
"import pandas as pd\n",
|
| 25 |
+
"from collections import defaultdict\n",
|
| 26 |
+
"import json\n",
|
| 27 |
+
"from pathlib import Path\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"current_dir = Path.cwd()\n",
|
| 30 |
+
"\n",
|
| 31 |
+
"sunburst_path = current_dir.parent / \"public/json/sunburst_countries_A.json\"\n",
|
| 32 |
+
"\n",
|
| 33 |
+
"\n",
|
| 34 |
+
"aggregated_poi = current_dir.parent / \"data/CSV/Deepseek_annotated_POI_aggregated.csv\"\n",
|
| 35 |
+
"\n",
|
| 36 |
+
"df = pd.read_csv(aggregated_poi)\n",
|
| 37 |
+
"df['country_cleaned'] = df['country'].apply(lambda x: x if x not in ['Unknown', '', None] else 'Other')\n",
|
| 38 |
+
"\n",
|
| 39 |
+
"# Now get top countries excluding what was forced into 'Other'\n",
|
| 40 |
+
"top_countries = df['country_cleaned'].value_counts().nlargest(15).index.tolist()\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"# Final limited country column\n",
|
| 43 |
+
"df['country_limited'] = df['country_cleaned'].apply(lambda x: x if x in top_countries else 'Other')\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"# ---- Step 2: Limit to top 7 professions and combine Unknown and Sports Professional with Other ----\n",
|
| 46 |
+
"top_professions = df['mapped_profession'].value_counts().nlargest(7).index.tolist()\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"# Explicitly remove 'Unknown' and 'Sports Professional' even if they are in the top 7\n",
|
| 49 |
+
"top_professions = [p for p in top_professions if p not in ['Unknown', 'Sports Professional']]\n",
|
| 50 |
+
"\n",
|
| 51 |
+
"# Normalize 'Unknown' and '\n",
|
| 52 |
+
"# Sports Professional' into 'Other'\n",
|
| 53 |
+
"# Normalize 'Unknown' and 'Sports Professional' into 'Other'\n",
|
| 54 |
+
"df['profession_limited'] = df['mapped_profession'].apply(\n",
|
| 55 |
+
" lambda x: 'Other' if x in ['Unknown', 'Sports Professional'] or x not in top_professions else x\n",
|
| 56 |
+
")\n",
|
| 57 |
+
"\n",
|
| 58 |
+
"\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"# ---- Step 3: Group by the limited country and profession ----\n",
|
| 61 |
+
"sunburst_data = df.groupby(['country_limited', 'profession_limited']).size().reset_index(name='count')\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"# ---- Step 4: Create a nested structure for D3.js ----\n",
|
| 64 |
+
"sunburst_dict = {\"name\": \"root\", \"children\": []}\n",
|
| 65 |
+
"country_map = defaultdict(list)\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"for _, row in sunburst_data.iterrows():\n",
|
| 68 |
+
" country = row['country_limited']\n",
|
| 69 |
+
" profession = row['profession_limited']\n",
|
| 70 |
+
" count = int(row['count'])\n",
|
| 71 |
+
" country_map[country].append({\"name\": profession, \"value\": count})\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"# For each country, sort the profession list so that \"Other\" appears at the end\n",
|
| 74 |
+
"for country, professions in country_map.items():\n",
|
| 75 |
+
" professions_sorted = sorted(professions, key=lambda d: (d[\"name\"] == \"Other\", d[\"name\"]))\n",
|
| 76 |
+
" country_map[country] = professions_sorted\n",
|
| 77 |
+
"\n",
|
| 78 |
+
"for country, professions in country_map.items():\n",
|
| 79 |
+
" sunburst_dict[\"children\"].append({\"name\": country, \"children\": professions})\n",
|
| 80 |
+
"\n",
|
| 81 |
+
"# ---- Step 5: Save to a JSON file ----\n",
|
| 82 |
+
"with open(sunburst_path, \"w\", encoding='utf-8') as f:\n",
|
| 83 |
+
" json.dump(sunburst_dict, f, ensure_ascii=False, indent=2)\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"print(\"✅ Sunburst data saved to sunburst_data.json\")\n"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"cell_type": "markdown",
|
| 91 |
+
"metadata": {},
|
| 92 |
+
"source": [
|
| 93 |
+
"the resulting *.json is the input for Figure_8.html"
|
| 94 |
+
]
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"cell_type": "markdown",
|
| 98 |
+
"metadata": {},
|
| 99 |
+
"source": []
|
| 100 |
+
}
|
| 101 |
+
],
|
| 102 |
+
"metadata": {
|
| 103 |
+
"kernelspec": {
|
| 104 |
+
"display_name": "latm",
|
| 105 |
+
"language": "python",
|
| 106 |
+
"name": "python3"
|
| 107 |
+
},
|
| 108 |
+
"language_info": {
|
| 109 |
+
"codemirror_mode": {
|
| 110 |
+
"name": "ipython",
|
| 111 |
+
"version": 3
|
| 112 |
+
},
|
| 113 |
+
"file_extension": ".py",
|
| 114 |
+
"mimetype": "text/x-python",
|
| 115 |
+
"name": "python",
|
| 116 |
+
"nbconvert_exporter": "python",
|
| 117 |
+
"pygments_lexer": "ipython3",
|
| 118 |
+
"version": "3.10.15"
|
| 119 |
+
}
|
| 120 |
+
},
|
| 121 |
+
"nbformat": 4,
|
| 122 |
+
"nbformat_minor": 2
|
| 123 |
+
}
|
jupyter_notebooks/Section_2-3_Figure_5_co-occurence_promotional_tags.ipynb
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Create *.json for figure 5 (Co-occurence network of Tags)"
|
| 8 |
+
]
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"cell_type": "code",
|
| 12 |
+
"execution_count": 5,
|
| 13 |
+
"metadata": {},
|
| 14 |
+
"outputs": [
|
| 15 |
+
{
|
| 16 |
+
"name": "stdout",
|
| 17 |
+
"output_type": "stream",
|
| 18 |
+
"text": [
|
| 19 |
+
"Processing: america\n",
|
| 20 |
+
" ✅ Saved to /home/lauhp/000_PHD/000_010_PUBLICATION/2025_SAGE/CODE/pm-paper_uzh_gitlab/pm-paper/public/json/tags_america.json\n"
|
| 21 |
+
]
|
| 22 |
+
}
|
| 23 |
+
],
|
| 24 |
+
"source": [
|
| 25 |
+
"import pandas as pd\n",
|
| 26 |
+
"from itertools import combinations\n",
|
| 27 |
+
"from collections import Counter, defaultdict\n",
|
| 28 |
+
"import json\n",
|
| 29 |
+
"import re\n",
|
| 30 |
+
"import os\n",
|
| 31 |
+
"\n",
|
| 32 |
+
"from pathlib import Path\n",
|
| 33 |
+
"current_dir = Path.cwd()\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"# === CONFIG ===\n",
|
| 36 |
+
"file_path = current_dir.parent / \"data/CSV/Models/Civi_models.csv\"\n",
|
| 37 |
+
"output_dir = current_dir.parent / \"public/json/\"\n",
|
| 38 |
+
"#target_terms = [\"asian\", \"indian\", \"man\", \"woman\", \"german\", \"korean\", \"american\", \"russian\", \"style\", \"japanese\", \"chinese\"] # Add any tags you want to process\n",
|
| 39 |
+
"#target_terms = [\"character\", \"instagram\", \"youtuber\", \"actor\", \"actress\", \"celebrity\", \"vtuber\", \"kpop\"] # Add any tags you want to process\n",
|
| 40 |
+
"target_terms = [\"america\"] # Add any tags you want to process\n",
|
| 41 |
+
"min_connections = 1 # minimum number of link connections per node\n",
|
| 42 |
+
"\n",
|
| 43 |
+
"# === LOAD DATA ===\n",
|
| 44 |
+
"df = pd.read_csv(file_path)\n",
|
| 45 |
+
"tag_columns = [f\"tag_{i}\" for i in range(1, 8)]\n",
|
| 46 |
+
"df_tags = df[tag_columns]\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"# === MAIN LOOP ===\n",
|
| 49 |
+
"for target_term in target_terms:\n",
|
| 50 |
+
" print(f\"Processing: {target_term}\")\n",
|
| 51 |
+
" \n",
|
| 52 |
+
" pattern = re.compile(rf'\\b{re.escape(target_term)}\\b', flags=re.IGNORECASE)\n",
|
| 53 |
+
" df_filtered = df_tags[df_tags.apply(\n",
|
| 54 |
+
" lambda row: row.astype(str).apply(lambda x: bool(pattern.search(x))).any(),\n",
|
| 55 |
+
" axis=1\n",
|
| 56 |
+
" )]\n",
|
| 57 |
+
"\n",
|
| 58 |
+
" # Skip if no data matches\n",
|
| 59 |
+
" if df_filtered.empty:\n",
|
| 60 |
+
" print(f\" ⚠️ No matches for '{target_term}', skipping.\")\n",
|
| 61 |
+
" continue\n",
|
| 62 |
+
"\n",
|
| 63 |
+
" # === COUNT INDIVIDUAL TAGS ===\n",
|
| 64 |
+
" all_tags = df_filtered.values.flatten()\n",
|
| 65 |
+
" all_tags = [tag for tag in all_tags if pd.notna(tag)]\n",
|
| 66 |
+
" tag_counts = Counter(all_tags)\n",
|
| 67 |
+
"\n",
|
| 68 |
+
" # === CO-OCCURRENCE ===\n",
|
| 69 |
+
" co_occurrences = defaultdict(int)\n",
|
| 70 |
+
" for tags in df_filtered.itertuples(index=False, name=None):\n",
|
| 71 |
+
" tags = [tag for tag in tags if pd.notna(tag)]\n",
|
| 72 |
+
" for tag1, tag2 in combinations(tags, 2):\n",
|
| 73 |
+
" co_occurrences[frozenset([tag1, tag2])] += 1\n",
|
| 74 |
+
"\n",
|
| 75 |
+
" edges = [(list(pair)[0], list(pair)[1], weight) for pair, weight in co_occurrences.items()]\n",
|
| 76 |
+
"\n",
|
| 77 |
+
" # === FILTER BY CONNECTIONS ===\n",
|
| 78 |
+
" connected_tags = Counter()\n",
|
| 79 |
+
" for tag1, tag2, _ in edges:\n",
|
| 80 |
+
" connected_tags[tag1] += 1\n",
|
| 81 |
+
" connected_tags[tag2] += 1\n",
|
| 82 |
+
"\n",
|
| 83 |
+
" nodes = [{\"id\": tag, \"size\": tag_counts[tag]} for tag in tag_counts if connected_tags[tag] >= min_connections]\n",
|
| 84 |
+
" valid_ids = set(node[\"id\"] for node in nodes)\n",
|
| 85 |
+
" links = [{\"source\": tag1, \"target\": tag2, \"value\": weight}\n",
|
| 86 |
+
" for tag1, tag2, weight in edges\n",
|
| 87 |
+
" if tag1 in valid_ids and tag2 in valid_ids]\n",
|
| 88 |
+
"\n",
|
| 89 |
+
" if not nodes or not links:\n",
|
| 90 |
+
" print(f\" ⚠️ Not enough connections for '{target_term}', skipping.\")\n",
|
| 91 |
+
" continue\n",
|
| 92 |
+
"\n",
|
| 93 |
+
" # === EXPORT ===\n",
|
| 94 |
+
" d3_data = {\"nodes\": nodes, \"links\": links}\n",
|
| 95 |
+
" safe_term = re.sub(r'\\W+', '_', target_term.lower())\n",
|
| 96 |
+
" output_file = os.path.join(output_dir, f\"tags_{safe_term}.json\")\n",
|
| 97 |
+
" \n",
|
| 98 |
+
" with open(output_file, \"w\") as f:\n",
|
| 99 |
+
" json.dump(d3_data, f, indent=4)\n",
|
| 100 |
+
" \n",
|
| 101 |
+
" print(f\" ✅ Saved to {output_file}\")\n"
|
| 102 |
+
]
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"cell_type": "markdown",
|
| 106 |
+
"metadata": {},
|
| 107 |
+
"source": [
|
| 108 |
+
"## Different Countries"
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"cell_type": "code",
|
| 113 |
+
"execution_count": 2,
|
| 114 |
+
"metadata": {},
|
| 115 |
+
"outputs": [
|
| 116 |
+
{
|
| 117 |
+
"name": "stderr",
|
| 118 |
+
"output_type": "stream",
|
| 119 |
+
"text": [
|
| 120 |
+
"/tmp/ipykernel_68582/2797381217.py:15: DtypeWarning: Columns (50,51,54,55,56,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,458) have mixed types. Specify dtype option on import or set low_memory=False.\n",
|
| 121 |
+
" df = pd.read_csv(file_path)\n"
|
| 122 |
+
]
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"name": "stdout",
|
| 126 |
+
"output_type": "stream",
|
| 127 |
+
"text": [
|
| 128 |
+
"Processing: united states\n",
|
| 129 |
+
" ⚠️ No matches for 'united states', skipping.\n",
|
| 130 |
+
"Processing: korea\n",
|
| 131 |
+
" ✅ Saved to data/json/tags_korea_poi.json\n",
|
| 132 |
+
"Processing: uk\n",
|
| 133 |
+
" ✅ Saved to data/json/tags_uk_poi.json\n",
|
| 134 |
+
"Processing: russia\n",
|
| 135 |
+
" ✅ Saved to data/json/tags_russia_poi.json\n",
|
| 136 |
+
"Processing: china\n",
|
| 137 |
+
" ✅ Saved to data/json/tags_china_poi.json\n",
|
| 138 |
+
"Processing: canada\n",
|
| 139 |
+
" ✅ Saved to data/json/tags_canada_poi.json\n",
|
| 140 |
+
"Processing: India\n",
|
| 141 |
+
" ✅ Saved to data/json/tags_india_poi.json\n",
|
| 142 |
+
"Processing: germany\n",
|
| 143 |
+
" ✅ Saved to data/json/tags_germany_poi.json\n"
|
| 144 |
+
]
|
| 145 |
+
}
|
| 146 |
+
],
|
| 147 |
+
"source": [
|
| 148 |
+
"import pandas as pd\n",
|
| 149 |
+
"from itertools import combinations\n",
|
| 150 |
+
"from collections import Counter, defaultdict\n",
|
| 151 |
+
"import json\n",
|
| 152 |
+
"import re\n",
|
| 153 |
+
"import os\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"# === CONFIG ===\n",
|
| 156 |
+
"file_path = \"data/model_subsets/all_models_poi_true.csv\"\n",
|
| 157 |
+
"output_dir = \"data/json/\"\n",
|
| 158 |
+
"target_terms = [\"united states\", \"korea\", \"uk\", \"russia\", \"china\", \"canada\", \"India\", \"germany\"] # Add any tags you want to process\n",
|
| 159 |
+
"min_connections = 1 # minimum number of link connections per node\n",
|
| 160 |
+
"\n",
|
| 161 |
+
"# === LOAD DATA ===\n",
|
| 162 |
+
"df = pd.read_csv(file_path)\n",
|
| 163 |
+
"tag_columns = [f\"tag_{i}\" for i in range(1, 8)]\n",
|
| 164 |
+
"df_tags = df[tag_columns]\n",
|
| 165 |
+
"\n",
|
| 166 |
+
"# === MAIN LOOP ===\n",
|
| 167 |
+
"for target_term in target_terms:\n",
|
| 168 |
+
" print(f\"Processing: {target_term}\")\n",
|
| 169 |
+
" \n",
|
| 170 |
+
" pattern = re.compile(rf'\\b{re.escape(target_term)}\\b', flags=re.IGNORECASE)\n",
|
| 171 |
+
" df_filtered = df_tags[df_tags.apply(\n",
|
| 172 |
+
" lambda row: row.astype(str).apply(lambda x: bool(pattern.search(x))).any(),\n",
|
| 173 |
+
" axis=1\n",
|
| 174 |
+
" )]\n",
|
| 175 |
+
"\n",
|
| 176 |
+
" # Skip if no data matches\n",
|
| 177 |
+
" if df_filtered.empty:\n",
|
| 178 |
+
" print(f\" ⚠️ No matches for '{target_term}', skipping.\")\n",
|
| 179 |
+
" continue\n",
|
| 180 |
+
"\n",
|
| 181 |
+
" # === COUNT INDIVIDUAL TAGS ===\n",
|
| 182 |
+
" all_tags = df_filtered.values.flatten()\n",
|
| 183 |
+
" all_tags = [tag for tag in all_tags if pd.notna(tag)]\n",
|
| 184 |
+
" tag_counts = Counter(all_tags)\n",
|
| 185 |
+
"\n",
|
| 186 |
+
" # === CO-OCCURRENCE ===\n",
|
| 187 |
+
" co_occurrences = defaultdict(int)\n",
|
| 188 |
+
" for tags in df_filtered.itertuples(index=False, name=None):\n",
|
| 189 |
+
" tags = [tag for tag in tags if pd.notna(tag)]\n",
|
| 190 |
+
" for tag1, tag2 in combinations(tags, 2):\n",
|
| 191 |
+
" co_occurrences[frozenset([tag1, tag2])] += 1\n",
|
| 192 |
+
"\n",
|
| 193 |
+
" edges = [(list(pair)[0], list(pair)[1], weight) for pair, weight in co_occurrences.items()]\n",
|
| 194 |
+
"\n",
|
| 195 |
+
" # === FILTER BY CONNECTIONS ===\n",
|
| 196 |
+
" connected_tags = Counter()\n",
|
| 197 |
+
" for tag1, tag2, _ in edges:\n",
|
| 198 |
+
" connected_tags[tag1] += 1\n",
|
| 199 |
+
" connected_tags[tag2] += 1\n",
|
| 200 |
+
"\n",
|
| 201 |
+
" nodes = [{\"id\": tag, \"size\": tag_counts[tag]} for tag in tag_counts if connected_tags[tag] >= min_connections]\n",
|
| 202 |
+
" valid_ids = set(node[\"id\"] for node in nodes)\n",
|
| 203 |
+
" links = [{\"source\": tag1, \"target\": tag2, \"value\": weight}\n",
|
| 204 |
+
" for tag1, tag2, weight in edges\n",
|
| 205 |
+
" if tag1 in valid_ids and tag2 in valid_ids]\n",
|
| 206 |
+
"\n",
|
| 207 |
+
" if not nodes or not links:\n",
|
| 208 |
+
" print(f\" ⚠️ Not enough connections for '{target_term}', skipping.\")\n",
|
| 209 |
+
" continue\n",
|
| 210 |
+
"\n",
|
| 211 |
+
" # === EXPORT ===\n",
|
| 212 |
+
" d3_data = {\"nodes\": nodes, \"links\": links}\n",
|
| 213 |
+
" safe_term = re.sub(r'\\W+', '_', target_term.lower())\n",
|
| 214 |
+
" output_file = os.path.join(output_dir, f\"tags_{safe_term}_poi.json\")\n",
|
| 215 |
+
" \n",
|
| 216 |
+
" with open(output_file, \"w\") as f:\n",
|
| 217 |
+
" json.dump(d3_data, f, indent=4)\n",
|
| 218 |
+
" \n",
|
| 219 |
+
" print(f\" ✅ Saved to {output_file}\")\n"
|
| 220 |
+
]
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"cell_type": "code",
|
| 224 |
+
"execution_count": 5,
|
| 225 |
+
"metadata": {},
|
| 226 |
+
"outputs": [
|
| 227 |
+
{
|
| 228 |
+
"name": "stderr",
|
| 229 |
+
"output_type": "stream",
|
| 230 |
+
"text": [
|
| 231 |
+
"/tmp/ipykernel_79893/1420003123.py:13: DtypeWarning: Columns (50,51,54,55,56,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,458) have mixed types. Specify dtype option on import or set low_memory=False.\n",
|
| 232 |
+
" df = pd.read_csv(file_path)\n"
|
| 233 |
+
]
|
| 234 |
+
},
|
| 235 |
+
{
|
| 236 |
+
"name": "stdout",
|
| 237 |
+
"output_type": "stream",
|
| 238 |
+
"text": [
|
| 239 |
+
"✅ Exported 60330 nodes and 16921 links to public/json/nodes_all.json\n"
|
| 240 |
+
]
|
| 241 |
+
}
|
| 242 |
+
],
|
| 243 |
+
"source": [
|
| 244 |
+
"import pandas as pd\n",
|
| 245 |
+
"from itertools import combinations\n",
|
| 246 |
+
"from collections import Counter, defaultdict\n",
|
| 247 |
+
"import json\n",
|
| 248 |
+
"import os\n",
|
| 249 |
+
"\n",
|
| 250 |
+
"# === CONFIG ===\n",
|
| 251 |
+
"file_path = \"data/model_subsets/all_models_poi_false.csv\"\n",
|
| 252 |
+
"output_file = \"public/json/nodes_all.json\"\n",
|
| 253 |
+
"min_link_threshold = 10 # Only keep edges with co-occurrence >= this\n",
|
| 254 |
+
"\n",
|
| 255 |
+
"# === LOAD DATA ===\n",
|
| 256 |
+
"df = pd.read_csv(file_path)\n",
|
| 257 |
+
"tag_columns = [f\"tag_{i}\" for i in range(1, 8)]\n",
|
| 258 |
+
"df_tags = df[tag_columns]\n",
|
| 259 |
+
"\n",
|
| 260 |
+
"# === COUNT INDIVIDUAL TAGS ===\n",
|
| 261 |
+
"all_tags = df_tags.values.flatten()\n",
|
| 262 |
+
"all_tags = [tag for tag in all_tags if pd.notna(tag)]\n",
|
| 263 |
+
"tag_counts = Counter(all_tags)\n",
|
| 264 |
+
"\n",
|
| 265 |
+
"# === CO-OCCURRENCE ===\n",
|
| 266 |
+
"co_occurrences = defaultdict(int)\n",
|
| 267 |
+
"for tags in df_tags.itertuples(index=False, name=None):\n",
|
| 268 |
+
" tags = [tag for tag in tags if pd.notna(tag)]\n",
|
| 269 |
+
" for tag1, tag2 in combinations(tags, 2):\n",
|
| 270 |
+
" co_occurrences[frozenset([tag1, tag2])] += 1\n",
|
| 271 |
+
"\n",
|
| 272 |
+
"# === Build Edges (Filtered by co-occurrence threshold)\n",
|
| 273 |
+
"edges = [\n",
|
| 274 |
+
" {\"source\": list(pair)[0], \"target\": list(pair)[1], \"value\": weight}\n",
|
| 275 |
+
" for pair, weight in co_occurrences.items()\n",
|
| 276 |
+
" if weight >= min_link_threshold\n",
|
| 277 |
+
"]\n",
|
| 278 |
+
"\n",
|
| 279 |
+
"# === Build Nodes (All tags that appear, regardless of links)\n",
|
| 280 |
+
"nodes = [{\"id\": tag, \"size\": tag_counts[tag]} for tag in tag_counts]\n",
|
| 281 |
+
"\n",
|
| 282 |
+
"# === EXPORT ===\n",
|
| 283 |
+
"d3_data = {\"nodes\": nodes, \"links\": edges}\n",
|
| 284 |
+
"\n",
|
| 285 |
+
"os.makedirs(os.path.dirname(output_file), exist_ok=True)\n",
|
| 286 |
+
"with open(output_file, \"w\") as f:\n",
|
| 287 |
+
" json.dump(d3_data, f, indent=4)\n",
|
| 288 |
+
"\n",
|
| 289 |
+
"print(f\"✅ Exported {len(nodes)} nodes and {len(edges)} links to {output_file}\")\n"
|
| 290 |
+
]
|
| 291 |
+
}
|
| 292 |
+
],
|
| 293 |
+
"metadata": {
|
| 294 |
+
"kernelspec": {
|
| 295 |
+
"display_name": "latm",
|
| 296 |
+
"language": "python",
|
| 297 |
+
"name": "python3"
|
| 298 |
+
},
|
| 299 |
+
"language_info": {
|
| 300 |
+
"codemirror_mode": {
|
| 301 |
+
"name": "ipython",
|
| 302 |
+
"version": 3
|
| 303 |
+
},
|
| 304 |
+
"file_extension": ".py",
|
| 305 |
+
"mimetype": "text/x-python",
|
| 306 |
+
"name": "python",
|
| 307 |
+
"nbconvert_exporter": "python",
|
| 308 |
+
"pygments_lexer": "ipython3",
|
| 309 |
+
"version": "3.10.15"
|
| 310 |
+
}
|
| 311 |
+
},
|
| 312 |
+
"nbformat": 4,
|
| 313 |
+
"nbformat_minor": 2
|
| 314 |
+
}
|
jupyter_notebooks/Section_2-4_Figure_9_Training_tags_Sankey.ipynb
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Sankey Diagram from model dataset"
|
| 8 |
+
]
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"cell_type": "code",
|
| 12 |
+
"execution_count": null,
|
| 13 |
+
"metadata": {},
|
| 14 |
+
"outputs": [],
|
| 15 |
+
"source": [
|
| 16 |
+
"import pandas as pd\n",
|
| 17 |
+
"from pathlib import Path\n",
|
| 18 |
+
"current_dir = Path.cwd()"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "code",
|
| 23 |
+
"execution_count": null,
|
| 24 |
+
"metadata": {},
|
| 25 |
+
"outputs": [
|
| 26 |
+
{
|
| 27 |
+
"name": "stderr",
|
| 28 |
+
"output_type": "stream",
|
| 29 |
+
"text": [
|
| 30 |
+
"/tmp/ipykernel_9667/181225218.py:8: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`\n",
|
| 31 |
+
" df['has_tags'] = df['has_tags'].fillna(False)\n",
|
| 32 |
+
"/tmp/ipykernel_9667/181225218.py:9: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`\n",
|
| 33 |
+
" df['has_sex_tags'] = df['has_sex_tags'].fillna(False)\n"
|
| 34 |
+
]
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"name": "stdout",
|
| 38 |
+
"output_type": "stream",
|
| 39 |
+
"text": [
|
| 40 |
+
"🟢 All Adapters with Tags → Explicit: 4970\n",
|
| 41 |
+
"🔵 All Adapters with Tags → Non-Explicit: 6181\n",
|
| 42 |
+
"✅ Sankey structure saved to sankey_tags_focus_output.csv\n"
|
| 43 |
+
]
|
| 44 |
+
}
|
| 45 |
+
],
|
| 46 |
+
"source": [
|
| 47 |
+
"import pandas as pd\n",
|
| 48 |
+
"# Load your dataset\n",
|
| 49 |
+
"df = pd.read_csv(\"data/CSV/all_models_with_tags.csv\", nrows=40000)\n",
|
| 50 |
+
"\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"# Fill missing values\n",
|
| 53 |
+
"df['has_tags'] = df['has_tags'].fillna(False)\n",
|
| 54 |
+
"df['has_sex_tags'] = df['has_sex_tags'].fillna(False)\n",
|
| 55 |
+
"df['poi'] = df['poi'].fillna(False)\n",
|
| 56 |
+
"\n",
|
| 57 |
+
"# Normalize and map types\n",
|
| 58 |
+
"df['type_normalized'] = df['type'].str.lower().str.strip()\n",
|
| 59 |
+
"type_mapping = {\n",
|
| 60 |
+
" 'checkpoint': 'Checkpoint',\n",
|
| 61 |
+
" 'lora': 'LoRA',\n",
|
| 62 |
+
" 'locon': 'LOCON',\n",
|
| 63 |
+
" 'textualinversion': 'TextualInversion',\n",
|
| 64 |
+
" 'controlnet': 'Other',\n",
|
| 65 |
+
" 'vae': 'Other',\n",
|
| 66 |
+
" 'upscaler': 'Other',\n",
|
| 67 |
+
" 'poses': 'Other',\n",
|
| 68 |
+
" 'workflows': 'Other',\n",
|
| 69 |
+
" 'other': 'Other'\n",
|
| 70 |
+
"}\n",
|
| 71 |
+
"df['type_mapped'] = df['type_normalized'].map(type_mapping).fillna('Other')\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"\n",
|
| 76 |
+
"\n",
|
| 77 |
+
"# Identify Adapters\n",
|
| 78 |
+
"adapter_types = ['LoRA', 'LOCON', 'TextualInversion', 'Controlnet']\n",
|
| 79 |
+
"df['is_adapter'] = df['type_mapped'].isin(adapter_types)\n",
|
| 80 |
+
"adapter_df = df[df['is_adapter']]\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"# Build Sankey links\n",
|
| 83 |
+
"sankey_links = []\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"# Total → each model type\n",
|
| 86 |
+
"for t in df['type_mapped'].unique():\n",
|
| 87 |
+
" count = df[df['type_mapped'] == t].shape[0]\n",
|
| 88 |
+
" sankey_links.append({'source': 'Total', 'target': t, 'value': count})\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"# Adapter types → Adapters\n",
|
| 91 |
+
"for t in adapter_types:\n",
|
| 92 |
+
" count = df[df['type_mapped'] == t].shape[0]\n",
|
| 93 |
+
" if count > 0:\n",
|
| 94 |
+
" sankey_links.append({'source': t, 'target': 'Adapters', 'value': count})\n",
|
| 95 |
+
"# Redefine adapter_types to only include those relevant for POI analysis\n",
|
| 96 |
+
"adapter_types = ['LoRA', 'LOCON', 'TextualInversion']\n",
|
| 97 |
+
"df['is_adapter'] = df['type_mapped'].isin(adapter_types)\n",
|
| 98 |
+
"adapter_df = df[df['is_adapter']]\n",
|
| 99 |
+
"\n",
|
| 100 |
+
"# Adapters → has_tags / has_no_tags\n",
|
| 101 |
+
"tagged_df = adapter_df[adapter_df['has_tags'] == True]\n",
|
| 102 |
+
"no_tag_df = adapter_df[adapter_df['has_tags'] == False]\n",
|
| 103 |
+
"\n",
|
| 104 |
+
"# Add has_no_tags count\n",
|
| 105 |
+
"no_tag_count = no_tag_df.shape[0]\n",
|
| 106 |
+
"if no_tag_count > 0:\n",
|
| 107 |
+
" sankey_links.append({'source': 'Adapters', 'target': 'has_no_tags', 'value': no_tag_count})\n",
|
| 108 |
+
"\n",
|
| 109 |
+
"# Now subdivide has_tags group\n",
|
| 110 |
+
"has_tag_count = tagged_df.shape[0]\n",
|
| 111 |
+
"if has_tag_count > 0:\n",
|
| 112 |
+
" sankey_links.append({'source': 'Adapters', 'target': 'has_tags', 'value': has_tag_count})\n",
|
| 113 |
+
"\n",
|
| 114 |
+
" for poi_val in [True, False]:\n",
|
| 115 |
+
" poi_label = f'has_tags + POI {\"True\" if poi_val else \"False\"}'\n",
|
| 116 |
+
" subset = tagged_df[tagged_df['poi'] == poi_val]\n",
|
| 117 |
+
" poi_count = subset.shape[0] # ✅ Fix: missing in original code\n",
|
| 118 |
+
"\n",
|
| 119 |
+
" if poi_count > 0:\n",
|
| 120 |
+
" sankey_links.append({'source': 'has_tags', 'target': poi_label, 'value': poi_count})\n",
|
| 121 |
+
"\n",
|
| 122 |
+
" for explicit in [True, False]:\n",
|
| 123 |
+
" explicit_label = f'{poi_label} + {\"explicit\" if explicit else \"non-explicit\"}'\n",
|
| 124 |
+
" exp_count = subset[subset['has_sex_tags'] == explicit].shape[0]\n",
|
| 125 |
+
" if exp_count > 0:\n",
|
| 126 |
+
" sankey_links.append({'source': poi_label, 'target': explicit_label, 'value': exp_count})\n",
|
| 127 |
+
"\n",
|
| 128 |
+
"# --- New Section: Count Models (Rows) Containing Specific Tags ---\n",
|
| 129 |
+
"\n",
|
| 130 |
+
"# --- Output total explicit and non-explicit from all adapters with tags ---\n",
|
| 131 |
+
"\n",
|
| 132 |
+
"explicit_total = tagged_df[tagged_df['has_sex_tags'] == True].shape[0]\n",
|
| 133 |
+
"non_explicit_total = tagged_df[tagged_df['has_sex_tags'] == False].shape[0]\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"# Add to sankey_links (optional)\n",
|
| 136 |
+
"if explicit_total > 0:\n",
|
| 137 |
+
" sankey_links.append({'source': 'has_tags', 'target': 'All Adapters has_tags + explicit', 'value': explicit_total})\n",
|
| 138 |
+
"\n",
|
| 139 |
+
"if non_explicit_total > 0:\n",
|
| 140 |
+
" sankey_links.append({'source': 'has_tags', 'target': 'All Adapters has_tags + non-explicit', 'value': non_explicit_total})\n",
|
| 141 |
+
"\n",
|
| 142 |
+
"# Also print them clearly\n",
|
| 143 |
+
"print(f\"🟢 All Adapters with Tags → Explicit: {explicit_total}\")\n",
|
| 144 |
+
"print(f\"🔵 All Adapters with Tags → Non-Explicit: {non_explicit_total}\")\n",
|
| 145 |
+
"\n",
|
| 146 |
+
"\n",
|
| 147 |
+
"# Define target tags to check\n",
|
| 148 |
+
"target_tags = ['rape', 'loli', 'shota', 'lolicon']\n",
|
| 149 |
+
"\n",
|
| 150 |
+
"# Identify all tag columns\n",
|
| 151 |
+
"tag_columns = [col for col in df.columns if col.startswith('tag') and col[3:].isdigit()]\n",
|
| 152 |
+
"\n",
|
| 153 |
+
"# Lowercase tag values for matching\n",
|
| 154 |
+
"df_tags_lower = df[tag_columns].astype(str).apply(lambda col: col.str.lower().str.strip())\n",
|
| 155 |
+
"\n",
|
| 156 |
+
"# For each target tag, check how many rows contain it\n",
|
| 157 |
+
"for tag in target_tags:\n",
|
| 158 |
+
" contains_tag = df_tags_lower.isin([tag])\n",
|
| 159 |
+
" \n",
|
| 160 |
+
" # Only count rows that are both explicit and contain the tag\n",
|
| 161 |
+
" matching_rows = df[(df['has_sex_tags'] == True) & contains_tag.any(axis=1)]\n",
|
| 162 |
+
" rows_with_tag = matching_rows.shape[0]\n",
|
| 163 |
+
" \n",
|
| 164 |
+
" if rows_with_tag > 0:\n",
|
| 165 |
+
" sankey_links.append({'source': 'has_tags', 'target': f'has_tag_{tag}', 'value': int(rows_with_tag)})\n",
|
| 166 |
+
"\n",
|
| 167 |
+
"\n",
|
| 168 |
+
"# Save to CSV\n",
|
| 169 |
+
"sankey_df = pd.DataFrame(sankey_links)\n",
|
| 170 |
+
"sankey_df.to_csv(\"data/CSV/sankey_data.csv\", index=False)\n",
|
| 171 |
+
"print(\"✅ Sankey structure saved to sankey_tags_focus_output.csv\")\n"
|
| 172 |
+
]
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"cell_type": "code",
|
| 176 |
+
"execution_count": null,
|
| 177 |
+
"metadata": {},
|
| 178 |
+
"outputs": [],
|
| 179 |
+
"source": []
|
| 180 |
+
}
|
| 181 |
+
],
|
| 182 |
+
"metadata": {
|
| 183 |
+
"kernelspec": {
|
| 184 |
+
"display_name": "latm",
|
| 185 |
+
"language": "python",
|
| 186 |
+
"name": "python3"
|
| 187 |
+
},
|
| 188 |
+
"language_info": {
|
| 189 |
+
"codemirror_mode": {
|
| 190 |
+
"name": "ipython",
|
| 191 |
+
"version": 3
|
| 192 |
+
},
|
| 193 |
+
"file_extension": ".py",
|
| 194 |
+
"mimetype": "text/x-python",
|
| 195 |
+
"name": "python",
|
| 196 |
+
"nbconvert_exporter": "python",
|
| 197 |
+
"pygments_lexer": "ipython3",
|
| 198 |
+
"version": "3.10.15"
|
| 199 |
+
}
|
| 200 |
+
},
|
| 201 |
+
"nbformat": 4,
|
| 202 |
+
"nbformat_minor": 2
|
| 203 |
+
}
|
jupyter_notebooks/Section_2-4_Figure_9_ectract_LoRA_metadata_v2.ipynb
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "f36422c8",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# LoRA metadata"
|
| 9 |
+
]
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
"cell_type": "raw",
|
| 13 |
+
"id": "8a2feb6e",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"LoRA Metadata Processing Workflow\n",
|
| 17 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 18 |
+
"│ Load CSV File │ --> │ Read adapter metadata CSV file. │\n",
|
| 19 |
+
"│ Read Model Versions │ │ Extract model version IDs and relevant data. │\n",
|
| 20 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 21 |
+
" ↓\n",
|
| 22 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 23 |
+
"│ Download Adapter │ --> │ Use stored download URLs to fetch adapter files │\n",
|
| 24 |
+
"│ Files Using API │ │ using rotating API keys. │\n",
|
| 25 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 26 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 27 |
+
"│ Parse Metadata │ --> │ Extract safetensors metadata, such as training │\n",
|
| 28 |
+
"│ from SafeTensor │ │ images, model type, and architecture. │\n",
|
| 29 |
+
"│ Files │ │ │\n",
|
| 30 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 31 |
+
" ↓\n",
|
| 32 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 33 |
+
"│ Store Parsed │ --> │ Save extracted metadata into structured JSON │\n",
|
| 34 |
+
"│ Metadata as JSON │ │ files for later analysis. │\n",
|
| 35 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 36 |
+
"\n",
|
| 37 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 38 |
+
"│ Process JSON Files │ --> │ Read saved JSON metadata, extract relevant │\n",
|
| 39 |
+
"│ for Consolidation │ │ details, and filter necessary attributes. │\n",
|
| 40 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 41 |
+
" ↓\n",
|
| 42 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 43 |
+
"│ Extract Training │ --> │ Identify most frequent training tags, architectures│\n",
|
| 44 |
+
"│ Tags & Model Info │ │ and systems used for model creation. │\n",
|
| 45 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 46 |
+
" ↓\n",
|
| 47 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 48 |
+
"│ Save Consolidated │ --> │ Store all processed metadata in a structured CSV │\n",
|
| 49 |
+
"│ Metadata to CSV │ │ format for final analysis. ���\n",
|
| 50 |
+
"└──────────────────────┘ └───────────────────────────────────────────────────┘\n"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"cell_type": "code",
|
| 55 |
+
"execution_count": null,
|
| 56 |
+
"id": "efc9939d",
|
| 57 |
+
"metadata": {},
|
| 58 |
+
"outputs": [],
|
| 59 |
+
"source": [
|
| 60 |
+
"import os\n",
|
| 61 |
+
"import re\n",
|
| 62 |
+
"import json\n",
|
| 63 |
+
"import csv\n",
|
| 64 |
+
"import struct\n",
|
| 65 |
+
"import requests\n",
|
| 66 |
+
"from pathlib import Path\n",
|
| 67 |
+
"import pandas as pd\n",
|
| 68 |
+
"from collections import Counter\n",
|
| 69 |
+
"from concurrent.futures import ProcessPoolExecutor\n",
|
| 70 |
+
"from pathlib import Path\n",
|
| 71 |
+
"import matplotlib.pyplot as plt\n",
|
| 72 |
+
"from matplotlib.font_manager import FontProperties\n",
|
| 73 |
+
"from matplotlib import font_manager\n",
|
| 74 |
+
"import pandas as pd\n",
|
| 75 |
+
"from collections import Counter\n",
|
| 76 |
+
"from concurrent.futures import ProcessPoolExecutor\n",
|
| 77 |
+
"\n",
|
| 78 |
+
"# Define the current directory and important file paths\n",
|
| 79 |
+
"current_dir = Path.cwd()\n",
|
| 80 |
+
"\n",
|
| 81 |
+
"# Define frequently used directories\n",
|
| 82 |
+
"\n",
|
| 83 |
+
"data_dir = current_dir.parent / 'data/csv/adapters.csv'\n",
|
| 84 |
+
"fonts_dir = current_dir.parent / 'misc/assets/fonts'\n",
|
| 85 |
+
"plots_dir = current_dir.parent / 'results/plots'\n",
|
| 86 |
+
"raw_data_dir = current_dir.parent / 'data/adapter_metadata/lora' ### location of the LoRA metadata (JSON)\n",
|
| 87 |
+
"temp_dir = current_dir.parent / 'data/raw/adapters_safetensors'\n",
|
| 88 |
+
"misc_dir = current_dir.parent / 'misc'\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"# File paths\n",
|
| 91 |
+
"adapters_csv = current_dir.parent / 'data/csv/adapters.csv'\n",
|
| 92 |
+
"output_json_dir = raw_data_dir\n",
|
| 93 |
+
"api_keys_file = misc_dir / 'credentials/civit.txt'\n",
|
| 94 |
+
"\n",
|
| 95 |
+
"# Ensure directories exist\n",
|
| 96 |
+
"os.makedirs(output_json_dir, exist_ok=True)\n",
|
| 97 |
+
"os.makedirs(temp_dir, exist_ok=True)\n",
|
| 98 |
+
"\n",
|
| 99 |
+
"\n",
|
| 100 |
+
"# Load fonts into Matplotlib\n",
|
| 101 |
+
"for font_path in font_paths:\n",
|
| 102 |
+
" font_manager.fontManager.addfont(font_path)\n",
|
| 103 |
+
"\n",
|
| 104 |
+
"# Set default font family for plots\n",
|
| 105 |
+
"plt.rcParams['font.family'] = ['Noto Sans JP', 'Noto Sans SC', 'sans-serif']\n",
|
| 106 |
+
"\n",
|
| 107 |
+
"print('Paths and fonts initialized successfully.')\n",
|
| 108 |
+
"\n",
|
| 109 |
+
"print('Paths initialized successfully.')"
|
| 110 |
+
]
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"cell_type": "markdown",
|
| 114 |
+
"id": "87a58593",
|
| 115 |
+
"metadata": {},
|
| 116 |
+
"source": [
|
| 117 |
+
"## Step 2: Download LoRA and extract *.safetensors metadata\n",
|
| 118 |
+
"This script downloads LoRA adapters from the filtered Civiverse-Models dataset and extracts the metadata found within the *.safetensors' data structure"
|
| 119 |
+
]
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"cell_type": "code",
|
| 123 |
+
"execution_count": null,
|
| 124 |
+
"id": "abd3a0bc",
|
| 125 |
+
"metadata": {},
|
| 126 |
+
"outputs": [],
|
| 127 |
+
"source": [
|
| 128 |
+
"import os\n",
|
| 129 |
+
"import sys\n",
|
| 130 |
+
"import csv\n",
|
| 131 |
+
"import json\n",
|
| 132 |
+
"import struct\n",
|
| 133 |
+
"import time\n",
|
| 134 |
+
"import requests\n",
|
| 135 |
+
"import signal\n",
|
| 136 |
+
"import contextlib\n",
|
| 137 |
+
"from pathlib import Path\n",
|
| 138 |
+
"import re\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"# === Paste your API keys here ===\n",
|
| 141 |
+
"API_KEYS = [\n",
|
| 142 |
+
" \"REDACTED-CIVITAI-API-KEY-1\", #DISCORD\n",
|
| 143 |
+
" \"REDACTED-CIVITAI-API-KEY-2\", #ASDD 1\n",
|
| 144 |
+
" \"REDACTED-CIVITAI-API-KEY-3\", #ASDD 2\n",
|
| 145 |
+
" \"REDACTED-CIVITAI-API-KEY-4\", #BSDD \n",
|
| 146 |
+
" \"REDACTED-CIVITAI-API-KEY-5\"\n",
|
| 147 |
+
"]\n",
|
| 148 |
+
"if not API_KEYS or any(not isinstance(k, str) or not k.strip() for k in API_KEYS):\n",
|
| 149 |
+
" raise ValueError(\"Please paste at least one valid API key into API_KEYS.\")\n",
|
| 150 |
+
"\n",
|
| 151 |
+
"# === Config (adjust paths as needed) ===\n",
|
| 152 |
+
"current_dir = Path.cwd()\n",
|
| 153 |
+
"output_json_dir = current_dir.parent / \"data/adapter_metadata/lora\" # where JSON outputs go\n",
|
| 154 |
+
"temp_dir = current_dir.parent / \"data/raw/adapters_safetensors\" # where downloads go\n",
|
| 155 |
+
"csv_path = current_dir.parent / \"data/csv/adapters_poi_false_sfw.csv\"\n",
|
| 156 |
+
"\n",
|
| 157 |
+
"os.makedirs(output_json_dir, exist_ok=True)\n",
|
| 158 |
+
"os.makedirs(temp_dir, exist_ok=True)\n",
|
| 159 |
+
"\n",
|
| 160 |
+
"# === API key state ===\n",
|
| 161 |
+
"current_key_index = 0\n",
|
| 162 |
+
"\n",
|
| 163 |
+
"\n",
|
| 164 |
+
"\n",
|
| 165 |
+
"def safe_filename(name: str, max_length: int = 100) -> str:\n",
|
| 166 |
+
" # Replace unsafe chars\n",
|
| 167 |
+
" sanitized = re.sub(r'[^a-zA-Z0-9_\\-]', '_', name)\n",
|
| 168 |
+
" # Truncate if too long\n",
|
| 169 |
+
" if len(sanitized) > max_length:\n",
|
| 170 |
+
" sanitized = sanitized[:max_length]\n",
|
| 171 |
+
" return sanitized\n",
|
| 172 |
+
"\n",
|
| 173 |
+
"\n",
|
| 174 |
+
"def get_headers():\n",
|
| 175 |
+
" global current_key_index\n",
|
| 176 |
+
" return {\n",
|
| 177 |
+
" \"Accept\": \"application/json\",\n",
|
| 178 |
+
" \"Authorization\": f\"Bearer {API_KEYS[current_key_index].strip()}\"\n",
|
| 179 |
+
" }\n",
|
| 180 |
+
"\n",
|
| 181 |
+
"def rotate_api_key():\n",
|
| 182 |
+
" global current_key_index\n",
|
| 183 |
+
" if current_key_index < len(API_KEYS) - 1:\n",
|
| 184 |
+
" current_key_index += 1\n",
|
| 185 |
+
" print(f\"🔁 Rotated to API key #{current_key_index + 1}\")\n",
|
| 186 |
+
" else:\n",
|
| 187 |
+
" raise Exception(\"All API keys have been exhausted.\")\n",
|
| 188 |
+
"\n",
|
| 189 |
+
"# === Utilities ===\n",
|
| 190 |
+
"def save_json(data, filename):\n",
|
| 191 |
+
" with open(filename, 'w', encoding=\"utf-8\") as f:\n",
|
| 192 |
+
" json.dump(data, f, indent=4, ensure_ascii=False)\n",
|
| 193 |
+
"\n",
|
| 194 |
+
"def parse_safetensors(file_path):\n",
|
| 195 |
+
" # Minimal, tolerant metadata reader; returns {} on failure.\n",
|
| 196 |
+
" try:\n",
|
| 197 |
+
" with open(file_path, 'rb') as f:\n",
|
| 198 |
+
" file_data = f.read()\n",
|
| 199 |
+
" # Many safetensors use 8-byte header length; this code follows your original logic\n",
|
| 200 |
+
" # (4-byte) but keeps the 8-byte skip. Keep if it's working in your dataset.\n",
|
| 201 |
+
" metadata_size = struct.unpack('<I', file_data[:4])[0]\n",
|
| 202 |
+
" metadata_bytes = file_data[8:8 + metadata_size]\n",
|
| 203 |
+
" metadata_str = metadata_bytes.decode('utf-8', errors='replace')\n",
|
| 204 |
+
" metadata = json.loads(metadata_str)\n",
|
| 205 |
+
" return metadata.get('__metadata__', {})\n",
|
| 206 |
+
" except Exception as e:\n",
|
| 207 |
+
" print(f\"Error parsing safetensors file: {e}\")\n",
|
| 208 |
+
" return {}\n",
|
| 209 |
+
"\n",
|
| 210 |
+
"# === Timeout context ===\n",
|
| 211 |
+
"class TimeoutException(Exception):\n",
|
| 212 |
+
" pass\n",
|
| 213 |
+
"\n",
|
| 214 |
+
"@contextlib.contextmanager\n",
|
| 215 |
+
"def time_limit(seconds):\n",
|
| 216 |
+
" def signal_handler(signum, frame):\n",
|
| 217 |
+
" raise TimeoutException(f\"Timed out after {seconds} seconds\")\n",
|
| 218 |
+
" # Note: SIGALRM works on Unix-like OS; on Windows this will be a no-op.\n",
|
| 219 |
+
" try:\n",
|
| 220 |
+
" signal.signal(signal.SIGALRM, signal_handler)\n",
|
| 221 |
+
" signal.alarm(seconds)\n",
|
| 222 |
+
" except Exception:\n",
|
| 223 |
+
" # Fallback: no hard alarm on non-Unix systems\n",
|
| 224 |
+
" pass\n",
|
| 225 |
+
" try:\n",
|
| 226 |
+
" yield\n",
|
| 227 |
+
" finally:\n",
|
| 228 |
+
" try:\n",
|
| 229 |
+
" signal.alarm(0)\n",
|
| 230 |
+
" except Exception:\n",
|
| 231 |
+
" pass\n",
|
| 232 |
+
"\n",
|
| 233 |
+
"# === Download with timeout, retries, backoff, and key rotation ===\n",
|
| 234 |
+
"def download_file(url, output_folder, timeout=30, overall_timeout=120, max_retries=3):\n",
|
| 235 |
+
" filename = url.split(\"/\")[-1]\n",
|
| 236 |
+
" output_path = os.path.join(output_folder, filename)\n",
|
| 237 |
+
"\n",
|
| 238 |
+
" global current_key_index\n",
|
| 239 |
+
" retries = 0\n",
|
| 240 |
+
" backoff = 2\n",
|
| 241 |
+
"\n",
|
| 242 |
+
" while current_key_index < len(API_KEYS):\n",
|
| 243 |
+
" try:\n",
|
| 244 |
+
" with time_limit(overall_timeout): # global cap per download\n",
|
| 245 |
+
" #print(f\"➡️ GET {url} using key #{current_key_index + 1}\")\n",
|
| 246 |
+
" resp = requests.get(\n",
|
| 247 |
+
" url,\n",
|
| 248 |
+
" headers=get_headers(),\n",
|
| 249 |
+
" stream=True,\n",
|
| 250 |
+
" timeout=(10, timeout), # (connect timeout, per-chunk read timeout)\n",
|
| 251 |
+
" )\n",
|
| 252 |
+
"\n",
|
| 253 |
+
" # Auth errors → rotate key\n",
|
| 254 |
+
" if resp.status_code in (401, 403):\n",
|
| 255 |
+
" print(f\"❌ Auth {resp.status_code} with key #{current_key_index + 1}. Rotating.\")\n",
|
| 256 |
+
" rotate_api_key()\n",
|
| 257 |
+
" retries = 0\n",
|
| 258 |
+
" backoff = 2\n",
|
| 259 |
+
" continue\n",
|
| 260 |
+
"\n",
|
| 261 |
+
" # Not found → bubble up as FileNotFoundError (do not rotate)\n",
|
| 262 |
+
" if resp.status_code == 404:\n",
|
| 263 |
+
" raise FileNotFoundError(f\"Model not found at {url}\")\n",
|
| 264 |
+
"\n",
|
| 265 |
+
" # Rate limit → either rotate or wait/backoff\n",
|
| 266 |
+
" if resp.status_code == 429:\n",
|
| 267 |
+
" print(\"⏳ Rate limited (429).\", end=\" \")\n",
|
| 268 |
+
" if current_key_index < len(API_KEYS) - 1:\n",
|
| 269 |
+
" print(\"Rotating key.\")\n",
|
| 270 |
+
" rotate_api_key()\n",
|
| 271 |
+
" retries = 0\n",
|
| 272 |
+
" backoff = 2\n",
|
| 273 |
+
" continue\n",
|
| 274 |
+
" else:\n",
|
| 275 |
+
" print(f\"Waiting {backoff}s (no other keys).\")\n",
|
| 276 |
+
" time.sleep(backoff)\n",
|
| 277 |
+
" backoff = min(backoff * 2, 60)\n",
|
| 278 |
+
" continue\n",
|
| 279 |
+
"\n",
|
| 280 |
+
" # Other HTTP errors → raise to RequestException path\n",
|
| 281 |
+
" resp.raise_for_status()\n",
|
| 282 |
+
"\n",
|
| 283 |
+
" # Save file\n",
|
| 284 |
+
" with open(output_path, 'wb') as fh:\n",
|
| 285 |
+
" for chunk in resp.iter_content(chunk_size=8192):\n",
|
| 286 |
+
" if chunk:\n",
|
| 287 |
+
" fh.write(chunk)\n",
|
| 288 |
+
"\n",
|
| 289 |
+
" return output_path, filename\n",
|
| 290 |
+
"\n",
|
| 291 |
+
" except TimeoutException as e:\n",
|
| 292 |
+
" # Hard overall timeout → propagate\n",
|
| 293 |
+
" raise e\n",
|
| 294 |
+
" except requests.exceptions.RequestException as e:\n",
|
| 295 |
+
" # Network-ish errors: retry same key with backoff up to max_retries\n",
|
| 296 |
+
" retries += 1\n",
|
| 297 |
+
" if retries <= max_retries:\n",
|
| 298 |
+
" print(f\"🌐 Network error (try {retries}/{max_retries}) with key #{current_key_index + 1}: {e}\")\n",
|
| 299 |
+
" time.sleep(backoff)\n",
|
| 300 |
+
" backoff = min(backoff * 2, 60)\n",
|
| 301 |
+
" continue\n",
|
| 302 |
+
" else:\n",
|
| 303 |
+
" raise Exception(f\"Failed to download {url} after {max_retries} retries: {e}\")\n",
|
| 304 |
+
"\n",
|
| 305 |
+
" # If we exit the loop, we truly ran out\n",
|
| 306 |
+
" raise Exception(\"All API keys have been exhausted or failed.\")\n",
|
| 307 |
+
"\n",
|
| 308 |
+
"# === Main processing ===\n",
|
| 309 |
+
"def process_csv(csv_path):\n",
|
| 310 |
+
" with open(csv_path, newline='', encoding='utf-8') as csvfile:\n",
|
| 311 |
+
" reader = csv.DictReader(csvfile)\n",
|
| 312 |
+
" for index, row in enumerate(reader):\n",
|
| 313 |
+
" # Collect up to 20 version IDs; use the most recent\n",
|
| 314 |
+
" version_ids = []\n",
|
| 315 |
+
" for i in range(1, 21):\n",
|
| 316 |
+
" k = f'version_id_{i}'\n",
|
| 317 |
+
" if k in row and row[k]:\n",
|
| 318 |
+
" try:\n",
|
| 319 |
+
" version_ids.append(int(float(row[k])))\n",
|
| 320 |
+
" except ValueError:\n",
|
| 321 |
+
" print(f\"Invalid version_id value '{row[k]}' in row: {row}\")\n",
|
| 322 |
+
"\n",
|
| 323 |
+
" if not version_ids:\n",
|
| 324 |
+
" print(f\"No valid version IDs found in row: {row}\")\n",
|
| 325 |
+
" continue\n",
|
| 326 |
+
"\n",
|
| 327 |
+
" most_recent_version_id = str(max(version_ids))\n",
|
| 328 |
+
" name = row.get('name', 'unknown')\n",
|
| 329 |
+
" sanitized_name = safe_filename(name, max_length=100)\n",
|
| 330 |
+
" new_json_file = os.path.join(\n",
|
| 331 |
+
" output_json_dir,\n",
|
| 332 |
+
" f\"{index:08d}_{most_recent_version_id}_{sanitized_name}.json\"\n",
|
| 333 |
+
" )\n",
|
| 334 |
+
"\n",
|
| 335 |
+
" # Skip if JSON already exists\n",
|
| 336 |
+
" if os.path.exists(new_json_file):\n",
|
| 337 |
+
" #print(f\"↩️ Skipping versionID {most_recent_version_id} (JSON already exists)\")\n",
|
| 338 |
+
" continue\n",
|
| 339 |
+
"\n",
|
| 340 |
+
" try:\n",
|
| 341 |
+
" adapter_file, fname = download_file(\n",
|
| 342 |
+
" row['downloadUrl'], str(temp_dir),\n",
|
| 343 |
+
" timeout=30, overall_timeout=180\n",
|
| 344 |
+
" )\n",
|
| 345 |
+
" metadata = parse_safetensors(adapter_file)\n",
|
| 346 |
+
"\n",
|
| 347 |
+
" civitaidata = {\n",
|
| 348 |
+
" k: (int(v) if str(v).isdigit() else v)\n",
|
| 349 |
+
" for k, v in row.items()\n",
|
| 350 |
+
" }\n",
|
| 351 |
+
" new_json_data = {\n",
|
| 352 |
+
" \"civitaidata\": civitaidata,\n",
|
| 353 |
+
" \"metadata\": metadata,\n",
|
| 354 |
+
" \"versionID\": most_recent_version_id\n",
|
| 355 |
+
" }\n",
|
| 356 |
+
" save_json(new_json_data, new_json_file)\n",
|
| 357 |
+
" #print(f\"✅ Created JSON for versionID {most_recent_version_id} with file {fname}\")\n",
|
| 358 |
+
"\n",
|
| 359 |
+
" except FileNotFoundError as e:\n",
|
| 360 |
+
" print(f\"⚠️ {e} — saving empty metadata.\")\n",
|
| 361 |
+
" civitaidata = {\n",
|
| 362 |
+
" k: (int(v) if str(v).isdigit() else v)\n",
|
| 363 |
+
" for k, v in row.items()\n",
|
| 364 |
+
" }\n",
|
| 365 |
+
" empty_json = {\n",
|
| 366 |
+
" \"civitaidata\": civitaidata,\n",
|
| 367 |
+
" \"metadata\": {},\n",
|
| 368 |
+
" \"versionID\": most_recent_version_id,\n",
|
| 369 |
+
" \"error\": \"Model not found (404)\"\n",
|
| 370 |
+
" }\n",
|
| 371 |
+
" save_json(empty_json, new_json_file)\n",
|
| 372 |
+
" except Exception as e:\n",
|
| 373 |
+
" print(f\"⚠️ Error processing versionID {most_recent_version_id}: {e}\")\n",
|
| 374 |
+
" civitaidata = {\n",
|
| 375 |
+
" k: (int(v) if str(v).isdigit() else v)\n",
|
| 376 |
+
" for k, v in row.items()\n",
|
| 377 |
+
" }\n",
|
| 378 |
+
" empty_json = {\n",
|
| 379 |
+
" \"civitaidata\": civitaidata,\n",
|
| 380 |
+
" \"metadata\": {},\n",
|
| 381 |
+
" \"versionID\": most_recent_version_id,\n",
|
| 382 |
+
" \"error\": str(e)\n",
|
| 383 |
+
" }\n",
|
| 384 |
+
" save_json(empty_json, new_json_file)\n",
|
| 385 |
+
" print(f\"💾 Saved empty JSON for versionID {most_recent_version_id} due to failure.\")\n",
|
| 386 |
+
"\n",
|
| 387 |
+
"# === Run ===\n",
|
| 388 |
+
"if __name__ == \"__main__\":\n",
|
| 389 |
+
" process_csv(csv_path)\n"
|
| 390 |
+
]
|
| 391 |
+
}
|
| 392 |
+
],
|
| 393 |
+
"metadata": {
|
| 394 |
+
"language_info": {
|
| 395 |
+
"name": "python"
|
| 396 |
+
}
|
| 397 |
+
},
|
| 398 |
+
"nbformat": 4,
|
| 399 |
+
"nbformat_minor": 5
|
| 400 |
+
}
|
jupyter_notebooks/Section_2-4_Figure_9_extract_LoRA_metadata.ipynb
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "781346f0-fab6-45f9-9193-dd547f201864",
|
| 6 |
+
"metadata": {
|
| 7 |
+
"execution": {
|
| 8 |
+
"iopub.execute_input": "2025-02-08T11:14:38.839305Z",
|
| 9 |
+
"iopub.status.busy": "2025-02-08T11:14:38.837963Z",
|
| 10 |
+
"iopub.status.idle": "2025-02-08T11:14:38.845161Z",
|
| 11 |
+
"shell.execute_reply": "2025-02-08T11:14:38.844121Z",
|
| 12 |
+
"shell.execute_reply.started": "2025-02-08T11:14:38.839221Z"
|
| 13 |
+
}
|
| 14 |
+
},
|
| 15 |
+
"source": [
|
| 16 |
+
"# Section 3-4: LoRA metadata"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "markdown",
|
| 21 |
+
"id": "90b42e45-31bc-4cbd-b820-cf2daccbd712",
|
| 22 |
+
"metadata": {
|
| 23 |
+
"execution": {
|
| 24 |
+
"iopub.execute_input": "2025-02-08T21:10:00.955922Z",
|
| 25 |
+
"iopub.status.busy": "2025-02-08T21:10:00.955285Z",
|
| 26 |
+
"iopub.status.idle": "2025-02-08T21:10:00.959160Z",
|
| 27 |
+
"shell.execute_reply": "2025-02-08T21:10:00.958709Z",
|
| 28 |
+
"shell.execute_reply.started": "2025-02-08T21:10:00.955902Z"
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
"source": [
|
| 32 |
+
"### How are models trained? \n",
|
| 33 |
+
"### What are the most common training tags? \n",
|
| 34 |
+
"### What tagging systems are used?"
|
| 35 |
+
]
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"cell_type": "raw",
|
| 39 |
+
"id": "79b10960-c7c6-4aa6-b91e-f0caf5c2a7dd",
|
| 40 |
+
"metadata": {},
|
| 41 |
+
"source": [
|
| 42 |
+
"LoRA Metadata Processing Workflow\n",
|
| 43 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 44 |
+
"│ Load CSV File │ --> │ Read adapter metadata CSV file. │\n",
|
| 45 |
+
"│ Read Model Versions │ │ Extract model version IDs and relevant data. │\n",
|
| 46 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 47 |
+
" ↓\n",
|
| 48 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 49 |
+
"│ Download Adapter │ --> │ Use stored download URLs to fetch adapter files │\n",
|
| 50 |
+
"│ Files Using API │ │ using rotating API keys. │\n",
|
| 51 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 52 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 53 |
+
"│ Parse Metadata │ --> │ Extract safetensors metadata, such as training │\n",
|
| 54 |
+
"│ from SafeTensor │ │ images, model type, and architecture. │\n",
|
| 55 |
+
"│ Files │ │ │\n",
|
| 56 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 57 |
+
" ↓\n",
|
| 58 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 59 |
+
"│ Store Parsed │ --> │ Save extracted metadata into structured JSON │\n",
|
| 60 |
+
"│ Metadata as JSON │ │ files for later analysis. │\n",
|
| 61 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 64 |
+
"│ Process JSON Files │ --> │ Read saved JSON metadata, extract relevant │\n",
|
| 65 |
+
"│ for Consolidation │ │ details, and filter necessary attributes. │\n",
|
| 66 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 67 |
+
" ↓\n",
|
| 68 |
+
"┌──────────────────────┐ ┌───────────────────────��───────────────────────────┐\n",
|
| 69 |
+
"│ Extract Training │ --> │ Identify most frequent training tags, architectures│\n",
|
| 70 |
+
"│ Tags & Model Info │ │ and systems used for model creation. │\n",
|
| 71 |
+
"└─────────┬────────────┘ └───────────────────────────────────────────────────┘\n",
|
| 72 |
+
" ↓\n",
|
| 73 |
+
"┌──────────────────────┐ ┌───────────────────────────────────────────────────┐\n",
|
| 74 |
+
"│ Save Consolidated │ --> │ Store all processed metadata in a structured CSV │\n",
|
| 75 |
+
"│ Metadata to CSV │ │ format for final analysis. │\n",
|
| 76 |
+
"└──────────────────────┘ └───────────────────────────────────────────────────┘\n"
|
| 77 |
+
]
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"cell_type": "markdown",
|
| 81 |
+
"id": "fd202dbd-fc0f-45f1-9d88-94c7d3635207",
|
| 82 |
+
"metadata": {
|
| 83 |
+
"execution": {
|
| 84 |
+
"iopub.execute_input": "2025-02-08T20:54:00.478266Z",
|
| 85 |
+
"iopub.status.busy": "2025-02-08T20:54:00.477080Z",
|
| 86 |
+
"iopub.status.idle": "2025-02-08T20:54:00.481464Z",
|
| 87 |
+
"shell.execute_reply": "2025-02-08T20:54:00.480830Z",
|
| 88 |
+
"shell.execute_reply.started": "2025-02-08T20:54:00.478244Z"
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
"source": [
|
| 92 |
+
"### Define Common Paths, Fonts etc."
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "code",
|
| 97 |
+
"execution_count": 24,
|
| 98 |
+
"id": "9da03b00",
|
| 99 |
+
"metadata": {
|
| 100 |
+
"execution": {
|
| 101 |
+
"iopub.execute_input": "2025-02-08T21:11:15.085872Z",
|
| 102 |
+
"iopub.status.busy": "2025-02-08T21:11:15.084865Z",
|
| 103 |
+
"iopub.status.idle": "2025-02-08T21:11:15.117479Z",
|
| 104 |
+
"shell.execute_reply": "2025-02-08T21:11:15.116955Z",
|
| 105 |
+
"shell.execute_reply.started": "2025-02-08T21:11:15.085846Z"
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"outputs": [
|
| 109 |
+
{
|
| 110 |
+
"name": "stdout",
|
| 111 |
+
"output_type": "stream",
|
| 112 |
+
"text": [
|
| 113 |
+
"Paths and fonts initialized successfully.\n",
|
| 114 |
+
"Paths initialized successfully.\n"
|
| 115 |
+
]
|
| 116 |
+
}
|
| 117 |
+
],
|
| 118 |
+
"source": [
|
| 119 |
+
"import os\n",
|
| 120 |
+
"import re\n",
|
| 121 |
+
"import json\n",
|
| 122 |
+
"import csv\n",
|
| 123 |
+
"import struct\n",
|
| 124 |
+
"import requests\n",
|
| 125 |
+
"from pathlib import Path\n",
|
| 126 |
+
"import pandas as pd\n",
|
| 127 |
+
"from collections import Counter\n",
|
| 128 |
+
"from concurrent.futures import ProcessPoolExecutor\n",
|
| 129 |
+
"from pathlib import Path\n",
|
| 130 |
+
"import matplotlib.pyplot as plt\n",
|
| 131 |
+
"from matplotlib.font_manager import FontProperties\n",
|
| 132 |
+
"from matplotlib import font_manager\n",
|
| 133 |
+
"import pandas as pd\n",
|
| 134 |
+
"from collections import Counter\n",
|
| 135 |
+
"from concurrent.futures import ProcessPoolExecutor\n",
|
| 136 |
+
"\n",
|
| 137 |
+
"# Define the current directory and important file paths\n",
|
| 138 |
+
"current_dir = Path.cwd()\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"# Define frequently used directories\n",
|
| 141 |
+
"data_dir = current_dir.parent / 'data/CSV'\n",
|
| 142 |
+
"fonts_dir = current_dir.parent / 'misc/assets/fonts'\n",
|
| 143 |
+
"plots_dir = current_dir.parent / 'plots'\n",
|
| 144 |
+
"raw_data_dir = current_dir.parent / 'data/raw/adapter_metadata'\n",
|
| 145 |
+
"temp_dir = current_dir.parent / 'data/models/adapter_temp'\n",
|
| 146 |
+
"misc_dir = current_dir.parent / 'misc'\n",
|
| 147 |
+
"\n",
|
| 148 |
+
"# File paths\n",
|
| 149 |
+
"adapters_csv = data_dir / 'model_subsets/Civiverse_adapters_poi_false.csv'\n",
|
| 150 |
+
"output_json_dir = raw_data_dir\n",
|
| 151 |
+
"api_keys_file = misc_dir / 'api_keys.txt'\n",
|
| 152 |
+
"\n",
|
| 153 |
+
"# Ensure directories exist\n",
|
| 154 |
+
"os.makedirs(output_json_dir, exist_ok=True)\n",
|
| 155 |
+
"os.makedirs(temp_dir, exist_ok=True)\n",
|
| 156 |
+
"\n",
|
| 157 |
+
"# Font paths for Matplotlib\n",
|
| 158 |
+
"font_paths = [\n",
|
| 159 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-ExtraBold.ttf',\n",
|
| 160 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-Bold.ttf',\n",
|
| 161 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-ExtraLight.ttf',\n",
|
| 162 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-Light.ttf',\n",
|
| 163 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-Medium.ttf',\n",
|
| 164 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf',\n",
|
| 165 |
+
" fonts_dir / 'Noto_Sans_JP/static/NotoSansJP-Regular.ttf',\n",
|
| 166 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-ExtraBold.ttf',\n",
|
| 167 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-Bold.ttf',\n",
|
| 168 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-ExtraLight.ttf',\n",
|
| 169 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-Light.ttf',\n",
|
| 170 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-Medium.ttf',\n",
|
| 171 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-SemiBold.ttf',\n",
|
| 172 |
+
" fonts_dir / 'Noto_Sans_SC/static/NotoSansSC-Regular.ttf'\n",
|
| 173 |
+
"]\n",
|
| 174 |
+
"\n",
|
| 175 |
+
"# Load fonts into Matplotlib\n",
|
| 176 |
+
"for font_path in font_paths:\n",
|
| 177 |
+
" font_manager.fontManager.addfont(font_path)\n",
|
| 178 |
+
"\n",
|
| 179 |
+
"# Set default font family for plots\n",
|
| 180 |
+
"plt.rcParams['font.family'] = ['Noto Sans JP', 'Noto Sans SC', 'sans-serif']\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"print('Paths and fonts initialized successfully.')\n",
|
| 183 |
+
"\n",
|
| 184 |
+
"print('Paths initialized successfully.')"
|
| 185 |
+
]
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"cell_type": "markdown",
|
| 189 |
+
"id": "9c6b84ec-c23e-4047-a288-cfab2f5d8fdb",
|
| 190 |
+
"metadata": {},
|
| 191 |
+
"source": [
|
| 192 |
+
"## Step 1: Download LoRA and extract *.safetensors metadata"
|
| 193 |
+
]
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"cell_type": "markdown",
|
| 197 |
+
"id": "e484c678-70a8-4b70-b0aa-9ce69414c43b",
|
| 198 |
+
"metadata": {},
|
| 199 |
+
"source": [
|
| 200 |
+
"This script downloads LoRA adapters from the filtered Civiverse-Models dataset and extracts the metadata found within the *.safetensors' data structure"
|
| 201 |
+
]
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"cell_type": "code",
|
| 205 |
+
"execution_count": 3,
|
| 206 |
+
"id": "ecf31f5c",
|
| 207 |
+
"metadata": {
|
| 208 |
+
"execution": {
|
| 209 |
+
"iopub.execute_input": "2025-02-08T20:49:20.487791Z",
|
| 210 |
+
"iopub.status.busy": "2025-02-08T20:49:20.487464Z",
|
| 211 |
+
"iopub.status.idle": "2025-02-08T20:49:20.499881Z",
|
| 212 |
+
"shell.execute_reply": "2025-02-08T20:49:20.499471Z",
|
| 213 |
+
"shell.execute_reply.started": "2025-02-08T20:49:20.487771Z"
|
| 214 |
+
}
|
| 215 |
+
},
|
| 216 |
+
"outputs": [],
|
| 217 |
+
"source": [
|
| 218 |
+
"output_json_dir = output_json_dir\n",
|
| 219 |
+
"temp_dir = temp_dir ### better delete these later for space efficiency\n",
|
| 220 |
+
"api_karussell = api_keys_file\n",
|
| 221 |
+
"os.makedirs(output_json_dir, exist_ok=True)\n",
|
| 222 |
+
"os.makedirs(temp_dir, exist_ok=True)\n",
|
| 223 |
+
"\n",
|
| 224 |
+
"# Load API keys\n",
|
| 225 |
+
"def load_api_keys(api_path):\n",
|
| 226 |
+
" if not os.path.exists(api_path):\n",
|
| 227 |
+
" raise FileNotFoundError(f\"API keys file does not exist: {api_path}\")\n",
|
| 228 |
+
" with open(api_path, 'r') as file:\n",
|
| 229 |
+
" return [line.strip() for line in file if line.strip()]\n",
|
| 230 |
+
"\n",
|
| 231 |
+
"api_keys = load_api_keys(api_karussell) # Replace with the path to your API keys file\n",
|
| 232 |
+
"current_key_index = 0\n",
|
| 233 |
+
"\n",
|
| 234 |
+
"def get_headers():\n",
|
| 235 |
+
" \"\"\"Generate request headers with the current API key.\"\"\"\n",
|
| 236 |
+
" global current_key_index\n",
|
| 237 |
+
" return {\n",
|
| 238 |
+
" \"Accept\": \"application/json\",\n",
|
| 239 |
+
" \"Authorization\": f\"Bearer {api_keys[current_key_index]}\"\n",
|
| 240 |
+
" }\n",
|
| 241 |
+
"\n",
|
| 242 |
+
"def rotate_api_key():\n",
|
| 243 |
+
" \"\"\"Rotate to the next API key.\"\"\"\n",
|
| 244 |
+
" global current_key_index\n",
|
| 245 |
+
" if current_key_index < len(api_keys) - 1:\n",
|
| 246 |
+
" current_key_index += 1\n",
|
| 247 |
+
" else:\n",
|
| 248 |
+
" raise Exception(\"All API keys have been exhausted.\")\n",
|
| 249 |
+
"\n",
|
| 250 |
+
"# Function to parse .safetensors metadata\n",
|
| 251 |
+
"def parse_safetensors(file_path):\n",
|
| 252 |
+
" try:\n",
|
| 253 |
+
" with open(file_path, 'rb') as f:\n",
|
| 254 |
+
" file_data = f.read()\n",
|
| 255 |
+
" metadata_size = struct.unpack('<I', file_data[:4])[0]\n",
|
| 256 |
+
" metadata_bytes = file_data[8:8 + metadata_size]\n",
|
| 257 |
+
" metadata_str = metadata_bytes.decode('utf-8')\n",
|
| 258 |
+
" metadata = json.loads(metadata_str)\n",
|
| 259 |
+
" return metadata.get('__metadata__', {})\n",
|
| 260 |
+
" except Exception as e:\n",
|
| 261 |
+
" print(f\"Error parsing safetensors file: {e}\")\n",
|
| 262 |
+
" return {}\n",
|
| 263 |
+
"\n",
|
| 264 |
+
"def save_json(data, filename):\n",
|
| 265 |
+
" with open(filename, 'w') as f:\n",
|
| 266 |
+
" json.dump(data, f, indent=4)\n",
|
| 267 |
+
"\n",
|
| 268 |
+
"def download_file(url, output_folder):\n",
|
| 269 |
+
" \"\"\"Download file with API key rotation.\"\"\"\n",
|
| 270 |
+
" filename = url.split(\"/\")[-1]\n",
|
| 271 |
+
" output_path = os.path.join(output_folder, filename)\n",
|
| 272 |
+
"\n",
|
| 273 |
+
" global current_key_index\n",
|
| 274 |
+
" while current_key_index < len(api_keys):\n",
|
| 275 |
+
" try:\n",
|
| 276 |
+
" response = requests.get(url, headers=get_headers(), stream=True)\n",
|
| 277 |
+
" if response.status_code == 401: # Unauthorized\n",
|
| 278 |
+
" print(f\"API key {current_key_index + 1} failed. Trying next key.\")\n",
|
| 279 |
+
" rotate_api_key()\n",
|
| 280 |
+
" continue\n",
|
| 281 |
+
" elif response.status_code == 403: # Forbidden\n",
|
| 282 |
+
" print(f\"Access forbidden for API key {current_key_index + 1}. Rotating to the next key.\")\n",
|
| 283 |
+
" rotate_api_key()\n",
|
| 284 |
+
" continue\n",
|
| 285 |
+
" response.raise_for_status()\n",
|
| 286 |
+
"\n",
|
| 287 |
+
" # Save the file to the specified output folder\n",
|
| 288 |
+
" with open(output_path, 'wb') as file:\n",
|
| 289 |
+
" for chunk in response.iter_content(chunk_size=8192):\n",
|
| 290 |
+
" file.write(chunk)\n",
|
| 291 |
+
" return output_path, filename\n",
|
| 292 |
+
" except requests.exceptions.RequestException as e:\n",
|
| 293 |
+
" print(f\"Error downloading file: {e}\")\n",
|
| 294 |
+
" rotate_api_key()\n",
|
| 295 |
+
" raise Exception(\"All API keys failed.\")\n",
|
| 296 |
+
"\n",
|
| 297 |
+
"def process_csv(csv_path):\n",
|
| 298 |
+
" # Read the CSV and process each row\n",
|
| 299 |
+
" with open(csv_path, newline='', encoding='utf-8') as csvfile:\n",
|
| 300 |
+
" reader = csv.DictReader(csvfile)\n",
|
| 301 |
+
" for index, row in enumerate(reader):\n",
|
| 302 |
+
" version_ids = []\n",
|
| 303 |
+
" for i in range(1, 21):\n",
|
| 304 |
+
" key = f'version_id_{i}'\n",
|
| 305 |
+
" if key in row and row[key]:\n",
|
| 306 |
+
" try:\n",
|
| 307 |
+
" version_ids.append(int(float(row[key])))\n",
|
| 308 |
+
" except ValueError:\n",
|
| 309 |
+
" print(f\"Invalid version_id value '{row[key]}' in row: {row}\")\n",
|
| 310 |
+
"\n",
|
| 311 |
+
" if not version_ids:\n",
|
| 312 |
+
" print(f\"No valid version IDs found in row: {row}\")\n",
|
| 313 |
+
" continue\n",
|
| 314 |
+
"\n",
|
| 315 |
+
" most_recent_version_id = str(max(version_ids))\n",
|
| 316 |
+
"\n",
|
| 317 |
+
" try:\n",
|
| 318 |
+
" adapter_file, filename = download_file(row['downloadUrl'], temp_dir)\n",
|
| 319 |
+
" metadata = parse_safetensors(adapter_file)\n",
|
| 320 |
+
" # Add all CSV data under 'civitaidata'\n",
|
| 321 |
+
" civitaidata = {key: int(value) if value.isdigit() else value for key, value in row.items()}\n",
|
| 322 |
+
" new_json_data = {\n",
|
| 323 |
+
" \"civitaidata\": civitaidata,\n",
|
| 324 |
+
" \"metadata\": metadata,\n",
|
| 325 |
+
" \"versionID\": most_recent_version_id\n",
|
| 326 |
+
" }\n",
|
| 327 |
+
" sanitized_name = row['name'].replace(\" \", \"_\").replace(\"/\", \"_\")\n",
|
| 328 |
+
" new_json_file = os.path.join(\n",
|
| 329 |
+
" output_json_dir,\n",
|
| 330 |
+
" f\"{index:08d}_{most_recent_version_id}_{sanitized_name}.json\"\n",
|
| 331 |
+
" )\n",
|
| 332 |
+
" save_json(new_json_data, new_json_file)\n",
|
| 333 |
+
" print(f\"Created new JSON for versionID {most_recent_version_id} with filename {filename}\")\n",
|
| 334 |
+
" except Exception as e:\n",
|
| 335 |
+
" print(f\"Error processing versionID {most_recent_version_id}: {e}\")"
|
| 336 |
+
]
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"cell_type": "markdown",
|
| 340 |
+
"id": "708131db-95d8-4427-9397-74c72d3edf48",
|
| 341 |
+
"metadata": {},
|
| 342 |
+
"source": [
|
| 343 |
+
"Uncomment the following to download and process model adapters"
|
| 344 |
+
]
|
| 345 |
+
},
|
| 346 |
+
{
|
| 347 |
+
"cell_type": "code",
|
| 348 |
+
"execution_count": 4,
|
| 349 |
+
"id": "51ce1943-e509-442d-8b92-1a67bc686471",
|
| 350 |
+
"metadata": {
|
| 351 |
+
"execution": {
|
| 352 |
+
"iopub.execute_input": "2025-02-08T20:49:21.547880Z",
|
| 353 |
+
"iopub.status.busy": "2025-02-08T20:49:21.547383Z",
|
| 354 |
+
"iopub.status.idle": "2025-02-08T20:49:21.550529Z",
|
| 355 |
+
"shell.execute_reply": "2025-02-08T20:49:21.550087Z",
|
| 356 |
+
"shell.execute_reply.started": "2025-02-08T20:49:21.547860Z"
|
| 357 |
+
}
|
| 358 |
+
},
|
| 359 |
+
"outputs": [],
|
| 360 |
+
"source": [
|
| 361 |
+
"csv_path = adapters_csv \n",
|
| 362 |
+
"#process_csv(csv_path) #### UNCOMMENT HERE"
|
| 363 |
+
]
|
| 364 |
+
},
|
| 365 |
+
{
|
| 366 |
+
"cell_type": "markdown",
|
| 367 |
+
"id": "4c2c52ee-83ad-4269-a7bf-16225b71f427",
|
| 368 |
+
"metadata": {},
|
| 369 |
+
"source": [
|
| 370 |
+
"## Step 2: Accumulate training tags and compare with auto-tagging vocabulary"
|
| 371 |
+
]
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"cell_type": "code",
|
| 375 |
+
"execution_count": 5,
|
| 376 |
+
"id": "6de2d9a6",
|
| 377 |
+
"metadata": {
|
| 378 |
+
"execution": {
|
| 379 |
+
"iopub.execute_input": "2025-02-08T20:49:31.218029Z",
|
| 380 |
+
"iopub.status.busy": "2025-02-08T20:49:31.217597Z",
|
| 381 |
+
"iopub.status.idle": "2025-02-08T20:49:32.646108Z",
|
| 382 |
+
"shell.execute_reply": "2025-02-08T20:49:32.645340Z",
|
| 383 |
+
"shell.execute_reply.started": "2025-02-08T20:49:31.218005Z"
|
| 384 |
+
}
|
| 385 |
+
},
|
| 386 |
+
"outputs": [
|
| 387 |
+
{
|
| 388 |
+
"name": "stdout",
|
| 389 |
+
"output_type": "stream",
|
| 390 |
+
"text": [
|
| 391 |
+
"Processed JSON files and summary saved to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/CSV/model_subsets/Section_6-5/Lora_metadata.csv\n",
|
| 392 |
+
"Tag summary saved to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/CSV/model_subsets/Section_6-5/LoRA_training_tags_acc.csv\n"
|
| 393 |
+
]
|
| 394 |
+
}
|
| 395 |
+
],
|
| 396 |
+
"source": [
|
| 397 |
+
"interrogator_vocab = current_dir.parent / 'misc/autotagging-vocabularies/clip_interrogator.csv'\n",
|
| 398 |
+
"deepbooru_vocab = current_dir.parent / 'misc/autotagging-vocabularies/deepbooru_tags.txt'\n",
|
| 399 |
+
"\n",
|
| 400 |
+
"\n",
|
| 401 |
+
"def load_tags():\n",
|
| 402 |
+
" try:\n",
|
| 403 |
+
" clip_interrogator_tags = pd.read_csv(\n",
|
| 404 |
+
" interrogator_vocab, header=None, usecols=[0], on_bad_lines='skip'\n",
|
| 405 |
+
" )[0].tolist()\n",
|
| 406 |
+
" except pd.errors.ParserError as e:\n",
|
| 407 |
+
" print(f\"Error reading 'clip_interrogator.csv': {e}\")\n",
|
| 408 |
+
" clip_interrogator_tags = []\n",
|
| 409 |
+
"\n",
|
| 410 |
+
" try:\n",
|
| 411 |
+
" with open(deepbooru_vocab, 'r', encoding='utf-8') as file:\n",
|
| 412 |
+
" deepbooru_tags = {line.strip() for line in file}\n",
|
| 413 |
+
" except FileNotFoundError as e:\n",
|
| 414 |
+
" print(f\"Error reading 'deepbooru_tags.txt': {e}\")\n",
|
| 415 |
+
" deepbooru_tags = set()\n",
|
| 416 |
+
"\n",
|
| 417 |
+
" return clip_interrogator_tags, deepbooru_tags\n",
|
| 418 |
+
"\n",
|
| 419 |
+
"clip_interrogator_tags, deepbooru_tags = load_tags()\n",
|
| 420 |
+
"\n",
|
| 421 |
+
"# Determines tagging system based on tag matches\n",
|
| 422 |
+
"def determine_tagging_system(tags):\n",
|
| 423 |
+
" clip_interrogator_matches = sum(1 for tag in tags if tag.strip() in clip_interrogator_tags)\n",
|
| 424 |
+
" deepbooru_matches = sum(1 for tag in tags if tag.strip() in deepbooru_tags or tag.strip().replace(' ', '_') in deepbooru_tags)\n",
|
| 425 |
+
" \n",
|
| 426 |
+
" if deepbooru_matches > clip_interrogator_matches:\n",
|
| 427 |
+
" return 'deepbooru'\n",
|
| 428 |
+
" elif clip_interrogator_matches > deepbooru_matches:\n",
|
| 429 |
+
" return 'clip-interrogator'\n",
|
| 430 |
+
" elif clip_interrogator_matches == 0 and deepbooru_matches == 0 and tags:\n",
|
| 431 |
+
" return 'other'\n",
|
| 432 |
+
" else:\n",
|
| 433 |
+
" return 'no tag metadata'\n",
|
| 434 |
+
"\n",
|
| 435 |
+
"def process_json_file(file_path):\n",
|
| 436 |
+
" try:\n",
|
| 437 |
+
" with open(file_path, 'r', encoding='utf-8') as file:\n",
|
| 438 |
+
" data = json.load(file)\n",
|
| 439 |
+
" except (json.JSONDecodeError, IOError) as e:\n",
|
| 440 |
+
" print(f\"Error processing file {file_path}: {e}\")\n",
|
| 441 |
+
" return None\n",
|
| 442 |
+
"\n",
|
| 443 |
+
" # Handle ss_tag_frequency - Ensure it's parsed correctly\n",
|
| 444 |
+
" tag_frequency_raw = data.get('metadata', {}).get('ss_tag_frequency', {})\n",
|
| 445 |
+
" if isinstance(tag_frequency_raw, str):\n",
|
| 446 |
+
" try:\n",
|
| 447 |
+
" tag_frequency_raw = json.loads(tag_frequency_raw)\n",
|
| 448 |
+
" except json.JSONDecodeError:\n",
|
| 449 |
+
" print(f\"Error decoding ss_tag_frequency in {file_path}\")\n",
|
| 450 |
+
" return None # Skip files with unreadable tag data\n",
|
| 451 |
+
"\n",
|
| 452 |
+
" if not isinstance(tag_frequency_raw, dict) or not tag_frequency_raw:\n",
|
| 453 |
+
" return None # Skip files with empty or nonexistent ss_tag_frequency\n",
|
| 454 |
+
"\n",
|
| 455 |
+
" filename = os.path.basename(file_path).replace('.json', '')\n",
|
| 456 |
+
" modelspec_title = data.get('metadata', {}).get('ss_output_name', '') # Using ss_output_name instead\n",
|
| 457 |
+
" modelspec_architecture = data.get('metadata', {}).get('ss_network_module', '')\n",
|
| 458 |
+
" ss_num_train_images = data.get('metadata', {}).get('ss_num_train_images', 0)\n",
|
| 459 |
+
" ss_steps = data.get('metadata', {}).get('ss_steps', 0)\n",
|
| 460 |
+
" ss_sd_model_name = data.get('metadata', {}).get('ss_sd_model_name', '')\n",
|
| 461 |
+
"\n",
|
| 462 |
+
" # Extract first-level tag frequencies\n",
|
| 463 |
+
" tag_frequency = next(iter(tag_frequency_raw.values()), {}) # Get the first nested dict\n",
|
| 464 |
+
" tags = list(tag_frequency.keys())\n",
|
| 465 |
+
" training_system = determine_tagging_system(tags) if tags else 'undetermined'\n",
|
| 466 |
+
"\n",
|
| 467 |
+
" tag_items = list(tag_frequency.items())[:20]\n",
|
| 468 |
+
" tag_data = {f'tag{i+1:02d}': tag_items[i][0] if i < len(tag_items) else None for i in range(20)}\n",
|
| 469 |
+
" tag_data.update({f'tag{i+1:02d}_no': tag_items[i][1] if i < len(tag_items) else None for i in range(20)})\n",
|
| 470 |
+
"\n",
|
| 471 |
+
" row = {\n",
|
| 472 |
+
" 'filename': filename,\n",
|
| 473 |
+
" 'modelspec_title': modelspec_title,\n",
|
| 474 |
+
" 'modelspec_architecture': modelspec_architecture,\n",
|
| 475 |
+
" 'ss_num_train_images': ss_num_train_images,\n",
|
| 476 |
+
" 'ss_steps': ss_steps,\n",
|
| 477 |
+
" 'ss_sd_model_name': ss_sd_model_name,\n",
|
| 478 |
+
" 'training_system': training_system\n",
|
| 479 |
+
" }\n",
|
| 480 |
+
" row.update(tag_data)\n",
|
| 481 |
+
"\n",
|
| 482 |
+
" return row, tag_frequency\n",
|
| 483 |
+
"\n",
|
| 484 |
+
"def parallel_process_files(folder_path):\n",
|
| 485 |
+
" rows = []\n",
|
| 486 |
+
" tag_occurrences = Counter()\n",
|
| 487 |
+
" json_files = [os.path.join(root, file) for root, _, files in os.walk(folder_path) for file in files if file.endswith('.json')]\n",
|
| 488 |
+
"\n",
|
| 489 |
+
" with ProcessPoolExecutor(max_workers=4) as executor:\n",
|
| 490 |
+
" results = executor.map(process_json_file, json_files, chunksize=1) # Using small chunksize for debugging\n",
|
| 491 |
+
"\n",
|
| 492 |
+
" for result in results:\n",
|
| 493 |
+
" if result:\n",
|
| 494 |
+
" row, tag_frequency = result\n",
|
| 495 |
+
" rows.append(row)\n",
|
| 496 |
+
" tag_occurrences.update(tag_frequency)\n",
|
| 497 |
+
"\n",
|
| 498 |
+
" total_tags_count = sum(tag_occurrences.values())\n",
|
| 499 |
+
" unique_tags_count = len(tag_occurrences)\n",
|
| 500 |
+
"\n",
|
| 501 |
+
" return rows, total_tags_count, unique_tags_count, tag_occurrences\n",
|
| 502 |
+
"\n",
|
| 503 |
+
"def write_summary_and_csv(rows, total_tags_count, unique_tags_count, tag_occurrences, output_file, tag_summary_file):\n",
|
| 504 |
+
" if not rows:\n",
|
| 505 |
+
" print(\"No valid data to write to CSV.\")\n",
|
| 506 |
+
" return\n",
|
| 507 |
+
"\n",
|
| 508 |
+
" df = pd.DataFrame(rows)\n",
|
| 509 |
+
" df.to_csv(output_file, index=False, encoding='utf-8')\n",
|
| 510 |
+
"\n",
|
| 511 |
+
" tag_summary_df = pd.DataFrame(list(tag_occurrences.items()), columns=['Tag (ss_tag_frequency/tag_frequency)', 'No. of Occurrences'])\n",
|
| 512 |
+
" tag_summary_df = tag_summary_df.sort_values(by='No. of Occurrences', ascending=False)\n",
|
| 513 |
+
" tag_summary_df.to_csv(tag_summary_file, index=False, encoding='utf-8')\n",
|
| 514 |
+
"\n",
|
| 515 |
+
" print(f\"Processed JSON files and summary saved to {output_file}\")\n",
|
| 516 |
+
" print(f\"Tag summary saved to {tag_summary_file}\")\n",
|
| 517 |
+
"\n",
|
| 518 |
+
"\n",
|
| 519 |
+
"def main():\n",
|
| 520 |
+
" folder_path = '/home/lauwag/shares/laura_wagner/Civitai_page_analysis/Civitai_visualizations/data/raw/adapter_metadata' # Path to your JSON files\n",
|
| 521 |
+
" \n",
|
| 522 |
+
" output_dir = current_dir.parent / 'data/CSV/model_subsets/Section_6-5/'\n",
|
| 523 |
+
" os.makedirs(output_dir, exist_ok=True) # Ensure the directory exists\n",
|
| 524 |
+
"\n",
|
| 525 |
+
" output_file = output_dir / 'Lora_metadata.csv'\n",
|
| 526 |
+
" tag_summary_file = output_dir / 'LoRA_training_tags_acc.csv'\n",
|
| 527 |
+
"\n",
|
| 528 |
+
" rows, total_tags_count, unique_tags_count, tag_occurrences = parallel_process_files(folder_path)\n",
|
| 529 |
+
" write_summary_and_csv(rows, total_tags_count, unique_tags_count, tag_occurrences, output_file, tag_summary_file)\n",
|
| 530 |
+
"\n",
|
| 531 |
+
"if __name__ == \"__main__\":\n",
|
| 532 |
+
" main()\n"
|
| 533 |
+
]
|
| 534 |
+
}
|
| 535 |
+
],
|
| 536 |
+
"metadata": {
|
| 537 |
+
"kernelspec": {
|
| 538 |
+
"display_name": "Python 3 (ipykernel)",
|
| 539 |
+
"language": "python",
|
| 540 |
+
"name": "python3"
|
| 541 |
+
},
|
| 542 |
+
"language_info": {
|
| 543 |
+
"codemirror_mode": {
|
| 544 |
+
"name": "ipython",
|
| 545 |
+
"version": 3
|
| 546 |
+
},
|
| 547 |
+
"file_extension": ".py",
|
| 548 |
+
"mimetype": "text/x-python",
|
| 549 |
+
"name": "python",
|
| 550 |
+
"nbconvert_exporter": "python",
|
| 551 |
+
"pygments_lexer": "ipython3",
|
| 552 |
+
"version": "3.11.9"
|
| 553 |
+
}
|
| 554 |
+
},
|
| 555 |
+
"nbformat": 4,
|
| 556 |
+
"nbformat_minor": 5
|
| 557 |
+
}
|
{scripts → jupyter_notebooks}/Section_3-3-4_deepfakes.ipynb
RENAMED
|
File without changes
|
jupyter_notebooks/SuppM_Figure_13_Danbooru_categories.ipynb
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 2,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [],
|
| 8 |
+
"source": [
|
| 9 |
+
"import json\n",
|
| 10 |
+
"from pathlib import Path\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"current_dir = Path.cwd()\n",
|
| 13 |
+
"input_json = current_dir.parent / \"misc/lists/danbooru.json\"\n",
|
| 14 |
+
"output_json = current_dir.parent / \"public/json/danbooru_flat.json\"\n",
|
| 15 |
+
"\n",
|
| 16 |
+
"# ---- CATEGORY COLORS ----\n",
|
| 17 |
+
"CATEGORY_COLORS = {\n",
|
| 18 |
+
" \"attire_and_body_accessories\": \"#DC143C\",\n",
|
| 19 |
+
" \"body\": \"coral\",\n",
|
| 20 |
+
" \"characters\": \"silver\",\n",
|
| 21 |
+
" \"copyrights\": \"#264653\",\n",
|
| 22 |
+
" \"creatures\": \"silver\",\n",
|
| 23 |
+
" \"drawing software\": \"#219ebc\",\n",
|
| 24 |
+
" \"games\": \"silver\",\n",
|
| 25 |
+
" \"metatags\": \"silver\",\n",
|
| 26 |
+
" \"more\": \"silver\",\n",
|
| 27 |
+
" \"objects\": \"#6d6875\",\n",
|
| 28 |
+
" \"plant\": \"#7cb518\",\n",
|
| 29 |
+
" \"real_world\": \"#a5a58d\",\n",
|
| 30 |
+
" \"sex\": \"#ef476f\",\n",
|
| 31 |
+
" \"visual_characteristics\": \"#06d6a0\",\n",
|
| 32 |
+
" \"subject\": \"#ffd166\",\n",
|
| 33 |
+
" \"uncategorized\": \"#adb5bd\",\n",
|
| 34 |
+
" \"actions_and_expressions\": \"#d00000\",\n",
|
| 35 |
+
" \"objects_and_backgrounds\": \"#118ab2\"\n",
|
| 36 |
+
"}\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"# ---- STEP 1: Load the original nested JSON ----\n",
|
| 39 |
+
"with open(input_json, \"r\", encoding=\"utf-8\") as f:\n",
|
| 40 |
+
" nested_data = json.load(f)\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"# ---- STEP 2: Recursively extract paths and limit tags ----\n",
|
| 43 |
+
"def extract_tags(data, path=None, result=None):\n",
|
| 44 |
+
" if path is None:\n",
|
| 45 |
+
" path = []\n",
|
| 46 |
+
" if result is None:\n",
|
| 47 |
+
" result = []\n",
|
| 48 |
+
"\n",
|
| 49 |
+
" if isinstance(data, dict):\n",
|
| 50 |
+
" for key, value in data.items():\n",
|
| 51 |
+
" extract_tags(value, path + [key], result)\n",
|
| 52 |
+
" elif isinstance(data, list) and len(data) > 0:\n",
|
| 53 |
+
" limited_tags = [data[0]] + [\"...\"] if len(data) > 1 else data\n",
|
| 54 |
+
" result.append({\n",
|
| 55 |
+
" \"level_path\": \" > \".join(path),\n",
|
| 56 |
+
" \"top_tags\": limited_tags,\n",
|
| 57 |
+
" \"tag_count\": len(data)\n",
|
| 58 |
+
" })\n",
|
| 59 |
+
"\n",
|
| 60 |
+
" return result\n",
|
| 61 |
+
"\n",
|
| 62 |
+
"# ---- STEP 3: Build the nested structure ----\n",
|
| 63 |
+
"def build_tree_from_flat_list(flat_data):\n",
|
| 64 |
+
" tree = {\"name\": \"root\", \"children\": []}\n",
|
| 65 |
+
"\n",
|
| 66 |
+
" for row in flat_data:\n",
|
| 67 |
+
" parts = row['level_path'].split(\" > \")\n",
|
| 68 |
+
" current = tree\n",
|
| 69 |
+
" for i, part in enumerate(parts):\n",
|
| 70 |
+
" match = next((child for child in current.get(\"children\", []) if child[\"name\"] == part), None)\n",
|
| 71 |
+
" if not match:\n",
|
| 72 |
+
" match = {\"name\": part, \"children\": []}\n",
|
| 73 |
+
" current.setdefault(\"children\", []).append(match)\n",
|
| 74 |
+
"\n",
|
| 75 |
+
" # If this is the top-level category, set its color\n",
|
| 76 |
+
" if current[\"name\"] == \"root\":\n",
|
| 77 |
+
" color = CATEGORY_COLORS.get(part.lower(), \"#888888\")\n",
|
| 78 |
+
" match[\"color\"] = color\n",
|
| 79 |
+
" else:\n",
|
| 80 |
+
" # Inherit from parent\n",
|
| 81 |
+
" match[\"color\"] = current.get(\"color\", \"#888888\")\n",
|
| 82 |
+
"\n",
|
| 83 |
+
" current = match\n",
|
| 84 |
+
"\n",
|
| 85 |
+
" current[\"tags\"] = row[\"top_tags\"]\n",
|
| 86 |
+
" current[\"direct_tag_count\"] = row[\"tag_count\"]\n",
|
| 87 |
+
"\n",
|
| 88 |
+
" return tree\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"# ---- STEP 4: Recursively compute tag and category sizes ----\n",
|
| 91 |
+
"def compute_sizes(node):\n",
|
| 92 |
+
" tag_count = node.get(\"direct_tag_count\", 0)\n",
|
| 93 |
+
" category_count = 0\n",
|
| 94 |
+
"\n",
|
| 95 |
+
" for child in node.get(\"children\", []):\n",
|
| 96 |
+
" compute_sizes(child)\n",
|
| 97 |
+
" tag_count += child.get(\"tag_count\", 0)\n",
|
| 98 |
+
" category_count += 1 + child.get(\"category_count\", 0)\n",
|
| 99 |
+
"\n",
|
| 100 |
+
" node[\"tag_count\"] = tag_count\n",
|
| 101 |
+
" node[\"category_count\"] = category_count\n",
|
| 102 |
+
"\n",
|
| 103 |
+
"# ---- STEP 5: Generate and save final JSON ----\n",
|
| 104 |
+
"flat_data = extract_tags(nested_data)\n",
|
| 105 |
+
"tree_data = build_tree_from_flat_list(flat_data)\n",
|
| 106 |
+
"compute_sizes(tree_data)\n",
|
| 107 |
+
"\n",
|
| 108 |
+
"with open(output_json, \"w\", encoding=\"utf-8\") as f:\n",
|
| 109 |
+
" json.dump(tree_data, f, ensure_ascii=False, indent=2)\n"
|
| 110 |
+
]
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"cell_type": "code",
|
| 114 |
+
"execution_count": null,
|
| 115 |
+
"metadata": {},
|
| 116 |
+
"outputs": [],
|
| 117 |
+
"source": []
|
| 118 |
+
}
|
| 119 |
+
],
|
| 120 |
+
"metadata": {
|
| 121 |
+
"kernelspec": {
|
| 122 |
+
"display_name": "latm",
|
| 123 |
+
"language": "python",
|
| 124 |
+
"name": "python3"
|
| 125 |
+
},
|
| 126 |
+
"language_info": {
|
| 127 |
+
"codemirror_mode": {
|
| 128 |
+
"name": "ipython",
|
| 129 |
+
"version": 3
|
| 130 |
+
},
|
| 131 |
+
"file_extension": ".py",
|
| 132 |
+
"mimetype": "text/x-python",
|
| 133 |
+
"name": "python",
|
| 134 |
+
"nbconvert_exporter": "python",
|
| 135 |
+
"pygments_lexer": "ipython3",
|
| 136 |
+
"version": "3.10.15"
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
"nbformat": 4,
|
| 140 |
+
"nbformat_minor": 2
|
| 141 |
+
}
|
jupyter_notebooks/SuppM_Figure_S12_asset_types.ipynb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "c0d18a6a",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import pandas as pd\n",
|
| 11 |
+
"import matplotlib.pyplot as plt\n",
|
| 12 |
+
"from matplotlib.ticker import FuncFormatter\n",
|
| 13 |
+
"from pathlib import Path"
|
| 14 |
+
]
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"cell_type": "code",
|
| 18 |
+
"execution_count": 2,
|
| 19 |
+
"id": "98d25755",
|
| 20 |
+
"metadata": {},
|
| 21 |
+
"outputs": [],
|
| 22 |
+
"source": [
|
| 23 |
+
"current_dir = Path.cwd()\n",
|
| 24 |
+
"\n",
|
| 25 |
+
"def sortByFrequency_model_types_csv(csv_path, output_svg_path):\n",
|
| 26 |
+
" hatch_pattern = '\\\\\\\\\\\\\\\\\\\\\\\\' # Hatch pattern for the bars\n",
|
| 27 |
+
"\n",
|
| 28 |
+
" # Read the CSV file\n",
|
| 29 |
+
" df = pd.read_csv(csv_path)\n",
|
| 30 |
+
"\n",
|
| 31 |
+
" if 'type' not in df.columns:\n",
|
| 32 |
+
" return \"The CSV file does not contain a 'type' column.\"\n",
|
| 33 |
+
"\n",
|
| 34 |
+
" # Count the occurrences of each model type\n",
|
| 35 |
+
" type_counts = df['type'].value_counts().reset_index()\n",
|
| 36 |
+
" type_counts.columns = ['Type', 'Count']\n",
|
| 37 |
+
" total = type_counts['Count'].sum()\n",
|
| 38 |
+
" type_counts['Percentage'] = (type_counts['Count'] / total * 100).round(2)\n",
|
| 39 |
+
"\n",
|
| 40 |
+
" # Sort the data in ascending order\n",
|
| 41 |
+
" type_counts = type_counts.sort_values(by='Count', ascending=True)\n",
|
| 42 |
+
"\n",
|
| 43 |
+
" # Plotting\n",
|
| 44 |
+
" plt.figure(figsize=(10, 3.5))\n",
|
| 45 |
+
" bars = plt.barh(type_counts['Type'], type_counts['Count'], color='white', hatch=hatch_pattern, edgecolor='coral')\n",
|
| 46 |
+
" plt.xlabel('Counts', fontweight='bold')\n",
|
| 47 |
+
" plt.ylabel('Asset Type', fontweight='bold')\n",
|
| 48 |
+
"\n",
|
| 49 |
+
" ax = plt.gca()\n",
|
| 50 |
+
"\n",
|
| 51 |
+
" # Hide all axis spines\n",
|
| 52 |
+
" for spine in ax.spines.values():\n",
|
| 53 |
+
" spine.set_visible(False)\n",
|
| 54 |
+
"\n",
|
| 55 |
+
" # Keep ticks visible\n",
|
| 56 |
+
" ax.xaxis.set_ticks_position('bottom')\n",
|
| 57 |
+
" ax.yaxis.set_ticks_position('left')\n",
|
| 58 |
+
"\n",
|
| 59 |
+
" # Bold tick labels\n",
|
| 60 |
+
" for label in ax.get_xticklabels() + ax.get_yticklabels():\n",
|
| 61 |
+
" label.set_fontweight('bold')\n",
|
| 62 |
+
"\n",
|
| 63 |
+
" # Format x-axis ticks: 25000 → 25 k\n",
|
| 64 |
+
" ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'{int(x/1000)} k' if x >= 1000 else f'{int(x)}'))\n",
|
| 65 |
+
"\n",
|
| 66 |
+
" # Add percentage labels to the bars\n",
|
| 67 |
+
" for bar, percentage in zip(bars, type_counts['Percentage']):\n",
|
| 68 |
+
" plt.text(bar.get_width() + 5, bar.get_y() + bar.get_height()/2,\n",
|
| 69 |
+
" f' {percentage}%', va='center', color='blueviolet', fontweight='bold')\n",
|
| 70 |
+
"\n",
|
| 71 |
+
" plt.tight_layout()\n",
|
| 72 |
+
" plt.savefig(out_file, format='svg')\n",
|
| 73 |
+
" plt.show()\n"
|
| 74 |
+
]
|
| 75 |
+
},
|
| 76 |
+
{
|
| 77 |
+
"cell_type": "code",
|
| 78 |
+
"execution_count": 4,
|
| 79 |
+
"id": "9e610dec",
|
| 80 |
+
"metadata": {},
|
| 81 |
+
"outputs": [
|
| 82 |
+
{
|
| 83 |
+
"data": {
|
| 84 |
+
"image/png": "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",
|
| 85 |
+
"text/plain": [
|
| 86 |
+
"<Figure size 1000x350 with 1 Axes>"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
"metadata": {},
|
| 90 |
+
"output_type": "display_data"
|
| 91 |
+
}
|
| 92 |
+
],
|
| 93 |
+
"source": [
|
| 94 |
+
"csv_file = current_dir.parent / \"data/CSV/Models/Civi_models.csv\"\n",
|
| 95 |
+
"out_file = current_dir.parent / \"plots/Figure_12.svg\"\n",
|
| 96 |
+
"sortByFrequency_model_types_csv(csv_file, out_file)"
|
| 97 |
+
]
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"cell_type": "code",
|
| 101 |
+
"execution_count": null,
|
| 102 |
+
"id": "e91e903f",
|
| 103 |
+
"metadata": {},
|
| 104 |
+
"outputs": [],
|
| 105 |
+
"source": []
|
| 106 |
+
}
|
| 107 |
+
],
|
| 108 |
+
"metadata": {
|
| 109 |
+
"kernelspec": {
|
| 110 |
+
"display_name": "latm",
|
| 111 |
+
"language": "python",
|
| 112 |
+
"name": "python3"
|
| 113 |
+
},
|
| 114 |
+
"language_info": {
|
| 115 |
+
"codemirror_mode": {
|
| 116 |
+
"name": "ipython",
|
| 117 |
+
"version": 3
|
| 118 |
+
},
|
| 119 |
+
"file_extension": ".py",
|
| 120 |
+
"mimetype": "text/x-python",
|
| 121 |
+
"name": "python",
|
| 122 |
+
"nbconvert_exporter": "python",
|
| 123 |
+
"pygments_lexer": "ipython3",
|
| 124 |
+
"version": "3.10.15"
|
| 125 |
+
}
|
| 126 |
+
},
|
| 127 |
+
"nbformat": 4,
|
| 128 |
+
"nbformat_minor": 5
|
| 129 |
+
}
|
jupyter_notebooks/SuppM_Figure_S13_Danbooru_Taxonomy.ipynb
ADDED
|
@@ -0,0 +1,1848 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "68a6e559-6ef4-4c7f-8736-93bc8588e3bc",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# \"Danbooru\" Taxonomy"
|
| 9 |
+
]
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
"cell_type": "markdown",
|
| 13 |
+
"id": "36aff030",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"### Getting Tags and Categories from Danbooru.donmai.us"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "code",
|
| 21 |
+
"execution_count": 14,
|
| 22 |
+
"id": "a97e3a64-5990-44a9-809d-45030e85f161",
|
| 23 |
+
"metadata": {
|
| 24 |
+
"execution": {
|
| 25 |
+
"iopub.execute_input": "2025-03-17T12:48:07.782534Z",
|
| 26 |
+
"iopub.status.busy": "2025-03-17T12:48:07.781679Z",
|
| 27 |
+
"iopub.status.idle": "2025-03-17T12:48:07.786016Z",
|
| 28 |
+
"shell.execute_reply": "2025-03-17T12:48:07.785465Z",
|
| 29 |
+
"shell.execute_reply.started": "2025-03-17T12:48:07.782509Z"
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"outputs": [],
|
| 33 |
+
"source": [
|
| 34 |
+
"from pathlib import Path\n",
|
| 35 |
+
"import subprocess\n",
|
| 36 |
+
"import json\n",
|
| 37 |
+
"import urllib.parse"
|
| 38 |
+
]
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"cell_type": "markdown",
|
| 42 |
+
"id": "97917f01-8f5b-4aa7-8869-9a7d853843d0",
|
| 43 |
+
"metadata": {},
|
| 44 |
+
"source": [
|
| 45 |
+
"#### Paths credentials"
|
| 46 |
+
]
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"cell_type": "code",
|
| 50 |
+
"execution_count": 2,
|
| 51 |
+
"id": "1f50a910-66c2-472a-8027-dd589b6ae755",
|
| 52 |
+
"metadata": {
|
| 53 |
+
"execution": {
|
| 54 |
+
"iopub.execute_input": "2025-03-17T12:48:08.799790Z",
|
| 55 |
+
"iopub.status.busy": "2025-03-17T12:48:08.798840Z",
|
| 56 |
+
"iopub.status.idle": "2025-03-17T12:48:08.905544Z",
|
| 57 |
+
"shell.execute_reply": "2025-03-17T12:48:08.904995Z",
|
| 58 |
+
"shell.execute_reply.started": "2025-03-17T12:48:08.799770Z"
|
| 59 |
+
}
|
| 60 |
+
},
|
| 61 |
+
"outputs": [],
|
| 62 |
+
"source": [
|
| 63 |
+
"current_dir = Path.cwd()"
|
| 64 |
+
]
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"cell_type": "code",
|
| 68 |
+
"execution_count": 6,
|
| 69 |
+
"id": "a0f846e3-1fcc-4839-a459-c974f9c46d06",
|
| 70 |
+
"metadata": {
|
| 71 |
+
"execution": {
|
| 72 |
+
"iopub.execute_input": "2025-03-17T12:48:09.490977Z",
|
| 73 |
+
"iopub.status.busy": "2025-03-17T12:48:09.490799Z",
|
| 74 |
+
"iopub.status.idle": "2025-03-17T12:48:09.701470Z",
|
| 75 |
+
"shell.execute_reply": "2025-03-17T12:48:09.700912Z",
|
| 76 |
+
"shell.execute_reply.started": "2025-03-17T12:48:09.490961Z"
|
| 77 |
+
}
|
| 78 |
+
},
|
| 79 |
+
"outputs": [],
|
| 80 |
+
"source": [
|
| 81 |
+
"api_key = (current_dir.parent / \"misc/credentials/api-key_danbooru\").read_text().strip()\n",
|
| 82 |
+
"username = (current_dir.parent / \"misc/credentials/username_danbooru\").read_text().strip()"
|
| 83 |
+
]
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"cell_type": "markdown",
|
| 87 |
+
"id": "c4c2290f-7705-4061-9b29-e1b74da732b8",
|
| 88 |
+
"metadata": {
|
| 89 |
+
"execution": {
|
| 90 |
+
"iopub.execute_input": "2025-03-12T20:39:34.470218Z",
|
| 91 |
+
"iopub.status.busy": "2025-03-12T20:39:34.469746Z",
|
| 92 |
+
"iopub.status.idle": "2025-03-12T20:39:34.472498Z",
|
| 93 |
+
"shell.execute_reply": "2025-03-12T20:39:34.472084Z",
|
| 94 |
+
"shell.execute_reply.started": "2025-03-12T20:39:34.470199Z"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"source": [
|
| 98 |
+
"### Query danbooru.donmai.us for a single Tag"
|
| 99 |
+
]
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"cell_type": "code",
|
| 103 |
+
"execution_count": 7,
|
| 104 |
+
"id": "00ffaf5f-a4ae-408a-8a7c-e6b174f091f8",
|
| 105 |
+
"metadata": {
|
| 106 |
+
"execution": {
|
| 107 |
+
"iopub.execute_input": "2025-03-17T12:48:11.712064Z",
|
| 108 |
+
"iopub.status.busy": "2025-03-17T12:48:11.711866Z",
|
| 109 |
+
"iopub.status.idle": "2025-03-17T12:48:20.143088Z",
|
| 110 |
+
"shell.execute_reply": "2025-03-17T12:48:20.142318Z",
|
| 111 |
+
"shell.execute_reply.started": "2025-03-17T12:48:11.712047Z"
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
"outputs": [
|
| 115 |
+
{
|
| 116 |
+
"name": "stdout",
|
| 117 |
+
"output_type": "stream",
|
| 118 |
+
"text": [
|
| 119 |
+
"🚀 Fetching tag details for 'wombat'...\n",
|
| 120 |
+
"🔎 Running cURL: curl -s -L --user parodyofsomething:FkzGApb17bfJayJMqKTzeyTw -X GET https://danbooru.donmai.us/tags.json?search%5Bname%5D=wombat&only=id,name,category,post_count,is_deprecated,created_at,updated_at,wiki_page,artist,antecedent_alias,consequent_aliases,antecedent_implications,consequent_implications,dtext_links\n",
|
| 121 |
+
"✅ Test complete! Data saved to `/home/lauhp/000_PHD/000_010_PUBLICATION/2025_SAGE/CODE/pm-paper_uzh_gitlab/pm-paper/misc/danbooru/tag_info_test/wombat_tag_info.json`.\n"
|
| 122 |
+
]
|
| 123 |
+
}
|
| 124 |
+
],
|
| 125 |
+
"source": [
|
| 126 |
+
"output_dir = current_dir.parent / \"misc\" / \"danbooru\" / \"tag_info_test\"\n",
|
| 127 |
+
"output_dir.mkdir(parents=True, exist_ok=True)\n",
|
| 128 |
+
"\n",
|
| 129 |
+
"# Get the tag to test\n",
|
| 130 |
+
"tag_name = input(\"Enter a single tag to test: \").strip()\n",
|
| 131 |
+
"if not tag_name:\n",
|
| 132 |
+
" print(\"❌ ERROR: No tag entered.\")\n",
|
| 133 |
+
" exit()\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"# Encode the tag name properly\n",
|
| 136 |
+
"encoded_tag = urllib.parse.quote(tag_name, safe=\"\") # Encode everything\n",
|
| 137 |
+
"\n",
|
| 138 |
+
"# API endpoints with correct encoding\n",
|
| 139 |
+
"BASE_URL = \"https://danbooru.donmai.us\"\n",
|
| 140 |
+
"#TAGS_API = f\"{BASE_URL}/tags.json?search%5Bname%5D={encoded_tag}\"\n",
|
| 141 |
+
"TAGS_API = f\"{BASE_URL}/tags.json?search%5Bname%5D={encoded_tag}&only=id,name,category,post_count,is_deprecated,created_at,updated_at,wiki_page,artist,antecedent_alias,consequent_aliases,antecedent_implications,consequent_implications,dtext_links\"\n",
|
| 142 |
+
"IMPLICATIONS_API = f\"{BASE_URL}/tag_implications.json?search%5Bantecedent_name%5D={encoded_tag}\"\n",
|
| 143 |
+
"ALIASES_API = f\"{BASE_URL}/tag_aliases.json?search%5Bantecedent_name%5D={encoded_tag}\"\n",
|
| 144 |
+
"WIKI_API = f\"{BASE_URL}/wiki_pages.json?search%5Btitle%5D={encoded_tag}\"\n",
|
| 145 |
+
"\n",
|
| 146 |
+
"# Function to run cURL command properly\n",
|
| 147 |
+
"def fetch_data(api_url, description):\n",
|
| 148 |
+
" print(f\"🚀 Fetching {description} for '{tag_name}'...\")\n",
|
| 149 |
+
"\n",
|
| 150 |
+
" # Build the cURL command\n",
|
| 151 |
+
" curl_command = [\n",
|
| 152 |
+
" \"curl\", \"-s\", \"-L\", # Silent & follow redirects\n",
|
| 153 |
+
" \"--user\", f\"{username}:{api_key}\", # Auth\n",
|
| 154 |
+
" \"-X\", \"GET\",\n",
|
| 155 |
+
" api_url\n",
|
| 156 |
+
" ]\n",
|
| 157 |
+
"\n",
|
| 158 |
+
" print(f\"🔎 Running cURL: {' '.join(curl_command)}\") # Show the command\n",
|
| 159 |
+
"\n",
|
| 160 |
+
" try:\n",
|
| 161 |
+
" result = subprocess.run(curl_command, capture_output=True, text=True, check=True)\n",
|
| 162 |
+
" if result.returncode == 0:\n",
|
| 163 |
+
" return json.loads(result.stdout) if result.stdout else None\n",
|
| 164 |
+
" else:\n",
|
| 165 |
+
" print(f\"⚠️ cURL failed (status {result.returncode}): {result.stderr}\")\n",
|
| 166 |
+
" return None\n",
|
| 167 |
+
" except subprocess.CalledProcessError as e:\n",
|
| 168 |
+
" print(f\"⚠️ cURL error: {e}\")\n",
|
| 169 |
+
" return None\n",
|
| 170 |
+
" except json.JSONDecodeError:\n",
|
| 171 |
+
" print(f\"⚠️ Failed to parse JSON: {result.stdout}\")\n",
|
| 172 |
+
" return None\n",
|
| 173 |
+
"\n",
|
| 174 |
+
"# Fetch tag details\n",
|
| 175 |
+
"tag_info = {\n",
|
| 176 |
+
" \"tag_details\": fetch_data(TAGS_API, \"tag details\"),\n",
|
| 177 |
+
" #\"implications\": fetch_data(IMPLICATIONS_API, \"tag implications\"),\n",
|
| 178 |
+
" #\"aliases\": fetch_data(ALIASES_API, \"tag aliases\"),\n",
|
| 179 |
+
" #\"wiki\": fetch_data(WIKI_API, \"wiki information\"),\n",
|
| 180 |
+
"}\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"# Save to JSON\n",
|
| 183 |
+
"output_file = output_dir / f\"{tag_name}_tag_info.json\"\n",
|
| 184 |
+
"with open(output_file, \"w\", encoding=\"utf-8\") as f:\n",
|
| 185 |
+
" json.dump(tag_info, f, indent=4, ensure_ascii=False)\n",
|
| 186 |
+
"\n",
|
| 187 |
+
"print(f\"✅ Test complete! Data saved to `{output_file}`.\")\n"
|
| 188 |
+
]
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"cell_type": "markdown",
|
| 192 |
+
"id": "75a28e4c-e06f-4578-a338-4f0014953302",
|
| 193 |
+
"metadata": {},
|
| 194 |
+
"source": [
|
| 195 |
+
"## Hierarchy"
|
| 196 |
+
]
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"cell_type": "code",
|
| 200 |
+
"execution_count": 8,
|
| 201 |
+
"id": "1a58f2d1-909b-41c0-bcb8-0a55e6835f53",
|
| 202 |
+
"metadata": {
|
| 203 |
+
"execution": {
|
| 204 |
+
"iopub.execute_input": "2025-03-17T12:48:42.365809Z",
|
| 205 |
+
"iopub.status.busy": "2025-03-17T12:48:42.365447Z",
|
| 206 |
+
"iopub.status.idle": "2025-03-17T12:48:42.376305Z",
|
| 207 |
+
"shell.execute_reply": "2025-03-17T12:48:42.375752Z",
|
| 208 |
+
"shell.execute_reply.started": "2025-03-17T12:48:42.365788Z"
|
| 209 |
+
}
|
| 210 |
+
},
|
| 211 |
+
"outputs": [],
|
| 212 |
+
"source": [
|
| 213 |
+
"manual_hierarchy = {\n",
|
| 214 |
+
" \"subject\": {\n",
|
| 215 |
+
" \"female\": {\n",
|
| 216 |
+
" \"female_general\": [\"woman\", \"girl\", \"1girl\", \"2girls\", \"3girls\", \"4girls\", \"5girls\", \"6+girls\", \"multiple girls\"]\n",
|
| 217 |
+
" },\n",
|
| 218 |
+
" \"male\": {\n",
|
| 219 |
+
" \"male_general\": [\"man\", \"boy\", \"1boy\", \"2boys\", \"3boys\", \"4boys\", \"5boys\", \"6+boys\", \"multiple boys\"]\n",
|
| 220 |
+
" },\n",
|
| 221 |
+
" \"koma\": {\n",
|
| 222 |
+
" \"koma_general\": [\"1koma\", \"2koma\"]\n",
|
| 223 |
+
" },\n",
|
| 224 |
+
" \"anthro\": {\n",
|
| 225 |
+
" \"anthro_general\": [\"cat girl\", \"fox girl\", \"dog girl\", \"cat boy\", \"furry\"]\n",
|
| 226 |
+
" }\n",
|
| 227 |
+
" },\n",
|
| 228 |
+
"\n",
|
| 229 |
+
" \"visual_characteristics\": {\n",
|
| 230 |
+
" \"image_composition_and_style\": {\n",
|
| 231 |
+
" \"artistic_license\": [],\n",
|
| 232 |
+
" \"image_composition\": {\n",
|
| 233 |
+
" \"backgrounds\": [],\n",
|
| 234 |
+
" \"censorship\": [],\n",
|
| 235 |
+
" \"colors\": [],\n",
|
| 236 |
+
" \"focus_tags\": [],\n",
|
| 237 |
+
" \"lighting\": [],\n",
|
| 238 |
+
" \"prints\": []\n",
|
| 239 |
+
" },\n",
|
| 240 |
+
" \"patterns\": [],\n",
|
| 241 |
+
" \"symbols\": [],\n",
|
| 242 |
+
" \"text\": [],\n",
|
| 243 |
+
" \"year_tags\": []\n",
|
| 244 |
+
" }\n",
|
| 245 |
+
" },\n",
|
| 246 |
+
" \"body\": {\n",
|
| 247 |
+
" \"body_parts\": {\n",
|
| 248 |
+
" \"ass\": [],\n",
|
| 249 |
+
" \"breasts_tags\": [],\n",
|
| 250 |
+
" \"face_tags\": {\n",
|
| 251 |
+
" \"eyes_tags\": []\n",
|
| 252 |
+
" },\n",
|
| 253 |
+
" \"ears_tags\": [],\n",
|
| 254 |
+
" \"hair\": {\n",
|
| 255 |
+
" \"hair_color\": [],\n",
|
| 256 |
+
" \"hair_styles\": []\n",
|
| 257 |
+
" },\n",
|
| 258 |
+
" \"hands\": {\n",
|
| 259 |
+
" \"gestures\": []\n",
|
| 260 |
+
" },\n",
|
| 261 |
+
" \"neck_and_neckwear\": [],\n",
|
| 262 |
+
" \"posture\": [],\n",
|
| 263 |
+
" \"pussy\": [],\n",
|
| 264 |
+
" \"penis\": [],\n",
|
| 265 |
+
" \"shoulders\": [],\n",
|
| 266 |
+
" \"skin_color\": [],\n",
|
| 267 |
+
" \"tail\": [],\n",
|
| 268 |
+
" \"wings\": []\n",
|
| 269 |
+
" },\n",
|
| 270 |
+
" \"injury\": []\n",
|
| 271 |
+
" },\n",
|
| 272 |
+
" \"attire_and_body_accessories\": {\n",
|
| 273 |
+
" \"attire\": {\n",
|
| 274 |
+
" \"dress\": [],\n",
|
| 275 |
+
" \"handwear\": [],\n",
|
| 276 |
+
" \"headwear\": [],\n",
|
| 277 |
+
" \"legwear\": [],\n",
|
| 278 |
+
" \"neck_and_neckwear\": [],\n",
|
| 279 |
+
" \"sexual_attire\": {\n",
|
| 280 |
+
" \"bra\": [],\n",
|
| 281 |
+
" \"panties\": []\n",
|
| 282 |
+
" },\n",
|
| 283 |
+
" \"sleeves\": [],\n",
|
| 284 |
+
" \"swimsuit\": []\n",
|
| 285 |
+
" },\n",
|
| 286 |
+
" \"embellishment\": [],\n",
|
| 287 |
+
" \"eyewear\": [],\n",
|
| 288 |
+
" \"fashion_style\": [],\n",
|
| 289 |
+
" \"nudity\": []\n",
|
| 290 |
+
" },\n",
|
| 291 |
+
" \"sex\": {\n",
|
| 292 |
+
" \"sex_acts\": {\n",
|
| 293 |
+
" \"simulated_sex_acts\": []\n",
|
| 294 |
+
" },\n",
|
| 295 |
+
" \"sexual_positions\": []\n",
|
| 296 |
+
" },\n",
|
| 297 |
+
" \"objects\": {\n",
|
| 298 |
+
" \"computer\": [],\n",
|
| 299 |
+
" \"airplanes\": [],\n",
|
| 300 |
+
" \"armor\": [],\n",
|
| 301 |
+
" \"ground_vehicles\": [],\n",
|
| 302 |
+
" \"helicopters\": [],\n",
|
| 303 |
+
" \"pokemon_objects\": [],\n",
|
| 304 |
+
" \"ships\": [],\n",
|
| 305 |
+
" \"weapons\": [],\n",
|
| 306 |
+
" \"audio_tags\": [],\n",
|
| 307 |
+
" \"cards\": {\n",
|
| 308 |
+
" \"playing_card_faces\": []\n",
|
| 309 |
+
" },\n",
|
| 310 |
+
" \"eyewear\": [],\n",
|
| 311 |
+
" \"piercings\": [],\n",
|
| 312 |
+
" \"sex_objects\": []\n",
|
| 313 |
+
" },\n",
|
| 314 |
+
" \"creatures\": {\n",
|
| 315 |
+
" \"animals\": {\n",
|
| 316 |
+
" \"birds\": [],\n",
|
| 317 |
+
" \"cats\": [],\n",
|
| 318 |
+
" \"dogs\": []\n",
|
| 319 |
+
" },\n",
|
| 320 |
+
" \"legendary_creatures\": []\n",
|
| 321 |
+
" },\n",
|
| 322 |
+
" \"plants\": {\n",
|
| 323 |
+
" \"plant\": {\n",
|
| 324 |
+
" \"tree\": []\n",
|
| 325 |
+
" },\n",
|
| 326 |
+
" \"flowers\": []\n",
|
| 327 |
+
" },\n",
|
| 328 |
+
" \"games\": {\n",
|
| 329 |
+
" \"game_activities\": [],\n",
|
| 330 |
+
" \"board_games\": [],\n",
|
| 331 |
+
" \"sports\": [],\n",
|
| 332 |
+
" \"video_game\": []\n",
|
| 333 |
+
" },\n",
|
| 334 |
+
" \"real_world\": {\n",
|
| 335 |
+
" \"companies_and_brand_names\": [],\n",
|
| 336 |
+
" \"holidays_and_celebrations\": [],\n",
|
| 337 |
+
" \"jobs\": [],\n",
|
| 338 |
+
" \"locations\": [],\n",
|
| 339 |
+
" \"people\": [],\n",
|
| 340 |
+
" \"real_world_locations\": []\n",
|
| 341 |
+
" },\n",
|
| 342 |
+
" \"more\": {\n",
|
| 343 |
+
" \"dances\": [],\n",
|
| 344 |
+
" \"family_relationships\": [],\n",
|
| 345 |
+
" \"food_tags\": [],\n",
|
| 346 |
+
" \"fire\": [],\n",
|
| 347 |
+
" \"groups\": [],\n",
|
| 348 |
+
" \"phrases\": [],\n",
|
| 349 |
+
" \"scan\": [],\n",
|
| 350 |
+
" \"subjective\": [],\n",
|
| 351 |
+
" \"technology\": [],\n",
|
| 352 |
+
" \"verbs_and_gerunds\": [],\n",
|
| 353 |
+
" \"water\": [],\n",
|
| 354 |
+
" \"disambiguation_pages\": [],\n",
|
| 355 |
+
" \"magazine_publications\": [],\n",
|
| 356 |
+
" \"special_moves\": [],\n",
|
| 357 |
+
" \"uniforms\": [],\n",
|
| 358 |
+
" \"pokemon_media\": [],\n",
|
| 359 |
+
" \"tagged_songs\": [],\n",
|
| 360 |
+
" \"vocaloid_derivatives\": [],\n",
|
| 361 |
+
" \"vocaloid_songs\": [],\n",
|
| 362 |
+
" \"vocal_synthesizers\": [],\n",
|
| 363 |
+
" \"vocal_synth_derivatives\": [],\n",
|
| 364 |
+
" \"vocal_synth_songs\": [],\n",
|
| 365 |
+
" \"deemo_songs\": []\n",
|
| 366 |
+
" },\n",
|
| 367 |
+
" \"copyrights_artists_projects_and_media\": {\n",
|
| 368 |
+
" \"genres_of_video_games\": {\n",
|
| 369 |
+
" \"fighting_games\": [],\n",
|
| 370 |
+
" \"platform_games\": [],\n",
|
| 371 |
+
" \"role-playing_games\": [],\n",
|
| 372 |
+
" \"shooter_games\": [],\n",
|
| 373 |
+
" \"visual_novel_games\": []\n",
|
| 374 |
+
" },\n",
|
| 375 |
+
" \"artists\": {\n",
|
| 376 |
+
" \"named_drawfags\": [],\n",
|
| 377 |
+
" \"pixiv_projects\": []\n",
|
| 378 |
+
" }\n",
|
| 379 |
+
" },\n",
|
| 380 |
+
" \"characters\": {\n",
|
| 381 |
+
" \"ace_attorney\": [],\n",
|
| 382 |
+
" \"arknights\": [],\n",
|
| 383 |
+
" \"atelier\": [],\n",
|
| 384 |
+
" \"azur_lane\": [],\n",
|
| 385 |
+
" \"bleach\": [],\n",
|
| 386 |
+
" \"bokujou_monogatari\": [],\n",
|
| 387 |
+
" \"brave_girl_ravens\": [],\n",
|
| 388 |
+
" \"cardcaptor_sakura\": [],\n",
|
| 389 |
+
" \"danganronpa\": [],\n",
|
| 390 |
+
" \"digimon\": {\n",
|
| 391 |
+
" \"digimon_characters\": []\n",
|
| 392 |
+
" },\n",
|
| 393 |
+
" \"dragon_ball\": [],\n",
|
| 394 |
+
" \"dragon_quest\": [],\n",
|
| 395 |
+
" \"fate_series\": [],\n",
|
| 396 |
+
" \"final_fantasy\": [],\n",
|
| 397 |
+
" \"fire_emblem\": [],\n",
|
| 398 |
+
" \"flower_knight_girl\": [],\n",
|
| 399 |
+
" \"gensou_suikoden\": [],\n",
|
| 400 |
+
" \"girls_frontline\": [],\n",
|
| 401 |
+
" \"girls_und_panzer\": [],\n",
|
| 402 |
+
" \"gundam_mechas\": [],\n",
|
| 403 |
+
" \"hunter_x_hunter\": [],\n",
|
| 404 |
+
" \"jojo_no_kimyou_na_bouken\": [],\n",
|
| 405 |
+
" \"kamen_rider\": [],\n",
|
| 406 |
+
" \"kantai_collection\": [],\n",
|
| 407 |
+
" \"kingdom_hearts\": [],\n",
|
| 408 |
+
" \"mahou_sensei_negima\": [],\n",
|
| 409 |
+
" \"meitantei_conan\": [],\n",
|
| 410 |
+
" \"minecraft\": [],\n",
|
| 411 |
+
" \"naruto\": [],\n",
|
| 412 |
+
" \"nippon_ichi\": [],\n",
|
| 413 |
+
" \"one_piece\": [],\n",
|
| 414 |
+
" \"oshiro_project\": [],\n",
|
| 415 |
+
" \"pokemon\": {\n",
|
| 416 |
+
" \"elite_four_members\": [],\n",
|
| 417 |
+
" \"gym_leaders\": [],\n",
|
| 418 |
+
" \"families_of_pokemon_main_characters\": [],\n",
|
| 419 |
+
" \"pokemon_ranger_characters\": [],\n",
|
| 420 |
+
" \"pokemon_trainer_classes\": []\n",
|
| 421 |
+
" },\n",
|
| 422 |
+
" \"pretty_cure\": [],\n",
|
| 423 |
+
" \"ragnarok_online\": [],\n",
|
| 424 |
+
" \"rosenkreuzstilette\": [],\n",
|
| 425 |
+
" \"sailor_moon\": [],\n",
|
| 426 |
+
" \"street_fighter\": [],\n",
|
| 427 |
+
" \"super_smash_bros\": [],\n",
|
| 428 |
+
" \"world_witches_series\": [],\n",
|
| 429 |
+
" \"tales_of\": [],\n",
|
| 430 |
+
" \"toaru_majutsu_no_index\": [],\n",
|
| 431 |
+
" \"touhou\": [],\n",
|
| 432 |
+
" \"touken_ranbu\": [],\n",
|
| 433 |
+
" \"ultra_series\": [],\n",
|
| 434 |
+
" \"umamusume\": [],\n",
|
| 435 |
+
" \"vocaloid\": [],\n",
|
| 436 |
+
" \"yu_gi_oh\": [],\n",
|
| 437 |
+
" \"genderswap_characters\": [],\n",
|
| 438 |
+
" \"official_mascots\": [],\n",
|
| 439 |
+
" \"real_life_racehorses\": []\n",
|
| 440 |
+
" },\n",
|
| 441 |
+
" \"metatags\": {\n",
|
| 442 |
+
" \"tag_group_metatags\": [],\n",
|
| 443 |
+
" \"drawing_software\": []\n",
|
| 444 |
+
" }\n",
|
| 445 |
+
"}\n"
|
| 446 |
+
]
|
| 447 |
+
},
|
| 448 |
+
{
|
| 449 |
+
"cell_type": "markdown",
|
| 450 |
+
"id": "824760af-a568-47f8-b076-b30e8606b9ac",
|
| 451 |
+
"metadata": {},
|
| 452 |
+
"source": [
|
| 453 |
+
"## Tag Groups"
|
| 454 |
+
]
|
| 455 |
+
},
|
| 456 |
+
{
|
| 457 |
+
"cell_type": "code",
|
| 458 |
+
"execution_count": 9,
|
| 459 |
+
"id": "557cdd69-f6b1-4c33-9f67-a13b00c0146b",
|
| 460 |
+
"metadata": {
|
| 461 |
+
"execution": {
|
| 462 |
+
"iopub.execute_input": "2025-03-17T12:48:43.777297Z",
|
| 463 |
+
"iopub.status.busy": "2025-03-17T12:48:43.776934Z",
|
| 464 |
+
"iopub.status.idle": "2025-03-17T12:48:43.787862Z",
|
| 465 |
+
"shell.execute_reply": "2025-03-17T12:48:43.787288Z",
|
| 466 |
+
"shell.execute_reply.started": "2025-03-17T12:48:43.777279Z"
|
| 467 |
+
}
|
| 468 |
+
},
|
| 469 |
+
"outputs": [],
|
| 470 |
+
"source": [
|
| 471 |
+
"tag_groups = {\n",
|
| 472 |
+
" # **Subjects (Humans, Anthro, etc.)**\n",
|
| 473 |
+
" \"subject\": \"subject\",\n",
|
| 474 |
+
" \"subject.female\": \"female\",\n",
|
| 475 |
+
" \"subject.female_general\": \"female_general\", # Added\n",
|
| 476 |
+
" \"subject.female.1girl\": \"1girl\",\n",
|
| 477 |
+
" \"subject.female.2girls\": \"2girls\",\n",
|
| 478 |
+
"\n",
|
| 479 |
+
"\n",
|
| 480 |
+
" # **Male Subjects**\n",
|
| 481 |
+
" \"subject.male\": \"male\",\n",
|
| 482 |
+
" \"subject.male.1boy\": \"1boy\",\n",
|
| 483 |
+
" \"subject.male.2boys\": \"2boys\",\n",
|
| 484 |
+
" \"subject.male.3boys\": \"3boys\",\n",
|
| 485 |
+
" \"subject.male.4boys\": \"4boys\",\n",
|
| 486 |
+
" \"subject.male.5boys\": \"5boys\",\n",
|
| 487 |
+
" \"subject.male.6+boys\": \"6+boys\",\n",
|
| 488 |
+
" \"subject.male.multiple_boys\": \"multiple_boys\",\n",
|
| 489 |
+
" \"subject.male.man\": \"man\",\n",
|
| 490 |
+
" \"subject.male.boy\": \"boy\",\n",
|
| 491 |
+
"\n",
|
| 492 |
+
" # **Koma (Manga Panel Counts)**\n",
|
| 493 |
+
" \"subject.koma\": \"koma\",\n",
|
| 494 |
+
" \"subject.koma.1koma\": \"1koma\",\n",
|
| 495 |
+
" \"subject.koma.2koma\": \"2koma\",\n",
|
| 496 |
+
"\n",
|
| 497 |
+
" # **Anthropomorphic Characters**\n",
|
| 498 |
+
" \"subject.anthro\": \"anthro\",\n",
|
| 499 |
+
" \"subject.anthro.cat_girl\": \"cat_girl\",\n",
|
| 500 |
+
" \"subject.anthro.fox_girl\": \"fox_girl\",\n",
|
| 501 |
+
" \"subject.anthro.dog_girl\": \"dog_girl\",\n",
|
| 502 |
+
" \"subject.anthro.cat_boy\": \"cat_boy\",\n",
|
| 503 |
+
" \"subject.anthro.furry\": \"furry\",\n",
|
| 504 |
+
"\n",
|
| 505 |
+
" # **Visual Characteristics**\n",
|
| 506 |
+
" \"visual_characteristics\": \"visual_characteristics\",\n",
|
| 507 |
+
" \"visual_characteristics.image_composition_and_style\": \"image_composition_and_style\",\n",
|
| 508 |
+
" \"visual_characteristics.image_composition_and_style.artistic_license\": \"artistic_license\",\n",
|
| 509 |
+
" \"visual_characteristics.image_composition_and_style.image_composition\": \"image_composition\",\n",
|
| 510 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.backgrounds\": \"backgrounds\",\n",
|
| 511 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.censorship\": \"censorship\",\n",
|
| 512 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.colors\": \"colors\",\n",
|
| 513 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.focus_tags\": \"focus_tags\",\n",
|
| 514 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.lighting\": \"lighting\",\n",
|
| 515 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.prints\": \"prints\",\n",
|
| 516 |
+
" \"visual_characteristics.image_composition_and_style.image_composition.style_parodies\": \"style_parodies\",\n",
|
| 517 |
+
" \"visual_characteristics.image_composition_and_style.patterns\": \"patterns\",\n",
|
| 518 |
+
" \"visual_characteristics.image_composition_and_style.symbols\": \"symbols\",\n",
|
| 519 |
+
" \"visual_characteristics.image_composition_and_style.text\": \"text\",\n",
|
| 520 |
+
" \"visual_characteristics.image_composition_and_style.year_tags\": \"year_tags\",\n",
|
| 521 |
+
"\n",
|
| 522 |
+
"\n",
|
| 523 |
+
" # Body\n",
|
| 524 |
+
" \"body\": \"body\",\n",
|
| 525 |
+
" \"body.body_parts\": \"body_parts\",\n",
|
| 526 |
+
" \"body.body_parts.ass\": \"ass\",\n",
|
| 527 |
+
" \"body.body_parts.breasts_tags\": \"breasts_tags\",\n",
|
| 528 |
+
" \"body.body_parts.face_tags\": \"face_tags\",\n",
|
| 529 |
+
" \"body.body_parts.face_tags.eyes_tags\": \"eyes_tags\",\n",
|
| 530 |
+
" \"body.body_parts.ears_tags\": \"ears_tags\",\n",
|
| 531 |
+
" \"body.body_parts.hair\": \"hair\",\n",
|
| 532 |
+
" \"body.body_parts.hair.hair_color\": \"hair_color\",\n",
|
| 533 |
+
" \"body.body_parts.hair.hair_styles\": \"hair_styles\",\n",
|
| 534 |
+
" \"body.body_parts.hands\": \"hands\",\n",
|
| 535 |
+
" \"body.body_parts.hands.gestures\": \"gestures\",\n",
|
| 536 |
+
" \"body.body_parts.neck_and_neckwear\": \"neck_and_neckwear\",\n",
|
| 537 |
+
" \"body.body_parts.posture\": \"posture\",\n",
|
| 538 |
+
" \"body.body_parts.pussy\": \"pussy\",\n",
|
| 539 |
+
" \"body.body_parts.penis\": \"penis\",\n",
|
| 540 |
+
" \"body.body_parts.shoulders\": \"shoulders\",\n",
|
| 541 |
+
" \"body.body_parts.skin_color\": \"skin_color\",\n",
|
| 542 |
+
" \"body.body_parts.tail\": \"tail\",\n",
|
| 543 |
+
" \"body.body_parts.wings\": \"wings\",\n",
|
| 544 |
+
" \"body.injury\": \"injury\",\n",
|
| 545 |
+
"\n",
|
| 546 |
+
" # Attire & Accessories\n",
|
| 547 |
+
" \"attire_and_body_accessories\": \"attire_and_body_accessories\",\n",
|
| 548 |
+
" \"attire_and_body_accessories.attire\": \"attire\",\n",
|
| 549 |
+
" \"attire_and_body_accessories.attire.dress\": \"dress\",\n",
|
| 550 |
+
" \"attire_and_body_accessories.attire.handwear\": \"handwear\",\n",
|
| 551 |
+
" \"attire_and_body_accessories.attire.headwear\": \"headwear\",\n",
|
| 552 |
+
" \"attire_and_body_accessories.attire.legwear\": \"legwear\",\n",
|
| 553 |
+
" \"attire_and_body_accessories.attire.mask\": \"mask\",\n",
|
| 554 |
+
" \"attire_and_body_accessories.attire.neck_and_neckwear\": \"neck_and_neckwear\",\n",
|
| 555 |
+
" \"attire_and_body_accessories.attire.sexual_attire\": \"sexual_attire\",\n",
|
| 556 |
+
" \"attire_and_body_accessories.attire.sexual_attire.bra\": \"bra\",\n",
|
| 557 |
+
" \"attire_and_body_accessories.attire.sexual_attire.panties\": \"panties\",\n",
|
| 558 |
+
" \"attire_and_body_accessories.attire.sleeves\": \"sleeves\",\n",
|
| 559 |
+
" \"attire_and_body_accessories.attire.swimsuit\": \"swimsuit\",\n",
|
| 560 |
+
" \"attire_and_body_accessories.embellishment\": \"embellishment\",\n",
|
| 561 |
+
" \"attire_and_body_accessories.eyewear\": \"eyewear\",\n",
|
| 562 |
+
" \"attire_and_body_accessories.fashion_style\": \"fashion_style\",\n",
|
| 563 |
+
" \"attire_and_body_accessories.nudity\": \"nudity\",\n",
|
| 564 |
+
"\n",
|
| 565 |
+
" # Sex\n",
|
| 566 |
+
" \"sex\": \"sex\",\n",
|
| 567 |
+
" \"sex.sex_acts\": \"sex_acts\",\n",
|
| 568 |
+
" \"sex.sex_acts.simulated_sex_acts\": \"simulated_sex_acts\",\n",
|
| 569 |
+
" \"sex.sexual_positions\": \"sexual_positions\",\n",
|
| 570 |
+
"\n",
|
| 571 |
+
" # Objects\n",
|
| 572 |
+
" \"objects\": \"objects\",\n",
|
| 573 |
+
" \"objects.computer\": \"computer\",\n",
|
| 574 |
+
" \"objects.airplanes\": \"airplanes\",\n",
|
| 575 |
+
" \"objects.armor\": \"armor\",\n",
|
| 576 |
+
" \"objects.ground_vehicles\": \"ground_vehicles\",\n",
|
| 577 |
+
" \"objects.helicopters\": \"helicopters\",\n",
|
| 578 |
+
" \"objects.pokemon_objects\": \"pokemon_objects\",\n",
|
| 579 |
+
" \"objects.ships\": \"ships\",\n",
|
| 580 |
+
" \"objects.weapons\": \"weapons\",\n",
|
| 581 |
+
" \"objects.audio_tags\": \"audio_tags\",\n",
|
| 582 |
+
" \"objects.cards\": \"cards\",\n",
|
| 583 |
+
" \"objects.cards.playing_card_faces\": \"playing_card_faces\",\n",
|
| 584 |
+
" \"objects.eyewear\": \"eyewear\",\n",
|
| 585 |
+
" \"objects.piercings\": \"piercings\",\n",
|
| 586 |
+
" \"objects.sex_objects\": \"sex_objects\",\n",
|
| 587 |
+
"\n",
|
| 588 |
+
" # Creatures\n",
|
| 589 |
+
" \"creatures\": \"creatures\",\n",
|
| 590 |
+
" \"creatures.animals\": \"animals\",\n",
|
| 591 |
+
" \"creatures.animals.birds\": \"birds\",\n",
|
| 592 |
+
" \"creatures.animals.cats\": \"cats\",\n",
|
| 593 |
+
" \"creatures.animals.dogs\": \"dogs\",\n",
|
| 594 |
+
" \"creatures.legendary_creatures\": \"legendary_creatures\",\n",
|
| 595 |
+
"\n",
|
| 596 |
+
" # Plants\n",
|
| 597 |
+
" \"plants\": \"plant\",\n",
|
| 598 |
+
" \"plant.plant\": \"plant\",\n",
|
| 599 |
+
" \"plant.tree\": \"tree\",\n",
|
| 600 |
+
" \"plant.flowers\": \"flowers\",\n",
|
| 601 |
+
"\n",
|
| 602 |
+
" # Games\n",
|
| 603 |
+
" \"games\": \"games\",\n",
|
| 604 |
+
" \"games.game_activities\": \"game_activities\",\n",
|
| 605 |
+
" \"games.board_games\": \"board_games\",\n",
|
| 606 |
+
" \"games.sports\": \"sports\",\n",
|
| 607 |
+
" \"games.video_game\": \"video_game\",\n",
|
| 608 |
+
" \"games.fighting_games\": \"fighting_games\",\n",
|
| 609 |
+
"\n",
|
| 610 |
+
" # Real World\n",
|
| 611 |
+
" \"real_world\": \"real_world\",\n",
|
| 612 |
+
" \"real_world.companies_and_brand_names\": \"companies_and_brand_names\",\n",
|
| 613 |
+
" \"real_world.holidays_and_celebrations\": \"holidays_and_celebrations\",\n",
|
| 614 |
+
" \"real_world.jobs\": \"jobs\",\n",
|
| 615 |
+
" \"real_world.locations\": \"locations\",\n",
|
| 616 |
+
" \"real_world.people\": \"people\",\n",
|
| 617 |
+
" \"real_world.real_world_locations\": \"real_world_locations\",\n",
|
| 618 |
+
"\n",
|
| 619 |
+
" # More Categories\n",
|
| 620 |
+
" \"more\": \"more\",\n",
|
| 621 |
+
" \"more.dances\": \"dances\",\n",
|
| 622 |
+
" \"more.family_relationships\": \"family_relationships\",\n",
|
| 623 |
+
" \"more.food_tags\": \"food_tags\",\n",
|
| 624 |
+
" \"more.fire\": \"fire\",\n",
|
| 625 |
+
" \"more.groups\": \"groups\",\n",
|
| 626 |
+
" \"more.phrases\": \"phrases\",\n",
|
| 627 |
+
" \"more.scan\": \"scan\",\n",
|
| 628 |
+
" \"more.subjective\": \"subjective\",\n",
|
| 629 |
+
" \"more.technology\": \"technology\",\n",
|
| 630 |
+
" \"more.verbs_and_gerunds\": \"verbs_and_gerunds\",\n",
|
| 631 |
+
" \"more.water\": \"water\",\n",
|
| 632 |
+
"\n",
|
| 633 |
+
" # Genres of Video Games\n",
|
| 634 |
+
" \"copyrights_artists_projects_and_media\": \"copyrights_artists_projects_and_media\",\n",
|
| 635 |
+
" \"copyrights_artists_projects_and_media.genres_of_video_games\": \"genres_of_video_games\",\n",
|
| 636 |
+
" \"copyrights_artists_projects_and_media.genres_of_video_games.fighting_games\": \"fighting_games\",\n",
|
| 637 |
+
" \"copyrights_artists_projects_and_media.genres_of_video_games.platform_games\": \"platform_games\",\n",
|
| 638 |
+
" \"copyrights_artists_projects_and_media.genres_of_video_games.role-playing_games\": \"role-playing_games\",\n",
|
| 639 |
+
" \"copyrights_artists_projects_and_media.genres_of_video_games.shooter_games\": \"shooter_games\",\n",
|
| 640 |
+
" \"copyrights_artists_projects_and_media.genres_of_video_games.visual_novel_games\": \"visual_novel_games\",\n",
|
| 641 |
+
"\n",
|
| 642 |
+
" \"characters\": \"characters\",\n",
|
| 643 |
+
" \"characters.ace_attorney\": \"ace_attorney_characters\",\n",
|
| 644 |
+
" \"characters.arknights\": \"arknights_characters\",\n",
|
| 645 |
+
" \"characters.atelier\": \"atelier_characters\",\n",
|
| 646 |
+
" \"characters.azur_lane\": \"azur_lane_characters\",\n",
|
| 647 |
+
" \"characters.bleach\": \"bleach_characters\",\n",
|
| 648 |
+
" \"characters.bokujou_monogatari\": \"bokujou_monogatari_characters\",\n",
|
| 649 |
+
" \"characters.brave_girl_ravens\": \"brave_girl_ravens_characters\",\n",
|
| 650 |
+
" \"characters.cardcaptor_sakura\": \"cardcaptor_sakura_characters\",\n",
|
| 651 |
+
" \"characters.danganronpa\": \"danganronpa_characters\",\n",
|
| 652 |
+
" \"characters.digimon\": \"digimon\",\n",
|
| 653 |
+
" \"characters.digimon.digimon_characters\": \"digimon_characters\",\n",
|
| 654 |
+
" \"characters.dragon_ball\": \"dragon_ball_characters\",\n",
|
| 655 |
+
" \"characters.dragon_quest\": \"dragon_quest_characters\",\n",
|
| 656 |
+
" \"characters.fate_series\": \"fate_series_characters\",\n",
|
| 657 |
+
" \"characters.final_fantasy\": \"final_fantasy_characters\",\n",
|
| 658 |
+
" \"characters.fire_emblem\": \"fire_emblem_characters\",\n",
|
| 659 |
+
" \"characters.flower_knight_girl\": \"flower_knight_girl_characters\",\n",
|
| 660 |
+
" \"characters.gensou_suikoden\": \"gensou_suikoden_characters\",\n",
|
| 661 |
+
" \"characters.girls_frontline\": \"girls_frontline_characters\",\n",
|
| 662 |
+
" \"characters.girls_und_panzer\": \"girls_und_panzer_characters\",\n",
|
| 663 |
+
" \"characters.gundam_mechas\": \"gundam_mechas\",\n",
|
| 664 |
+
" \"characters.hunter_x_hunter\": \"hunter_x_hunter_characters\",\n",
|
| 665 |
+
" \"characters.jojo_no_kimyou_na_bouken\": \"jojo_no_kimyou_na_bouken_characters\",\n",
|
| 666 |
+
" \"characters.kamen_rider\": \"kamen_rider_characters\",\n",
|
| 667 |
+
" \"characters.kantai_collection\": \"kantai_collection_characters\",\n",
|
| 668 |
+
" \"characters.kingdom_hearts\": \"kingdom_hearts_characters\",\n",
|
| 669 |
+
" \"characters.mahou_sensei_negima\": \"mahou_sensei_negima_characters\",\n",
|
| 670 |
+
" \"characters.meitantei_conan\": \"meitantei_conan_characters\",\n",
|
| 671 |
+
" \"characters.minecraft\": \"minecraft_characters\",\n",
|
| 672 |
+
" \"characters.naruto\": \"naruto_characters\",\n",
|
| 673 |
+
" \"characters.nippon_ichi\": \"nippon_ichi_characters\",\n",
|
| 674 |
+
" \"characters.one_piece\": \"one_piece_characters\",\n",
|
| 675 |
+
" \"characters.oshiro_project\": \"oshiro_project_characters\",\n",
|
| 676 |
+
" \"characters.pokemon\": \"pokemon\",\n",
|
| 677 |
+
" \"characters.pokemon.elite_four_members\": \"elite_four_members\",\n",
|
| 678 |
+
" \"characters.pokemon.gym_leaders\": \"gym_leaders\",\n",
|
| 679 |
+
" \"characters.pokemon.families_of_pokemon_main_characters\": \"families_of_pokemon_main_characters\",\n",
|
| 680 |
+
" \"characters.pokemon.pokemon_ranger_characters\": \"pokemon_ranger_characters\",\n",
|
| 681 |
+
" \"characters.pokemon.pokemon_trainer_classes\": \"pokemon_trainer_classes\",\n",
|
| 682 |
+
" \"characters.pretty_cure\": \"pretty_cure_characters\",\n",
|
| 683 |
+
" \"characters.ragnarok_online\": \"ragnarok_online_characters\",\n",
|
| 684 |
+
" \"characters.rosenkreuzstilette\": \"rosenkreuzstilette_characters\",\n",
|
| 685 |
+
" \"characters.sailor_moon\": \"sailor_moon_characters\",\n",
|
| 686 |
+
" \"characters.street_fighter\": \"street_fighter_characters\",\n",
|
| 687 |
+
" \"characters.super_smash_bros\": \"super_smash_bros_characters\",\n",
|
| 688 |
+
" \"characters.world_witches_series\": \"world_witches_series_characters\",\n",
|
| 689 |
+
" \"characters.tales_of\": \"tales_of_characters\",\n",
|
| 690 |
+
" \"characters.toaru_majutsu_no_index\": \"toaru_majutsu_no_index_characters\",\n",
|
| 691 |
+
" \"characters.touhou\": \"touhou_characters\",\n",
|
| 692 |
+
" \"characters.touken_ranbu\": \"touken_ranbu_characters\",\n",
|
| 693 |
+
" \"characters.ultra_series\": \"ultra_series_characters\",\n",
|
| 694 |
+
" \"characters.umamusume\": \"umamusume_characters\",\n",
|
| 695 |
+
" \"characters.vocaloid\": \"vocaloid_characters\",\n",
|
| 696 |
+
" \"characters.yu_gi_oh\": \"yu_gi_oh_characters\",\n",
|
| 697 |
+
" \"characters.genderswap\": \"genderswap_characters\",\n",
|
| 698 |
+
" \"characters.official_mascots\": \"official_mascots\",\n",
|
| 699 |
+
" \"characters.real_life_racehorses\": \"real_life_racehorses\",\n",
|
| 700 |
+
"\n",
|
| 701 |
+
"\n",
|
| 702 |
+
" # metatags\n",
|
| 703 |
+
" \"metatags\": \"metatags\",\n",
|
| 704 |
+
" \"drawing_software\": \"drawing_software\",\n",
|
| 705 |
+
" \n",
|
| 706 |
+
"}\n"
|
| 707 |
+
]
|
| 708 |
+
},
|
| 709 |
+
{
|
| 710 |
+
"cell_type": "code",
|
| 711 |
+
"execution_count": 10,
|
| 712 |
+
"id": "c4759995-396a-4ec1-9f35-e75b3875e13c",
|
| 713 |
+
"metadata": {
|
| 714 |
+
"execution": {
|
| 715 |
+
"iopub.execute_input": "2025-03-17T12:48:44.138013Z",
|
| 716 |
+
"iopub.status.busy": "2025-03-17T12:48:44.137852Z",
|
| 717 |
+
"iopub.status.idle": "2025-03-17T12:48:44.491853Z",
|
| 718 |
+
"shell.execute_reply": "2025-03-17T12:48:44.491271Z",
|
| 719 |
+
"shell.execute_reply.started": "2025-03-17T12:48:44.137999Z"
|
| 720 |
+
}
|
| 721 |
+
},
|
| 722 |
+
"outputs": [],
|
| 723 |
+
"source": [
|
| 724 |
+
"list_based_categories = [\n",
|
| 725 |
+
" # Image composition style\n",
|
| 726 |
+
" \"style_parodies\",\n",
|
| 727 |
+
" # Objects\n",
|
| 728 |
+
" \"computer\",\n",
|
| 729 |
+
" \"airplanes\",\n",
|
| 730 |
+
" \"armor\",\n",
|
| 731 |
+
" \"ground_vehicles\",\n",
|
| 732 |
+
" \"helicopters\",\n",
|
| 733 |
+
" \"pokemon_objects\",\n",
|
| 734 |
+
" \"ships\",\n",
|
| 735 |
+
" \"weapons\",\n",
|
| 736 |
+
" \"playing_card_faces\",\n",
|
| 737 |
+
" \n",
|
| 738 |
+
" # Creatures\n",
|
| 739 |
+
" \"animals\",\n",
|
| 740 |
+
" \"birds\",\n",
|
| 741 |
+
" \"cats\",\n",
|
| 742 |
+
" \"dogs\",\n",
|
| 743 |
+
" \"legendary_creatures\",\n",
|
| 744 |
+
"\n",
|
| 745 |
+
" # Plants\n",
|
| 746 |
+
" \"plant\",\n",
|
| 747 |
+
" \"tree\",\n",
|
| 748 |
+
" \"flowers\",\n",
|
| 749 |
+
"\n",
|
| 750 |
+
" # Games\n",
|
| 751 |
+
" \"game_activities\",\n",
|
| 752 |
+
" \"board_games\",\n",
|
| 753 |
+
" \"sports\",\n",
|
| 754 |
+
" \"video_game\",\n",
|
| 755 |
+
" \"fighting_games\",\n",
|
| 756 |
+
" \"platform_games\",\n",
|
| 757 |
+
" #\"role-playing_games\",\n",
|
| 758 |
+
" \"shooter_games\",\n",
|
| 759 |
+
" \"visual_novel_games\",\n",
|
| 760 |
+
"\n",
|
| 761 |
+
" # Real World\n",
|
| 762 |
+
" \"companies_and_brand_names\",\n",
|
| 763 |
+
" \"holidays_and_celebrations\",\n",
|
| 764 |
+
" \"jobs\",\n",
|
| 765 |
+
" \"locations\",\n",
|
| 766 |
+
" \"people\",\n",
|
| 767 |
+
" \"real_world_locations\",\n",
|
| 768 |
+
"\n",
|
| 769 |
+
" # More Categories\n",
|
| 770 |
+
" \"dances\",\n",
|
| 771 |
+
" \"family_relationships\",\n",
|
| 772 |
+
" \"food_tags\",\n",
|
| 773 |
+
" \"fire\",\n",
|
| 774 |
+
" \"groups\",\n",
|
| 775 |
+
" \"phrases\",\n",
|
| 776 |
+
" \"scan\",\n",
|
| 777 |
+
" \"subjective\",\n",
|
| 778 |
+
" \"technology\",\n",
|
| 779 |
+
" \"verbs_and_gerunds\",\n",
|
| 780 |
+
" \"water\",\n",
|
| 781 |
+
" \"airplanes\",\n",
|
| 782 |
+
"\n",
|
| 783 |
+
" # Artists\n",
|
| 784 |
+
" \"named_drawfags\",\n",
|
| 785 |
+
" \"pixiv_projects\",\n",
|
| 786 |
+
"\n",
|
| 787 |
+
" # Characters\n",
|
| 788 |
+
" \"ace_attorney_characters\",\n",
|
| 789 |
+
" \"arknights_characters\",\n",
|
| 790 |
+
" \"atelier_characters\",\n",
|
| 791 |
+
" \"azur_lane_characters\",\n",
|
| 792 |
+
" \"bleach_characters\",\n",
|
| 793 |
+
" \"bokujou_monogatari_characters\",\n",
|
| 794 |
+
" \"brave_girl_ravens_characters\",\n",
|
| 795 |
+
" \"cardcaptor_sakura_characters\",\n",
|
| 796 |
+
" \"danganronpa_characters\",\n",
|
| 797 |
+
" \"digimon\",\n",
|
| 798 |
+
" \"digimon_characters\",\n",
|
| 799 |
+
" \"dragon_ball_characters\",\n",
|
| 800 |
+
" \"dragon_quest_characters\",\n",
|
| 801 |
+
" \"fate_series_characters\",\n",
|
| 802 |
+
" \"final_fantasy_characters\",\n",
|
| 803 |
+
" \"fire_emblem_characters\",\n",
|
| 804 |
+
" \"flower_knight_girl_characters\",\n",
|
| 805 |
+
" \"gensou_suikoden_characters\",\n",
|
| 806 |
+
" \"girls_frontline_characters\",\n",
|
| 807 |
+
" \"girls_und_panzer_characters\",\n",
|
| 808 |
+
" \"gundam_mechas\",\n",
|
| 809 |
+
" \"hunter_x_hunter_characters\",\n",
|
| 810 |
+
" \"jojo_no_kimyou_na_bouken_characters\",\n",
|
| 811 |
+
" \"kamen_rider_characters\",\n",
|
| 812 |
+
" \"kantai_collection_characters\",\n",
|
| 813 |
+
" \"kingdom_hearts_characters\",\n",
|
| 814 |
+
" \"mahou_sensei_negima_characters\",\n",
|
| 815 |
+
" \"meitantei_conan_characters\",\n",
|
| 816 |
+
" \"minecraft_characters\",\n",
|
| 817 |
+
" \"naruto_characters\",\n",
|
| 818 |
+
" \"nippon_ichi_characters\",\n",
|
| 819 |
+
" \"one_piece_characters\",\n",
|
| 820 |
+
" \"oshiro_project_characters\",\n",
|
| 821 |
+
" \"pokemon_characters\",\n",
|
| 822 |
+
" \"elite_four_members\",\n",
|
| 823 |
+
" \"gym_leaders\",\n",
|
| 824 |
+
" \"families_of_pokemon_main_characters\",\n",
|
| 825 |
+
" \"pokemon_ranger_characters\",\n",
|
| 826 |
+
" \"pokemon_trainer_classes\",\n",
|
| 827 |
+
" \"pokemon\",\n",
|
| 828 |
+
" \"pretty_cure_characters\",\n",
|
| 829 |
+
" \"ragnarok_online_characters\",\n",
|
| 830 |
+
" \"rosenkreuzstilette_characters\",\n",
|
| 831 |
+
" \"sailor_moon_characters\",\n",
|
| 832 |
+
" \"street_fighter_characters\",\n",
|
| 833 |
+
" \"super_smash_bros_characters\",\n",
|
| 834 |
+
" \"world_witches_series_characters\",\n",
|
| 835 |
+
" \"tales_of_characters\",\n",
|
| 836 |
+
" \"toaru_majutsu_no_index_characters\",\n",
|
| 837 |
+
" \"touhou_characters\",\n",
|
| 838 |
+
" \"touken_ranbu_characters\",\n",
|
| 839 |
+
" \"ultra_series_characters\",\n",
|
| 840 |
+
" \"umamusume_characters\",\n",
|
| 841 |
+
" \"vocaloid_characters\",\n",
|
| 842 |
+
" \"yu_gi_oh_characters\",\n",
|
| 843 |
+
" \"genderswap_characters\",\n",
|
| 844 |
+
" \"official_mascots\",\n",
|
| 845 |
+
" \"real_life_racehorses\",\n",
|
| 846 |
+
"\n",
|
| 847 |
+
"\n",
|
| 848 |
+
" \n",
|
| 849 |
+
" # Other Lists\n",
|
| 850 |
+
" \"disambiguation_pages\",\n",
|
| 851 |
+
" \"magazine_publications\",\n",
|
| 852 |
+
" \"special_moves\",\n",
|
| 853 |
+
" \"uniforms\",\n",
|
| 854 |
+
" \"pokemon_media\",\n",
|
| 855 |
+
" \"tagged_songs\",\n",
|
| 856 |
+
" \"vocaloid_derivatives\",\n",
|
| 857 |
+
" \"vocaloid_songs\",\n",
|
| 858 |
+
" \"vocal_synthesizers\",\n",
|
| 859 |
+
" \"vocal_synth_derivatives\",\n",
|
| 860 |
+
" \"vocal_synth_songs\",\n",
|
| 861 |
+
" \"deemo_songs\",\n",
|
| 862 |
+
" #\"role_playing_games\",\n",
|
| 863 |
+
"\n",
|
| 864 |
+
" # Metatags\n",
|
| 865 |
+
" #\"metatags\",\n",
|
| 866 |
+
" #\"drawing_software\",\n",
|
| 867 |
+
"\n",
|
| 868 |
+
" # Pool Groups & Meta-Wikis\n",
|
| 869 |
+
" \"meta_wikis\"\n",
|
| 870 |
+
"]\n"
|
| 871 |
+
]
|
| 872 |
+
},
|
| 873 |
+
{
|
| 874 |
+
"cell_type": "code",
|
| 875 |
+
"execution_count": 11,
|
| 876 |
+
"id": "8f8b6b29-e7bd-4814-8705-2b7a68f2d660",
|
| 877 |
+
"metadata": {
|
| 878 |
+
"execution": {
|
| 879 |
+
"iopub.execute_input": "2025-03-17T12:48:52.253272Z",
|
| 880 |
+
"iopub.status.busy": "2025-03-17T12:48:52.252077Z",
|
| 881 |
+
"iopub.status.idle": "2025-03-17T12:48:52.256578Z",
|
| 882 |
+
"shell.execute_reply": "2025-03-17T12:48:52.256016Z",
|
| 883 |
+
"shell.execute_reply.started": "2025-03-17T12:48:52.253248Z"
|
| 884 |
+
}
|
| 885 |
+
},
|
| 886 |
+
"outputs": [],
|
| 887 |
+
"source": [
|
| 888 |
+
"special_wiki_pages = [\n",
|
| 889 |
+
" \"plant\", \"tree\", \"computer\", \"on_object\", \"injury\", \"swimsuit\", \"on\" , \"mask\" # Add more here if needed\n",
|
| 890 |
+
"]"
|
| 891 |
+
]
|
| 892 |
+
},
|
| 893 |
+
{
|
| 894 |
+
"cell_type": "code",
|
| 895 |
+
"execution_count": 12,
|
| 896 |
+
"id": "79234bc5-b287-4306-85cd-a27ddba769ea",
|
| 897 |
+
"metadata": {
|
| 898 |
+
"execution": {
|
| 899 |
+
"iopub.execute_input": "2025-03-17T12:48:55.161786Z",
|
| 900 |
+
"iopub.status.busy": "2025-03-17T12:48:55.160800Z",
|
| 901 |
+
"iopub.status.idle": "2025-03-17T12:48:55.165137Z",
|
| 902 |
+
"shell.execute_reply": "2025-03-17T12:48:55.164568Z",
|
| 903 |
+
"shell.execute_reply.started": "2025-03-17T12:48:55.161760Z"
|
| 904 |
+
}
|
| 905 |
+
},
|
| 906 |
+
"outputs": [],
|
| 907 |
+
"source": [
|
| 908 |
+
"import base64\n",
|
| 909 |
+
"HEADERS = {\n",
|
| 910 |
+
" \"Authorization\": f\"Basic {base64.b64encode(f'{username}:{api_key}'.encode()).decode()}\"\n",
|
| 911 |
+
"}"
|
| 912 |
+
]
|
| 913 |
+
},
|
| 914 |
+
{
|
| 915 |
+
"cell_type": "code",
|
| 916 |
+
"execution_count": 13,
|
| 917 |
+
"id": "64d47678-78a4-4d55-a3a0-1c1b251b03b6",
|
| 918 |
+
"metadata": {
|
| 919 |
+
"execution": {
|
| 920 |
+
"iopub.execute_input": "2025-03-17T12:40:12.358876Z",
|
| 921 |
+
"iopub.status.busy": "2025-03-17T12:40:12.358573Z",
|
| 922 |
+
"iopub.status.idle": "2025-03-17T12:40:43.547891Z",
|
| 923 |
+
"shell.execute_reply": "2025-03-17T12:40:43.547451Z",
|
| 924 |
+
"shell.execute_reply.started": "2025-03-17T12:40:12.358851Z"
|
| 925 |
+
}
|
| 926 |
+
},
|
| 927 |
+
"outputs": [
|
| 928 |
+
{
|
| 929 |
+
"name": "stdout",
|
| 930 |
+
"output_type": "stream",
|
| 931 |
+
"text": [
|
| 932 |
+
"🚀 Starting Danbooru Tag Hierarchy API Fetcher...\n",
|
| 933 |
+
"❌ No data found for attire_and_body_accessories in any format\n",
|
| 934 |
+
"✅ Added 91 tags directly under attire_and_body_accessories.attire.dress\n",
|
| 935 |
+
"✅ Added 100 tags directly under attire_and_body_accessories.attire.handwear\n",
|
| 936 |
+
"✅ Added 251 tags directly under attire_and_body_accessories.attire.headwear\n",
|
| 937 |
+
"✅ Added 69 tags directly under attire_and_body_accessories.attire.legwear\n",
|
| 938 |
+
"✅ Added 64 tags directly under attire_and_body_accessories.attire.mask\n",
|
| 939 |
+
"✅ Added 264 tags directly under attire_and_body_accessories.attire.neck_and_neckwear\n",
|
| 940 |
+
"✅ Added 54 tags directly under attire_and_body_accessories.attire.sexual_attire.bra\n",
|
| 941 |
+
"✅ Added 83 tags directly under attire_and_body_accessories.attire.sexual_attire.panties\n",
|
| 942 |
+
"✅ Added 87 tags directly under attire_and_body_accessories.attire.sleeves\n",
|
| 943 |
+
"✅ Added 83 tags directly under attire_and_body_accessories.attire.swimsuit\n",
|
| 944 |
+
"✅ Added 4 tags directly under attire_and_body_accessories.embellishment\n",
|
| 945 |
+
"✅ Added 94 tags directly under attire_and_body_accessories.eyewear\n",
|
| 946 |
+
"✅ Added 62 tags directly under attire_and_body_accessories.fashion_style\n",
|
| 947 |
+
"✅ Added 189 tags directly under attire_and_body_accessories.nudity\n",
|
| 948 |
+
"❌ No data found for body in any format\n",
|
| 949 |
+
"✅ Added 40 tags directly under body.body_parts.ass\n",
|
| 950 |
+
"✅ Added 213 tags directly under body.body_parts.breasts_tags\n",
|
| 951 |
+
"✅ Added 54 tags directly under body.body_parts.ears_tags\n",
|
| 952 |
+
"✅ Added 200 tags directly under body.body_parts.face_tags.eyes_tags\n",
|
| 953 |
+
"✅ Added 31 tags directly under body.body_parts.hair.hair_color\n",
|
| 954 |
+
"✅ Added 157 tags directly under body.body_parts.hair.hair_styles\n",
|
| 955 |
+
"✅ Added 117 tags directly under body.body_parts.hands.gestures\n",
|
| 956 |
+
"✅ Added 264 tags directly under body.body_parts.neck_and_neckwear\n",
|
| 957 |
+
"✅ Added 74 tags directly under body.body_parts.penis\n",
|
| 958 |
+
"✅ Added 230 tags directly under body.body_parts.posture\n",
|
| 959 |
+
"✅ Added 50 tags directly under body.body_parts.pussy\n",
|
| 960 |
+
"✅ Added 64 tags directly under body.body_parts.shoulders\n",
|
| 961 |
+
"✅ Added 26 tags directly under body.body_parts.skin_color\n",
|
| 962 |
+
"✅ Added 79 tags directly under body.body_parts.tail\n",
|
| 963 |
+
"✅ Added 90 tags directly under body.body_parts.wings\n",
|
| 964 |
+
"✅ Added 55 tags directly under body.injury\n",
|
| 965 |
+
"❌ No data found for characters in any format\n",
|
| 966 |
+
"✅ Added 300 tags directly under characters.ace_attorney\n",
|
| 967 |
+
"✅ Added 579 tags directly under characters.arknights\n",
|
| 968 |
+
"✅ Added 296 tags directly under characters.atelier\n",
|
| 969 |
+
"✅ Added 706 tags directly under characters.azur_lane\n",
|
| 970 |
+
"✅ Added 223 tags directly under characters.bleach\n",
|
| 971 |
+
"✅ Added 98 tags directly under characters.bokujou_monogatari\n",
|
| 972 |
+
"✅ Added 46 tags directly under characters.brave_girl_ravens\n",
|
| 973 |
+
"✅ Added 144 tags directly under characters.cardcaptor_sakura\n",
|
| 974 |
+
"✅ Added 128 tags directly under characters.danganronpa\n",
|
| 975 |
+
"✅ Added 240 tags directly under characters.digimon.digimon_characters\n",
|
| 976 |
+
"✅ Added 613 tags directly under characters.dragon_ball\n",
|
| 977 |
+
"✅ Added 331 tags directly under characters.dragon_quest\n",
|
| 978 |
+
"✅ Added 814 tags directly under characters.fate_series\n",
|
| 979 |
+
"✅ Added 566 tags directly under characters.final_fantasy\n",
|
| 980 |
+
"✅ Added 727 tags directly under characters.fire_emblem\n",
|
| 981 |
+
"✅ Added 441 tags directly under characters.flower_knight_girl\n",
|
| 982 |
+
"✅ Added 221 tags directly under characters.genderswap\n",
|
| 983 |
+
"✅ Added 431 tags directly under characters.gensou_suikoden\n",
|
| 984 |
+
"❌ No data found for girls_frontline_characters in any format\n",
|
| 985 |
+
"✅ Added 0 tags directly under characters.girls_frontline\n",
|
| 986 |
+
"✅ Added 305 tags directly under characters.girls_und_panzer\n",
|
| 987 |
+
"✅ Added 267 tags directly under characters.gundam_mechas\n",
|
| 988 |
+
"✅ Added 313 tags directly under characters.hunter_x_hunter\n",
|
| 989 |
+
"✅ Added 348 tags directly under characters.jojo_no_kimyou_na_bouken\n",
|
| 990 |
+
"✅ Added 308 tags directly under characters.kamen_rider\n",
|
| 991 |
+
"✅ Added 465 tags directly under characters.kantai_collection\n",
|
| 992 |
+
"✅ Added 64 tags directly under characters.kingdom_hearts\n",
|
| 993 |
+
"✅ Added 72 tags directly under characters.mahou_sensei_negima\n",
|
| 994 |
+
"✅ Added 158 tags directly under characters.meitantei_conan\n",
|
| 995 |
+
"✅ Added 153 tags directly under characters.minecraft\n",
|
| 996 |
+
"✅ Added 198 tags directly under characters.naruto\n",
|
| 997 |
+
"✅ Added 336 tags directly under characters.nippon_ichi\n",
|
| 998 |
+
"✅ Added 375 tags directly under characters.official_mascots\n",
|
| 999 |
+
"✅ Added 464 tags directly under characters.one_piece\n",
|
| 1000 |
+
"✅ Added 305 tags directly under characters.oshiro_project\n",
|
| 1001 |
+
"✅ Added 72 tags directly under characters.pokemon.elite_four_members\n",
|
| 1002 |
+
"✅ Added 165 tags directly under characters.pokemon.families_of_pokemon_main_characters\n",
|
| 1003 |
+
"✅ Added 100 tags directly under characters.pokemon.gym_leaders\n",
|
| 1004 |
+
"✅ Added 37 tags directly under characters.pokemon.pokemon_ranger_characters\n",
|
| 1005 |
+
"✅ Added 149 tags directly under characters.pokemon.pokemon_trainer_classes\n",
|
| 1006 |
+
"✅ Added 925 tags directly under characters.pretty_cure\n",
|
| 1007 |
+
"✅ Added 1214 tags directly under characters.ragnarok_online\n",
|
| 1008 |
+
"✅ Added 434 tags directly under characters.real_life_racehorses\n",
|
| 1009 |
+
"✅ Added 23 tags directly under characters.rosenkreuzstilette\n",
|
| 1010 |
+
"✅ Added 320 tags directly under characters.sailor_moon\n",
|
| 1011 |
+
"✅ Added 170 tags directly under characters.street_fighter\n",
|
| 1012 |
+
"❌ No data found for super_smash_bros_characters in any format\n",
|
| 1013 |
+
"✅ Added 0 tags directly under characters.super_smash_bros\n",
|
| 1014 |
+
"❌ No data found for tales_of_characters in any format\n",
|
| 1015 |
+
"✅ Added 0 tags directly under characters.tales_of\n",
|
| 1016 |
+
"✅ Added 189 tags directly under characters.toaru_majutsu_no_index\n",
|
| 1017 |
+
"✅ Added 381 tags directly under characters.touhou\n",
|
| 1018 |
+
"✅ Added 128 tags directly under characters.touken_ranbu\n",
|
| 1019 |
+
"✅ Added 540 tags directly under characters.ultra_series\n",
|
| 1020 |
+
"✅ Added 250 tags directly under characters.umamusume\n",
|
| 1021 |
+
"✅ Added 140 tags directly under characters.vocaloid\n",
|
| 1022 |
+
"✅ Added 184 tags directly under characters.world_witches_series\n",
|
| 1023 |
+
"❌ No data found for yu_gi_oh_characters in any format\n",
|
| 1024 |
+
"✅ Added 0 tags directly under characters.yu_gi_oh\n",
|
| 1025 |
+
"❌ No data found for copyrights_artists_projects_and_media in any format\n",
|
| 1026 |
+
"❌ No data found for genres_of_video_games in any format\n",
|
| 1027 |
+
"✅ Added 175 tags directly under copyrights_artists_projects_and_media.genres_of_video_games.fighting_games\n",
|
| 1028 |
+
"✅ Added 42 tags directly under copyrights_artists_projects_and_media.genres_of_video_games.platform_games\n",
|
| 1029 |
+
"✅ Added 515 tags directly under copyrights_artists_projects_and_media.genres_of_video_games.role-playing_games\n",
|
| 1030 |
+
"✅ Added 114 tags directly under copyrights_artists_projects_and_media.genres_of_video_games.shooter_games\n",
|
| 1031 |
+
"✅ Added 185 tags directly under copyrights_artists_projects_and_media.genres_of_video_games.visual_novel_games\n",
|
| 1032 |
+
"❌ No data found for creatures in any format\n",
|
| 1033 |
+
"✅ Added 232 tags directly under creatures.animals.birds\n",
|
| 1034 |
+
"✅ Added 224 tags directly under creatures.animals.cats\n",
|
| 1035 |
+
"✅ Added 237 tags directly under creatures.animals.dogs\n",
|
| 1036 |
+
"✅ Added 285 tags directly under creatures.legendary_creatures\n",
|
| 1037 |
+
"✅ Added 55 tags directly under drawing_software\n",
|
| 1038 |
+
"❌ No data found for games in any format\n",
|
| 1039 |
+
"✅ Added 21 tags directly under games.board_games\n",
|
| 1040 |
+
"✅ Added 175 tags directly under games.fighting_games\n",
|
| 1041 |
+
"✅ Added 61 tags directly under games.game_activities\n",
|
| 1042 |
+
"✅ Added 420 tags directly under games.sports\n",
|
| 1043 |
+
"✅ Added 144 tags directly under games.video_game\n",
|
| 1044 |
+
"✅ Added 213 tags directly under metatags\n",
|
| 1045 |
+
"❌ No data found for more in any format\n",
|
| 1046 |
+
"✅ Added 110 tags directly under more.dances\n",
|
| 1047 |
+
"✅ Added 29 tags directly under more.family_relationships\n",
|
| 1048 |
+
"✅ Added 68 tags directly under more.fire\n",
|
| 1049 |
+
"✅ Added 1051 tags directly under more.food_tags\n",
|
| 1050 |
+
"✅ Added 31 tags directly under more.groups\n",
|
| 1051 |
+
"✅ Added 76 tags directly under more.phrases\n",
|
| 1052 |
+
"✅ Added 35 tags directly under more.scan\n",
|
| 1053 |
+
"✅ Added 45 tags directly under more.subjective\n",
|
| 1054 |
+
"✅ Added 237 tags directly under more.technology\n",
|
| 1055 |
+
"✅ Added 446 tags directly under more.verbs_and_gerunds\n",
|
| 1056 |
+
"✅ Added 54 tags directly under more.water\n",
|
| 1057 |
+
"❌ No data found for objects in any format\n",
|
| 1058 |
+
"✅ Added 410 tags directly under objects.airplanes\n",
|
| 1059 |
+
"✅ Added 145 tags directly under objects.armor\n",
|
| 1060 |
+
"✅ Added 377 tags directly under objects.audio_tags\n",
|
| 1061 |
+
"✅ Added 66 tags directly under objects.cards.playing_card_faces\n",
|
| 1062 |
+
"✅ Added 80 tags directly under objects.computer\n",
|
| 1063 |
+
"✅ Added 94 tags directly under objects.eyewear\n",
|
| 1064 |
+
"✅ Added 407 tags directly under objects.ground_vehicles\n",
|
| 1065 |
+
"✅ Added 25 tags directly under objects.helicopters\n",
|
| 1066 |
+
"✅ Added 48 tags directly under objects.piercings\n",
|
| 1067 |
+
"✅ Added 94 tags directly under objects.pokemon_objects\n",
|
| 1068 |
+
"✅ Added 105 tags directly under objects.sex_objects\n",
|
| 1069 |
+
"✅ Added 256 tags directly under objects.ships\n",
|
| 1070 |
+
"✅ Added 917 tags directly under objects.weapons\n",
|
| 1071 |
+
"✅ Added 21 tags directly under plant.flowers\n",
|
| 1072 |
+
"✅ Added 59 tags directly under plant.plant\n",
|
| 1073 |
+
"✅ Added 44 tags directly under plant.tree\n",
|
| 1074 |
+
"✅ Added 59 tags directly under plants\n",
|
| 1075 |
+
"❌ No data found for real_world in any format\n",
|
| 1076 |
+
"✅ Added 363 tags directly under real_world.companies_and_brand_names\n",
|
| 1077 |
+
"✅ Added 138 tags directly under real_world.holidays_and_celebrations\n",
|
| 1078 |
+
"✅ Added 75 tags directly under real_world.jobs\n",
|
| 1079 |
+
"✅ Added 263 tags directly under real_world.locations\n",
|
| 1080 |
+
"✅ Added 1526 tags directly under real_world.people\n",
|
| 1081 |
+
"✅ Added 463 tags directly under real_world.real_world_locations\n",
|
| 1082 |
+
"❌ No data found for sex in any format\n",
|
| 1083 |
+
"✅ Added 16 tags directly under sex.sex_acts.simulated_sex_acts\n",
|
| 1084 |
+
"✅ Added 56 tags directly under sex.sexual_positions\n",
|
| 1085 |
+
"❌ No data found for subject in any format\n",
|
| 1086 |
+
"❌ No data found for anthro in any format\n",
|
| 1087 |
+
"❌ No data found for cat_boy in any format\n",
|
| 1088 |
+
"✅ Added 0 tags directly under subject.anthro.cat_boy\n",
|
| 1089 |
+
"❌ No data found for cat_girl in any format\n",
|
| 1090 |
+
"✅ Added 0 tags directly under subject.anthro.cat_girl\n",
|
| 1091 |
+
"❌ No data found for dog_girl in any format\n",
|
| 1092 |
+
"✅ Added 0 tags directly under subject.anthro.dog_girl\n",
|
| 1093 |
+
"❌ No data found for fox_girl in any format\n",
|
| 1094 |
+
"✅ Added 0 tags directly under subject.anthro.fox_girl\n",
|
| 1095 |
+
"❌ No data found for furry in any format\n",
|
| 1096 |
+
"✅ Added 0 tags directly under subject.anthro.furry\n",
|
| 1097 |
+
"❌ No data found for female in any format\n",
|
| 1098 |
+
"❌ No data found for 1girl in any format\n",
|
| 1099 |
+
"✅ Added 0 tags directly under subject.female.1girl\n",
|
| 1100 |
+
"❌ No data found for 2girls in any format\n",
|
| 1101 |
+
"✅ Added 0 tags directly under subject.female.2girls\n",
|
| 1102 |
+
"❌ No data found for female_general in any format\n",
|
| 1103 |
+
"✅ Added 0 tags directly under subject.female_general\n",
|
| 1104 |
+
"❌ No data found for koma in any format\n",
|
| 1105 |
+
"❌ No data found for 1koma in any format\n",
|
| 1106 |
+
"✅ Added 0 tags directly under subject.koma.1koma\n",
|
| 1107 |
+
"❌ No data found for 2koma in any format\n",
|
| 1108 |
+
"✅ Added 0 tags directly under subject.koma.2koma\n",
|
| 1109 |
+
"❌ No data found for male in any format\n",
|
| 1110 |
+
"❌ No data found for 1boy in any format\n",
|
| 1111 |
+
"✅ Added 0 tags directly under subject.male.1boy\n",
|
| 1112 |
+
"❌ No data found for 2boys in any format\n",
|
| 1113 |
+
"✅ Added 0 tags directly under subject.male.2boys\n",
|
| 1114 |
+
"❌ No data found for 3boys in any format\n",
|
| 1115 |
+
"✅ Added 0 tags directly under subject.male.3boys\n",
|
| 1116 |
+
"❌ No data found for 4boys in any format\n",
|
| 1117 |
+
"✅ Added 0 tags directly under subject.male.4boys\n",
|
| 1118 |
+
"❌ No data found for 5boys in any format\n",
|
| 1119 |
+
"✅ Added 0 tags directly under subject.male.5boys\n",
|
| 1120 |
+
"❌ No data found for 6+boys in any format\n",
|
| 1121 |
+
"✅ Added 0 tags directly under subject.male.6+boys\n",
|
| 1122 |
+
"❌ No data found for boy in any format\n",
|
| 1123 |
+
"✅ Added 0 tags directly under subject.male.boy\n",
|
| 1124 |
+
"❌ No data found for man in any format\n",
|
| 1125 |
+
"✅ Added 0 tags directly under subject.male.man\n",
|
| 1126 |
+
"❌ No data found for multiple_boys in any format\n",
|
| 1127 |
+
"✅ Added 0 tags directly under subject.male.multiple_boys\n",
|
| 1128 |
+
"❌ No data found for visual_characteristics in any format\n",
|
| 1129 |
+
"❌ No data found for image_composition_and_style in any format\n",
|
| 1130 |
+
"✅ Added 73 tags directly under visual_characteristics.image_composition_and_style.artistic_license\n",
|
| 1131 |
+
"✅ Added 115 tags directly under visual_characteristics.image_composition_and_style.image_composition.backgrounds\n",
|
| 1132 |
+
"✅ Added 90 tags directly under visual_characteristics.image_composition_and_style.image_composition.censorship\n",
|
| 1133 |
+
"✅ Added 54 tags directly under visual_characteristics.image_composition_and_style.image_composition.colors\n",
|
| 1134 |
+
"✅ Added 28 tags directly under visual_characteristics.image_composition_and_style.image_composition.focus_tags\n",
|
| 1135 |
+
"✅ Added 55 tags directly under visual_characteristics.image_composition_and_style.image_composition.lighting\n",
|
| 1136 |
+
"✅ Added 76 tags directly under visual_characteristics.image_composition_and_style.image_composition.prints\n",
|
| 1137 |
+
"✅ Added 495 tags directly under visual_characteristics.image_composition_and_style.image_composition.style_parodies\n",
|
| 1138 |
+
"✅ Added 41 tags directly under visual_characteristics.image_composition_and_style.patterns\n",
|
| 1139 |
+
"✅ Added 310 tags directly under visual_characteristics.image_composition_and_style.symbols\n",
|
| 1140 |
+
"✅ Added 242 tags directly under visual_characteristics.image_composition_and_style.text\n",
|
| 1141 |
+
"✅ Added 62 tags directly under visual_characteristics.image_composition_and_style.year_tags\n",
|
| 1142 |
+
"✅ Finished building hierarchy.\n"
|
| 1143 |
+
]
|
| 1144 |
+
},
|
| 1145 |
+
{
|
| 1146 |
+
"ename": "FileNotFoundError",
|
| 1147 |
+
"evalue": "[Errno 2] No such file or directory: '/home/lauhp/000_PHD/000_010_PUBLICATION/2025_SAGE/CODE/pm-paper_uzh_gitlab/pm-paper/misc/danbooru_donmai/danbooru_tags_step_01.json'",
|
| 1148 |
+
"output_type": "error",
|
| 1149 |
+
"traceback": [
|
| 1150 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
| 1151 |
+
"\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)",
|
| 1152 |
+
"Cell \u001b[0;32mIn[13], line 198\u001b[0m\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# Save cleaned JSON\u001b[39;00m\n\u001b[1;32m 197\u001b[0m output_file \u001b[38;5;241m=\u001b[39m current_dir\u001b[38;5;241m.\u001b[39mparent \u001b[38;5;241m/\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmisc/danbooru_donmai/danbooru_tags_step_01.json\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m--> 198\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43moutput_file\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mw\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mutf-8\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[1;32m 199\u001b[0m json\u001b[38;5;241m.\u001b[39mdump(manual_hierarchy, f, indent\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m4\u001b[39m, ensure_ascii\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m 201\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m✅ Hierarchy saved to `\u001b[39m\u001b[38;5;132;01m{\u001b[39;00moutput_file\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m`\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
|
| 1153 |
+
"File \u001b[0;32m~/.local/lib/python3.10/site-packages/IPython/core/interactiveshell.py:324\u001b[0m, in \u001b[0;36m_modified_open\u001b[0;34m(file, *args, **kwargs)\u001b[0m\n\u001b[1;32m 317\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m file \u001b[38;5;129;01min\u001b[39;00m {\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m2\u001b[39m}:\n\u001b[1;32m 318\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[1;32m 319\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIPython won\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mt let you open fd=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfile\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m by default \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 320\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mas it is likely to crash IPython. If you know what you are doing, \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 321\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myou can use builtins\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m open.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 322\u001b[0m )\n\u001b[0;32m--> 324\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mio_open\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
|
| 1154 |
+
"\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/home/lauhp/000_PHD/000_010_PUBLICATION/2025_SAGE/CODE/pm-paper_uzh_gitlab/pm-paper/misc/danbooru_donmai/danbooru_tags_step_01.json'"
|
| 1155 |
+
]
|
| 1156 |
+
}
|
| 1157 |
+
],
|
| 1158 |
+
"source": [
|
| 1159 |
+
"import requests\n",
|
| 1160 |
+
"import json\n",
|
| 1161 |
+
"import time\n",
|
| 1162 |
+
"import re\n",
|
| 1163 |
+
"from urllib.parse import quote\n",
|
| 1164 |
+
"\n",
|
| 1165 |
+
"# Base API URL\n",
|
| 1166 |
+
"BASE_URL = \"https://danbooru.donmai.us\"\n",
|
| 1167 |
+
"\n",
|
| 1168 |
+
"# Authentication (Replace with your credentials)\n",
|
| 1169 |
+
"AUTH = (username, api_key)\n",
|
| 1170 |
+
"\n",
|
| 1171 |
+
"# Storage for missing tags\n",
|
| 1172 |
+
"missing_tags = []\n",
|
| 1173 |
+
"\n",
|
| 1174 |
+
"# ✅ Initialize manual_hierarchy\n",
|
| 1175 |
+
"manual_hierarchy = {}\n",
|
| 1176 |
+
"\n",
|
| 1177 |
+
"def clean_hierarchy_and_count_tags(hierarchy):\n",
|
| 1178 |
+
" \"\"\"\n",
|
| 1179 |
+
" Recursively removes empty categories from the JSON hierarchy and counts total tags.\n",
|
| 1180 |
+
" \"\"\"\n",
|
| 1181 |
+
" total_tag_count = 0\n",
|
| 1182 |
+
"\n",
|
| 1183 |
+
" def clean_dict(d):\n",
|
| 1184 |
+
" nonlocal total_tag_count\n",
|
| 1185 |
+
" keys_to_delete = []\n",
|
| 1186 |
+
"\n",
|
| 1187 |
+
" for key, value in d.items():\n",
|
| 1188 |
+
" if isinstance(value, dict):\n",
|
| 1189 |
+
" clean_dict(value) # Recursively clean subcategories\n",
|
| 1190 |
+
" \n",
|
| 1191 |
+
" # ✅ If the subcategory is empty after cleaning, mark it for removal\n",
|
| 1192 |
+
" if not value:\n",
|
| 1193 |
+
" keys_to_delete.append(key)\n",
|
| 1194 |
+
" elif isinstance(value, list):\n",
|
| 1195 |
+
" # ✅ Count the total number of tags\n",
|
| 1196 |
+
" total_tag_count += len(value)\n",
|
| 1197 |
+
"\n",
|
| 1198 |
+
" # ✅ If the list is empty, mark it for deletion\n",
|
| 1199 |
+
" if not value:\n",
|
| 1200 |
+
" keys_to_delete.append(key)\n",
|
| 1201 |
+
"\n",
|
| 1202 |
+
" # ✅ Remove empty keys\n",
|
| 1203 |
+
" for key in keys_to_delete:\n",
|
| 1204 |
+
" del d[key]\n",
|
| 1205 |
+
"\n",
|
| 1206 |
+
" clean_dict(hierarchy)\n",
|
| 1207 |
+
"\n",
|
| 1208 |
+
" return hierarchy, total_tag_count\n",
|
| 1209 |
+
"\n",
|
| 1210 |
+
"\n",
|
| 1211 |
+
"def fetch_wiki_page(tag, is_list=False):\n",
|
| 1212 |
+
" \"\"\"\n",
|
| 1213 |
+
" Fetches the wiki page of a tag group, list category, or special wiki page using the API.\n",
|
| 1214 |
+
" Uses list_of_ for lists, tag_group: for regular groups, \n",
|
| 1215 |
+
" and direct queries for special cases.\n",
|
| 1216 |
+
" \"\"\"\n",
|
| 1217 |
+
" if tag in special_wiki_pages:\n",
|
| 1218 |
+
" prefixes = [\"\"] # Query directly with no prefix\n",
|
| 1219 |
+
" #print(f\"🔍 {tag} is a special wiki page, querying directly...\")\n",
|
| 1220 |
+
" elif is_list:\n",
|
| 1221 |
+
" prefixes = [\"list_of_\", \"tag_group:\"]\n",
|
| 1222 |
+
" else:\n",
|
| 1223 |
+
" prefixes = [\"tag_group:\"]\n",
|
| 1224 |
+
"\n",
|
| 1225 |
+
" # print(f\"🚀 Fetching {prefixes} {tag}\") # Debugging print\n",
|
| 1226 |
+
"\n",
|
| 1227 |
+
" for prefix in prefixes:\n",
|
| 1228 |
+
" query_tag = f\"{prefix}{tag}\".strip() # Avoid unnecessary :\n",
|
| 1229 |
+
" encoded_query = quote(query_tag, safe=\"\") # Proper URL encoding\n",
|
| 1230 |
+
" url = f\"{BASE_URL}/wiki_pages.json?search[title]={encoded_query}\"\n",
|
| 1231 |
+
"\n",
|
| 1232 |
+
" # print(f\"🚀 Fetching: {query_tag}\")\n",
|
| 1233 |
+
"\n",
|
| 1234 |
+
" try:\n",
|
| 1235 |
+
" response = requests.get(url, auth=AUTH) # Use authentication\n",
|
| 1236 |
+
" response.raise_for_status() # Raise error for bad responses (4xx, 5xx)\n",
|
| 1237 |
+
" wiki_data = response.json()\n",
|
| 1238 |
+
"\n",
|
| 1239 |
+
" if wiki_data and isinstance(wiki_data, list) and len(wiki_data) > 0:\n",
|
| 1240 |
+
" #print(f\"✅ Data found using {query_tag}\")\n",
|
| 1241 |
+
" return wiki_data[0].get(\"body\", \"\") # Extract the \"body\" text\n",
|
| 1242 |
+
"\n",
|
| 1243 |
+
" except requests.exceptions.HTTPError as e:\n",
|
| 1244 |
+
" if response.status_code == 401:\n",
|
| 1245 |
+
" print(f\"❌ Authentication Error (401 Unauthorized) for {query_tag}. Check your API key!\")\n",
|
| 1246 |
+
" exit() # Stop execution if authentication fails\n",
|
| 1247 |
+
" else:\n",
|
| 1248 |
+
" print(f\"❌ Error fetching {query_tag}: {e}\")\n",
|
| 1249 |
+
"\n",
|
| 1250 |
+
" # If all attempts fail\n",
|
| 1251 |
+
" print(f\"❌ No data found for {tag} in any format\")\n",
|
| 1252 |
+
" missing_tags.append(tag)\n",
|
| 1253 |
+
" return None\n",
|
| 1254 |
+
"\n",
|
| 1255 |
+
"\n",
|
| 1256 |
+
"\n",
|
| 1257 |
+
"\n",
|
| 1258 |
+
"def build_tag_hierarchy(tag_groups):\n",
|
| 1259 |
+
" \"\"\"\n",
|
| 1260 |
+
" Builds and structures the hierarchy properly, ensuring:\n",
|
| 1261 |
+
" - Categories with subcategories store their direct tags in `_general`\n",
|
| 1262 |
+
" - Parent categories exist before adding children\n",
|
| 1263 |
+
" - Ensures subject and other key groups are correctly retained\n",
|
| 1264 |
+
" \"\"\"\n",
|
| 1265 |
+
" processed_groups = set()\n",
|
| 1266 |
+
"\n",
|
| 1267 |
+
" for hierarchy_path, tag_group in sorted(tag_groups.items(), key=lambda x: x[0]):\n",
|
| 1268 |
+
" is_list = tag_group in list_based_categories # Check if it's a list-based category\n",
|
| 1269 |
+
"\n",
|
| 1270 |
+
" levels = hierarchy_path.split(\".\")\n",
|
| 1271 |
+
" current_level = manual_hierarchy\n",
|
| 1272 |
+
"\n",
|
| 1273 |
+
" for key in levels[:-1]: # Ensure each parent level exists\n",
|
| 1274 |
+
" if key not in current_level or not isinstance(current_level[key], dict):\n",
|
| 1275 |
+
" current_level[key] = {} # Create dictionary if missing\n",
|
| 1276 |
+
" current_level = current_level[key]\n",
|
| 1277 |
+
"\n",
|
| 1278 |
+
" last_level = levels[-1]\n",
|
| 1279 |
+
" has_subcategories = any(k.startswith(hierarchy_path + \".\") for k in tag_groups.keys())\n",
|
| 1280 |
+
"\n",
|
| 1281 |
+
" # ✅ Ensure the category itself exists\n",
|
| 1282 |
+
" if last_level not in current_level:\n",
|
| 1283 |
+
" current_level[last_level] = {} if has_subcategories else []\n",
|
| 1284 |
+
"\n",
|
| 1285 |
+
" # ✅ Fetch tags from API\n",
|
| 1286 |
+
" wiki_text = fetch_wiki_page(tag_group, is_list)\n",
|
| 1287 |
+
" extracted_tags = extract_tags_from_wiki(wiki_text, is_list)\n",
|
| 1288 |
+
"\n",
|
| 1289 |
+
" # ✅ Store tags under `<category>_general` if there are subcategories\n",
|
| 1290 |
+
" if has_subcategories:\n",
|
| 1291 |
+
" general_key = f\"{last_level}_general\"\n",
|
| 1292 |
+
"\n",
|
| 1293 |
+
" if isinstance(current_level[last_level], list):\n",
|
| 1294 |
+
" # print(f\"⚠️ Warning: {last_level} was a list but has subcategories. Converting to dictionary.\")\n",
|
| 1295 |
+
" current_level[last_level] = {}\n",
|
| 1296 |
+
"\n",
|
| 1297 |
+
" if general_key not in current_level[last_level]:\n",
|
| 1298 |
+
" current_level[last_level][general_key] = [] # Initialize `_general` list\n",
|
| 1299 |
+
"\n",
|
| 1300 |
+
" current_level[last_level][general_key].extend(extracted_tags)\n",
|
| 1301 |
+
" #print(f\"✅ Added {len(extracted_tags)} tags under {hierarchy_path} → {general_key}\")\n",
|
| 1302 |
+
"\n",
|
| 1303 |
+
" else:\n",
|
| 1304 |
+
" # ✅ If no subcategories exist, store tags directly\n",
|
| 1305 |
+
" if isinstance(current_level[last_level], list):\n",
|
| 1306 |
+
" current_level[last_level].extend(extracted_tags)\n",
|
| 1307 |
+
" else:\n",
|
| 1308 |
+
" # print(f\"⚠️ Warning: {last_level} was a dictionary but has no subcategories. Converting to list.\")\n",
|
| 1309 |
+
" current_level[last_level] = extracted_tags\n",
|
| 1310 |
+
"\n",
|
| 1311 |
+
" print(f\"✅ Added {len(extracted_tags)} tags directly under {hierarchy_path}\")\n",
|
| 1312 |
+
"\n",
|
| 1313 |
+
" print(f\"✅ Finished building hierarchy.\")\n",
|
| 1314 |
+
"\n",
|
| 1315 |
+
"\n",
|
| 1316 |
+
"\n",
|
| 1317 |
+
"\n",
|
| 1318 |
+
"\n",
|
| 1319 |
+
"def extract_tags_from_wiki(wiki_text, is_list=False):\n",
|
| 1320 |
+
" \"\"\"\n",
|
| 1321 |
+
" Extracts valid tags from the wiki text.\n",
|
| 1322 |
+
" Uses different extraction logic for tag groups and list-based pages.\n",
|
| 1323 |
+
" \"\"\"\n",
|
| 1324 |
+
" if not wiki_text:\n",
|
| 1325 |
+
" return []\n",
|
| 1326 |
+
"\n",
|
| 1327 |
+
" if is_list:\n",
|
| 1328 |
+
" # Extract `[[tag_name]]` from list pages\n",
|
| 1329 |
+
" tag_pattern = re.compile(r\"\\[\\[(.*?)\\]\\]\")\n",
|
| 1330 |
+
" tags = tag_pattern.findall(wiki_text)\n",
|
| 1331 |
+
" else:\n",
|
| 1332 |
+
" # Extract `[[tag_name]]` from tag groups (skip first item if \"Tag Groups\")\n",
|
| 1333 |
+
" tag_pattern = re.compile(r\"\\[\\[(.*?)\\]\\]\")\n",
|
| 1334 |
+
" tags = tag_pattern.findall(wiki_text)\n",
|
| 1335 |
+
" tags = tags[1:] if tags else [] # Skip first tag\n",
|
| 1336 |
+
"\n",
|
| 1337 |
+
" # Clean tags by removing alternative names `[[Tag|Alternative]]`\n",
|
| 1338 |
+
" cleaned_tags = [tag.split(\"|\")[0].strip() for tag in tags]\n",
|
| 1339 |
+
"\n",
|
| 1340 |
+
" return cleaned_tags\n",
|
| 1341 |
+
"\n",
|
| 1342 |
+
"if __name__ == \"__main__\":\n",
|
| 1343 |
+
" print(\"🚀 Starting Danbooru Tag Hierarchy API Fetcher...\")\n",
|
| 1344 |
+
"\n",
|
| 1345 |
+
" # ✅ Ensure manual_hierarchy exists before cleaning\n",
|
| 1346 |
+
" manual_hierarchy = {}\n",
|
| 1347 |
+
"\n",
|
| 1348 |
+
" # Build hierarchy dynamically\n",
|
| 1349 |
+
" build_tag_hierarchy(tag_groups)\n",
|
| 1350 |
+
"\n",
|
| 1351 |
+
" # ✅ Clean hierarchy and count total tags\n",
|
| 1352 |
+
" manual_hierarchy, total_tags = clean_hierarchy_and_count_tags(manual_hierarchy)\n",
|
| 1353 |
+
"\n",
|
| 1354 |
+
" # Save cleaned JSON\n",
|
| 1355 |
+
" output_file = current_dir.parent / \"misc/danbooru_donmai/danbooru_tags_step_01.json\"\n",
|
| 1356 |
+
" with open(output_file, \"w\", encoding=\"utf-8\") as f:\n",
|
| 1357 |
+
" json.dump(manual_hierarchy, f, indent=4, ensure_ascii=False)\n",
|
| 1358 |
+
"\n",
|
| 1359 |
+
" print(f\"\\n✅ Hierarchy saved to `{output_file}`\")\n",
|
| 1360 |
+
" print(f\"📊 Total tags in the final hierarchy: {total_tags}\")\n",
|
| 1361 |
+
"\n",
|
| 1362 |
+
" if missing_tags:\n",
|
| 1363 |
+
" print(f\"⚠️ The following tag groups were not found: {missing_tags}\")\n"
|
| 1364 |
+
]
|
| 1365 |
+
},
|
| 1366 |
+
{
|
| 1367 |
+
"cell_type": "markdown",
|
| 1368 |
+
"id": "5bf6fa19-04dd-4799-8220-e37a4434a1f8",
|
| 1369 |
+
"metadata": {},
|
| 1370 |
+
"source": [
|
| 1371 |
+
"### Add subject keys etc"
|
| 1372 |
+
]
|
| 1373 |
+
},
|
| 1374 |
+
{
|
| 1375 |
+
"cell_type": "code",
|
| 1376 |
+
"execution_count": 15,
|
| 1377 |
+
"id": "9bf7c0ed-0cfd-41c8-89a4-c59f1783c823",
|
| 1378 |
+
"metadata": {
|
| 1379 |
+
"execution": {
|
| 1380 |
+
"iopub.execute_input": "2025-03-17T12:49:06.895515Z",
|
| 1381 |
+
"iopub.status.busy": "2025-03-17T12:49:06.895119Z",
|
| 1382 |
+
"iopub.status.idle": "2025-03-17T12:49:07.135844Z",
|
| 1383 |
+
"shell.execute_reply": "2025-03-17T12:49:07.135358Z",
|
| 1384 |
+
"shell.execute_reply.started": "2025-03-17T12:49:06.895492Z"
|
| 1385 |
+
}
|
| 1386 |
+
},
|
| 1387 |
+
"outputs": [
|
| 1388 |
+
{
|
| 1389 |
+
"name": "stdout",
|
| 1390 |
+
"output_type": "stream",
|
| 1391 |
+
"text": [
|
| 1392 |
+
"✅ Adding woman to subject.female.female_general\n",
|
| 1393 |
+
"✅ Adding girl to subject.female.female_general\n",
|
| 1394 |
+
"✅ Adding 1girl to subject.female.female_general\n",
|
| 1395 |
+
"🔄 Moving 2girls from more.groups to subject.female.female_general\n",
|
| 1396 |
+
"✅ Adding 2girls to subject.female.female_general\n",
|
| 1397 |
+
"🔄 Moving 3girls from more.groups to subject.female.female_general\n",
|
| 1398 |
+
"✅ Adding 3girls to subject.female.female_general\n",
|
| 1399 |
+
"🔄 Moving 4girls from more.groups to subject.female.female_general\n",
|
| 1400 |
+
"✅ Adding 4girls to subject.female.female_general\n",
|
| 1401 |
+
"🔄 Moving 5girls from more.groups to subject.female.female_general\n",
|
| 1402 |
+
"✅ Adding 5girls to subject.female.female_general\n",
|
| 1403 |
+
"🔄 Moving 6+girls from more.groups to subject.female.female_general\n",
|
| 1404 |
+
"✅ Adding 6+girls to subject.female.female_general\n",
|
| 1405 |
+
"🔄 Moving multiple girls from more.groups to subject.female.female_general\n",
|
| 1406 |
+
"✅ Adding multiple girls to subject.female.female_general\n",
|
| 1407 |
+
"🔄 Moving guitar girl from objects.audio_tags to subject.female.female_general\n",
|
| 1408 |
+
"✅ Adding guitar girl to subject.female.female_general\n",
|
| 1409 |
+
"✅ Adding man to subject.male.male_general\n",
|
| 1410 |
+
"✅ Adding boy to subject.male.male_general\n",
|
| 1411 |
+
"✅ Adding 1boy to subject.male.male_general\n",
|
| 1412 |
+
"🔄 Moving 2boys from more.groups to subject.male.male_general\n",
|
| 1413 |
+
"✅ Adding 2boys to subject.male.male_general\n",
|
| 1414 |
+
"🔄 Moving 3boys from more.groups to subject.male.male_general\n",
|
| 1415 |
+
"✅ Adding 3boys to subject.male.male_general\n",
|
| 1416 |
+
"🔄 Moving 4boys from more.groups to subject.male.male_general\n",
|
| 1417 |
+
"✅ Adding 4boys to subject.male.male_general\n",
|
| 1418 |
+
"🔄 Moving 5boys from more.groups to subject.male.male_general\n",
|
| 1419 |
+
"✅ Adding 5boys to subject.male.male_general\n",
|
| 1420 |
+
"🔄 Moving 6+boys from more.groups to subject.male.male_general\n",
|
| 1421 |
+
"✅ Adding 6+boys to subject.male.male_general\n",
|
| 1422 |
+
"🔄 Moving multiple boys from more.groups to subject.male.male_general\n",
|
| 1423 |
+
"✅ Adding multiple boys to subject.male.male_general\n",
|
| 1424 |
+
"✅ Adding guitar boy to subject.male.male_general\n",
|
| 1425 |
+
"✅ Adding 1koma to subject.koma.koma_general\n",
|
| 1426 |
+
"✅ Adding 2koma to subject.koma.koma_general\n",
|
| 1427 |
+
"🔄 Moving cat girl from creatures.animals.cats to subject.anthro.anthro_general\n",
|
| 1428 |
+
"✅ Adding cat girl to subject.anthro.anthro_general\n",
|
| 1429 |
+
"✅ Adding fox girl to subject.anthro.anthro_general\n",
|
| 1430 |
+
"✅ Adding dog girl to subject.anthro.anthro_general\n",
|
| 1431 |
+
"🔄 Moving plant girl from plant.plant to subject.anthro.anthro_general\n",
|
| 1432 |
+
"🔄 Moving plant girl from plants to subject.anthro.anthro_general\n",
|
| 1433 |
+
"✅ Adding plant girl to subject.anthro.anthro_general\n",
|
| 1434 |
+
"🔄 Moving plant boy from plant.plant to subject.anthro.anthro_general\n",
|
| 1435 |
+
"🔄 Moving plant boy from plants to subject.anthro.anthro_general\n",
|
| 1436 |
+
"✅ Adding plant boy to subject.anthro.anthro_general\n",
|
| 1437 |
+
"🔄 Moving cat boy from creatures.animals.cats to subject.anthro.anthro_general\n",
|
| 1438 |
+
"✅ Adding cat boy to subject.anthro.anthro_general\n",
|
| 1439 |
+
"✅ Adding furry to subject.anthro.anthro_general\n",
|
| 1440 |
+
"✅ Adding monster boy to subject.anthro.anthro_general\n",
|
| 1441 |
+
"✅ Adding monster girl to subject.anthro.anthro_general\n",
|
| 1442 |
+
"✅ Adding demon girl to subject.anthro.anthro_general\n",
|
| 1443 |
+
"✅ Adding demon boy to subject.anthro.anthro_general\n",
|
| 1444 |
+
"✅ Adding magical boy to subject.anthro.anthro_general\n",
|
| 1445 |
+
"🔄 Moving magical girl from characters.sailor_moon to subject.anthro.anthro_general\n",
|
| 1446 |
+
"✅ Adding magical girl to subject.anthro.anthro_general\n",
|
| 1447 |
+
"✅ Updated JSON saved as `/shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/danbooru_tags_step_02.json`\n"
|
| 1448 |
+
]
|
| 1449 |
+
}
|
| 1450 |
+
],
|
| 1451 |
+
"source": [
|
| 1452 |
+
"import json\n",
|
| 1453 |
+
"\n",
|
| 1454 |
+
"# Load the existing JSON file\n",
|
| 1455 |
+
"input_file = current_dir.parent / \"misc/danbooru_donmai/danbooru_tags_step_01.json\"\n",
|
| 1456 |
+
"output_file = current_dir.parent / \"misc/danbooru_donmai/danbooru_tags_step_02.json\"\n",
|
| 1457 |
+
"\n",
|
| 1458 |
+
"with open(input_file, \"r\", encoding=\"utf-8\") as f:\n",
|
| 1459 |
+
" manual_hierarchy = json.load(f)\n",
|
| 1460 |
+
"\n",
|
| 1461 |
+
"# ✅ Define the correct subject structure\n",
|
| 1462 |
+
"subject_structure = {\n",
|
| 1463 |
+
" \"subject\": {\n",
|
| 1464 |
+
" \"female\": {\n",
|
| 1465 |
+
" \"female_general\": [\"woman\", \"girl\", \"1girl\", \"2girls\", \"3girls\", \"4girls\", \"5girls\", \"6+girls\", \"multiple girls\", \"guitar girl\" ]\n",
|
| 1466 |
+
" },\n",
|
| 1467 |
+
" \"male\": {\n",
|
| 1468 |
+
" \"male_general\": [\"man\", \"boy\", \"1boy\", \"2boys\", \"3boys\", \"4boys\", \"5boys\", \"6+boys\", \"multiple boys\", \"guitar boy\"]\n",
|
| 1469 |
+
" },\n",
|
| 1470 |
+
" \"koma\": {\n",
|
| 1471 |
+
" \"koma_general\": [\"1koma\", \"2koma\"]\n",
|
| 1472 |
+
" },\n",
|
| 1473 |
+
" \"anthro\": {\n",
|
| 1474 |
+
" \"anthro_general\": [\"cat girl\", \"fox girl\", \"dog girl\", \"plant girl\", \"plant boy\", \"cat boy\", \"furry\", \"monster boy\", \"monster girl\", \"demon girl\" , \"demon boy\", \"magical boy\", \"magical girl\", ]\n",
|
| 1475 |
+
" }\n",
|
| 1476 |
+
" }\n",
|
| 1477 |
+
"}\n",
|
| 1478 |
+
"\n",
|
| 1479 |
+
"# ✅ Ensure \"subject\" exists in the hierarchy\n",
|
| 1480 |
+
"if \"subject\" not in manual_hierarchy:\n",
|
| 1481 |
+
" manual_hierarchy[\"subject\"] = {}\n",
|
| 1482 |
+
"\n",
|
| 1483 |
+
"# ✅ Ensure all subcategories exist\n",
|
| 1484 |
+
"for category, subcategories in subject_structure[\"subject\"].items():\n",
|
| 1485 |
+
" if category not in manual_hierarchy[\"subject\"]:\n",
|
| 1486 |
+
" manual_hierarchy[\"subject\"][category] = {}\n",
|
| 1487 |
+
"\n",
|
| 1488 |
+
" for subcategory, tags in subcategories.items():\n",
|
| 1489 |
+
" if subcategory not in manual_hierarchy[\"subject\"][category]:\n",
|
| 1490 |
+
" manual_hierarchy[\"subject\"][category][subcategory] = []\n",
|
| 1491 |
+
"\n",
|
| 1492 |
+
"# ✅ Move misplaced tags and also ensure missing tags are added\n",
|
| 1493 |
+
"for category, subcategories in subject_structure[\"subject\"].items():\n",
|
| 1494 |
+
" for subcategory, tags in subcategories.items():\n",
|
| 1495 |
+
" target_list = manual_hierarchy[\"subject\"][category][subcategory]\n",
|
| 1496 |
+
"\n",
|
| 1497 |
+
" for tag in tags:\n",
|
| 1498 |
+
" found_in_wrong_place = False # Track if tag was found elsewhere\n",
|
| 1499 |
+
"\n",
|
| 1500 |
+
" # ✅ Search for the tag in other categories and remove if found\n",
|
| 1501 |
+
" for key, value in list(manual_hierarchy.items()): # Use list() to avoid runtime changes\n",
|
| 1502 |
+
" if isinstance(value, list) and tag in value:\n",
|
| 1503 |
+
" print(f\"🔄 Moving {tag} from {key} to subject.{category}.{subcategory}\")\n",
|
| 1504 |
+
" value.remove(tag)\n",
|
| 1505 |
+
" found_in_wrong_place = True\n",
|
| 1506 |
+
"\n",
|
| 1507 |
+
" elif isinstance(value, dict): # Search deeper\n",
|
| 1508 |
+
" for subkey, subvalue in list(value.items()):\n",
|
| 1509 |
+
" if isinstance(subvalue, list) and tag in subvalue:\n",
|
| 1510 |
+
" print(f\"🔄 Moving {tag} from {key}.{subkey} to subject.{category}.{subcategory}\")\n",
|
| 1511 |
+
" subvalue.remove(tag)\n",
|
| 1512 |
+
" found_in_wrong_place = True\n",
|
| 1513 |
+
"\n",
|
| 1514 |
+
" elif isinstance(subvalue, dict):\n",
|
| 1515 |
+
" for deepkey, deepvalue in list(subvalue.items()):\n",
|
| 1516 |
+
" if isinstance(deepvalue, list) and tag in deepvalue:\n",
|
| 1517 |
+
" print(f\"🔄 Moving {tag} from {key}.{subkey}.{deepkey} to subject.{category}.{subcategory}\")\n",
|
| 1518 |
+
" deepvalue.remove(tag)\n",
|
| 1519 |
+
" found_in_wrong_place = True\n",
|
| 1520 |
+
"\n",
|
| 1521 |
+
" # ✅ Add the tag to the correct subject category if missing\n",
|
| 1522 |
+
" if tag not in target_list:\n",
|
| 1523 |
+
" print(f\"✅ Adding {tag} to subject.{category}.{subcategory}\")\n",
|
| 1524 |
+
" target_list.append(tag)\n",
|
| 1525 |
+
"\n",
|
| 1526 |
+
"# ✅ Save the updated JSON\n",
|
| 1527 |
+
"with open(output_file, \"w\", encoding=\"utf-8\") as f:\n",
|
| 1528 |
+
" json.dump(manual_hierarchy, f, indent=4, ensure_ascii=False)\n",
|
| 1529 |
+
"\n",
|
| 1530 |
+
"print(f\"✅ Updated JSON saved as `{output_file}`\")\n",
|
| 1531 |
+
"\n"
|
| 1532 |
+
]
|
| 1533 |
+
},
|
| 1534 |
+
{
|
| 1535 |
+
"cell_type": "markdown",
|
| 1536 |
+
"id": "aff1a30d-17e7-4e55-b881-b5ce622a533a",
|
| 1537 |
+
"metadata": {},
|
| 1538 |
+
"source": [
|
| 1539 |
+
"### Compare with wd-14 vocabulary"
|
| 1540 |
+
]
|
| 1541 |
+
},
|
| 1542 |
+
{
|
| 1543 |
+
"cell_type": "code",
|
| 1544 |
+
"execution_count": 16,
|
| 1545 |
+
"id": "2a8f986c-0ccd-4235-ac50-be3d6e495f9f",
|
| 1546 |
+
"metadata": {
|
| 1547 |
+
"execution": {
|
| 1548 |
+
"iopub.execute_input": "2025-03-17T12:49:10.749271Z",
|
| 1549 |
+
"iopub.status.busy": "2025-03-17T12:49:10.748481Z",
|
| 1550 |
+
"iopub.status.idle": "2025-03-17T12:49:11.628872Z",
|
| 1551 |
+
"shell.execute_reply": "2025-03-17T12:49:11.628200Z",
|
| 1552 |
+
"shell.execute_reply.started": "2025-03-17T12:49:10.749248Z"
|
| 1553 |
+
}
|
| 1554 |
+
},
|
| 1555 |
+
"outputs": [
|
| 1556 |
+
{
|
| 1557 |
+
"name": "stdout",
|
| 1558 |
+
"output_type": "stream",
|
| 1559 |
+
"text": [
|
| 1560 |
+
"\n",
|
| 1561 |
+
"📌 Tags in CSV but NOT in JSON:\n",
|
| 1562 |
+
"\n",
|
| 1563 |
+
"✅ Missing tags saved to `missing_tags.txt`\n"
|
| 1564 |
+
]
|
| 1565 |
+
}
|
| 1566 |
+
],
|
| 1567 |
+
"source": [
|
| 1568 |
+
"import json\n",
|
| 1569 |
+
"import pandas as pd\n",
|
| 1570 |
+
"\n",
|
| 1571 |
+
"# Load CSV file\n",
|
| 1572 |
+
"csv_file = current_dir.parent / \"misc/autotagging-vocabularies/danbooru.csv\" # Update with the actual CSV file name\n",
|
| 1573 |
+
"df = pd.read_csv(csv_file)\n",
|
| 1574 |
+
"\n",
|
| 1575 |
+
"# Load JSON file\n",
|
| 1576 |
+
"json_file = input_file = current_dir.parent / \"misc/danbooru_donmai/danbooru_tags_step_03.json\"\n",
|
| 1577 |
+
"with open(json_file, \"r\", encoding=\"utf-8\") as f:\n",
|
| 1578 |
+
" manual_hierarchy = json.load(f)\n",
|
| 1579 |
+
"\n",
|
| 1580 |
+
"# ✅ Extract all tags from the CSV\n",
|
| 1581 |
+
"csv_tags = set(df[\"name\"].astype(str).str.replace(\"_\", \" \").str.lower()) # Normalize tags\n",
|
| 1582 |
+
"\n",
|
| 1583 |
+
"# ✅ Extract all tags from the JSON recursively\n",
|
| 1584 |
+
"def extract_tags_from_json(data):\n",
|
| 1585 |
+
" tags = set()\n",
|
| 1586 |
+
" if isinstance(data, dict):\n",
|
| 1587 |
+
" for value in data.values():\n",
|
| 1588 |
+
" tags.update(extract_tags_from_json(value))\n",
|
| 1589 |
+
" elif isinstance(data, list):\n",
|
| 1590 |
+
" tags.update(str(tag).replace(\"_\", \" \").lower() for tag in data)\n",
|
| 1591 |
+
" return tags\n",
|
| 1592 |
+
"\n",
|
| 1593 |
+
"json_tags = extract_tags_from_json(manual_hierarchy)\n",
|
| 1594 |
+
"\n",
|
| 1595 |
+
"# ✅ Find tags in CSV but NOT in JSON\n",
|
| 1596 |
+
"missing_tags = csv_tags - json_tags\n",
|
| 1597 |
+
"\n",
|
| 1598 |
+
"# ✅ Print the missing tags\n",
|
| 1599 |
+
"print(\"\\n📌 Tags in CSV but NOT in JSON:\")\n",
|
| 1600 |
+
"for tag in sorted(missing_tags):\n",
|
| 1601 |
+
" print(tag)\n",
|
| 1602 |
+
"\n",
|
| 1603 |
+
"# ✅ Save missing tags to a file for review (optional)\n",
|
| 1604 |
+
"missing_tags_file = \"missing_tags.txt\"\n",
|
| 1605 |
+
"with open(missing_tags_file, \"w\", encoding=\"utf-8\") as f:\n",
|
| 1606 |
+
" f.write(\"\\n\".join(sorted(missing_tags)))\n",
|
| 1607 |
+
"\n",
|
| 1608 |
+
"print(f\"\\n✅ Missing tags saved to `{missing_tags_file}`\")\n"
|
| 1609 |
+
]
|
| 1610 |
+
},
|
| 1611 |
+
{
|
| 1612 |
+
"cell_type": "markdown",
|
| 1613 |
+
"id": "706317c1-498d-40ab-a29a-ee658e1735e2",
|
| 1614 |
+
"metadata": {
|
| 1615 |
+
"execution": {
|
| 1616 |
+
"iopub.execute_input": "2025-03-17T10:53:52.106543Z",
|
| 1617 |
+
"iopub.status.busy": "2025-03-17T10:53:52.105944Z",
|
| 1618 |
+
"iopub.status.idle": "2025-03-17T10:53:52.108812Z",
|
| 1619 |
+
"shell.execute_reply": "2025-03-17T10:53:52.108451Z",
|
| 1620 |
+
"shell.execute_reply.started": "2025-03-17T10:53:52.106526Z"
|
| 1621 |
+
}
|
| 1622 |
+
},
|
| 1623 |
+
"source": [
|
| 1624 |
+
"### Fetch wikidata for collected tags"
|
| 1625 |
+
]
|
| 1626 |
+
},
|
| 1627 |
+
{
|
| 1628 |
+
"cell_type": "code",
|
| 1629 |
+
"execution_count": null,
|
| 1630 |
+
"id": "ec696e5b-be39-4407-a04f-6ce6b0b08855",
|
| 1631 |
+
"metadata": {
|
| 1632 |
+
"execution": {
|
| 1633 |
+
"execution_failed": "2025-03-17T12:46:52.921Z",
|
| 1634 |
+
"iopub.execute_input": "2025-03-17T12:45:32.542088Z",
|
| 1635 |
+
"iopub.status.busy": "2025-03-17T12:45:32.541554Z"
|
| 1636 |
+
}
|
| 1637 |
+
},
|
| 1638 |
+
"outputs": [
|
| 1639 |
+
{
|
| 1640 |
+
"name": "stdout",
|
| 1641 |
+
"output_type": "stream",
|
| 1642 |
+
"text": [
|
| 1643 |
+
"✅ Extracted 35559 unique tags.\n",
|
| 1644 |
+
"🚀 Fetching data for amamiya_elena (1/35559)...\n",
|
| 1645 |
+
"✅ Saved amamiya_elena to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/amamiya_elena.json\n",
|
| 1646 |
+
"🚀 Fetching data for gilles_de_rais (2/35559)...\n",
|
| 1647 |
+
"✅ Saved gilles_de_rais to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/gilles_de_rais.json\n",
|
| 1648 |
+
"🚀 Fetching data for blue_scrunchie (3/35559)...\n",
|
| 1649 |
+
"✅ Saved blue_scrunchie to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/blue_scrunchie.json\n",
|
| 1650 |
+
"🚀 Fetching data for playing (4/35559)...\n",
|
| 1651 |
+
"✅ Saved playing to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/playing.json\n",
|
| 1652 |
+
"🚀 Fetching data for album_cover_redraw (5/35559)...\n",
|
| 1653 |
+
"✅ Saved album_cover_redraw to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/album_cover_redraw.json\n",
|
| 1654 |
+
"🚀 Fetching data for >3< (6/35559)...\n",
|
| 1655 |
+
"✅ Saved >3< to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/>3<.json\n",
|
| 1656 |
+
"🚀 Fetching data for damom (7/35559)...\n",
|
| 1657 |
+
"🚀 Fetching data for shoulder_cannon (8/35559)...\n",
|
| 1658 |
+
"✅ Saved shoulder_cannon to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/shoulder_cannon.json\n",
|
| 1659 |
+
"🚀 Fetching data for cover_image (9/35559)...\n",
|
| 1660 |
+
"✅ Saved cover_image to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/cover_image.json\n",
|
| 1661 |
+
"🚀 Fetching data for ultraman_legend (10/35559)...\n",
|
| 1662 |
+
"✅ Saved ultraman_legend to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/ultraman_legend.json\n",
|
| 1663 |
+
"🚀 Fetching data for mastiff (11/35559)...\n",
|
| 1664 |
+
"✅ Saved mastiff to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/mastiff.json\n",
|
| 1665 |
+
"🚀 Fetching data for oingo (12/35559)...\n",
|
| 1666 |
+
"✅ Saved oingo to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/oingo.json\n",
|
| 1667 |
+
"🚀 Fetching data for mario_strikers_(series) (13/35559)...\n",
|
| 1668 |
+
"✅ Saved mario_strikers_(series) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/mario_strikers_(series).json\n",
|
| 1669 |
+
"🚀 Fetching data for phallic_symbol (14/35559)...\n",
|
| 1670 |
+
"✅ Saved phallic_symbol to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/phallic_symbol.json\n",
|
| 1671 |
+
"🚀 Fetching data for 4girls (15/35559)...\n",
|
| 1672 |
+
"✅ Saved 4girls to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/4girls.json\n",
|
| 1673 |
+
"🚀 Fetching data for nice_nature_(racehorse) (16/35559)...\n",
|
| 1674 |
+
"✅ Saved nice_nature_(racehorse) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/nice_nature_(racehorse).json\n",
|
| 1675 |
+
"🚀 Fetching data for hair_beads (17/35559)...\n",
|
| 1676 |
+
"✅ Saved hair_beads to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/hair_beads.json\n",
|
| 1677 |
+
"🚀 Fetching data for fu_po_(azur_lane) (18/35559)...\n",
|
| 1678 |
+
"✅ Saved fu_po_(azur_lane) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/fu_po_(azur_lane).json\n",
|
| 1679 |
+
"🚀 Fetching data for togepi (19/35559)...\n",
|
| 1680 |
+
"✅ Saved togepi to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/togepi.json\n",
|
| 1681 |
+
"🚀 Fetching data for yasopp (20/35559)...\n",
|
| 1682 |
+
"✅ Saved yasopp to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/yasopp.json\n",
|
| 1683 |
+
"🚀 Fetching data for oyafune_suama (21/35559)...\n",
|
| 1684 |
+
"✅ Saved oyafune_suama to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/oyafune_suama.json\n",
|
| 1685 |
+
"🚀 Fetching data for phantasy_star_iii (22/35559)...\n",
|
| 1686 |
+
"✅ Saved phantasy_star_iii to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/phantasy_star_iii.json\n",
|
| 1687 |
+
"🚀 Fetching data for qingdai_guanmao (23/35559)...\n",
|
| 1688 |
+
"✅ Saved qingdai_guanmao to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/qingdai_guanmao.json\n",
|
| 1689 |
+
"🚀 Fetching data for kufei (24/35559)...\n",
|
| 1690 |
+
"✅ Saved kufei to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/kufei.json\n",
|
| 1691 |
+
"🚀 Fetching data for stefan_(atelier) (25/35559)...\n",
|
| 1692 |
+
"✅ Saved stefan_(atelier) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/stefan_(atelier).json\n",
|
| 1693 |
+
"🚀 Fetching data for dille_blood (26/35559)...\n",
|
| 1694 |
+
"✅ Saved dille_blood to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/dille_blood.json\n",
|
| 1695 |
+
"🚀 Fetching data for vivillon_(modern) (27/35559)...\n",
|
| 1696 |
+
"✅ Saved vivillon_(modern) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/vivillon_(modern).json\n",
|
| 1697 |
+
"🚀 Fetching data for sweetie_(ragnarok_online) (28/35559)...\n",
|
| 1698 |
+
"✅ Saved sweetie_(ragnarok_online) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/sweetie_(ragnarok_online).json\n",
|
| 1699 |
+
"🚀 Fetching data for whisking (29/35559)...\n",
|
| 1700 |
+
"✅ Saved whisking to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/whisking.json\n",
|
| 1701 |
+
"🚀 Fetching data for h&k_hk33 (30/35559)...\n",
|
| 1702 |
+
"✅ Saved h&k_hk33 to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/h&k_hk33.json\n",
|
| 1703 |
+
"🚀 Fetching data for winx_club (31/35559)...\n",
|
| 1704 |
+
"✅ Saved winx_club to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/winx_club.json\n",
|
| 1705 |
+
"🚀 Fetching data for anti-tank_grenade (32/35559)...\n",
|
| 1706 |
+
"✅ Saved anti-tank_grenade to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/anti-tank_grenade.json\n",
|
| 1707 |
+
"🚀 Fetching data for devin_booker (33/35559)...\n",
|
| 1708 |
+
"✅ Saved devin_booker to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/devin_booker.json\n",
|
| 1709 |
+
"🚀 Fetching data for scylla_(azur_lane) (34/35559)...\n",
|
| 1710 |
+
"✅ Saved scylla_(azur_lane) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/scylla_(azur_lane).json\n",
|
| 1711 |
+
"🚀 Fetching data for penance_(arknights) (35/35559)...\n",
|
| 1712 |
+
"✅ Saved penance_(arknights) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/penance_(arknights).json\n",
|
| 1713 |
+
"🚀 Fetching data for toba_(oshiro_project) (36/35559)...\n",
|
| 1714 |
+
"✅ Saved toba_(oshiro_project) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/toba_(oshiro_project).json\n",
|
| 1715 |
+
"🚀 Fetching data for scott_adams_(style) (37/35559)...\n",
|
| 1716 |
+
"✅ Saved scott_adams_(style) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/scott_adams_(style).json\n",
|
| 1717 |
+
"🚀 Fetching data for 502nd_joint_fighter_wing (38/35559)...\n",
|
| 1718 |
+
"✅ Saved 502nd_joint_fighter_wing to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/502nd_joint_fighter_wing.json\n",
|
| 1719 |
+
"🚀 Fetching data for komica_wiki (39/35559)...\n",
|
| 1720 |
+
"✅ Saved komica_wiki to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/komica_wiki.json\n",
|
| 1721 |
+
"🚀 Fetching data for final_fantasy_vi (40/35559)...\n",
|
| 1722 |
+
"✅ Saved final_fantasy_vi to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/final_fantasy_vi.json\n",
|
| 1723 |
+
"🚀 Fetching data for h&k_hk45 (41/35559)...\n",
|
| 1724 |
+
"✅ Saved h&k_hk45 to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/h&k_hk45.json\n",
|
| 1725 |
+
"🚀 Fetching data for saint_seiya (42/35559)...\n",
|
| 1726 |
+
"✅ Saved saint_seiya to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/saint_seiya.json\n",
|
| 1727 |
+
"🚀 Fetching data for ike_(fire_emblem) (43/35559)...\n",
|
| 1728 |
+
"✅ Saved ike_(fire_emblem) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/ike_(fire_emblem).json\n",
|
| 1729 |
+
"🚀 Fetching data for cooperative_breast_smother (44/35559)...\n",
|
| 1730 |
+
"✅ Saved cooperative_breast_smother to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/cooperative_breast_smother.json\n",
|
| 1731 |
+
"🚀 Fetching data for pamiat_merkuria_(azur_lane) (45/35559)...\n",
|
| 1732 |
+
"✅ Saved pamiat_merkuria_(azur_lane) to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/pamiat_merkuria_(azur_lane).json\n",
|
| 1733 |
+
"🚀 Fetching data for ribbon-trimmed_skirt (46/35559)...\n",
|
| 1734 |
+
"✅ Saved ribbon-trimmed_skirt to /shares/weddigen.ki.uzh/laura_wagner/Civitai_page_analysis/Civitai_visualizations/misc/danbooru_donmai/tags_wiki/ribbon-trimmed_skirt.json\n"
|
| 1735 |
+
]
|
| 1736 |
+
}
|
| 1737 |
+
],
|
| 1738 |
+
"source": [
|
| 1739 |
+
"import json\n",
|
| 1740 |
+
"import os\n",
|
| 1741 |
+
"import requests\n",
|
| 1742 |
+
"import time\n",
|
| 1743 |
+
"import urllib.parse\n",
|
| 1744 |
+
"\n",
|
| 1745 |
+
"# Load the cleaned hierarchy JSON\n",
|
| 1746 |
+
"json_file_path = current_dir.parent / \"misc/danbooru_donmai/danbooru_tags_step_03.json\"\n",
|
| 1747 |
+
"with open(json_file_path, \"r\", encoding=\"utf-8\") as file:\n",
|
| 1748 |
+
" tag_hierarchy = json.load(file)\n",
|
| 1749 |
+
"\n",
|
| 1750 |
+
"# Authentication (Replace with your credentials)\n",
|
| 1751 |
+
"USERNAME = username\n",
|
| 1752 |
+
"API_KEY = api_key\n",
|
| 1753 |
+
"\n",
|
| 1754 |
+
"# Base API URL\n",
|
| 1755 |
+
"BASE_URL = \"https://danbooru.donmai.us\"\n",
|
| 1756 |
+
"\n",
|
| 1757 |
+
"# Output folder for JSON results\n",
|
| 1758 |
+
"output_folder = current_dir.parent / \"misc/danbooru_donmai/tags_wiki\"\n",
|
| 1759 |
+
"os.makedirs(output_folder, exist_ok=True) # Ensure the directory exists\n",
|
| 1760 |
+
"\n",
|
| 1761 |
+
"# Function to extract all unique tags from nested dictionaries/lists\n",
|
| 1762 |
+
"def extract_tags(data):\n",
|
| 1763 |
+
" tags = set()\n",
|
| 1764 |
+
" if isinstance(data, dict):\n",
|
| 1765 |
+
" for key, value in data.items():\n",
|
| 1766 |
+
" tags.update(extract_tags(value)) # Recursively extract tags\n",
|
| 1767 |
+
" elif isinstance(data, list):\n",
|
| 1768 |
+
" for item in data:\n",
|
| 1769 |
+
" if isinstance(item, str):\n",
|
| 1770 |
+
" formatted_tag = item.lower().replace(\" \", \"_\") # ✅ Convert to lowercase and replace spaces\n",
|
| 1771 |
+
" tags.add(formatted_tag)\n",
|
| 1772 |
+
" else:\n",
|
| 1773 |
+
" tags.update(extract_tags(item)) # Handle nested lists\n",
|
| 1774 |
+
" return tags\n",
|
| 1775 |
+
"\n",
|
| 1776 |
+
"# Extract all unique tags\n",
|
| 1777 |
+
"all_tags = extract_tags(tag_hierarchy)\n",
|
| 1778 |
+
"print(f\"✅ Extracted {len(all_tags)} unique tags.\")\n",
|
| 1779 |
+
"\n",
|
| 1780 |
+
"# API query function\n",
|
| 1781 |
+
"def fetch_tag_data(tag):\n",
|
| 1782 |
+
" \"\"\"Fetch tag details from Danbooru API.\"\"\"\n",
|
| 1783 |
+
" encoded_tag = urllib.parse.quote(tag, safe=\"\")\n",
|
| 1784 |
+
" api_url = f\"{BASE_URL}/tags.json?search[name]={encoded_tag}&only=id,name,category,post_count,is_deprecated,created_at,updated_at,wiki_page,artist,antecedent_alias,consequent_aliases,antecedent_implications,consequent_implications,dtext_links\"\n",
|
| 1785 |
+
"\n",
|
| 1786 |
+
" try:\n",
|
| 1787 |
+
" response = requests.get(api_url, auth=(USERNAME, API_KEY))\n",
|
| 1788 |
+
" response.raise_for_status()\n",
|
| 1789 |
+
" return response.json()\n",
|
| 1790 |
+
" except requests.exceptions.RequestException as e:\n",
|
| 1791 |
+
" print(f\"⚠️ Error fetching data for '{tag}': {e}\")\n",
|
| 1792 |
+
" return None\n",
|
| 1793 |
+
"\n",
|
| 1794 |
+
"# Process each tag\n",
|
| 1795 |
+
"for idx, tag in enumerate(all_tags):\n",
|
| 1796 |
+
" tag_filename = os.path.join(output_folder, f\"{tag}.json\")\n",
|
| 1797 |
+
"\n",
|
| 1798 |
+
" # Skip if the file already exists to avoid redundant API calls\n",
|
| 1799 |
+
" if os.path.exists(tag_filename):\n",
|
| 1800 |
+
" print(f\"🔄 Skipping {tag}, already saved.\")\n",
|
| 1801 |
+
" continue\n",
|
| 1802 |
+
"\n",
|
| 1803 |
+
" print(f\"🚀 Fetching data for {tag} ({idx+1}/{len(all_tags)})...\")\n",
|
| 1804 |
+
"\n",
|
| 1805 |
+
" tag_data = fetch_tag_data(tag)\n",
|
| 1806 |
+
"\n",
|
| 1807 |
+
" if tag_data:\n",
|
| 1808 |
+
" with open(tag_filename, \"w\", encoding=\"utf-8\") as f:\n",
|
| 1809 |
+
" json.dump(tag_data, f, indent=4, ensure_ascii=False)\n",
|
| 1810 |
+
" print(f\"✅ Saved {tag} to {tag_filename}\")\n",
|
| 1811 |
+
"\n",
|
| 1812 |
+
" # Respect API rate limits\n",
|
| 1813 |
+
" time.sleep(1.5) # Adjust delay if necessary\n",
|
| 1814 |
+
"\n",
|
| 1815 |
+
"print(\"\\n✅ All tags processed and saved in the 'tags/' folder.\")\n"
|
| 1816 |
+
]
|
| 1817 |
+
},
|
| 1818 |
+
{
|
| 1819 |
+
"cell_type": "code",
|
| 1820 |
+
"execution_count": null,
|
| 1821 |
+
"id": "8e364060-36e9-4418-8bdd-6018c9edcc33",
|
| 1822 |
+
"metadata": {},
|
| 1823 |
+
"outputs": [],
|
| 1824 |
+
"source": []
|
| 1825 |
+
}
|
| 1826 |
+
],
|
| 1827 |
+
"metadata": {
|
| 1828 |
+
"kernelspec": {
|
| 1829 |
+
"display_name": "latm",
|
| 1830 |
+
"language": "python",
|
| 1831 |
+
"name": "python3"
|
| 1832 |
+
},
|
| 1833 |
+
"language_info": {
|
| 1834 |
+
"codemirror_mode": {
|
| 1835 |
+
"name": "ipython",
|
| 1836 |
+
"version": 3
|
| 1837 |
+
},
|
| 1838 |
+
"file_extension": ".py",
|
| 1839 |
+
"mimetype": "text/x-python",
|
| 1840 |
+
"name": "python",
|
| 1841 |
+
"nbconvert_exporter": "python",
|
| 1842 |
+
"pygments_lexer": "ipython3",
|
| 1843 |
+
"version": "3.10.15"
|
| 1844 |
+
}
|
| 1845 |
+
},
|
| 1846 |
+
"nbformat": 4,
|
| 1847 |
+
"nbformat_minor": 5
|
| 1848 |
+
}
|
jupyter_notebooks/SuppM_Figure_S14_co-occurence_training_data.ipynb
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# Prepare *.json for Figure 13"
|
| 8 |
+
]
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"cell_type": "code",
|
| 12 |
+
"execution_count": null,
|
| 13 |
+
"metadata": {},
|
| 14 |
+
"outputs": [],
|
| 15 |
+
"source": [
|
| 16 |
+
"import pandas as pd\n",
|
| 17 |
+
"import json\n",
|
| 18 |
+
"from itertools import combinations\n",
|
| 19 |
+
"from collections import Counter\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"# Load the data with only needed columns\n",
|
| 22 |
+
"csv_file_path = \"all_models_with_tags.csv\" # Update with your actual file path\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"df = pd.read_csv(csv_file_path, low_memory=False)\n",
|
| 25 |
+
"\n",
|
| 26 |
+
"# Identify the starting point of tag columns (after 'civitai_id')\n",
|
| 27 |
+
"civitai_index = df.columns.get_loc(\"civitai_id\") + 1\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"df_tags = df.iloc[:, civitai_index:]\n",
|
| 30 |
+
"\n",
|
| 31 |
+
"# Extract tag columns (tag01 to tag199) and corresponding counts\n",
|
| 32 |
+
"tag_columns = [col for col in df_tags.columns if col.startswith(\"tag\") and col[3:].isdigit() and int(col[3:]) <= 199]\n",
|
| 33 |
+
"tag_no_columns = [col for col in df_tags.columns if col.startswith(\"tag\") and col.endswith(\"_no\")]\n",
|
| 34 |
+
"\n",
|
| 35 |
+
"if not tag_columns:\n",
|
| 36 |
+
" print(\"No tag columns found in the dataset.\")\n",
|
| 37 |
+
" exit()\n",
|
| 38 |
+
"\n",
|
| 39 |
+
"df_tags = df[tag_columns]\n",
|
| 40 |
+
"df_tag_counts = df[tag_no_columns] if tag_no_columns else None\n",
|
| 41 |
+
"\n",
|
| 42 |
+
"# Load tag categories from JSON\n",
|
| 43 |
+
"json_file_path = \"danbooru_tags_step_03.json\" # Update with your actual file path\n",
|
| 44 |
+
"with open(json_file_path, \"r\", encoding=\"utf-8\") as f:\n",
|
| 45 |
+
" tag_categories = json.load(f)\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"# Function to normalize tags (lowercase and replace underscores)\n",
|
| 48 |
+
"def normalize_tag(tag):\n",
|
| 49 |
+
" return tag.lower().replace(\"_\", \" \") if isinstance(tag, str) else tag\n",
|
| 50 |
+
"\n",
|
| 51 |
+
"# Flatten JSON structure to map tags to top-level categories\n",
|
| 52 |
+
"def extract_tag_categories(json_data, current_category=None):\n",
|
| 53 |
+
" tag_mapping = {}\n",
|
| 54 |
+
" for key, value in json_data.items():\n",
|
| 55 |
+
" if isinstance(value, dict):\n",
|
| 56 |
+
" tag_mapping.update(extract_tag_categories(value, key))\n",
|
| 57 |
+
" elif isinstance(value, list):\n",
|
| 58 |
+
" for tag in value:\n",
|
| 59 |
+
" normalized_tag = normalize_tag(tag)\n",
|
| 60 |
+
" if normalized_tag:\n",
|
| 61 |
+
" tag_mapping[normalized_tag] = current_category\n",
|
| 62 |
+
" return tag_mapping\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"# Create mapping of tags to categories\n",
|
| 65 |
+
"tag_category_mapping = extract_tag_categories(tag_categories)\n",
|
| 66 |
+
"\n",
|
| 67 |
+
"# Flatten and count occurrences of individual tags\n",
|
| 68 |
+
"all_tags = []\n",
|
| 69 |
+
"tag_counts = Counter()\n",
|
| 70 |
+
"\n",
|
| 71 |
+
"for i, row in df_tags.iterrows():\n",
|
| 72 |
+
" tags = [normalize_tag(tag) for tag in row if pd.notna(tag)]\n",
|
| 73 |
+
" if df_tag_counts is not None:\n",
|
| 74 |
+
" counts = df_tag_counts.iloc[i].fillna(1).tolist()\n",
|
| 75 |
+
" else:\n",
|
| 76 |
+
" counts = [1] * len(tags)\n",
|
| 77 |
+
" for tag, count in zip(tags, counts):\n",
|
| 78 |
+
" all_tags.append(tag)\n",
|
| 79 |
+
" tag_counts[tag] += count\n",
|
| 80 |
+
"\n",
|
| 81 |
+
"if not all_tags:\n",
|
| 82 |
+
" print(\"No valid tags found in the dataset.\")\n",
|
| 83 |
+
" exit()\n",
|
| 84 |
+
"print(f\"Extracted {len(all_tags)} total tags.\")\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"# Compute co-occurrence frequencies efficiently\n",
|
| 87 |
+
"co_occurrences = Counter()\n",
|
| 88 |
+
"for i, row in df_tags.iterrows():\n",
|
| 89 |
+
" tags = [normalize_tag(tag) for tag in row if pd.notna(tag)]\n",
|
| 90 |
+
" if len(tags) < 2:\n",
|
| 91 |
+
" continue # Skip rows with fewer than two tags\n",
|
| 92 |
+
" for tag1, tag2 in combinations(tags, 2):\n",
|
| 93 |
+
" co_occurrences[frozenset([tag1, tag2])] += 1\n",
|
| 94 |
+
"\n",
|
| 95 |
+
"if not co_occurrences:\n",
|
| 96 |
+
" print(\"No co-occurrence data found.\")\n",
|
| 97 |
+
" exit()\n",
|
| 98 |
+
"print(f\"Computed {len(co_occurrences)} co-occurrence pairs.\")\n",
|
| 99 |
+
"\n",
|
| 100 |
+
"# Convert co-occurrence counts to a weighted edge list\n",
|
| 101 |
+
"edges = [(tuple(pair)[0], tuple(pair)[1], weight) for pair, weight in co_occurrences.items() if len(pair) == 2]\n",
|
| 102 |
+
"\n",
|
| 103 |
+
"# Create a set of connected tags with a minimum connection threshold\n",
|
| 104 |
+
"min_connections = 5 # Increased threshold to reduce memory usage\n",
|
| 105 |
+
"connected_tags = Counter()\n",
|
| 106 |
+
"for tag1, tag2, _ in edges:\n",
|
| 107 |
+
" connected_tags[tag1] += 1\n",
|
| 108 |
+
" connected_tags[tag2] += 1\n",
|
| 109 |
+
"\n",
|
| 110 |
+
"# Filter nodes to keep only those that meet the minimum connection threshold\n",
|
| 111 |
+
"nodes = [\n",
|
| 112 |
+
" {\"id\": tag, \"size\": count, \"category\": tag_category_mapping.get(tag, \"unknown\")}\n",
|
| 113 |
+
" for tag, count in tag_counts.items()\n",
|
| 114 |
+
" if connected_tags[tag] >= min_connections\n",
|
| 115 |
+
"]\n",
|
| 116 |
+
"\n",
|
| 117 |
+
"if not nodes:\n",
|
| 118 |
+
" print(\"No nodes meet the connection threshold.\")\n",
|
| 119 |
+
" exit()\n",
|
| 120 |
+
"print(f\"Final node count: {len(nodes)}\")\n",
|
| 121 |
+
"\n",
|
| 122 |
+
"links = [\n",
|
| 123 |
+
" {\"source\": tag1, \"target\": tag2, \"value\": weight}\n",
|
| 124 |
+
" for tag1, tag2, weight in edges\n",
|
| 125 |
+
" if connected_tags[tag1] >= min_connections and connected_tags[tag2] >= min_connections\n",
|
| 126 |
+
"]\n",
|
| 127 |
+
"\n",
|
| 128 |
+
"if not links:\n",
|
| 129 |
+
" print(\"No links meet the connection threshold.\")\n",
|
| 130 |
+
" exit()\n",
|
| 131 |
+
"print(f\"Final link count: {len(links)}\")\n",
|
| 132 |
+
"\n",
|
| 133 |
+
"# Prepare JSON output\n",
|
| 134 |
+
"d3_data = {\"nodes\": nodes, \"links\": links}\n",
|
| 135 |
+
"\n",
|
| 136 |
+
"# Save as JSON for visualization\n",
|
| 137 |
+
"output_file = \"co_occurrence_network.json\"\n",
|
| 138 |
+
"with open(output_file, \"w\", encoding=\"utf-8\") as f:\n",
|
| 139 |
+
" json.dump(d3_data, f, indent=4)\n",
|
| 140 |
+
"\n",
|
| 141 |
+
"print(f\"D3.js data saved to {output_file}\")"
|
| 142 |
+
]
|
| 143 |
+
}
|
| 144 |
+
],
|
| 145 |
+
"metadata": {
|
| 146 |
+
"language_info": {
|
| 147 |
+
"name": "python"
|
| 148 |
+
}
|
| 149 |
+
},
|
| 150 |
+
"nbformat": 4,
|
| 151 |
+
"nbformat_minor": 2
|
| 152 |
+
}
|
misc/api_keys_example.txt
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
api_key_01
|
| 2 |
-
api_key_02
|
|
|
|
|
|
|
|
|
misc/danbooru/tag_info_test/wombat_tag_info.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tag_details": [
|
| 3 |
+
{
|
| 4 |
+
"id": 462478,
|
| 5 |
+
"name": "wombat",
|
| 6 |
+
"post_count": 32,
|
| 7 |
+
"category": 0,
|
| 8 |
+
"created_at": "2013-02-28T04:35:29.653-05:00",
|
| 9 |
+
"updated_at": "2019-08-30T03:37:52.993-04:00",
|
| 10 |
+
"is_deprecated": false,
|
| 11 |
+
"consequent_aliases": [],
|
| 12 |
+
"antecedent_implications": [],
|
| 13 |
+
"consequent_implications": [],
|
| 14 |
+
"dtext_links": [
|
| 15 |
+
{
|
| 16 |
+
"id": 11937824,
|
| 17 |
+
"created_at": "2024-07-30T13:49:16.169-04:00",
|
| 18 |
+
"updated_at": "2024-07-30T13:49:16.169-04:00",
|
| 19 |
+
"model_type": "WikiPage",
|
| 20 |
+
"model_id": 57363,
|
| 21 |
+
"link_type": "wiki_link",
|
| 22 |
+
"link_target": "wombat"
|
| 23 |
+
}
|
| 24 |
+
]
|
| 25 |
+
}
|
| 26 |
+
]
|
| 27 |
+
}
|
misc/deepseek_query_index.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
4
|
misc/deletemememe.txt
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
misc/lists/countries.csv
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
num_code,alpha_2_code,alpha_3_code,en_short_name,nationality
|
| 2 |
+
4,AF,AFG,Afghanistan,Afghan
|
| 3 |
+
248,AX,ALA,Åland Islands,Åland Island
|
| 4 |
+
8,AL,ALB,Albania,Albanian
|
| 5 |
+
12,DZ,DZA,Algeria,Algerian
|
| 6 |
+
16,AS,ASM,American Samoa,American Samoan
|
| 7 |
+
20,AD,AND,Andorra,Andorran
|
| 8 |
+
24,AO,AGO,Angola,Angolan
|
| 9 |
+
660,AI,AIA,Anguilla,Anguillan
|
| 10 |
+
10,AQ,ATA,Antarctica,Antarctic
|
| 11 |
+
28,AG,ATG,Antigua and Barbuda,Antiguan or Barbudan
|
| 12 |
+
32,AR,ARG,Argentina,Argentine
|
| 13 |
+
51,AM,ARM,Armenia,Armenian
|
| 14 |
+
533,AW,ABW,Aruba,Aruban
|
| 15 |
+
36,AU,AUS,Australia,Australian
|
| 16 |
+
40,AT,AUT,Austria,Austrian
|
| 17 |
+
31,AZ,AZE,Azerbaijan,"Azerbaijani, Azeri"
|
| 18 |
+
44,BS,BHS,Bahamas,Bahamian
|
| 19 |
+
48,BH,BHR,Bahrain,Bahraini
|
| 20 |
+
50,BD,BGD,Bangladesh,Bangladeshi
|
| 21 |
+
52,BB,BRB,Barbados,Barbadian
|
| 22 |
+
112,BY,BLR,Belarus,Belarusian
|
| 23 |
+
56,BE,BEL,Belgium,Belgian
|
| 24 |
+
84,BZ,BLZ,Belize,Belizean
|
| 25 |
+
204,BJ,BEN,Benin,"Beninese, Beninois"
|
| 26 |
+
60,BM,BMU,Bermuda,"Bermudian, Bermudan"
|
| 27 |
+
64,BT,BTN,Bhutan,Bhutanese
|
| 28 |
+
68,BO,BOL,Bolivia (Plurinational State of),Bolivian
|
| 29 |
+
535,BQ,BES,"Bonaire, Sint Eustatius and Saba",Bonaire
|
| 30 |
+
70,BA,BIH,Bosnia and Herzegovina,Bosnian or Herzegovinian
|
| 31 |
+
72,BW,BWA,Botswana,"Motswana, Botswanan"
|
| 32 |
+
74,BV,BVT,Bouvet Island,Bouvet Island
|
| 33 |
+
76,BR,BRA,Brazil,Brazilian
|
| 34 |
+
86,IO,IOT,British Indian Ocean Territory,BIOT
|
| 35 |
+
96,BN,BRN,Brunei Darussalam,Bruneian
|
| 36 |
+
100,BG,BGR,Bulgaria,Bulgarian
|
| 37 |
+
854,BF,BFA,Burkina Faso,Burkinabé
|
| 38 |
+
108,BI,BDI,Burundi,Burundian
|
| 39 |
+
132,CV,CPV,Cabo Verde,Cabo Verdean
|
| 40 |
+
116,KH,KHM,Cambodia,Cambodian
|
| 41 |
+
120,CM,CMR,Cameroon,Cameroonian
|
| 42 |
+
124,CA,CAN,Canada,Canadian
|
| 43 |
+
136,KY,CYM,Cayman Islands,Caymanian
|
| 44 |
+
140,CF,CAF,Central African Republic,Central African
|
| 45 |
+
148,TD,TCD,Chad,Chadian
|
| 46 |
+
152,CL,CHL,Chile,Chilean
|
| 47 |
+
156,CN,CHN,China,Chinese
|
| 48 |
+
162,CX,CXR,Christmas Island,Christmas Island
|
| 49 |
+
166,CC,CCK,Cocos (Keeling) Islands,Cocos Island
|
| 50 |
+
170,CO,COL,Colombia,Colombian
|
| 51 |
+
174,KM,COM,Comoros,"Comoran, Comorian"
|
| 52 |
+
178,CG,COG,Congo (Republic of the),Congolese
|
| 53 |
+
180,CD,COD,Congo (Democratic Republic of the),Congolese
|
| 54 |
+
184,CK,COK,Cook Islands,Cook Island
|
| 55 |
+
188,CR,CRI,Costa Rica,Costa Rican
|
| 56 |
+
384,CI,CIV,Côte d'Ivoire,Ivorian
|
| 57 |
+
191,HR,HRV,Croatia,Croatian
|
| 58 |
+
192,CU,CUB,Cuba,Cuban
|
| 59 |
+
531,CW,CUW,Curaçao,Curaçaoan
|
| 60 |
+
196,CY,CYP,Cyprus,Cypriot
|
| 61 |
+
203,CZ,CZE,Czech Republic,Czech
|
| 62 |
+
208,DK,DNK,Denmark,Danish
|
| 63 |
+
262,DJ,DJI,Djibouti,Djiboutian
|
| 64 |
+
212,DM,DMA,Dominica,Dominican
|
| 65 |
+
214,DO,DOM,Dominican Republic,Dominican
|
| 66 |
+
218,EC,ECU,Ecuador,Ecuadorian
|
| 67 |
+
818,EG,EGY,Egypt,Egyptian
|
| 68 |
+
222,SV,SLV,El Salvador,Salvadoran
|
| 69 |
+
226,GQ,GNQ,Equatorial Guinea,"Equatorial Guinean, Equatoguinean"
|
| 70 |
+
232,ER,ERI,Eritrea,Eritrean
|
| 71 |
+
233,EE,EST,Estonia,Estonian
|
| 72 |
+
231,ET,ETH,Ethiopia,Ethiopian
|
| 73 |
+
238,FK,FLK,Falkland Islands (Malvinas),Falkland Island
|
| 74 |
+
234,FO,FRO,Faroe Islands,Faroese
|
| 75 |
+
242,FJ,FJI,Fiji,Fijian
|
| 76 |
+
246,FI,FIN,Finland,Finnish
|
| 77 |
+
250,FR,FRA,France,French
|
| 78 |
+
254,GF,GUF,French Guiana,French Guianese
|
| 79 |
+
258,PF,PYF,French Polynesia,French Polynesian
|
| 80 |
+
260,TF,ATF,French Southern Territories,French Southern Territories
|
| 81 |
+
266,GA,GAB,Gabon,Gabonese
|
| 82 |
+
270,GM,GMB,Gambia,Gambian
|
| 83 |
+
268,GE,GEO,Georgia,Georgian
|
| 84 |
+
276,DE,DEU,Germany,German
|
| 85 |
+
288,GH,GHA,Ghana,Ghanaian
|
| 86 |
+
292,GI,GIB,Gibraltar,Gibraltar
|
| 87 |
+
300,GR,GRC,Greece,"Greek, Hellenic"
|
| 88 |
+
304,GL,GRL,Greenland,Greenlandic
|
| 89 |
+
308,GD,GRD,Grenada,Grenadian
|
| 90 |
+
312,GP,GLP,Guadeloupe,Guadeloupe
|
| 91 |
+
316,GU,GUM,Guam,"Guamanian, Guambat"
|
| 92 |
+
320,GT,GTM,Guatemala,Guatemalan
|
| 93 |
+
831,GG,GGY,Guernsey,Channel Island
|
| 94 |
+
324,GN,GIN,Guinea,Guinean
|
| 95 |
+
624,GW,GNB,Guinea-Bissau,Bissau-Guinean
|
| 96 |
+
328,GY,GUY,Guyana,Guyanese
|
| 97 |
+
332,HT,HTI,Haiti,Haitian
|
| 98 |
+
334,HM,HMD,Heard Island and McDonald Islands,Heard Island or McDonald Islands
|
| 99 |
+
336,VA,VAT,Vatican City State,Vatican
|
| 100 |
+
340,HN,HND,Honduras,Honduran
|
| 101 |
+
344,HK,HKG,Hong Kong,"Hong Kong, Hong Kongese"
|
| 102 |
+
348,HU,HUN,Hungary,"Hungarian, Magyar"
|
| 103 |
+
352,IS,ISL,Iceland,Icelandic
|
| 104 |
+
356,IN,IND,India,Indian
|
| 105 |
+
360,ID,IDN,Indonesia,Indonesian
|
| 106 |
+
364,IR,IRN,Iran,"Iranian, Persian"
|
| 107 |
+
368,IQ,IRQ,Iraq,Iraqi
|
| 108 |
+
372,IE,IRL,Ireland,Irish
|
| 109 |
+
833,IM,IMN,Isle of Man,Manx
|
| 110 |
+
376,IL,ISR,Israel,Israeli
|
| 111 |
+
380,IT,ITA,Italy,Italian
|
| 112 |
+
388,JM,JAM,Jamaica,Jamaican
|
| 113 |
+
392,JP,JPN,Japan,Japanese
|
| 114 |
+
832,JE,JEY,Jersey,Channel Island
|
| 115 |
+
400,JO,JOR,Jordan,Jordanian
|
| 116 |
+
398,KZ,KAZ,Kazakhstan,"Kazakhstani, Kazakh"
|
| 117 |
+
404,KE,KEN,Kenya,Kenyan
|
| 118 |
+
296,KI,KIR,Kiribati,I-Kiribati
|
| 119 |
+
408,KP,PRK,Korea (Democratic People's Republic of),North Korean
|
| 120 |
+
410,KR,KOR,Korea (Republic of),South Korean
|
| 121 |
+
414,KW,KWT,Kuwait,Kuwaiti
|
| 122 |
+
417,KG,KGZ,Kyrgyzstan,"Kyrgyzstani, Kyrgyz, Kirgiz, Kirghiz"
|
| 123 |
+
418,LA,LAO,Lao People's Democratic Republic,"Lao, Laotian"
|
| 124 |
+
428,LV,LVA,Latvia,Latvian
|
| 125 |
+
422,LB,LBN,Lebanon,Lebanese
|
| 126 |
+
426,LS,LSO,Lesotho,Basotho
|
| 127 |
+
430,LR,LBR,Liberia,Liberian
|
| 128 |
+
434,LY,LBY,Libya,Libyan
|
| 129 |
+
438,LI,LIE,Liechtenstein,Liechtenstein
|
| 130 |
+
440,LT,LTU,Lithuania,Lithuanian
|
| 131 |
+
442,LU,LUX,Luxembourg,"Luxembourg, Luxembourgish"
|
| 132 |
+
446,MO,MAC,Macao,"Macanese, Chinese"
|
| 133 |
+
807,MK,MKD,Macedonia (the former Yugoslav Republic of),Macedonian
|
| 134 |
+
450,MG,MDG,Madagascar,Malagasy
|
| 135 |
+
454,MW,MWI,Malawi,Malawian
|
| 136 |
+
458,MY,MYS,Malaysia,Malaysian
|
| 137 |
+
462,MV,MDV,Maldives,Maldivian
|
| 138 |
+
466,ML,MLI,Mali,"Malian, Malinese"
|
| 139 |
+
470,MT,MLT,Malta,Maltese
|
| 140 |
+
584,MH,MHL,Marshall Islands,Marshallese
|
| 141 |
+
474,MQ,MTQ,Martinique,"Martiniquais, Martinican"
|
| 142 |
+
478,MR,MRT,Mauritania,Mauritanian
|
| 143 |
+
480,MU,MUS,Mauritius,Mauritian
|
| 144 |
+
175,YT,MYT,Mayotte,Mahoran
|
| 145 |
+
484,MX,MEX,Mexico,Mexican
|
| 146 |
+
583,FM,FSM,Micronesia (Federated States of),Micronesian
|
| 147 |
+
498,MD,MDA,Moldova (Republic of),Moldovan
|
| 148 |
+
492,MC,MCO,Monaco,"Monégasque, Monacan"
|
| 149 |
+
496,MN,MNG,Mongolia,Mongolian
|
| 150 |
+
499,ME,MNE,Montenegro,Montenegrin
|
| 151 |
+
500,MS,MSR,Montserrat,Montserratian
|
| 152 |
+
504,MA,MAR,Morocco,Moroccan
|
| 153 |
+
508,MZ,MOZ,Mozambique,Mozambican
|
| 154 |
+
104,MM,MMR,Myanmar,Burmese
|
| 155 |
+
516,NA,NAM,Namibia,Namibian
|
| 156 |
+
520,NR,NRU,Nauru,Nauruan
|
| 157 |
+
524,NP,NPL,Nepal,"Nepali, Nepalese"
|
| 158 |
+
528,NL,NLD,Netherlands,"Dutch, Netherlandic"
|
| 159 |
+
540,NC,NCL,New Caledonia,New Caledonian
|
| 160 |
+
554,NZ,NZL,New Zealand,"New Zealand, NZ"
|
| 161 |
+
558,NI,NIC,Nicaragua,Nicaraguan
|
| 162 |
+
562,NE,NER,Niger,Nigerien
|
| 163 |
+
566,NG,NGA,Nigeria,Nigerian
|
| 164 |
+
570,NU,NIU,Niue,Niuean
|
| 165 |
+
574,NF,NFK,Norfolk Island,Norfolk Island
|
| 166 |
+
580,MP,MNP,Northern Mariana Islands,Northern Marianan
|
| 167 |
+
578,NO,NOR,Norway,Norwegian
|
| 168 |
+
512,OM,OMN,Oman,Omani
|
| 169 |
+
586,PK,PAK,Pakistan,Pakistani
|
| 170 |
+
585,PW,PLW,Palau,Palauan
|
| 171 |
+
275,PS,PSE,"Palestine, State of",Palestinian
|
| 172 |
+
591,PA,PAN,Panama,Panamanian
|
| 173 |
+
598,PG,PNG,Papua New Guinea,"Papua New Guinean, Papuan"
|
| 174 |
+
600,PY,PRY,Paraguay,Paraguayan
|
| 175 |
+
604,PE,PER,Peru,Peruvian
|
| 176 |
+
608,PH,PHL,Philippines,"Philippine, Filipino"
|
| 177 |
+
612,PN,PCN,Pitcairn,Pitcairn Island
|
| 178 |
+
616,PL,POL,Poland,Polish
|
| 179 |
+
620,PT,PRT,Portugal,Portuguese
|
| 180 |
+
630,PR,PRI,Puerto Rico,Puerto Rican
|
| 181 |
+
634,QA,QAT,Qatar,Qatari
|
| 182 |
+
638,RE,REU,Réunion,"Réunionese, Réunionnais"
|
| 183 |
+
642,RO,ROU,Romania,Romanian
|
| 184 |
+
643,RU,RUS,Russian Federation,Russian
|
| 185 |
+
646,RW,RWA,Rwanda,Rwandan
|
| 186 |
+
652,BL,BLM,Saint Barthélemy,Barthélemois
|
| 187 |
+
654,SH,SHN,"Saint Helena, Ascension and Tristan da Cunha",Saint Helenian
|
| 188 |
+
659,KN,KNA,Saint Kitts and Nevis,Kittitian or Nevisian
|
| 189 |
+
662,LC,LCA,Saint Lucia,Saint Lucian
|
| 190 |
+
663,MF,MAF,Saint Martin (French part),Saint-Martinoise
|
| 191 |
+
666,PM,SPM,Saint Pierre and Miquelon,Saint-Pierrais or Miquelonnais
|
| 192 |
+
670,VC,VCT,Saint Vincent and the Grenadines,"Saint Vincentian, Vincentian"
|
| 193 |
+
882,WS,WSM,Samoa,Samoan
|
| 194 |
+
674,SM,SMR,San Marino,Sammarinese
|
| 195 |
+
678,ST,STP,Sao Tome and Principe,São Toméan
|
| 196 |
+
682,SA,SAU,Saudi Arabia,"Saudi, Saudi Arabian"
|
| 197 |
+
686,SN,SEN,Senegal,Senegalese
|
| 198 |
+
688,RS,SRB,Serbia,Serbian
|
| 199 |
+
690,SC,SYC,Seychelles,Seychellois
|
| 200 |
+
694,SL,SLE,Sierra Leone,Sierra Leonean
|
| 201 |
+
702,SG,SGP,Singapore,Singaporean
|
| 202 |
+
534,SX,SXM,Sint Maarten (Dutch part),Sint Maarten
|
| 203 |
+
703,SK,SVK,Slovakia,Slovak
|
| 204 |
+
705,SI,SVN,Slovenia,"Slovenian, Slovene"
|
| 205 |
+
90,SB,SLB,Solomon Islands,Solomon Island
|
| 206 |
+
706,SO,SOM,Somalia,"Somali, Somalian"
|
| 207 |
+
710,ZA,ZAF,South Africa,South African
|
| 208 |
+
239,GS,SGS,South Georgia and the South Sandwich Islands,South Georgia or South Sandwich Islands
|
| 209 |
+
728,SS,SSD,South Sudan,South Sudanese
|
| 210 |
+
724,ES,ESP,Spain,Spanish
|
| 211 |
+
144,LK,LKA,Sri Lanka,Sri Lankan
|
| 212 |
+
729,SD,SDN,Sudan,Sudanese
|
| 213 |
+
740,SR,SUR,Suriname,Surinamese
|
| 214 |
+
744,SJ,SJM,Svalbard and Jan Mayen,Svalbard
|
| 215 |
+
748,SZ,SWZ,Swaziland,Swazi
|
| 216 |
+
752,SE,SWE,Sweden,Swedish
|
| 217 |
+
756,CH,CHE,Switzerland,Swiss
|
| 218 |
+
760,SY,SYR,Syrian Arab Republic,Syrian
|
| 219 |
+
158,TW,TWN,"Taiwan, Province of China","Chinese, Taiwanese"
|
| 220 |
+
762,TJ,TJK,Tajikistan,Tajikistani
|
| 221 |
+
834,TZ,TZA,"Tanzania, United Republic of",Tanzanian
|
| 222 |
+
764,TH,THA,Thailand,Thai
|
| 223 |
+
626,TL,TLS,Timor-Leste,Timorese
|
| 224 |
+
768,TG,TGO,Togo,Togolese
|
| 225 |
+
772,TK,TKL,Tokelau,Tokelauan
|
| 226 |
+
776,TO,TON,Tonga,Tongan
|
| 227 |
+
780,TT,TTO,Trinidad and Tobago,Trinidadian or Tobagonian
|
| 228 |
+
788,TN,TUN,Tunisia,Tunisian
|
| 229 |
+
792,TR,TUR,Turkey,Turkish
|
| 230 |
+
795,TM,TKM,Turkmenistan,Turkmen
|
| 231 |
+
796,TC,TCA,Turks and Caicos Islands,Turks and Caicos Island
|
| 232 |
+
798,TV,TUV,Tuvalu,Tuvaluan
|
| 233 |
+
800,UG,UGA,Uganda,Ugandan
|
| 234 |
+
804,UA,UKR,Ukraine,Ukrainian
|
| 235 |
+
784,AE,ARE,United Arab Emirates,"Emirati, Emirian, Emiri"
|
| 236 |
+
826,GB,GBR,United Kingdom of Great Britain and Northern Ireland,"British, UK"
|
| 237 |
+
581,UM,UMI,United States Minor Outlying Islands,American
|
| 238 |
+
840,US,USA,United States of America,American
|
| 239 |
+
858,UY,URY,Uruguay,Uruguayan
|
| 240 |
+
860,UZ,UZB,Uzbekistan,"Uzbekistani, Uzbek"
|
| 241 |
+
548,VU,VUT,Vanuatu,"Ni-Vanuatu, Vanuatuan"
|
| 242 |
+
862,VE,VEN,Venezuela (Bolivarian Republic of),Venezuelan
|
| 243 |
+
704,VN,VNM,Vietnam,Vietnamese
|
| 244 |
+
92,VG,VGB,Virgin Islands (British),British Virgin Island
|
| 245 |
+
850,VI,VIR,Virgin Islands (U.S.),U.S. Virgin Island
|
| 246 |
+
876,WF,WLF,Wallis and Futuna,"Wallis and Futuna, Wallisian or Futunan"
|
| 247 |
+
732,EH,ESH,Western Sahara,"Sahrawi, Sahrawian, Sahraouian"
|
| 248 |
+
887,YE,YEM,Yemen,Yemeni
|
| 249 |
+
894,ZM,ZMB,Zambia,Zambian
|
| 250 |
+
716,ZW,ZWE,Zimbabwe,Zimbabwean
|
misc/lists/danbooru.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
misc/lists/mapped_professions.csv
ADDED
|
@@ -0,0 +1,1074 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
profession_short,category
|
| 2 |
+
Actor,Actor
|
| 3 |
+
R&B singer,"Singer, Musician"
|
| 4 |
+
Adult film actor,Adult Performer
|
| 5 |
+
J-pop singer,"Singer, Musician"
|
| 6 |
+
Adult film actress,Adult Performer
|
| 7 |
+
Paramedic,Other
|
| 8 |
+
American football quarterback,Sports Professional
|
| 9 |
+
Film actor,Actor
|
| 10 |
+
Musician,"Singer, Musician"
|
| 11 |
+
Baseball player,Sports Professional
|
| 12 |
+
Men's Fashion YouTuber,Influencer
|
| 13 |
+
Unknown,Other
|
| 14 |
+
Television actor,Actor
|
| 15 |
+
Public FIgure,Public FIgure
|
| 16 |
+
Television producer,TV Personality
|
| 17 |
+
Social media influencer,Online Personality
|
| 18 |
+
Fashion model,Model
|
| 19 |
+
Singer,"Singer, Musician"
|
| 20 |
+
Model,Model
|
| 21 |
+
Political commentator,TV Personality
|
| 22 |
+
Fitness influencer,Online Personality
|
| 23 |
+
News anchor,TV Personality
|
| 24 |
+
Television actress,Actor
|
| 25 |
+
DJ,"Singer, Musician"
|
| 26 |
+
Instagram Influencer,Online Personality
|
| 27 |
+
Reality TV personality,Fictional Character
|
| 28 |
+
Fighting game character,Fictional Character
|
| 29 |
+
Video game character,Fictional Character
|
| 30 |
+
Stand-up comedian,Actor
|
| 31 |
+
Comedian,TV Personality
|
| 32 |
+
Medical doctor,Other
|
| 33 |
+
Beauty YouTuber,Online Personality
|
| 34 |
+
Instagram model,Online Personality
|
| 35 |
+
Conservative commentator,TV Personality
|
| 36 |
+
Professional footballer,Sports Professional
|
| 37 |
+
Spy,Fictional Character
|
| 38 |
+
Television presenter,TV Personality
|
| 39 |
+
Professional wrestler,Sports Professional
|
| 40 |
+
U.S. Congressman,Public FIgure
|
| 41 |
+
Film producer,Other
|
| 42 |
+
YouTube vlogger,Online Personality
|
| 43 |
+
U.S. Senator,Public FIgure
|
| 44 |
+
Soul singer,"Singer, Musician"
|
| 45 |
+
Blues singer-songwriter,Online Personality
|
| 46 |
+
Streamer,Online Personality
|
| 47 |
+
Instagram influencer,Online Personality
|
| 48 |
+
Superhero,Fictional Character
|
| 49 |
+
Adult content creator,Adult Performer
|
| 50 |
+
Professional soccer player,Sports Professional
|
| 51 |
+
Yoga instructor,Online Personality
|
| 52 |
+
Prince,Other
|
| 53 |
+
YouTuber,Online Personality
|
| 54 |
+
Amphibian,Other
|
| 55 |
+
Sentient program,Fictional Character
|
| 56 |
+
Pro-democracy activist,Public FIgure
|
| 57 |
+
Electronic musician,"Singer, Musician"
|
| 58 |
+
Pop singer,"Singer, Musician"
|
| 59 |
+
Ballet dancer,Performer
|
| 60 |
+
Voice actress,Voice Actor
|
| 61 |
+
K-pop singer,"Singer, Musician"
|
| 62 |
+
Jedi Padawan,Fictional Character
|
| 63 |
+
Table tennis player,Sports Professional
|
| 64 |
+
Gravure model,Model
|
| 65 |
+
Opera singer,Performer
|
| 66 |
+
Jazz singer,"Singer, Musician"
|
| 67 |
+
Singer-songwriter,"Singer, Musician"
|
| 68 |
+
Dancer,Sports Professional
|
| 69 |
+
Lifestyle YouTuber,Online Personality
|
| 70 |
+
JAV actress,Adult Performer
|
| 71 |
+
AV actress,Adult Performer
|
| 72 |
+
Japanese idol,Idol
|
| 73 |
+
Japanese idol singer,"Singer, Musician"
|
| 74 |
+
Gravure idol,Model
|
| 75 |
+
AV idol,Adult Performer
|
| 76 |
+
Poet,Fictional Character
|
| 77 |
+
Politician,Public Figure
|
| 78 |
+
Manga artist,Fictional Character
|
| 79 |
+
Shoe salesman,Other
|
| 80 |
+
Film director,Other
|
| 81 |
+
Formula One racing driver,Sports Professional
|
| 82 |
+
Mathematician,Other
|
| 83 |
+
Video game protagonist,Fictional Character
|
| 84 |
+
Video game journalist,Journalist
|
| 85 |
+
Auror,Fictional Character
|
| 86 |
+
Landscape painter,Fictional Character
|
| 87 |
+
Theoretical physicist,Other
|
| 88 |
+
Headmaster of Hogwarts School of Witchcraft and Wizardry,Fictional Character
|
| 89 |
+
Wrestler,Sports Professional
|
| 90 |
+
Pop singer-songwriter,"Singer, Musician"
|
| 91 |
+
Rock musician,"Singer, Musician"
|
| 92 |
+
Adult film performer,Adult Performer
|
| 93 |
+
Footballer,Sports Professional
|
| 94 |
+
Professional Dancer,Sports Professional
|
| 95 |
+
Film actress,Actor
|
| 96 |
+
Volleyball player,Sports Professional
|
| 97 |
+
Opposition Politician,Public Figure
|
| 98 |
+
Political strategist,Public Figure
|
| 99 |
+
Medical ethicist,Other
|
| 100 |
+
Tennis player,Sports Professional
|
| 101 |
+
Music producer,"Singer, Musician"
|
| 102 |
+
Radio personality,Voice Actor
|
| 103 |
+
Visionary artist,Fictional Character
|
| 104 |
+
Racing driver,Sports Professional
|
| 105 |
+
Child actor,Actor
|
| 106 |
+
Twitch streamer,Online Personality
|
| 107 |
+
Minecraft YouTuber,Online Personality
|
| 108 |
+
Formula One Driver,Sports Professional
|
| 109 |
+
Radio talk show host,Voice Actor
|
| 110 |
+
Software Engineer,Other
|
| 111 |
+
Stage actor,Performer
|
| 112 |
+
Fictional character,Fictional Character
|
| 113 |
+
Podcast host,Voice Actor
|
| 114 |
+
Freelance Writer,Other
|
| 115 |
+
Socialite,Other
|
| 116 |
+
Chess streamer,Other
|
| 117 |
+
Figure skater,Sports Professional
|
| 118 |
+
Firearms instructor,Other
|
| 119 |
+
Student,Other
|
| 120 |
+
Television writer,Other
|
| 121 |
+
U.S. Representative,Public FIgure
|
| 122 |
+
Journalist,Journalist
|
| 123 |
+
YouTube content creator,"Singer, Musician"
|
| 124 |
+
Parody musician,"Singer, Musician"
|
| 125 |
+
Rapper,"Singer, Musician"
|
| 126 |
+
Supreme Leader of Iran,Public Figure
|
| 127 |
+
Track and field athlete,Sports Professional
|
| 128 |
+
Shock rock musician,"Singer, Musician"
|
| 129 |
+
Vampire,Fictional Character
|
| 130 |
+
Radio presenter,Voice Actor
|
| 131 |
+
Historian,Other
|
| 132 |
+
Webcam model,Adult Performer
|
| 133 |
+
Anime character,Fictional Character
|
| 134 |
+
Author's muse,Other
|
| 135 |
+
Anthropologist,Other
|
| 136 |
+
Noblewoman,Fictional Character
|
| 137 |
+
R&B singer-songwriter,"Singer, Musician"
|
| 138 |
+
Bassist,"Singer, Musician"
|
| 139 |
+
Cosplayer,Model
|
| 140 |
+
Lawyer,Other
|
| 141 |
+
Rhythmic gymnast,Sports Professional
|
| 142 |
+
Classical guitarist,"Singer, Musician"
|
| 143 |
+
Playback singer,"Singer, Musician"
|
| 144 |
+
Professional Footballer,Sports Professional
|
| 145 |
+
Digital artist,Other
|
| 146 |
+
Photorealistic digital artist,Other
|
| 147 |
+
Television host,TV Personality
|
| 148 |
+
Science fiction writer,Other
|
| 149 |
+
Metal vocalist,"Singer, Musician"
|
| 150 |
+
Football goalkeeper,Sports Professional
|
| 151 |
+
Satirical comedian,TV Personality
|
| 152 |
+
News Anchor,TV Personality
|
| 153 |
+
Virtual YouTuber,Fictional Character
|
| 154 |
+
Professional tennis player,Sports Professional
|
| 155 |
+
French pop singer,"Singer, Musician"
|
| 156 |
+
Professional basketball player,Sports Professional
|
| 157 |
+
Country music singer,"Singer, Musician"
|
| 158 |
+
Film critic,Other
|
| 159 |
+
Pole vaulter,Sports Professional
|
| 160 |
+
Hunter-gatherer,Other
|
| 161 |
+
Organized crime boss,Other
|
| 162 |
+
Photorealistic character artist,Other
|
| 163 |
+
Astronaut trainee,Sports Professional
|
| 164 |
+
Mixed martial artist,Sports Professional
|
| 165 |
+
Starfleet officer,Fictional Character
|
| 166 |
+
Hacker,Other
|
| 167 |
+
Aviator,Other
|
| 168 |
+
Techno DJ,Other
|
| 169 |
+
Neuroscientist,Other
|
| 170 |
+
ASMR content creator,Voice Actor
|
| 171 |
+
Alternative rock singer,"Singer, Musician"
|
| 172 |
+
Spaceflight historian,Other
|
| 173 |
+
Associate Justice of the Supreme Court of the United States,Public FIgure
|
| 174 |
+
Sniper,Sports Professional
|
| 175 |
+
Waitress,Other
|
| 176 |
+
MPB singer,"Singer, Musician"
|
| 177 |
+
Jedi Knight,Fictional Character
|
| 178 |
+
Professional surfer,Sports Professional
|
| 179 |
+
Child model,Model
|
| 180 |
+
Glamour model,Model
|
| 181 |
+
Heavy metal vocalist,"Singer, Musician"
|
| 182 |
+
Football midfielder,Sports Professional
|
| 183 |
+
Television character,TV Personality
|
| 184 |
+
Resistance fighter,Public FIgure
|
| 185 |
+
Instagrammer,Online Personality
|
| 186 |
+
Economist,Other
|
| 187 |
+
Motorcycle racer,Sports Professional
|
| 188 |
+
Children's television presenter,TV Personality
|
| 189 |
+
Kickboxer,Sports Professional
|
| 190 |
+
Visual artist,Other
|
| 191 |
+
Electrical engineer,Other
|
| 192 |
+
Cantopop singer,"Singer, Musician"
|
| 193 |
+
Ranchera singer,"Singer, Musician"
|
| 194 |
+
Cybersecurity expert,Other
|
| 195 |
+
Author,Other
|
| 196 |
+
Chef,TV Personality
|
| 197 |
+
Death metal vocalist,"Singer, Musician"
|
| 198 |
+
Live streamer,"Singer, Musician"
|
| 199 |
+
Artistic gymnast,Sports Professional
|
| 200 |
+
Internet personality,Online Personality
|
| 201 |
+
Secret agent,Fictional Character
|
| 202 |
+
Electric guitarist,"Singer, Musician"
|
| 203 |
+
Pornographic actor,Adult Performer
|
| 204 |
+
Android technician,Fictional Character
|
| 205 |
+
Media critic,Other
|
| 206 |
+
News presenter,TV Personality
|
| 207 |
+
Fitness model,Model
|
| 208 |
+
Chess Streamer,Online Personality
|
| 209 |
+
J-pop idol,"Singer, Musician"
|
| 210 |
+
Artist,Other
|
| 211 |
+
Painter,Other
|
| 212 |
+
Rock singer,"Singer, Musician"
|
| 213 |
+
Chess player,Sports Professional
|
| 214 |
+
Fraudster,Fictional Character
|
| 215 |
+
Queen consort,Fictional Character
|
| 216 |
+
Digital model,Model
|
| 217 |
+
Classical violinist,"Singer, Musician"
|
| 218 |
+
Progressive metal vocalist,"Singer, Musician"
|
| 219 |
+
Community college student,Other
|
| 220 |
+
CrossFit athlete,Sports Professional
|
| 221 |
+
Astronaut,Other
|
| 222 |
+
Music critic,Other
|
| 223 |
+
Professional wrestling executive,Sports Professional
|
| 224 |
+
Celebrity chef,TV Personality
|
| 225 |
+
Rugby union player,Sports Professional
|
| 226 |
+
Hitman,Other
|
| 227 |
+
Fashion designer,Other
|
| 228 |
+
Occultist,Other
|
| 229 |
+
Theatre director,Other
|
| 230 |
+
Drug trafficker,Other
|
| 231 |
+
Diplomat,Public FIgure
|
| 232 |
+
Paranormal investigator,Fictional Character
|
| 233 |
+
Voice actor,Voice Actor
|
| 234 |
+
Badminton player,Sports Professional
|
| 235 |
+
Ring card girl,Sports Professional
|
| 236 |
+
Animated film character,Fictional Character
|
| 237 |
+
K-pop idol,"Singer, Musician"
|
| 238 |
+
Military officer,Other
|
| 239 |
+
Action film actor,Actor
|
| 240 |
+
Holographic technician,Other
|
| 241 |
+
Gymnast,Sports Professional
|
| 242 |
+
Environmental activist,Public Figure
|
| 243 |
+
Photorealistic artist,Other
|
| 244 |
+
Businessman,Other
|
| 245 |
+
Elf nobility,Fictional Character
|
| 246 |
+
Pokémon Trainer,Fictional Character
|
| 247 |
+
Idol singer,"Singer, Musician"
|
| 248 |
+
Child actress,Actor
|
| 249 |
+
Pop rock singer,"Singer, Musician"
|
| 250 |
+
Horror film protagonist,Fictional Character
|
| 251 |
+
Musical theatre actress,Performer
|
| 252 |
+
Astronomer,Other
|
| 253 |
+
Labyrinth guardian,Fictional Character
|
| 254 |
+
Mecha pilot,Fictional Character
|
| 255 |
+
Deity of wisdom,Fictional Character
|
| 256 |
+
Science communicator,Other
|
| 257 |
+
Wildlife subject,Other
|
| 258 |
+
J-rock musician,"Singer, Musician"
|
| 259 |
+
Freestyle skier,Sports Professional
|
| 260 |
+
TikTok content creator,Online Personality
|
| 261 |
+
Television personality,TV Personality
|
| 262 |
+
Reality television personality,TV Personality
|
| 263 |
+
Professional gamer,Online Personality
|
| 264 |
+
Contemporary dancer,Performer
|
| 265 |
+
Football coach,Sports Professional
|
| 266 |
+
J-pop singer-songwriter,"Singer, Musician"
|
| 267 |
+
Enka singer,"Singer, Musician"
|
| 268 |
+
Bodybuilder,Fictional Character
|
| 269 |
+
Starfleet Engineer,Fictional Character
|
| 270 |
+
Monarch,Public Figure
|
| 271 |
+
44th President of the United States,Public FIgure
|
| 272 |
+
Fashion doll,Model
|
| 273 |
+
Science fiction film character,Fictional Character
|
| 274 |
+
Children's television character,Actor
|
| 275 |
+
Feudal ruler,Public FIgure
|
| 276 |
+
Forensic scientist,Other
|
| 277 |
+
Crime novelist,Other
|
| 278 |
+
Dark Jedi,Fictional Character
|
| 279 |
+
Stage actress,Actor
|
| 280 |
+
Schlager singer,"Singer, Musician"
|
| 281 |
+
Assassin,Fictional Character
|
| 282 |
+
Knight,Fictional Character
|
| 283 |
+
Social media personality,Online Personality
|
| 284 |
+
Luxury hotel,Other
|
| 285 |
+
Action film character,Fictional Character
|
| 286 |
+
Dark Side Force user,Fictional Character
|
| 287 |
+
Dictator,Public FIgure
|
| 288 |
+
Reggaeton singer,"Singer, Musician"
|
| 289 |
+
Statesman,Public FIgure
|
| 290 |
+
Others Island leader,Fictional Character
|
| 291 |
+
Rugby player,Sports Professional
|
| 292 |
+
Microbiologist,Other
|
| 293 |
+
Business executive,Other
|
| 294 |
+
Pin-up model,Model
|
| 295 |
+
Chief Medical Officer,Other
|
| 296 |
+
Fetish model,Adult Performer
|
| 297 |
+
Influencer,Online Personality
|
| 298 |
+
Undead henchman,Fictional Character
|
| 299 |
+
Laptop technician,Other
|
| 300 |
+
Novelist,Other
|
| 301 |
+
Mandalorian warrior,Fictional Character
|
| 302 |
+
Bounty hunter,Fictional Character
|
| 303 |
+
Jaffa warrior,Fictional Character
|
| 304 |
+
High-performance vehicle tuning,Other
|
| 305 |
+
Starfleet Ensign,Fictional Character
|
| 306 |
+
Fantasy character,Fictional Character
|
| 307 |
+
Fictional noble,Fictional Character
|
| 308 |
+
Former professional motorcycle racer,Sports Professional
|
| 309 |
+
Soccer player,Sports Professional
|
| 310 |
+
Legal tender,Other
|
| 311 |
+
Punk rock singer,"Singer, Musician"
|
| 312 |
+
Soap opera actress,Actor
|
| 313 |
+
Physician,Other
|
| 314 |
+
Federal judge,Public FIgure
|
| 315 |
+
Particle physicist,Other
|
| 316 |
+
Drag performer,Performer
|
| 317 |
+
Political activist,Public FIgure
|
| 318 |
+
Fictional knight,Fictional Character
|
| 319 |
+
Educator,Other
|
| 320 |
+
Sports influencer,Online Personality
|
| 321 |
+
Martial artist,Sports Professional
|
| 322 |
+
Police officer,Other
|
| 323 |
+
Crimefighter,Fictional Character
|
| 324 |
+
Asgardian warrior,Fictional Character
|
| 325 |
+
Professional baseball player,Sports Professional
|
| 326 |
+
Vampire Slayer,Fictional Character
|
| 327 |
+
Soap opera actor,Actor
|
| 328 |
+
Basketball player,Sports Professional
|
| 329 |
+
Cryogenic engineer,Other
|
| 330 |
+
Romance novelist,Other
|
| 331 |
+
Street racer,Sports Professional
|
| 332 |
+
Synthetic humanoid,Other
|
| 333 |
+
Ballet Dancer,Performer
|
| 334 |
+
Sculptor,Other
|
| 335 |
+
Mercenary,Other
|
| 336 |
+
Biologist,Other
|
| 337 |
+
Climate activist,Public FIgure
|
| 338 |
+
Television news anchor,TV Personality
|
| 339 |
+
Photorealistic 3D artist,Other
|
| 340 |
+
Comic book superhero,Fictional Character
|
| 341 |
+
Survivor,Other
|
| 342 |
+
Voice Actress,Voice Actor
|
| 343 |
+
Criminal defendant,Other
|
| 344 |
+
Filmmaker,Other
|
| 345 |
+
Forensic Scientist,Other
|
| 346 |
+
Comic book character,Fictional Character
|
| 347 |
+
Horror film actress,Actor
|
| 348 |
+
Philanthropist,Other
|
| 349 |
+
Sports broadcaster,TV Personality
|
| 350 |
+
Beauty Pageant Titleholder,Model
|
| 351 |
+
Meteorologist,Other
|
| 352 |
+
Musical theatre actor,Performer
|
| 353 |
+
Martial arts actor,Actor
|
| 354 |
+
Statistical analysis and data reconfiguration,Other
|
| 355 |
+
Cryptocurrency executive,Other
|
| 356 |
+
Silent film actor,Actor
|
| 357 |
+
Cryptocurrency entrepreneur,Other
|
| 358 |
+
Organized crime leader,Other
|
| 359 |
+
Cult leader,Public FIgure
|
| 360 |
+
Naturalist,Other
|
| 361 |
+
TikTok personality,Online Personality
|
| 362 |
+
Adult performer,Adult Performer
|
| 363 |
+
Triathlete,Sports Professional
|
| 364 |
+
Internet celebrity,Online Personality
|
| 365 |
+
Alternative metal vocalist,"Singer, Musician"
|
| 366 |
+
Fashion influencer,Online Personality
|
| 367 |
+
Philosophy professor,Other
|
| 368 |
+
Voice acting,Voice Actor
|
| 369 |
+
Drummer,"Singer, Musician"
|
| 370 |
+
Sports presenter,Sports Professional
|
| 371 |
+
K-pop singer-songwriter,"Singer, Musician"
|
| 372 |
+
Technology journalist,Other
|
| 373 |
+
Professional bodybuilder,Other
|
| 374 |
+
Video game designer,Other
|
| 375 |
+
Percussionist,"Singer, Musician"
|
| 376 |
+
Music composer,"Singer, Musician"
|
| 377 |
+
Enchantress,Fictional Character
|
| 378 |
+
Witcher,Fictional Character
|
| 379 |
+
Funeral director,Other
|
| 380 |
+
Special operations agent,Fictional Character
|
| 381 |
+
Adult model,Adult Performer
|
| 382 |
+
Medical practitioner,Other
|
| 383 |
+
Scientist,Other
|
| 384 |
+
Football club president,Sports Professional
|
| 385 |
+
Soldier,Other
|
| 386 |
+
Erotic film actor,Adult Performer
|
| 387 |
+
Flutist,"Singer, Musician"
|
| 388 |
+
Investor,Other
|
| 389 |
+
Stunt performer,Sports Professional
|
| 390 |
+
Fictional warrior,Other
|
| 391 |
+
Political scientist,Other
|
| 392 |
+
Game show model,TV Personality
|
| 393 |
+
Mixed Martial Artist,Sports Professional
|
| 394 |
+
Alpine skier,Sports Professional
|
| 395 |
+
Nobleman,Other
|
| 396 |
+
Metalcore vocalist,"Singer, Musician"
|
| 397 |
+
YouTube Personality,Online Personality
|
| 398 |
+
Software engineer,Other
|
| 399 |
+
Sertanejo singer,"Singer, Musician"
|
| 400 |
+
Explorer,Other
|
| 401 |
+
Documentary filmmaker,Other
|
| 402 |
+
Jazz vocalist,"Singer, Musician"
|
| 403 |
+
Military commander,Other
|
| 404 |
+
Sprinter,Sports Professional
|
| 405 |
+
New wave singer,"Singer, Musician"
|
| 406 |
+
Japanese adult film actor,Adult Performer
|
| 407 |
+
Concert cellist,"Singer, Musician"
|
| 408 |
+
FBI Special Agent,Fictional Character
|
| 409 |
+
Professional race car driver,Sports Professional
|
| 410 |
+
Professional motorcycle racer,Sports Professional
|
| 411 |
+
Film editor,Other
|
| 412 |
+
American football wide receiver,Sports Professional
|
| 413 |
+
Leadership Speaker,Public FIgure
|
| 414 |
+
Video game developer,Other
|
| 415 |
+
Heavy metal singer,"Singer, Musician"
|
| 416 |
+
Japanese comedian,TV Personality
|
| 417 |
+
Heavy metal guitarist,"Singer, Musician"
|
| 418 |
+
Biathlete,Sports Professional
|
| 419 |
+
Automotive model,Model
|
| 420 |
+
Wrestling journalist,Sports Professional
|
| 421 |
+
Broadcaster,TV Personality
|
| 422 |
+
Ultramarathon runner,Sports Professional
|
| 423 |
+
Spaceship technician,Fictional Character
|
| 424 |
+
Religious leader,Other
|
| 425 |
+
Fitness trainer,Other
|
| 426 |
+
Ship's counselor,Fictional Character
|
| 427 |
+
Magician,Other
|
| 428 |
+
Xiangsheng performer,Performer
|
| 429 |
+
Traffic reporter,Other
|
| 430 |
+
Entrepreneur,Other
|
| 431 |
+
Cybersecurity researcher,Other
|
| 432 |
+
Torturer,Other
|
| 433 |
+
Car dealership owner,Other
|
| 434 |
+
Bartender,Other
|
| 435 |
+
Male model,Model
|
| 436 |
+
American football player,Sports Professional
|
| 437 |
+
Television comedian,TV Personality
|
| 438 |
+
Gigolo,Adult Performer
|
| 439 |
+
Forensic blood spatter analyst,Other
|
| 440 |
+
Swordswoman,Fictional Character
|
| 441 |
+
Symphonic metal vocalist,"Singer, Musician"
|
| 442 |
+
Record producer,Other
|
| 443 |
+
Photographer,Other
|
| 444 |
+
Track and Field Athlete,Sports Professional
|
| 445 |
+
Fashion YouTuber,Online Personality
|
| 446 |
+
Thai actor,Actor
|
| 447 |
+
Automotive designer,Other
|
| 448 |
+
Burlesque performer,Actor
|
| 449 |
+
Film translator,Other
|
| 450 |
+
Gambler,Other
|
| 451 |
+
Country music singer-songwriter,"Singer, Musician"
|
| 452 |
+
Host,TV Personality
|
| 453 |
+
Bureaucrat,Other
|
| 454 |
+
Alternative rock singer-songwriter,"Singer, Musician"
|
| 455 |
+
Professional wrestling manager,Sports Professional
|
| 456 |
+
Advertising executive,Other
|
| 457 |
+
Real estate developer,Other
|
| 458 |
+
Romulan Military Officer,Other
|
| 459 |
+
High school student,Other
|
| 460 |
+
Sitcom character,Actor
|
| 461 |
+
Sith Lord,Fictional Character
|
| 462 |
+
Computer scientist,Other
|
| 463 |
+
Silent film actress,Actor
|
| 464 |
+
Supervillain,Fictional Character
|
| 465 |
+
Intergalactic warrior,Fictional Character
|
| 466 |
+
American football cornerback,Sports Professional
|
| 467 |
+
Anthropomorphic personification of dreams,Fictional Character
|
| 468 |
+
Bail bondsman,Fictional Character
|
| 469 |
+
Science fiction television character,Fictional Character
|
| 470 |
+
Paper salesman,Other
|
| 471 |
+
Investigative journalist,Other
|
| 472 |
+
Motorcycle mechanic,Other
|
| 473 |
+
Professional dancer,Sports Professional
|
| 474 |
+
Esports host,Online Personality
|
| 475 |
+
Punk musician,"Singer, Musician"
|
| 476 |
+
New-age musician,"Singer, Musician"
|
| 477 |
+
Sitcom actor,Actor
|
| 478 |
+
Supreme Court Justice,Other
|
| 479 |
+
Anime voice actor,Voice Actor
|
| 480 |
+
Prophet,Public FIgure
|
| 481 |
+
Pre-Raphaelite model,Other
|
| 482 |
+
Equestrian athlete,Sports Professional
|
| 483 |
+
Rock vocalist,"Singer, Musician"
|
| 484 |
+
Talk show host,TV Personality
|
| 485 |
+
Warrant Officer,Other
|
| 486 |
+
Performance artist,Other
|
| 487 |
+
Cricketer,Sports Professional
|
| 488 |
+
Movie character,Fictional Character
|
| 489 |
+
CEO of Tesla,Other
|
| 490 |
+
Horror host,Other
|
| 491 |
+
Rock and roll singer,"Singer, Musician"
|
| 492 |
+
Artificial Intelligence Researcher,Other
|
| 493 |
+
Turkish classical music singer,"Singer, Musician"
|
| 494 |
+
Automotive YouTuber,Online Personality
|
| 495 |
+
Latin pop singer,"Singer, Musician"
|
| 496 |
+
Fictional animatronic character,Fictional Character
|
| 497 |
+
Professional boxer,Sports Professional
|
| 498 |
+
Saxophonist,"Singer, Musician"
|
| 499 |
+
Rock singer-songwriter,"Singer, Musician"
|
| 500 |
+
Time traveler,Fictional Character
|
| 501 |
+
Shieldmaiden,Fictional Character
|
| 502 |
+
Vampire hunter,Fictional Character
|
| 503 |
+
Gospel singer,"Singer, Musician"
|
| 504 |
+
Violinist,"Singer, Musician"
|
| 505 |
+
Convicted felon,Other
|
| 506 |
+
Classical pianist,"Singer, Musician"
|
| 507 |
+
Fashion Stylist,Other
|
| 508 |
+
Game designer,Other
|
| 509 |
+
Revolutionary leader,Public FIgure
|
| 510 |
+
Revolutionary,Public FIgure
|
| 511 |
+
Neo-soul singer-songwriter,"Singer, Musician"
|
| 512 |
+
Formula One driver,Sports Professional
|
| 513 |
+
Evangelist,Other
|
| 514 |
+
Game show host,TV Personality
|
| 515 |
+
Travel vlogger,Online Personality
|
| 516 |
+
Fitness YouTuber,Online Personality
|
| 517 |
+
Professional poker player,Other
|
| 518 |
+
Football club owner,Sports Professional
|
| 519 |
+
Archaeologist,Other
|
| 520 |
+
Folk metal vocalist,"Singer, Musician"
|
| 521 |
+
Country singer,"Singer, Musician"
|
| 522 |
+
Professional darts player,Sports Professional
|
| 523 |
+
Sports commentator,TV Personality
|
| 524 |
+
Kryptonian Military Officer,Other
|
| 525 |
+
Information Technology Specialist,Other
|
| 526 |
+
Fictional villain,Other
|
| 527 |
+
Newsreader,TV Personality
|
| 528 |
+
Retail Manager,Other
|
| 529 |
+
Insurance spokesperson,Other
|
| 530 |
+
Symphonic metal singer,"Singer, Musician"
|
| 531 |
+
Fine art photographer,Other
|
| 532 |
+
Civil rights attorney,Other
|
| 533 |
+
Indie rock singer-songwriter,"Singer, Musician"
|
| 534 |
+
Competitive swimmer,Sports Professional
|
| 535 |
+
Artist's model,Model
|
| 536 |
+
Criminal,Other
|
| 537 |
+
Vigilante,Other
|
| 538 |
+
U.S. President,Public FIgure
|
| 539 |
+
Abolitionist,Public FIgure
|
| 540 |
+
Fantasy warrior,Fictional Character
|
| 541 |
+
Open-world action-adventure game protagonist,Fictional Character
|
| 542 |
+
Viking explorer,Other
|
| 543 |
+
Cross-country skier,Sports Professional
|
| 544 |
+
Philosopher,Other
|
| 545 |
+
Historical figure,Other
|
| 546 |
+
Angel,Fictional Character
|
| 547 |
+
Starship captain,Fictional Character
|
| 548 |
+
Telenovela actress,Actor
|
| 549 |
+
Warrior,Fictional Character
|
| 550 |
+
Olympic gymnast,Sports Professional
|
| 551 |
+
Urban fantasy author,Other
|
| 552 |
+
Roman general,Other
|
| 553 |
+
Elf ruler,Fictional Character
|
| 554 |
+
Wizard,Fictional Character
|
| 555 |
+
Gladiator,Fictional Character
|
| 556 |
+
Archery combatant,Other
|
| 557 |
+
Wandmaker,Fictional Character
|
| 558 |
+
Funk musician,"Singer, Musician"
|
| 559 |
+
Comic book writer,Other
|
| 560 |
+
Heptathlete,Fictional Character
|
| 561 |
+
Monster hunter,Fictional Character
|
| 562 |
+
Professional Basketball Player,Sports Professional
|
| 563 |
+
K-pop rapper,"Singer, Musician"
|
| 564 |
+
Cantopop singer-songwriter,"Singer, Musician"
|
| 565 |
+
Leisure center manager,Other
|
| 566 |
+
Corporate raider,Other
|
| 567 |
+
Michelin-starred chef,Other
|
| 568 |
+
Wildlife species,Other
|
| 569 |
+
Klingon Chancellor,Fictional Character
|
| 570 |
+
Golf influencer,Online Personality
|
| 571 |
+
Tattoo artist,Other
|
| 572 |
+
Diagnostic physician,Other
|
| 573 |
+
Gaming YouTuber,Online Personality
|
| 574 |
+
Drug lord,Other
|
| 575 |
+
Olympic triathlete,Sports Professional
|
| 576 |
+
Writer,Other
|
| 577 |
+
High jumper,Sports Professional
|
| 578 |
+
Ancient ruler,Public FIgure
|
| 579 |
+
Smuggler,Other
|
| 580 |
+
VTuber,Online Personality
|
| 581 |
+
Outdoorswoman,Other
|
| 582 |
+
YouTube personality,Online Personality
|
| 583 |
+
Professional snowboarder,Sports Professional
|
| 584 |
+
Esports player,Online Personality
|
| 585 |
+
Restaurateur,Other
|
| 586 |
+
Criminal psychologist,Other
|
| 587 |
+
Fictional supervillain,Fictional Character
|
| 588 |
+
Fictional police detective,Fictional Character
|
| 589 |
+
Fictional wizard,Fictional Character
|
| 590 |
+
Vocaloid singer,"Singer, Musician"
|
| 591 |
+
Head of State,Public FIgure
|
| 592 |
+
Real estate agent,Other
|
| 593 |
+
Comic book villain,Fictional Character
|
| 594 |
+
Bollywood actress,Actor
|
| 595 |
+
Mythological figure,Fictional Character
|
| 596 |
+
Office worker,Other
|
| 597 |
+
Supernatural entity,Fictional Character
|
| 598 |
+
Starfighter pilot,Fictional Character
|
| 599 |
+
Mining engineer,Other
|
| 600 |
+
Fictional scientist,Fictional Character
|
| 601 |
+
Mythological hero,Fictional Character
|
| 602 |
+
Political Strategist,Public FIgure
|
| 603 |
+
Professional streamer,Online Personality
|
| 604 |
+
Video game director,"Singer, Musician"
|
| 605 |
+
Visual kei musician,"Singer, Musician"
|
| 606 |
+
Ice hockey player,Sports Professional
|
| 607 |
+
Fictional character actor,Fictional Character
|
| 608 |
+
Adult film director,Other
|
| 609 |
+
Trot singer,"Singer, Musician"
|
| 610 |
+
Bollywood actor,Actor
|
| 611 |
+
Makeup artist,Other
|
| 612 |
+
Political aide,Other
|
| 613 |
+
3D artist,Other
|
| 614 |
+
Heavy metal musician,"Singer, Musician"
|
| 615 |
+
Schoolteacher,Other
|
| 616 |
+
Television presenters,TV Personality
|
| 617 |
+
Roman noblewoman,Other
|
| 618 |
+
Mutant superhero,Fictional Character
|
| 619 |
+
Intelligence officer,Other
|
| 620 |
+
Ancient Egyptian priest,Fictional Character
|
| 621 |
+
Companion,Other
|
| 622 |
+
Idol,TV Personality
|
| 623 |
+
Flamenco singer,"Singer, Musician"
|
| 624 |
+
Princess,Other
|
| 625 |
+
Swordsman,Fictional Character
|
| 626 |
+
Film character,Fictional Character
|
| 627 |
+
Sports journalist,TV Personality
|
| 628 |
+
Olympic athlete,Sports Professional
|
| 629 |
+
Mythologist,Other
|
| 630 |
+
Speed Skater,Sports Professional
|
| 631 |
+
Psychologist,Other
|
| 632 |
+
Chanson singer,"Singer, Musician"
|
| 633 |
+
Crown dependency,Other
|
| 634 |
+
Fictional boxer,Fictional Character
|
| 635 |
+
Mad scientist,Fictional Character
|
| 636 |
+
Satirical religious figure,Fictional Character
|
| 637 |
+
Aviation tracking developer,Other
|
| 638 |
+
K-pop artist,"Singer, Musician"
|
| 639 |
+
First Lady of the United States,Public FIgure
|
| 640 |
+
Art model,Other
|
| 641 |
+
Police detective,Fictional Character
|
| 642 |
+
Video game producer,Other
|
| 643 |
+
Starfleet Science Officer,Fictional Character
|
| 644 |
+
Fictional monarch,Other
|
| 645 |
+
Marine,Other
|
| 646 |
+
Mughal Emperor,Fictional Character
|
| 647 |
+
Fictional spy,Fictional Character
|
| 648 |
+
Fictional super soldier,Other
|
| 649 |
+
Financial analyst,Other
|
| 650 |
+
Oncologist,Other
|
| 651 |
+
Rock guitarist,"Singer, Musician"
|
| 652 |
+
Folk musician,"Singer, Musician"
|
| 653 |
+
Professional football player,Sports Professional
|
| 654 |
+
Professional cyclist,Sports Professional
|
| 655 |
+
Can-can dancer,Actor
|
| 656 |
+
Art historian,Other
|
| 657 |
+
Subway sandwich chain spokesperson,Other
|
| 658 |
+
Eurodance singer,"Singer, Musician"
|
| 659 |
+
Fictional princess,Fictional Character
|
| 660 |
+
Physicist,Other
|
| 661 |
+
Computer engineer,Other
|
| 662 |
+
Political analyst,Other
|
| 663 |
+
Martial artist actor,Actor
|
| 664 |
+
Illustrator,Other
|
| 665 |
+
Financier,Other
|
| 666 |
+
Unemployed slacker,Other
|
| 667 |
+
Serial killer,Other
|
| 668 |
+
Political advisor,Other
|
| 669 |
+
Clock tower caretaker,Other
|
| 670 |
+
Perfume reviewer,Other
|
| 671 |
+
Football referee,Sports Professional
|
| 672 |
+
Private investigator,Other
|
| 673 |
+
Special Operations Agent,Other
|
| 674 |
+
Samurai,Other
|
| 675 |
+
Webtoon artist,Other
|
| 676 |
+
Secretary,Other
|
| 677 |
+
Military leader,Other
|
| 678 |
+
Professional Go player,Sports Professional
|
| 679 |
+
Convicted murderer,Other
|
| 680 |
+
Children's entertainer,Actor
|
| 681 |
+
Special forces operative,Other
|
| 682 |
+
Death row inmate,Other
|
| 683 |
+
Internet marketer,Online Personality
|
| 684 |
+
Political consultant,Other
|
| 685 |
+
Superhero actor,Fictional Character
|
| 686 |
+
Chief Justice,Public FIgure
|
| 687 |
+
American football coach,Sports Professional
|
| 688 |
+
Young adult fiction author,Other
|
| 689 |
+
Baseball manager,Sports Professional
|
| 690 |
+
Punk rock musician,"Singer, Musician"
|
| 691 |
+
Martial arts instructor,Sports Professional
|
| 692 |
+
Holographic AI companion,Other
|
| 693 |
+
Mandopop singer,"Singer, Musician"
|
| 694 |
+
Astrophysicist,Other
|
| 695 |
+
Professional road cyclist,Sports Professional
|
| 696 |
+
Adventurer,Other
|
| 697 |
+
Martial arts film actor,Other
|
| 698 |
+
Clinical psychologist,Other
|
| 699 |
+
Olympic wrestler,Sports Professional
|
| 700 |
+
Stockbroker,Other
|
| 701 |
+
Catholic priest,Other
|
| 702 |
+
Janitor,Other
|
| 703 |
+
Law enforcement officer,Other
|
| 704 |
+
Political leader,Public FIgure
|
| 705 |
+
Television meteorologist,TV Personality
|
| 706 |
+
Folk singer,"Singer, Musician"
|
| 707 |
+
Copla singer,"Singer, Musician"
|
| 708 |
+
Speed skater,Sports Professional
|
| 709 |
+
Latin pop singer-songwriter,"Singer, Musician"
|
| 710 |
+
Amateur model,Model
|
| 711 |
+
Reality TV star,TV Personality
|
| 712 |
+
YouTube commentator,Online Personality
|
| 713 |
+
Professional American football player,Sports Professional
|
| 714 |
+
Professional golfer,Sports Professional
|
| 715 |
+
Tech YouTuber,Online Personality
|
| 716 |
+
Professional speed skater,Sports Professional
|
| 717 |
+
Rebel operative,Other
|
| 718 |
+
Military personnel,Other
|
| 719 |
+
Lunar noblewoman,Fictional Character
|
| 720 |
+
Fantasy television actor,Actor
|
| 721 |
+
Vice President of the United States,Public FIgure
|
| 722 |
+
Guitarist,"Singer, Musician"
|
| 723 |
+
Hip-hop artist,"Singer, Musician"
|
| 724 |
+
Catholic Pope,Public Figure
|
| 725 |
+
Film historian,Other
|
| 726 |
+
Fugitive,Other
|
| 727 |
+
Sports television host,TV Personality
|
| 728 |
+
Fictional archer,Fictional Character
|
| 729 |
+
Jewelry retail,Other
|
| 730 |
+
Spaceship mechanic,Fictional Character
|
| 731 |
+
Jazz guitarist,"Singer, Musician"
|
| 732 |
+
Electronic music vocalist,"Singer, Musician"
|
| 733 |
+
Media proprietor,Other
|
| 734 |
+
Sports podcaster,Voice Actor
|
| 735 |
+
Paralympic skier,Sports Professional
|
| 736 |
+
Country pop singer,"Singer, Musician"
|
| 737 |
+
Classical vocalist,"Singer, Musician"
|
| 738 |
+
Royal family member,Other
|
| 739 |
+
Supreme Leader of North Korea,Public Figure
|
| 740 |
+
K-drama actress,Actor
|
| 741 |
+
Counterterrorism agent,Other
|
| 742 |
+
Defense attorney,Other
|
| 743 |
+
Video game antagonist,Fictional Character
|
| 744 |
+
Bajoran Militia Officer,Other
|
| 745 |
+
Conductor,Other
|
| 746 |
+
Yakuza member,Other
|
| 747 |
+
Countertenor singer,"Singer, Musician"
|
| 748 |
+
Rocket scientist,Other
|
| 749 |
+
Victim of crime,Other
|
| 750 |
+
Travel blogger,Online Personality
|
| 751 |
+
Japanese idol actor,Actor
|
| 752 |
+
Grunge musician,"Singer, Musician"
|
| 753 |
+
Disco singer,"Singer, Musician"
|
| 754 |
+
Tokusatsu actor,Actor
|
| 755 |
+
Textual inversion researcher,Other
|
| 756 |
+
Broadway actress,Actor
|
| 757 |
+
Fantasy author,Other
|
| 758 |
+
Investment manager,Other
|
| 759 |
+
Radio host,Voice Actor
|
| 760 |
+
Horror film character,Other
|
| 761 |
+
Powerlifter,Fictional Character
|
| 762 |
+
Starship propulsion engineer,Fictional Character
|
| 763 |
+
Architectural landmark,Other
|
| 764 |
+
Olympic fencer,Sports Professional
|
| 765 |
+
Rebel leader,Other
|
| 766 |
+
Criminologist,Other
|
| 767 |
+
Songbird,Other
|
| 768 |
+
Cumbia singer,"Singer, Musician"
|
| 769 |
+
Royalty,Other
|
| 770 |
+
Television director,TV Personality
|
| 771 |
+
Fictional vampire,Fictional Character
|
| 772 |
+
Choreographer,Sports Professional
|
| 773 |
+
Organized crime enforcer,Other
|
| 774 |
+
Defense Attorney,Other
|
| 775 |
+
Video essayist,Online Personality
|
| 776 |
+
Technology YouTuber,Online Personality
|
| 777 |
+
Professional thief,Other
|
| 778 |
+
Eurovision Song Contest winner,"Singer, Musician"
|
| 779 |
+
Innkeeper,Fictional Character
|
| 780 |
+
Sexologist,Other
|
| 781 |
+
Wildlife filmmaker,Other
|
| 782 |
+
Operatic tenor,Other
|
| 783 |
+
Nightclub owner,Other
|
| 784 |
+
Gladiator's wife,Fictional Character
|
| 785 |
+
Vault dweller,Fictional Character
|
| 786 |
+
Classical composer,"Singer, Musician"
|
| 787 |
+
Football administrator,Sports Professional
|
| 788 |
+
Art patron,Other
|
| 789 |
+
Gothic fashion model,Model
|
| 790 |
+
High school cheerleader,Other
|
| 791 |
+
Country singer-songwriter,"Singer, Musician"
|
| 792 |
+
Fictional protagonist,Fictional Character
|
| 793 |
+
Telepath,Other
|
| 794 |
+
Jazz musician,"Singer, Musician"
|
| 795 |
+
Gothic literature character,Fictional Character
|
| 796 |
+
Corporate executive,Other
|
| 797 |
+
Counter-terrorism operative,Other
|
| 798 |
+
Human rights activist,Public Figure
|
| 799 |
+
Public health official,Other
|
| 800 |
+
Racing Driver,Sports Professional
|
| 801 |
+
Farmer,Other
|
| 802 |
+
Javelin thrower,Fictional Character
|
| 803 |
+
Industrial metal musician,"Singer, Musician"
|
| 804 |
+
Photorealistic model,Model
|
| 805 |
+
Technology executive,Other
|
| 806 |
+
Strongman competitor,Other
|
| 807 |
+
YouTube Let's Player,Online Personality
|
| 808 |
+
Technology entrepreneur,Other
|
| 809 |
+
Civil rights activist,Public Figure
|
| 810 |
+
Anime character designer,Other
|
| 811 |
+
Video game streamer,Online Personality
|
| 812 |
+
Biblical figure,Fictional Character
|
| 813 |
+
Nanny,Other
|
| 814 |
+
Witch,Fictional Character
|
| 815 |
+
Anime director,Other
|
| 816 |
+
Criminal defense attorney,Other
|
| 817 |
+
Sailor,Other
|
| 818 |
+
Demon bounty hunter,Fictional Character
|
| 819 |
+
Monster,Fictional Character
|
| 820 |
+
Television journalist,Journalist
|
| 821 |
+
Former First Lady of the United States,Public Figure
|
| 822 |
+
Video game actress,Fictional Character
|
| 823 |
+
Music video director,Other
|
| 824 |
+
Priestess,Fictional Character
|
| 825 |
+
Metal singer,"Singer, Musician"
|
| 826 |
+
Automotive manufacturer,Other
|
| 827 |
+
Crime-fighting operative,Other
|
| 828 |
+
Crime boss,Other
|
| 829 |
+
Grime artist,"Singer, Musician"
|
| 830 |
+
Regional manager,Other
|
| 831 |
+
Video game composer,Other
|
| 832 |
+
Digital Artist,Other
|
| 833 |
+
Hurdler,Sports Professional
|
| 834 |
+
Cartoon character,Fictional Character
|
| 835 |
+
Ottoman princess,Other
|
| 836 |
+
Family Medicine Physician,Other
|
| 837 |
+
Psychiatric nurse,Other
|
| 838 |
+
Starfleet operations officer,Fictional Character
|
| 839 |
+
Classical soprano singer,"Singer, Musician"
|
| 840 |
+
Professor of Transfiguration,Fictional Character
|
| 841 |
+
Military strategist,Other
|
| 842 |
+
Crown Prince,Public Figure
|
| 843 |
+
Homemaker,Other
|
| 844 |
+
Food blogger,Online Personality
|
| 845 |
+
K-pop idol group,"Singer, Musician"
|
| 846 |
+
Fictional pirate captain,Fictional Character
|
| 847 |
+
Barista,Other
|
| 848 |
+
Freedom fighter,Other
|
| 849 |
+
Gothic homemaker,Fictional Character
|
| 850 |
+
Central Bank Governor,Public Figure
|
| 851 |
+
Virtual influencer,Online Personality
|
| 852 |
+
Shapeshifter,Fictional Character
|
| 853 |
+
Photographic model,Model
|
| 854 |
+
Gospel musician,"Singer, Musician"
|
| 855 |
+
Supermodel,Model
|
| 856 |
+
Maker,Other
|
| 857 |
+
Fictional ninja,Other
|
| 858 |
+
Japanese AV idol,Adult Performer
|
| 859 |
+
Missing person,Other
|
| 860 |
+
Intelligence operative,Other
|
| 861 |
+
Investment banker,Other
|
| 862 |
+
Fire Dragon Slayer,Fictional Character
|
| 863 |
+
Ecosystem regulator,Other
|
| 864 |
+
Interstellar diplomat,Fictional Character
|
| 865 |
+
Queen of Egypt,Public Figure
|
| 866 |
+
Pro Hero,Other
|
| 867 |
+
Dangdut singer,Other
|
| 868 |
+
Anti-apartheid activist,Public Figure
|
| 869 |
+
Light novel character,Fictional Character
|
| 870 |
+
Architect,Other
|
| 871 |
+
Na'vi warrior,Fictional Character
|
| 872 |
+
K-pop group,"Singer, Musician"
|
| 873 |
+
Livestreamer,Online Personality
|
| 874 |
+
Professional Bodybuilder,TV Personality
|
| 875 |
+
Television cook,TV Personality
|
| 876 |
+
Handball player,Sports Professional
|
| 877 |
+
Business magnate,Public Figure
|
| 878 |
+
Fitness Influencer,Online Personality
|
| 879 |
+
Blues rock musician,"Singer, Musician"
|
| 880 |
+
Mongolian traditional singer,"Singer, Musician"
|
| 881 |
+
Communications Officer,Other
|
| 882 |
+
Security officer,Fictional Character
|
| 883 |
+
TV presenter,TV Personality
|
| 884 |
+
AI model,Other
|
| 885 |
+
Mobile Legends: Bang Bang player,Online Personality
|
| 886 |
+
Mobile Legends player,Online Personality
|
| 887 |
+
Terrorist leader,Public Figure
|
| 888 |
+
Literary translator,Other
|
| 889 |
+
Wuxia novelist,Other
|
| 890 |
+
Bullfighter,Other
|
| 891 |
+
Galactic Senator,Fictional Character
|
| 892 |
+
Professional Golfer,Sports Professional
|
| 893 |
+
Botanist,Other
|
| 894 |
+
K-pop producer,"Singer, Musician"
|
| 895 |
+
Librarian,Other
|
| 896 |
+
Jazz pianist,"Singer, Musician"
|
| 897 |
+
Fitness Trainer,Other
|
| 898 |
+
Fictional nobleman,Fictional Character
|
| 899 |
+
Judoka,Sports Professional
|
| 900 |
+
Software Developer,Other
|
| 901 |
+
A cappella musician,"Singer, Musician"
|
| 902 |
+
Televangelist,TV Personality
|
| 903 |
+
Methamphetamine manufacturer,Other
|
| 904 |
+
College student,Other
|
| 905 |
+
Electronic dance music producer,"Singer, Musician"
|
| 906 |
+
BSAA operative,Other
|
| 907 |
+
Roman Prefect of Judea,Other
|
| 908 |
+
Omnipotent being,Fictional Character
|
| 909 |
+
Jedi Master,Fictional Character
|
| 910 |
+
Virtual program,Other
|
| 911 |
+
Digital entity,Fictional Character
|
| 912 |
+
Fictional legal associate,Fictional Character
|
| 913 |
+
Viking warrior,Fictional Character
|
| 914 |
+
Delivery courier,Other
|
| 915 |
+
Supernatural antagonist,Fictional Character
|
| 916 |
+
Japanese adult film actress,Adult Performer
|
| 917 |
+
Industrialist,Other
|
| 918 |
+
Soul musician,"Singer, Musician"
|
| 919 |
+
Criminal mastermind,Other
|
| 920 |
+
Fictional criminal mastermind,Other
|
| 921 |
+
Evangelion Pilot,Other
|
| 922 |
+
Shrine maiden,Fictional Character
|
| 923 |
+
Bandit leader,Other
|
| 924 |
+
Jedi,Fictional Character
|
| 925 |
+
Fictional royal heir,Fictional Character
|
| 926 |
+
Heavy metal bassist,"Singer, Musician"
|
| 927 |
+
Internet meme personality,Other
|
| 928 |
+
Electronic music producer,"Singer, Musician"
|
| 929 |
+
Blade Runner,Fictional Character
|
| 930 |
+
Software developer,Other
|
| 931 |
+
Fitness instructor,Online Personality
|
| 932 |
+
Sheriff's deputy,Other
|
| 933 |
+
Dungeons & Dragons player,Other
|
| 934 |
+
Ranch foreman,Other
|
| 935 |
+
Neurologist,Other
|
| 936 |
+
Reggae musician,"Singer, Musician"
|
| 937 |
+
Nuclear physicist,Other
|
| 938 |
+
Wildlife conservationist,Other
|
| 939 |
+
Pulp fiction writer,Other
|
| 940 |
+
Environmental lawyer,Public Figure
|
| 941 |
+
Business consultant,Other
|
| 942 |
+
Television painter,TV Personality
|
| 943 |
+
Spacecraft pilot,Fictional Character
|
| 944 |
+
Cuarteto singer,"Singer, Musician"
|
| 945 |
+
Quidditch referee,Fictional Character
|
| 946 |
+
Luxury automobile manufacturer,Other
|
| 947 |
+
Time traveler's companion,Fictional Character
|
| 948 |
+
Content creator,Online Personality
|
| 949 |
+
Paleontologist,Other
|
| 950 |
+
Darknet market operator,Other
|
| 951 |
+
Replicant,Other
|
| 952 |
+
Gamekeeper,Fictional Character
|
| 953 |
+
Shia cleric,Public Figure
|
| 954 |
+
Businessperson,Other
|
| 955 |
+
Fairy tale character,Fictional Character
|
| 956 |
+
Detective,Other
|
| 957 |
+
Composer,Other
|
| 958 |
+
Showgirl,TV Personality
|
| 959 |
+
Screenwriter,Other
|
| 960 |
+
FBI Agent,Fictional Character
|
| 961 |
+
Video game writer,Other
|
| 962 |
+
Professional wrestling ring announcer,Sports Professional
|
| 963 |
+
Firearms inventor,Other
|
| 964 |
+
Europop singer,"Singer, Musician"
|
| 965 |
+
Tokusatsu actress,Actor
|
| 966 |
+
Cartoonist,Other
|
| 967 |
+
Pianist,"Singer, Musician"
|
| 968 |
+
Dark Wizard,Fictional Character
|
| 969 |
+
Cryptocurrency developer,Other
|
| 970 |
+
Curler,Sports Professional
|
| 971 |
+
Mahjong player,Other
|
| 972 |
+
Tejano singer,"Singer, Musician"
|
| 973 |
+
Vampire assassin,Fictional Character
|
| 974 |
+
Vampire warrior,Fictional Character
|
| 975 |
+
Thief,Other
|
| 976 |
+
Police sergeant,Other
|
| 977 |
+
Potions Master,Fictional Character
|
| 978 |
+
Motorcycle Racer,Sports Professional
|
| 979 |
+
Cybersecurity journalist,Other
|
| 980 |
+
Jazz saxophonist,"Singer, Musician"
|
| 981 |
+
Australian rules footballer,Sports Professional
|
| 982 |
+
Galactic Emperor,Fictional Character
|
| 983 |
+
Lightsaber duelist,Fictional Character
|
| 984 |
+
Ballroom dancer,Actor
|
| 985 |
+
Mercenary leader,Other
|
| 986 |
+
Media mogul,Other
|
| 987 |
+
Special forces operator,Other
|
| 988 |
+
Intelligence Agent,Other
|
| 989 |
+
Muay Thai fighter,Other
|
| 990 |
+
Shogi player,"Singer, Musician"
|
| 991 |
+
Science Officer,Other
|
| 992 |
+
Science fiction film franchise,Other
|
| 993 |
+
Horror novelist,Other
|
| 994 |
+
Late-night talk show host,TV Personality
|
| 995 |
+
Neurosurgeon,Other
|
| 996 |
+
Fictional superhero,Fictional Character
|
| 997 |
+
Post-punk singer,"Singer, Musician"
|
| 998 |
+
Lexicographer,Other
|
| 999 |
+
Thai actress,Actor
|
| 1000 |
+
Playwright,Other
|
| 1001 |
+
Short track speed skater,Sports Professional
|
| 1002 |
+
Divination teacher,Fictional Character
|
| 1003 |
+
Imperial Security Officer,Other
|
| 1004 |
+
Rally driver,Sports Professional
|
| 1005 |
+
Lifestyle blogger,Online Personality
|
| 1006 |
+
Photojournalist,Other
|
| 1007 |
+
Demon Slayer,Fictional Character
|
| 1008 |
+
Swimmer,Sports Professional
|
| 1009 |
+
Opera Singer,Performer
|
| 1010 |
+
City pop musician,"Singer, Musician"
|
| 1011 |
+
Elf warrior,Fictional Character
|
| 1012 |
+
Buddhist monk,Other
|
| 1013 |
+
Fantasy novelist,Other
|
| 1014 |
+
Catholic nun,Other
|
| 1015 |
+
Computer programmer,Other
|
| 1016 |
+
Rock musicians,"Singer, Musician"
|
| 1017 |
+
Inventor,Other
|
| 1018 |
+
Satirical songwriter,"Singer, Musician"
|
| 1019 |
+
Alternative rock musician,"Singer, Musician"
|
| 1020 |
+
Olympic diver,Sports Professional
|
| 1021 |
+
Dark sorcerer,Fictional Character
|
| 1022 |
+
Digital Illustrator,Other
|
| 1023 |
+
Industrial metal vocalist,"Singer, Musician"
|
| 1024 |
+
Rock climber,Sports Professional
|
| 1025 |
+
Dark wizard,Fictional Character
|
| 1026 |
+
Canoe slalom athlete,Sports Professional
|
| 1027 |
+
Compact MPV,Other
|
| 1028 |
+
Gangsta rapper,"Singer, Musician"
|
| 1029 |
+
Soap maker,Other
|
| 1030 |
+
Military general,Other
|
| 1031 |
+
Terrorist,Other
|
| 1032 |
+
Federal republic,Other
|
| 1033 |
+
Animated film protagonist,Fictional Character
|
| 1034 |
+
Comedic actor,TV Personality
|
| 1035 |
+
Beauty pageant winner,Model
|
| 1036 |
+
Naval officer,Other
|
| 1037 |
+
Fictional Asgardian warrior,Fictional Character
|
| 1038 |
+
Director,Other
|
| 1039 |
+
Professional Cyclist,Sports Professional
|
| 1040 |
+
Cybernetic Surgeon,Other
|
| 1041 |
+
Furry artist,Other
|
| 1042 |
+
Dungeons & Dragons content creator,Online Personality
|
| 1043 |
+
Cryptocurrency Developer,Other
|
| 1044 |
+
Fictional crime boss,Fictional Character
|
| 1045 |
+
Futurist poet,Other
|
| 1046 |
+
Minivan,Other
|
| 1047 |
+
High school chemistry teacher,Other
|
| 1048 |
+
Intelligence Officer,Other
|
| 1049 |
+
Hard rock singer,"Singer, Musician"
|
| 1050 |
+
Television pitchman,TV Personality
|
| 1051 |
+
Blues rock guitarist,Singer, Musician
|
| 1052 |
+
Science fiction author,Other
|
| 1053 |
+
Military General,Other
|
| 1054 |
+
Confectioner,Other
|
| 1055 |
+
Witchcraft practitioner,Fictional Character
|
| 1056 |
+
Prime Minister,Public Figure
|
| 1057 |
+
Alternative pop musician,"Singer, Musician"
|
| 1058 |
+
Electric vehicle,Other
|
| 1059 |
+
Engineer,Other
|
| 1060 |
+
Shrine Priestess,Fictional Character
|
| 1061 |
+
Electric vehicle manufacturer,Other
|
| 1062 |
+
Contemporary artist,Other
|
| 1063 |
+
Sorceress,Fictional Character
|
| 1064 |
+
New Age musician,"Singer, Musician"
|
| 1065 |
+
Conceptual artist,Other
|
| 1066 |
+
Prosecutor,Other
|
| 1067 |
+
Concept artist,Other
|
| 1068 |
+
Bariatric surgeon,other
|
| 1069 |
+
Archer,Sports Professional
|
| 1070 |
+
Japanese adult video actress,Adult Performer
|
| 1071 |
+
Olympic swimmer,Sports Professional
|
| 1072 |
+
Football manager,Sports Professional
|
| 1073 |
+
Mentor,Other
|
| 1074 |
+
Regional Mexican singer,"Singer, Musician"
|
misc/lists/professions.csv
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
profession,alias_1,alias_2,alias_3
|
| 2 |
+
celebrity,celeb,,
|
| 3 |
+
actor,actress,,
|
| 4 |
+
singer,,,
|
| 5 |
+
musician,,,
|
| 6 |
+
rapper,rap,,
|
| 7 |
+
influencer,,,
|
| 8 |
+
model,,,
|
| 9 |
+
tv host,,,
|
| 10 |
+
tv moderator,,,
|
| 11 |
+
streamer,,,
|
| 12 |
+
twitch streamer,twitch,,
|
| 13 |
+
youtuber,youtube,,
|
| 14 |
+
content creator,,,
|
| 15 |
+
media personality,,,
|
| 16 |
+
reality tv star,reality,,
|
| 17 |
+
e-girl,egirl,,
|
| 18 |
+
e-boy,eboy,,
|
| 19 |
+
cosplayer,,,
|
| 20 |
+
voice actor,voice actress,,
|
| 21 |
+
asmr creator,asmr,,
|
| 22 |
+
comedian,,,
|
| 23 |
+
talk show host,talkshow,,
|
| 24 |
+
radio host,,,
|
| 25 |
+
journalist,,,
|
| 26 |
+
politician,,,
|
| 27 |
+
activist,,,
|
| 28 |
+
public speaker,,,
|
| 29 |
+
fitness influencer,fitness,,
|
| 30 |
+
chef,,,
|
| 31 |
+
author,writer,,
|
| 32 |
+
adult entertainer,,,
|
| 33 |
+
onlyfans creator,onlyfans,,
|
| 34 |
+
cam model,camgirl,webcam,
|
| 35 |
+
pornographic actor,porn,pornstar,
|
| 36 |
+
instagrammer,instagram,insta,
|
plots/2024-06_image_contributions.svg
DELETED
plots/Figure_12.svg
ADDED
|
|
plots/Sec-6-5_lora_tags_categories.svg
DELETED
plots/Sec_6-5_taggers.svg
DELETED
plots/Section_6-7_model_contributions_sankey.svg
DELETED
plots/models_nsfw_monthly_histogram_with_quarters.svg
DELETED
public/Figure_13.html
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>Danbooru Tree from JSON</title>
|
| 6 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
| 7 |
+
<style>
|
| 8 |
+
body {
|
| 9 |
+
font-family: sans-serif;
|
| 10 |
+
}
|
| 11 |
+
svg {
|
| 12 |
+
width: 100%;
|
| 13 |
+
height: 1500px;
|
| 14 |
+
}
|
| 15 |
+
.node circle {
|
| 16 |
+
fill: steelblue;
|
| 17 |
+
}
|
| 18 |
+
.node text {
|
| 19 |
+
font-size: 12px;
|
| 20 |
+
fill: #333;
|
| 21 |
+
}
|
| 22 |
+
.link {
|
| 23 |
+
fill: none;
|
| 24 |
+
stroke: #ccc;
|
| 25 |
+
stroke-width: 1.5px;
|
| 26 |
+
}
|
| 27 |
+
</style>
|
| 28 |
+
</head>
|
| 29 |
+
<body>
|
| 30 |
+
<h2>Danbooru Categories</h2>
|
| 31 |
+
<button id="downloadBtn">Download SVG</button>
|
| 32 |
+
<svg></svg>
|
| 33 |
+
|
| 34 |
+
<script>
|
| 35 |
+
d3.json("json/danbooru_flat.json").then(function(data) {
|
| 36 |
+
const svg = d3.select("svg");
|
| 37 |
+
const width = 750;
|
| 38 |
+
const height = 1200;
|
| 39 |
+
|
| 40 |
+
// ---- STEP 1: Limit second-level children ----
|
| 41 |
+
function limitSecondLevel(node, depth = 0) {
|
| 42 |
+
if (node.children && depth === 1 && node.children.length > 5) {
|
| 43 |
+
const visible = node.children.slice(0, 5);
|
| 44 |
+
visible.push({ name: "...", children: [] });
|
| 45 |
+
node.children = visible;
|
| 46 |
+
}
|
| 47 |
+
if (node.children) {
|
| 48 |
+
node.children.forEach(child => limitSecondLevel(child, depth + 1));
|
| 49 |
+
}
|
| 50 |
+
return node;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
const limited = limitSecondLevel(structuredClone(data));
|
| 54 |
+
|
| 55 |
+
const root = d3.hierarchy(limited, d => d.children);
|
| 56 |
+
|
| 57 |
+
// ---- STEP 2: Track root category for coloring ----
|
| 58 |
+
root.eachAfter(d => {
|
| 59 |
+
if (d.depth === 1) {
|
| 60 |
+
d.data.rootCategory = d.data.name;
|
| 61 |
+
} else if (d.parent) {
|
| 62 |
+
d.data.rootCategory = d.parent.data.rootCategory;
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
const treeLayout = d3.tree().size([height, width]);
|
| 67 |
+
treeLayout(root);
|
| 68 |
+
|
| 69 |
+
// ---- STEP 3: Scale for tag_count → radius ----
|
| 70 |
+
const allTagCounts = root.descendants()
|
| 71 |
+
.filter(d => d.depth > 0)
|
| 72 |
+
.map(d => d.data.tag_count || 0);
|
| 73 |
+
const sizeScale = d3.scaleSqrt()
|
| 74 |
+
.domain([0, d3.max(allTagCounts)])
|
| 75 |
+
.range([4, 20]);
|
| 76 |
+
|
| 77 |
+
// ---- STEP 4: Color Map ----
|
| 78 |
+
const categoryColors = {
|
| 79 |
+
"attire": "#f4a261",
|
| 80 |
+
"body": "#e76f51",
|
| 81 |
+
"characters": "#2a9d8f",
|
| 82 |
+
"copyrights": "#264653",
|
| 83 |
+
"creatures": "#8ecae6",
|
| 84 |
+
"drawing software": "#219ebc",
|
| 85 |
+
"games": "#3a86ff",
|
| 86 |
+
"metatags": "#ffbe0b",
|
| 87 |
+
"more": "#b5179e",
|
| 88 |
+
"objects": "#6d6875",
|
| 89 |
+
"plant": "#7cb518",
|
| 90 |
+
"real_world": "#a5a58d",
|
| 91 |
+
"sex": "#ef476f",
|
| 92 |
+
"visual_characteristics": "#06d6a0",
|
| 93 |
+
"subject": "#ffd166",
|
| 94 |
+
"uncategorized": "#adb5bd",
|
| 95 |
+
"actions_and_expressions": "#d00000",
|
| 96 |
+
"objects_and_backgrounds": "#118ab2"
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
function getColor(d) {
|
| 100 |
+
if (d.data.name === "...") return "gray";
|
| 101 |
+
const category = d.data.rootCategory?.toLowerCase();
|
| 102 |
+
return categoryColors[category] || "steelblue";
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// ---- STEP 5: Draw Links ----
|
| 106 |
+
svg.selectAll(".link")
|
| 107 |
+
.data(root.links())
|
| 108 |
+
.join("path")
|
| 109 |
+
.attr("class", "link")
|
| 110 |
+
.attr("d", d3.linkHorizontal()
|
| 111 |
+
.x(d => d.y)
|
| 112 |
+
.y(d => d.x)
|
| 113 |
+
);
|
| 114 |
+
|
| 115 |
+
// ---- STEP 6: Draw Nodes ----
|
| 116 |
+
const node = svg.selectAll(".node")
|
| 117 |
+
.data(root.descendants())
|
| 118 |
+
.join("g")
|
| 119 |
+
.attr("class", "node")
|
| 120 |
+
.attr("transform", d => `translate(${d.y},${d.x})`);
|
| 121 |
+
|
| 122 |
+
node.append("circle")
|
| 123 |
+
.attr("r", d => d.depth === 0 ? 6 : sizeScale(d.data.tag_count || 0))
|
| 124 |
+
.style("fill", d => d.data.color || "steelblue");
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
node.append("title")
|
| 128 |
+
.text(d => `${d.data.name}\nTags: ${d.data.tag_count || 0}`);
|
| 129 |
+
|
| 130 |
+
node.append("text")
|
| 131 |
+
.attr("x", 10)
|
| 132 |
+
.style("font-weight", "bold")
|
| 133 |
+
.attr("dy", "0.32em")
|
| 134 |
+
.text(d => d.data.name);
|
| 135 |
+
});
|
| 136 |
+
|
| 137 |
+
document.getElementById("downloadBtn").addEventListener("click", () => {
|
| 138 |
+
const svgNode = document.querySelector("svg");
|
| 139 |
+
|
| 140 |
+
// Clone the SVG node to preserve original
|
| 141 |
+
const clonedSvg = svgNode.cloneNode(true);
|
| 142 |
+
const outer = document.createElement("div");
|
| 143 |
+
outer.appendChild(clonedSvg);
|
| 144 |
+
|
| 145 |
+
// Add xmlns so it can be saved properly
|
| 146 |
+
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
| 147 |
+
|
| 148 |
+
// Serialize the SVG
|
| 149 |
+
const svgData = new XMLSerializer().serializeToString(clonedSvg);
|
| 150 |
+
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
| 151 |
+
|
| 152 |
+
// Create download link
|
| 153 |
+
const url = URL.createObjectURL(svgBlob);
|
| 154 |
+
const a = document.createElement("a");
|
| 155 |
+
a.href = url;
|
| 156 |
+
a.download = "danbooru_tree.svg";
|
| 157 |
+
document.body.appendChild(a);
|
| 158 |
+
a.click();
|
| 159 |
+
document.body.removeChild(a);
|
| 160 |
+
URL.revokeObjectURL(url);
|
| 161 |
+
});
|
| 162 |
+
</script>
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
</body>
|
| 166 |
+
</html>
|
public/Figure_8a_sunburst.html
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<meta charset="utf-8">
|
| 3 |
+
<style>
|
| 4 |
+
body {
|
| 5 |
+
font-family: "Segoe UI", sans-serif;
|
| 6 |
+
text-align: center;
|
| 7 |
+
margin-top: 20px;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
svg {
|
| 11 |
+
font: 10px sans-serif;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.label {
|
| 15 |
+
font-weight: bold;
|
| 16 |
+
font-size: 12px;
|
| 17 |
+
text-anchor: middle;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.legend {
|
| 21 |
+
font-size: 12px;
|
| 22 |
+
text-anchor: start;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.legend rect {
|
| 26 |
+
stroke-width: 1;
|
| 27 |
+
stroke: #000;
|
| 28 |
+
}
|
| 29 |
+
</style>
|
| 30 |
+
<body>
|
| 31 |
+
<h2>Sunburst Chart: Country → Profession</h2>
|
| 32 |
+
<button id="downloadBtn">Download SVG</button>
|
| 33 |
+
<svg width="800" height="800"></svg>
|
| 34 |
+
<script src="https://d3js.org/d3.v6.min.js"></script>
|
| 35 |
+
<script>
|
| 36 |
+
const width = 800;
|
| 37 |
+
const radius = width / 3;
|
| 38 |
+
|
| 39 |
+
const colorMap = {
|
| 40 |
+
"Adult Performer": "#8A2BE2",
|
| 41 |
+
"Model": "#DC143C",
|
| 42 |
+
"Actor": "#FF7F50",
|
| 43 |
+
"Performer": "magenta",
|
| 44 |
+
"Singer, Musician": "wheat",
|
| 45 |
+
"TV Personality": "#708090",
|
| 46 |
+
"Sports Professional": "gold",
|
| 47 |
+
"Public Figure": "maroon",
|
| 48 |
+
"Voice Actor": "lightgreen",
|
| 49 |
+
"Online Personality": "#4682B4",
|
| 50 |
+
"Other": "#ccc"
|
| 51 |
+
};
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
// Count total values per gender (assuming gender is at depth 1)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
const professionOrder = [
|
| 58 |
+
"Adult Performer", "Actor", "Singer, Musician", "Model", "Online Personality", "Public Figure", "Sports Professional", "Voice Actor", "TV Personality", "Other"
|
| 59 |
+
];
|
| 60 |
+
const manualOrder = [
|
| 61 |
+
"United States", "Japan", "South Korea", "United Kingdom",
|
| 62 |
+
"Russia", "China", "Canada", "India", "Australia", "France", "Germany", "Other"
|
| 63 |
+
];
|
| 64 |
+
|
| 65 |
+
function customSort(a, b, order) {
|
| 66 |
+
const aIndex = order.indexOf(a.data.name);
|
| 67 |
+
const bIndex = order.indexOf(b.data.name);
|
| 68 |
+
return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
function partition(data) {
|
| 72 |
+
const root = d3.hierarchy(data).sum(d => d.value);
|
| 73 |
+
|
| 74 |
+
// Sort countries by manual order before partitioning
|
| 75 |
+
root.children.sort((a, b) => customSort(a, b, manualOrder));
|
| 76 |
+
|
| 77 |
+
// Sort professions by value (descending), with "Other" always last
|
| 78 |
+
root.children.forEach(country => {
|
| 79 |
+
if (country.children) {
|
| 80 |
+
country.children.sort((a, b) => {
|
| 81 |
+
if (a.data.name === "Other") return 1;
|
| 82 |
+
if (b.data.name === "Other") return -1;
|
| 83 |
+
return b.value - a.value;
|
| 84 |
+
});
|
| 85 |
+
}
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
return d3.partition().size([2 * Math.PI, root.height + 1])(root);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
d3.json("json/sunburst_gender.json").then(data => {
|
| 94 |
+
const root = partition(data);
|
| 95 |
+
root.each(d => d.current = d);
|
| 96 |
+
|
| 97 |
+
// Sort countries with 'Other' last
|
| 98 |
+
root.children.sort((a, b) => customSort(a, b, manualOrder));
|
| 99 |
+
|
| 100 |
+
// Sort professions within countries with 'Other' last
|
| 101 |
+
root.children.forEach(country => {
|
| 102 |
+
if (country.children) {
|
| 103 |
+
country.children.sort((a, b) => customSort(a, b, professionOrder));
|
| 104 |
+
}
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
const svg = d3.select("svg")
|
| 108 |
+
.attr("viewBox", [0, 0, width, width])
|
| 109 |
+
.style("font", "10px sans-serif");
|
| 110 |
+
|
| 111 |
+
const g = svg.append("g")
|
| 112 |
+
.attr("transform", `translate(${width / 2},${width / 2})`);
|
| 113 |
+
|
| 114 |
+
const angleOffset = -80 * Math.PI / 180;
|
| 115 |
+
|
| 116 |
+
const arc = d3.arc()
|
| 117 |
+
.startAngle(d => d.x0 + angleOffset)
|
| 118 |
+
.endAngle(d => d.x1 + angleOffset)
|
| 119 |
+
.innerRadius(d => {
|
| 120 |
+
if (d.depth === 1) return radius * 0.1;
|
| 121 |
+
if (d.depth === 2) return radius * 0.5;
|
| 122 |
+
})
|
| 123 |
+
.outerRadius(d => {
|
| 124 |
+
if (d.depth === 1) return radius * 0.5;
|
| 125 |
+
if (d.depth === 2) return radius * 0.65;
|
| 126 |
+
});
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
const path = g.append("g")
|
| 130 |
+
.selectAll("path")
|
| 131 |
+
.data(root.descendants().slice(1))
|
| 132 |
+
.join("path")
|
| 133 |
+
.attr("fill", d => {
|
| 134 |
+
if (d.depth === 2) return colorMap[d.data.name] || "#ccc";
|
| 135 |
+
if (d.depth === 1) return "#fff";
|
| 136 |
+
return "none";
|
| 137 |
+
})
|
| 138 |
+
.attr("stroke", d => d.depth === 1 ? "#000" : null)
|
| 139 |
+
.attr("stroke-width", d => d.depth > 0 ? 2 : null)
|
| 140 |
+
.attr("d", arc);
|
| 141 |
+
|
| 142 |
+
path.append("title")
|
| 143 |
+
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join(" → ")}\n${d.value}`);
|
| 144 |
+
// Count total values per gender (assuming gender is at depth 1)
|
| 145 |
+
const genderCounts = {};
|
| 146 |
+
root.children.forEach(d => {
|
| 147 |
+
const gender = d.data.name;
|
| 148 |
+
genderCounts[gender] = d.value;
|
| 149 |
+
});
|
| 150 |
+
|
| 151 |
+
// Add bold labels to country segments
|
| 152 |
+
g.selectAll("text")
|
| 153 |
+
.data(root.children)
|
| 154 |
+
.join("text")
|
| 155 |
+
.attr("transform", function(d) {
|
| 156 |
+
const angle = ((d.x0 + d.x1) / 2) + angleOffset;
|
| 157 |
+
const x = Math.cos(angle - Math.PI / 2) * (radius / root.height + -50);
|
| 158 |
+
const y = Math.sin(angle - Math.PI / 2) * (radius / root.height + -50);
|
| 159 |
+
return `translate(${x},${y}) rotate(${(angle / Math.PI)})`;
|
| 160 |
+
})
|
| 161 |
+
.attr("dy", "0.35em")
|
| 162 |
+
.attr("class", "label")
|
| 163 |
+
.text(d => `${d.data.name}: ${genderCounts[d.data.name] || 0}`);
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
// Count total values per profession (depth 2)
|
| 167 |
+
const professionCounts = {};
|
| 168 |
+
root.descendants().forEach(d => {
|
| 169 |
+
if (d.depth === 2) {
|
| 170 |
+
const name = d.data.name;
|
| 171 |
+
professionCounts[name] = (professionCounts[name] || 0) + d.value;
|
| 172 |
+
}
|
| 173 |
+
});
|
| 174 |
+
|
| 175 |
+
// Legend for profession colors
|
| 176 |
+
const legend = svg.append("g")
|
| 177 |
+
.attr("class", "legend")
|
| 178 |
+
.attr("transform", `translate(20,20)`);
|
| 179 |
+
|
| 180 |
+
professionOrder.forEach((prof, i) => {
|
| 181 |
+
const row = legend.append("g")
|
| 182 |
+
.attr("transform", `translate(0,${i * 20})`);
|
| 183 |
+
|
| 184 |
+
row.append("rect")
|
| 185 |
+
.attr("width", 16)
|
| 186 |
+
.attr("height", 16)
|
| 187 |
+
.attr("fill", colorMap[prof]);
|
| 188 |
+
|
| 189 |
+
row.append("text")
|
| 190 |
+
.attr("x", 22)
|
| 191 |
+
.attr("y", 12)
|
| 192 |
+
.text(`${prof} (${professionCounts[prof] || 0})`);
|
| 193 |
+
});
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
document.getElementById("downloadBtn").addEventListener("click", () => {
|
| 197 |
+
const svgNode = document.querySelector("svg");
|
| 198 |
+
|
| 199 |
+
// Clone the SVG node to preserve original
|
| 200 |
+
const clonedSvg = svgNode.cloneNode(true);
|
| 201 |
+
const outer = document.createElement("div");
|
| 202 |
+
outer.appendChild(clonedSvg);
|
| 203 |
+
|
| 204 |
+
// Add xmlns so it can be saved properly
|
| 205 |
+
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
| 206 |
+
|
| 207 |
+
// Serialize the SVG
|
| 208 |
+
const svgData = new XMLSerializer().serializeToString(clonedSvg);
|
| 209 |
+
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
| 210 |
+
|
| 211 |
+
// Create download link
|
| 212 |
+
const url = URL.createObjectURL(svgBlob);
|
| 213 |
+
const a = document.createElement("a");
|
| 214 |
+
a.href = url;
|
| 215 |
+
a.download = "sunburst_chart.svg";
|
| 216 |
+
document.body.appendChild(a);
|
| 217 |
+
a.click();
|
| 218 |
+
document.body.removeChild(a);
|
| 219 |
+
URL.revokeObjectURL(url);
|
| 220 |
+
});
|
| 221 |
+
|
| 222 |
+
</script>
|
| 223 |
+
</body>
|
public/Figure_8b_sunburst.html
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<meta charset="utf-8">
|
| 3 |
+
<style>
|
| 4 |
+
body {
|
| 5 |
+
font-family: "Segoe UI", sans-serif;
|
| 6 |
+
text-align: center;
|
| 7 |
+
margin-top: 20px;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
svg {
|
| 11 |
+
font: 10px sans-serif;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.label {
|
| 15 |
+
font-weight: bold;
|
| 16 |
+
font-size: 12px;
|
| 17 |
+
text-anchor: middle;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.legend {
|
| 21 |
+
font-size: 12px;
|
| 22 |
+
text-anchor: start;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.legend rect {
|
| 26 |
+
stroke-width: 1;
|
| 27 |
+
stroke: #000;
|
| 28 |
+
}
|
| 29 |
+
</style>
|
| 30 |
+
<body>
|
| 31 |
+
<h2>Sunburst Chart: Country → Profession</h2>
|
| 32 |
+
<button id="downloadBtn">Download SVG</button>
|
| 33 |
+
<svg width="800" height="800"></svg>
|
| 34 |
+
<script src="https://d3js.org/d3.v6.min.js"></script>
|
| 35 |
+
<script>
|
| 36 |
+
const width = 800;
|
| 37 |
+
const radius = width / 3;
|
| 38 |
+
|
| 39 |
+
const colorMap = {
|
| 40 |
+
"Adult Performer": "#8A2BE2",
|
| 41 |
+
"Model": "#DC143C",
|
| 42 |
+
"Actor": "#FF7F50",
|
| 43 |
+
"Performer": "magenta",
|
| 44 |
+
"Singer, Musician": "wheat",
|
| 45 |
+
//"Fictional Character": "#708090",
|
| 46 |
+
"Sports Professional": "gold",
|
| 47 |
+
"Voice Actor": "lightgreen",
|
| 48 |
+
"Online Personality": "#4682B4",
|
| 49 |
+
"Other": "#ccc"
|
| 50 |
+
};
|
| 51 |
+
|
| 52 |
+
const professionOrder = [
|
| 53 |
+
"Actor", "Adult Performer", "Singer, Musician", "Model", "Online Personality", "Sports Professional", "Voice Actor", "Other"
|
| 54 |
+
];
|
| 55 |
+
|
| 56 |
+
const manualOrder = [
|
| 57 |
+
"United States",
|
| 58 |
+
"Japan",
|
| 59 |
+
"United Kingdom",
|
| 60 |
+
"South Korea",
|
| 61 |
+
"Russia",
|
| 62 |
+
"India",
|
| 63 |
+
"France",
|
| 64 |
+
"China",
|
| 65 |
+
"Canada",
|
| 66 |
+
"Brazil",
|
| 67 |
+
"Germany",
|
| 68 |
+
"Italy",
|
| 69 |
+
"Australia",
|
| 70 |
+
"Spain",
|
| 71 |
+
"Ukraine",
|
| 72 |
+
"Turkey",
|
| 73 |
+
"Netherlands",
|
| 74 |
+
"Indonesia",
|
| 75 |
+
"Poland",
|
| 76 |
+
"Czech Republic",
|
| 77 |
+
"Argentina",
|
| 78 |
+
"Sweden",
|
| 79 |
+
"Mexico",
|
| 80 |
+
"Taiwan",
|
| 81 |
+
"Thailand",
|
| 82 |
+
"Ireland",
|
| 83 |
+
"Philippines",
|
| 84 |
+
"Hong Kong",
|
| 85 |
+
"Macau",
|
| 86 |
+
"British Virgin Islands",
|
| 87 |
+
"Other"
|
| 88 |
+
];
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
function customSort(a, b, order) {
|
| 92 |
+
const aIndex = order.indexOf(a.data.name);
|
| 93 |
+
const bIndex = order.indexOf(b.data.name);
|
| 94 |
+
return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex);
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
function partition(data) {
|
| 98 |
+
const root = d3.hierarchy(data).sum(d => d.value);
|
| 99 |
+
|
| 100 |
+
// Sort countries by manual order before partitioning
|
| 101 |
+
root.children.sort((a, b) => customSort(a, b, manualOrder));
|
| 102 |
+
|
| 103 |
+
// Sort professions by value (descending), with "Other" always last
|
| 104 |
+
root.children.forEach(country => {
|
| 105 |
+
if (country.children) {
|
| 106 |
+
country.children.sort((a, b) => {
|
| 107 |
+
if (a.data.name === "Other") return 1;
|
| 108 |
+
if (b.data.name === "Other") return -1;
|
| 109 |
+
return b.value - a.value;
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
return d3.partition().size([2 * Math.PI, root.height + 1])(root);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
d3.json("json/sunburst_countries_A.json").then(data => {
|
| 120 |
+
const root = partition(data);
|
| 121 |
+
root.each(d => d.current = d);
|
| 122 |
+
|
| 123 |
+
// Sort countries with 'Other' last
|
| 124 |
+
root.children.sort((a, b) => customSort(a, b, manualOrder));
|
| 125 |
+
|
| 126 |
+
// Sort professions within countries with 'Other' last
|
| 127 |
+
root.children.forEach(country => {
|
| 128 |
+
if (country.children) {
|
| 129 |
+
country.children.sort((a, b) => customSort(a, b, professionOrder));
|
| 130 |
+
}
|
| 131 |
+
});
|
| 132 |
+
|
| 133 |
+
const svg = d3.select("svg")
|
| 134 |
+
.attr("viewBox", [0, 0, width, width])
|
| 135 |
+
.style("font", "10px sans-serif");
|
| 136 |
+
|
| 137 |
+
const g = svg.append("g")
|
| 138 |
+
.attr("transform", `translate(${width / 2},${width / 2})`);
|
| 139 |
+
|
| 140 |
+
const arc = d3.arc()
|
| 141 |
+
.startAngle(d => d.x0)
|
| 142 |
+
.endAngle(d => d.x1)
|
| 143 |
+
.innerRadius(d => {
|
| 144 |
+
if (d.depth === 1) return radius * 0.1;
|
| 145 |
+
if (d.depth === 2) return radius * 0.5;
|
| 146 |
+
})
|
| 147 |
+
.outerRadius(d => {
|
| 148 |
+
if (d.depth === 1) return radius * 0.5;
|
| 149 |
+
if (d.depth === 2) return radius * 0.65;
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
const path = g.append("g")
|
| 153 |
+
.selectAll("path")
|
| 154 |
+
.data(root.descendants().slice(1))
|
| 155 |
+
.join("path")
|
| 156 |
+
.attr("fill", d => {
|
| 157 |
+
if (d.depth === 2) return colorMap[d.data.name] || "#ccc";
|
| 158 |
+
if (d.depth === 1) return "#fff";
|
| 159 |
+
return "none";
|
| 160 |
+
})
|
| 161 |
+
.attr("stroke", d => d.depth === 1 ? "#000" : null)
|
| 162 |
+
.attr("stroke-width", d => d.depth > 0 ? 2 : null)
|
| 163 |
+
.attr("d", arc);
|
| 164 |
+
|
| 165 |
+
path.append("title")
|
| 166 |
+
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join(" → ")}\n${d.value}`);
|
| 167 |
+
|
| 168 |
+
// Add bold labels to country segments
|
| 169 |
+
g.selectAll("text")
|
| 170 |
+
.data(root.children)
|
| 171 |
+
.join("text")
|
| 172 |
+
.attr("transform", function(d) {
|
| 173 |
+
const angle = (d.x0 + d.x1) / 2;
|
| 174 |
+
const x = Math.cos(angle - Math.PI / 2) * (radius / root.height + -50);
|
| 175 |
+
const y = Math.sin(angle - Math.PI / 2) * (radius / root.height + -50);
|
| 176 |
+
return `translate(${x},${y}) rotate(${(angle / Math.PI)})`;
|
| 177 |
+
})
|
| 178 |
+
.attr("dy", "0.35em")
|
| 179 |
+
.attr("class", "label")
|
| 180 |
+
.text(d => d.data.name);
|
| 181 |
+
|
| 182 |
+
// Count total values per profession (depth 2)
|
| 183 |
+
const professionCounts = {};
|
| 184 |
+
root.descendants().forEach(d => {
|
| 185 |
+
if (d.depth === 2) {
|
| 186 |
+
const name = d.data.name;
|
| 187 |
+
professionCounts[name] = (professionCounts[name] || 0) + d.value;
|
| 188 |
+
}
|
| 189 |
+
});
|
| 190 |
+
|
| 191 |
+
// Legend for profession colors
|
| 192 |
+
const legend = svg.append("g")
|
| 193 |
+
.attr("class", "legend")
|
| 194 |
+
.attr("transform", `translate(20,20)`);
|
| 195 |
+
|
| 196 |
+
professionOrder.forEach((prof, i) => {
|
| 197 |
+
const row = legend.append("g")
|
| 198 |
+
.attr("transform", `translate(0,${i * 20})`);
|
| 199 |
+
|
| 200 |
+
row.append("rect")
|
| 201 |
+
.attr("width", 16)
|
| 202 |
+
.attr("height", 16)
|
| 203 |
+
.attr("fill", colorMap[prof]);
|
| 204 |
+
|
| 205 |
+
row.append("text")
|
| 206 |
+
.attr("x", 22)
|
| 207 |
+
.attr("y", 12)
|
| 208 |
+
.text(`${prof} (${professionCounts[prof] || 0})`);
|
| 209 |
+
});
|
| 210 |
+
|
| 211 |
+
document.getElementById("downloadBtn").addEventListener("click", () => {
|
| 212 |
+
const svgNode = document.querySelector("svg");
|
| 213 |
+
|
| 214 |
+
// Clone the SVG node to preserve the original
|
| 215 |
+
const clonedSvg = svgNode.cloneNode(true);
|
| 216 |
+
const outer = document.createElement("div");
|
| 217 |
+
outer.appendChild(clonedSvg);
|
| 218 |
+
|
| 219 |
+
// Add xmlns so it can be saved properly
|
| 220 |
+
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
| 221 |
+
|
| 222 |
+
// Inline all computed styles
|
| 223 |
+
const allElements = clonedSvg.querySelectorAll("*");
|
| 224 |
+
const computedStyles = window.getComputedStyle(svgNode);
|
| 225 |
+
clonedSvg.style.font = computedStyles.font;
|
| 226 |
+
|
| 227 |
+
allElements.forEach(el => {
|
| 228 |
+
const style = window.getComputedStyle(el);
|
| 229 |
+
el.setAttribute("style", `
|
| 230 |
+
font: ${style.font};
|
| 231 |
+
fill: ${style.fill};
|
| 232 |
+
stroke: ${style.stroke};
|
| 233 |
+
stroke-width: ${style.strokeWidth};
|
| 234 |
+
`);
|
| 235 |
+
});
|
| 236 |
+
|
| 237 |
+
// Serialize the SVG
|
| 238 |
+
const svgData = new XMLSerializer().serializeToString(clonedSvg);
|
| 239 |
+
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
| 240 |
+
|
| 241 |
+
// Create download link
|
| 242 |
+
const url = URL.createObjectURL(svgBlob);
|
| 243 |
+
const a = document.createElement("a");
|
| 244 |
+
a.href = url;
|
| 245 |
+
a.download = "sunburst_chart.svg";
|
| 246 |
+
document.body.appendChild(a);
|
| 247 |
+
a.click();
|
| 248 |
+
document.body.removeChild(a);
|
| 249 |
+
URL.revokeObjectURL(url);
|
| 250 |
+
});
|
| 251 |
+
});
|
| 252 |
+
</script> <!-- ✅ You were missing this -->
|
| 253 |
+
</body>
|
| 254 |
+
</html>
|
public/Figure_9.html
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<title>Updated Sankey Diagram</title>
|
| 6 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
| 7 |
+
<script src="https://cdn.jsdelivr.net/npm/d3-sankey@0.12.3/dist/d3-sankey.min.js"></script>
|
| 8 |
+
<style>
|
| 9 |
+
body { font-family: sans-serif; margin: 0; padding: 0; }
|
| 10 |
+
svg { font: bold 14px sans-serif; }
|
| 11 |
+
</style>
|
| 12 |
+
</head>
|
| 13 |
+
<body>
|
| 14 |
+
<svg id="sankey"></svg>
|
| 15 |
+
<button onclick="saveSVG()" style="margin: 10px; padding: 8px 16px;">Download SVG</button>
|
| 16 |
+
<svg id="sankey"></svg>
|
| 17 |
+
|
| 18 |
+
<script>
|
| 19 |
+
const data = [
|
| 20 |
+
{ source: "Total", target: "Checkpoint", value: 4520 },
|
| 21 |
+
{ source: "Total", target: "TextualInversion", value: 1590 },
|
| 22 |
+
{ source: "Total", target: "LoRA", value: 30687 },
|
| 23 |
+
{ source: "Total", target: "Other", value: 1327 },
|
| 24 |
+
{ source: "Total", target: "LOCON", value: 1876 },
|
| 25 |
+
{ source: "LoRA", target: "Adapters", value: 30687 },
|
| 26 |
+
{ source: "LOCON", target: "Adapters", value: 1876 },
|
| 27 |
+
{ source: "TextualInversion", target: "Adapters", value: 1590 },
|
| 28 |
+
{ source: "Adapters", target: "has_no_tags", value: 23002 },
|
| 29 |
+
{ source: "Adapters", target: "has_tags", value: 11151 },
|
| 30 |
+
{ source: "has_tags", target: "has_tags + POI True", value: 2327 },
|
| 31 |
+
{ source: "has_tags + POI True", target: "Danbooru", value: 991 },
|
| 32 |
+
{ source: "Danbooru", target: "explicit", value: 233 },
|
| 33 |
+
{ source: "Danbooru", target: "non-explicit", value: 758 },
|
| 34 |
+
{ source: "has_tags + POI True", target: "CLIP Interrogator", value: 772 },
|
| 35 |
+
{ source: "CLIP Interrogator", target: "explicit", value: 5 },
|
| 36 |
+
{ source: "CLIP Interrogator", target: "non-explicit", value: 767 },
|
| 37 |
+
{ source: "has_tags + POI True", target: "unknown", value: 564 },
|
| 38 |
+
{ source: "unknown", target: "non-explicit", value: 564 },
|
| 39 |
+
{ source: "has_tags", target: "has_tags + POI False", value: 8824 },
|
| 40 |
+
{ source: "has_tags + POI False", target: "Danbooru", value: 6839 },
|
| 41 |
+
{ source: "Danbooru", target: "explicit", value: 4692 },
|
| 42 |
+
{ source: "Danbooru", target: "non-explicit", value: 2147 },
|
| 43 |
+
{ source: "has_tags + POI False", target: "CLIP Interrogator", value: 800 },
|
| 44 |
+
{ source: "CLIP Interrogator", target: "explicit", value: 37 },
|
| 45 |
+
{ source: "CLIP Interrogator", target: "non-explicit", value: 763 },
|
| 46 |
+
{ source: "has_tags + POI False", target: "unknown", value: 1185 },
|
| 47 |
+
{ source: "unknown", target: "explicit", value: 3 },
|
| 48 |
+
{ source: "unknown", target: "non-explicit", value: 1182 },
|
| 49 |
+
{ source: "explicit", target: "explicit_keyword_1", value: 558 },
|
| 50 |
+
{ source: "explicit", target: "explicit_keyword_2", value: 69 },
|
| 51 |
+
{ source: "explicit", target: "explicit_keyword_3", value: 189 }
|
| 52 |
+
];
|
| 53 |
+
|
| 54 |
+
const width = 1600;
|
| 55 |
+
const height = 800;
|
| 56 |
+
|
| 57 |
+
const svg = d3.select("#sankey")
|
| 58 |
+
.attr("width", width)
|
| 59 |
+
.attr("height", height);
|
| 60 |
+
|
| 61 |
+
// Extract unique node names
|
| 62 |
+
const nodes = Array.from(
|
| 63 |
+
new Set(data.flatMap(d => [d.source, d.target])),
|
| 64 |
+
name => ({ name })
|
| 65 |
+
);
|
| 66 |
+
|
| 67 |
+
const sankeyData = {
|
| 68 |
+
nodes,
|
| 69 |
+
links: data.map(d => Object.assign({}, d))
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
// Sankey layout
|
| 73 |
+
const sankey = d3.sankey()
|
| 74 |
+
.nodeId(d => d.name)
|
| 75 |
+
.nodeAlign(d3.sankeyLeft)
|
| 76 |
+
.nodeWidth(10)
|
| 77 |
+
.nodePadding(60)
|
| 78 |
+
.extent([[1, 1], [width - 1, height - 6]]);
|
| 79 |
+
|
| 80 |
+
const { nodes: layoutNodes, links: layoutLinks } = sankey(sankeyData);
|
| 81 |
+
|
| 82 |
+
// Color map (updated)
|
| 83 |
+
const color = name => {
|
| 84 |
+
const map = {
|
| 85 |
+
"Total": "#C0C0C0",
|
| 86 |
+
"Adapters": "rosybrown",
|
| 87 |
+
"Checkpoint": "#C0C0C0",
|
| 88 |
+
"LoRA": "rosybrown",
|
| 89 |
+
"LOCON": "rosybrown",
|
| 90 |
+
"Danbooru": "crimson",
|
| 91 |
+
"Other": "#C0C0C0",
|
| 92 |
+
"Textual Training Data": "coral",
|
| 93 |
+
"No Textual Training Data": "#ccc",
|
| 94 |
+
"has_tags + POI False": "silver",
|
| 95 |
+
"has_tags + POI True": "coral",
|
| 96 |
+
"Real person": "FF7F50",
|
| 97 |
+
"POI False": "#BC8F8F",
|
| 98 |
+
"explicit": "maroon",
|
| 99 |
+
"non-explicit": "#C0C0C0",
|
| 100 |
+
"has_tags": "coral",
|
| 101 |
+
"explicit_keyword_1": "#8A2BE2",
|
| 102 |
+
"explicit_keyword_2": "#8A2BE2",
|
| 103 |
+
"explicit_keyword_3": "#8A2BE2"
|
| 104 |
+
};
|
| 105 |
+
return map[name] || "#ccc";
|
| 106 |
+
};
|
| 107 |
+
|
| 108 |
+
// Label map (optional, can extend if needed)
|
| 109 |
+
const labelMap = {
|
| 110 |
+
"has_tags + POI True": "Real person",
|
| 111 |
+
"has_tags + POI False": "Not real person",
|
| 112 |
+
"has_no_tags": "No text training data",
|
| 113 |
+
"explicit_keyword_1": "Loli",
|
| 114 |
+
"explicit_keyword_2": "Shota",
|
| 115 |
+
"explicit_keyword_3": "Rape",
|
| 116 |
+
"has_tags": "Textual training data",
|
| 117 |
+
"explicit": "Explicit",
|
| 118 |
+
"non-explicit": "Not explicit",
|
| 119 |
+
|
| 120 |
+
// keep other mappings if needed
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
const defs = svg.append("defs");
|
| 124 |
+
|
| 125 |
+
layoutLinks.forEach((d, i) => {
|
| 126 |
+
const grad = defs.append("linearGradient")
|
| 127 |
+
.attr("id", d.uid = `link-gradient-${i}`)
|
| 128 |
+
.attr("gradientUnits", "userSpaceOnUse")
|
| 129 |
+
.attr("x1", d.source.x1)
|
| 130 |
+
.attr("x2", d.target.x0);
|
| 131 |
+
|
| 132 |
+
grad.append("stop")
|
| 133 |
+
.attr("offset", "0%")
|
| 134 |
+
.attr("stop-color", color(d.source.name));
|
| 135 |
+
|
| 136 |
+
grad.append("stop")
|
| 137 |
+
.attr("offset", "100%")
|
| 138 |
+
.attr("stop-color", color(d.target.name));
|
| 139 |
+
});
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
// Draw links
|
| 143 |
+
svg.append("g")
|
| 144 |
+
.attr("fill", "none")
|
| 145 |
+
.attr("stroke-opacity", 0.5)
|
| 146 |
+
.selectAll("path")
|
| 147 |
+
.data(layoutLinks)
|
| 148 |
+
.join("path")
|
| 149 |
+
.attr("d", d3.sankeyLinkHorizontal())
|
| 150 |
+
.attr("stroke", d => `url(#${d.uid})`)
|
| 151 |
+
.attr("stroke-width", d => Math.max(1, d.width));
|
| 152 |
+
|
| 153 |
+
// Draw nodes
|
| 154 |
+
svg.append("g")
|
| 155 |
+
// .attr("stroke", "#000")
|
| 156 |
+
.selectAll("rect")
|
| 157 |
+
.data(layoutNodes)
|
| 158 |
+
.join("rect")
|
| 159 |
+
.attr("x", d => d.x0)
|
| 160 |
+
.attr("y", d => d.y0)
|
| 161 |
+
.attr("height", d => d.y1 - d.y0)
|
| 162 |
+
.attr("width", d => d.x1 - d.x0)
|
| 163 |
+
.attr("fill", d => color(d.name))
|
| 164 |
+
.append("title")
|
| 165 |
+
.text(d => `${d.name}\n${d.value}`);
|
| 166 |
+
|
| 167 |
+
// Draw labels
|
| 168 |
+
svg.append("g")
|
| 169 |
+
.selectAll("text")
|
| 170 |
+
.data(layoutNodes)
|
| 171 |
+
.join("text")
|
| 172 |
+
.attr("x", d => d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
|
| 173 |
+
.attr("y", d => (d.y1 + d.y0) / 2)
|
| 174 |
+
.attr("dy", "0.35em")
|
| 175 |
+
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
|
| 176 |
+
.style("font-size", "22px")
|
| 177 |
+
.each(function(d) {
|
| 178 |
+
const text = d3.select(this);
|
| 179 |
+
const label = labelMap[d.name] || d.name;
|
| 180 |
+
const lines = label.split(/(?<=\w)\s+(?=\w)/);
|
| 181 |
+
|
| 182 |
+
lines.forEach((line, i) => {
|
| 183 |
+
text.append("tspan")
|
| 184 |
+
.attr("x", d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
|
| 185 |
+
.attr("dy", i === 0 ? "0.35em" : "1.1em")
|
| 186 |
+
.text(line);
|
| 187 |
+
});
|
| 188 |
+
|
| 189 |
+
const format = d3.format(",");
|
| 190 |
+
text.append("tspan")
|
| 191 |
+
.attr("x", d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
|
| 192 |
+
.attr("dy", "1.5em")
|
| 193 |
+
.style("font-size", "20px")
|
| 194 |
+
.style("fill", "#555")
|
| 195 |
+
.text(`${format(d.value)}`);
|
| 196 |
+
});
|
| 197 |
+
|
| 198 |
+
function inlineStyles(svgElement) {
|
| 199 |
+
const styles = `
|
| 200 |
+
text { font-family: sans-serif; fill: black; font-weight: bold; }
|
| 201 |
+
rect { stroke: none; }
|
| 202 |
+
path { stroke-opacity: 0.5; }
|
| 203 |
+
`;
|
| 204 |
+
const styleElem = document.createElementNS("http://www.w3.org/2000/svg", "style");
|
| 205 |
+
styleElem.textContent = styles;
|
| 206 |
+
svgElement.insertBefore(styleElem, svgElement.firstChild);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
function saveSVG() {
|
| 210 |
+
const svgElement = document.querySelector("svg");
|
| 211 |
+
inlineStyles(svgElement); // inject inline CSS
|
| 212 |
+
const serializer = new XMLSerializer();
|
| 213 |
+
const source = serializer.serializeToString(svgElement);
|
| 214 |
+
const svgBlob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
|
| 215 |
+
const svgUrl = URL.createObjectURL(svgBlob);
|
| 216 |
+
const downloadLink = document.createElement("a");
|
| 217 |
+
downloadLink.href = svgUrl;
|
| 218 |
+
downloadLink.download = "sankey-diagram.svg";
|
| 219 |
+
document.body.appendChild(downloadLink);
|
| 220 |
+
downloadLink.click();
|
| 221 |
+
document.body.removeChild(downloadLink);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
</script>
|
| 225 |
+
</body>
|
| 226 |
+
</html>
|
public/Figure_9_old.html
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<title>Unified Sankey Diagram</title>
|
| 6 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
| 7 |
+
<script src="https://cdn.jsdelivr.net/npm/d3-sankey@0.12.3/dist/d3-sankey.min.js"></script>
|
| 8 |
+
<style>
|
| 9 |
+
body { font-family: sans-serif; }
|
| 10 |
+
svg { font: bold 14px sans-serif; }
|
| 11 |
+
</style>
|
| 12 |
+
</head>
|
| 13 |
+
<body>
|
| 14 |
+
<svg id="sankey"></svg>
|
| 15 |
+
<script>
|
| 16 |
+
const data = [
|
| 17 |
+
{ source: "Total", target: "Checkpoint", value: 4520 },
|
| 18 |
+
{ source: "Total", target: "Other", value: 1327 },
|
| 19 |
+
{ source: "Total", target: "Adapters", value: 34153 },
|
| 20 |
+
{ source: "Adapters", target: "Textual Training Data", value: 11151 },
|
| 21 |
+
{ source: "Adapters", target: "Unknown Textual Training Data", value: 23002 },
|
| 22 |
+
{ source: "Textual Training Data", target: "POI True", value: 2327 },
|
| 23 |
+
{ source: "Textual Training Data", target: "POI False", value: 8824 },
|
| 24 |
+
{ source: "POI True", target: "explicit", value: 238 },
|
| 25 |
+
{ source: "POI True", target: "non-explicit", value: 2089 },
|
| 26 |
+
{ source: "POI False", target: "explicit", value: 4732 },
|
| 27 |
+
{ source: "POI False", target: "non-explicit", value: 4092 },
|
| 28 |
+
{ source: "explicit", target: "loli", value: 558 },
|
| 29 |
+
{ source: "explicit", target: "shota", value: 69 },
|
| 30 |
+
{ source: "explicit", target: "rape", value: 189 }
|
| 31 |
+
];
|
| 32 |
+
|
| 33 |
+
////////////////////////////////////////// SAVE SVG //////////////////////////////////////////
|
| 34 |
+
|
| 35 |
+
function inlineStyles(svgElement) {
|
| 36 |
+
const styles = `
|
| 37 |
+
text { font-family: sans-serif; fill: black; font-weight: bold; }
|
| 38 |
+
rect { stroke: none; }
|
| 39 |
+
`;
|
| 40 |
+
const styleElem = document.createElementNS("http://www.w3.org/2000/svg", "style");
|
| 41 |
+
styleElem.textContent = styles;
|
| 42 |
+
svgElement.insertBefore(styleElem, svgElement.firstChild);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
function saveSVG() {
|
| 46 |
+
const svgElement = document.querySelector("svg");
|
| 47 |
+
inlineStyles(svgElement); // inject style
|
| 48 |
+
const serializer = new XMLSerializer();
|
| 49 |
+
const source = serializer.serializeToString(svgElement);
|
| 50 |
+
const svgBlob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
|
| 51 |
+
const svgUrl = URL.createObjectURL(svgBlob);
|
| 52 |
+
const downloadLink = document.createElement("a");
|
| 53 |
+
downloadLink.href = svgUrl;
|
| 54 |
+
downloadLink.download = "sankey-diagram.svg";
|
| 55 |
+
document.body.appendChild(downloadLink);
|
| 56 |
+
downloadLink.click();
|
| 57 |
+
document.body.removeChild(downloadLink);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
window.addEventListener("keydown", function (e) {
|
| 61 |
+
if (e.code === "Space") {
|
| 62 |
+
e.preventDefault(); // prevent page scrolling
|
| 63 |
+
saveSVG();
|
| 64 |
+
}
|
| 65 |
+
});
|
| 66 |
+
////////////////////////////////////////// SAVE SVG //////////////////////////////////////////
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
const width = 680;
|
| 71 |
+
const height = 400;
|
| 72 |
+
|
| 73 |
+
const svg = d3.select("#sankey")
|
| 74 |
+
.attr("width", width)
|
| 75 |
+
.attr("height", height);
|
| 76 |
+
|
| 77 |
+
const sankey = d3.sankey()
|
| 78 |
+
.nodeId(d => d.name)
|
| 79 |
+
.nodeAlign(d3.sankeyLeft)
|
| 80 |
+
.nodeWidth(15)
|
| 81 |
+
.nodePadding(20)
|
| 82 |
+
.extent([[1, 1], [width - 1, height - 6]]);
|
| 83 |
+
|
| 84 |
+
const nodeSet = new Set();
|
| 85 |
+
data.forEach(d => {
|
| 86 |
+
nodeSet.add(d.source);
|
| 87 |
+
nodeSet.add(d.target);
|
| 88 |
+
});
|
| 89 |
+
const nodes = Array.from(nodeSet).map(name => ({ name }));
|
| 90 |
+
|
| 91 |
+
const { nodes: layoutNodes, links: layoutLinks } = sankey({
|
| 92 |
+
nodes: nodes.map(d => Object.assign({}, d)),
|
| 93 |
+
links: data.map(d => Object.assign({}, d))
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
const color = name => {
|
| 99 |
+
const map = {
|
| 100 |
+
"Total": "#C0C0C0",
|
| 101 |
+
"Adapters": "#C0C0C0",
|
| 102 |
+
"Checkpoint": "#C0C0C0",
|
| 103 |
+
"Other": "#C0C0C0",
|
| 104 |
+
"Textual Training Data": "#BC8F8F",
|
| 105 |
+
"No Textual Training Data": "#ccc",
|
| 106 |
+
"POI True": "#FFA07A",
|
| 107 |
+
"POI False": "#BC8F8F",
|
| 108 |
+
"explicit": "#DC143C",
|
| 109 |
+
"non-explicit": "#C0C0C0",
|
| 110 |
+
"loli": "#8A2BE2",
|
| 111 |
+
"shota": "#8A2BE2",
|
| 112 |
+
"rape": "#8A2BE2"
|
| 113 |
+
};
|
| 114 |
+
return map[name] || "#ccc";
|
| 115 |
+
};
|
| 116 |
+
|
| 117 |
+
const labelMap = {
|
| 118 |
+
"Textual Training Data": "Textual training data found",
|
| 119 |
+
"No Textual Training Data": "No textual training data",
|
| 120 |
+
"POI True": "POI true",
|
| 121 |
+
"POI False": "POI false",
|
| 122 |
+
"non-explicit": "Non-explicit",
|
| 123 |
+
"explicit": "Explicit",
|
| 124 |
+
"Total": "Total",
|
| 125 |
+
"Adapters": "Adapters",
|
| 126 |
+
"Checkpoint": "Checkpoint",
|
| 127 |
+
"Other": "Other",
|
| 128 |
+
"loli": "Loli",
|
| 129 |
+
"shota": "Shota",
|
| 130 |
+
"rape": "Rape"
|
| 131 |
+
};
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
const defs = svg.append("defs");
|
| 135 |
+
layoutLinks.forEach((d, i) => {
|
| 136 |
+
const grad = defs.append("linearGradient")
|
| 137 |
+
.attr("id", d.uid = `link-${i}`)
|
| 138 |
+
.attr("gradientUnits", "userSpaceOnUse")
|
| 139 |
+
.attr("x1", d.source.x1)
|
| 140 |
+
.attr("x2", d.target.x0);
|
| 141 |
+
|
| 142 |
+
grad.append("stop").attr("offset", "0%").attr("stop-color", color(d.source.name));
|
| 143 |
+
grad.append("stop").attr("offset", "100%").attr("stop-color", color(d.target.name));
|
| 144 |
+
});
|
| 145 |
+
|
| 146 |
+
svg.append("g")
|
| 147 |
+
.attr("fill", "none")
|
| 148 |
+
.attr("stroke-opacity", 0.5)
|
| 149 |
+
.selectAll("path")
|
| 150 |
+
.data(layoutLinks)
|
| 151 |
+
.join("path")
|
| 152 |
+
.attr("d", d3.sankeyLinkHorizontal())
|
| 153 |
+
.attr("stroke", d => `url(#${d.uid})`)
|
| 154 |
+
.attr("stroke-width", d => Math.max(1, d.width))
|
| 155 |
+
.append("title")
|
| 156 |
+
.text(d => `${d.source.name} → ${d.target.name}\n${d.value}`);
|
| 157 |
+
|
| 158 |
+
svg.append("g")
|
| 159 |
+
.selectAll("rect")
|
| 160 |
+
.data(layoutNodes)
|
| 161 |
+
.join("rect")
|
| 162 |
+
.attr("x", d => d.x0)
|
| 163 |
+
.attr("y", d => d.y0)
|
| 164 |
+
.attr("height", d => d.y1 - d.y0)
|
| 165 |
+
.attr("width", d => d.x1 - d.x0)
|
| 166 |
+
.attr("fill", d => color(d.name))
|
| 167 |
+
.append("title")
|
| 168 |
+
.text(d => `${d.name}\n${d.value}`);
|
| 169 |
+
|
| 170 |
+
svg.append("g")
|
| 171 |
+
.selectAll("text")
|
| 172 |
+
.data(layoutNodes)
|
| 173 |
+
.join("text")
|
| 174 |
+
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
|
| 175 |
+
.attr("y", d => (d.y1 + d.y0) / 2)
|
| 176 |
+
.attr("dy", "0.35em")
|
| 177 |
+
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
|
| 178 |
+
.style("font-size", "16px")
|
| 179 |
+
.each(function(d) {
|
| 180 |
+
const text = d3.select(this);
|
| 181 |
+
const label = labelMap[d.name] || d.name;
|
| 182 |
+
const lines = label.split(/(?<=\w)\s+(?=\w)/); // split long words
|
| 183 |
+
|
| 184 |
+
lines.forEach((line, i) => {
|
| 185 |
+
text.append("tspan")
|
| 186 |
+
.attr("x", d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
|
| 187 |
+
.attr("dy", i === 0 ? "0.35em" : "1.2em")
|
| 188 |
+
.text(line);
|
| 189 |
+
});
|
| 190 |
+
|
| 191 |
+
/* const base = layoutNodes.find(n => n.name === "Total"); /// Percentages
|
| 192 |
+
if (base && base.value > 0) {
|
| 193 |
+
const pct = ((d.value / base.value) * 100).toFixed(1);
|
| 194 |
+
text.append("tspan")
|
| 195 |
+
.attr("x", d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
|
| 196 |
+
.attr("dy", "1.5em")
|
| 197 |
+
.style("font-size", "14px")
|
| 198 |
+
.style("fill", "#333")
|
| 199 |
+
.text(`${pct}%`); */
|
| 200 |
+
|
| 201 |
+
const format = d3.format(",");
|
| 202 |
+
text.append("tspan")
|
| 203 |
+
.attr("x", d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
|
| 204 |
+
.attr("dy", "1.5em")
|
| 205 |
+
.style("font-size", "14px")
|
| 206 |
+
.style("fill", "#333")
|
| 207 |
+
.text(`${format(d.value)}`);
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
});
|
| 211 |
+
</script>
|
| 212 |
+
</body>
|
| 213 |
+
</html>
|
public/csv/sankey_data.csv
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
source,target,value
|
| 2 |
+
Total,Checkpoint,4520
|
| 3 |
+
Total,TextualInversion,1590
|
| 4 |
+
Total,LoRA,30687
|
| 5 |
+
Total,Other,1327
|
| 6 |
+
Total,LOCON,1876
|
| 7 |
+
LoRA,Adapters,30687
|
| 8 |
+
LOCON,Adapters,1876
|
| 9 |
+
TextualInversion,Adapters,1590
|
| 10 |
+
Adapters,has_no_tags,23002
|
| 11 |
+
Adapters,has_tags,11151
|
| 12 |
+
has_tags,has_tags + POI True,2327
|
| 13 |
+
has_tags + POI True,has_tags + POI True + explicit,238
|
| 14 |
+
has_tags + POI True,has_tags + POI True + non-explicit,2089
|
| 15 |
+
has_tags,has_tags + POI False,8824
|
| 16 |
+
has_tags + POI False,has_tags + POI False + explicit,4732
|
| 17 |
+
has_tags + POI False,has_tags + POI False + non-explicit,4092
|
| 18 |
+
has_tags,All Adapters has_tags + explicit,4970
|
| 19 |
+
has_tags,All Adapters has_tags + non-explicit,6181
|
| 20 |
+
explicit,has_tag_rape,189
|
| 21 |
+
explicit,has_tag_loli,558
|
| 22 |
+
explicit,has_tag_shota,69
|
public/figure_15.html
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
| 6 |
+
<link rel="stylesheet" href="styles.css">
|
| 7 |
+
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<!-- Header Overlay -->
|
| 11 |
+
<div class="header-overlay">
|
| 12 |
+
<div class="visualization-container">
|
| 13 |
+
<h1>Semantic Network of Textual Training Data</h1>
|
| 14 |
+
<h2 class="caption">Co-occurence of textual captions (tags) extracted from model adapters downloaded from CivitAI</h2>
|
| 15 |
+
|
| 16 |
+
<div class="nav-buttons">
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
</div>
|
| 24 |
+
<button class="nav-button" onclick="location.href='index.html'">Home</button>
|
| 25 |
+
<button class="nav-button" onclick="location.href='figure_5.html'">Next: Promotional Tags</button>
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
<div class="theme-toggle">
|
| 29 |
+
<label><input type="checkbox" id="modeToggle"> Toggle Light Mode</label>
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
<div class="controls">
|
| 33 |
+
<label>Number of tags shown:
|
| 34 |
+
<input type="range" id="tagSlider" min="0" max="2000" value="100" step="10" />
|
| 35 |
+
<output id="tagCount">500</output>
|
| 36 |
+
</label><br/>
|
| 37 |
+
|
| 38 |
+
<button id="resetBtn">Reset</button>
|
| 39 |
+
<button id="downloadBtn">Download SVG</button>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
</div>
|
| 50 |
+
|
| 51 |
+
<!-- Visualization SVG -->
|
| 52 |
+
<svg viewBox="0 0 1600 1600" preserveAspectRatio="xMidYMid meet"></svg>
|
| 53 |
+
|
| 54 |
+
<script>
|
| 55 |
+
const width = 1600, height = 1600;
|
| 56 |
+
let fullGraph = null;
|
| 57 |
+
let currentTransform = d3.zoomIdentity;
|
| 58 |
+
|
| 59 |
+
const categoryColors = {
|
| 60 |
+
visual_characteristics: "#708090",
|
| 61 |
+
attire_and_body_accessories: "#DC143C",
|
| 62 |
+
body: "#FF7F50",
|
| 63 |
+
more: "#BC8F8F",
|
| 64 |
+
subject: "black",
|
| 65 |
+
sex: "#8A2BE2",
|
| 66 |
+
default: "#C0C0C0"
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
const categoryLabels = {
|
| 70 |
+
visual_characteristics: "Image Characteristics",
|
| 71 |
+
attire_and_body_accessories: "Attire & Accessories",
|
| 72 |
+
body: "Body & Face",
|
| 73 |
+
more: "Actions & Probs",
|
| 74 |
+
subject: "Subjects",
|
| 75 |
+
sex: "Adult Content",
|
| 76 |
+
default: "Other"
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
const categoryPositions = {};
|
| 80 |
+
const categories = Object.keys(categoryColors);
|
| 81 |
+
categories.forEach((cat, i) => {
|
| 82 |
+
const angle = (i / categories.length) * 2 * Math.PI;
|
| 83 |
+
categoryPositions[cat] = {
|
| 84 |
+
x: width / 2 * Math.cos(angle),
|
| 85 |
+
y: height / 2 * Math.sin(angle)
|
| 86 |
+
};
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
const svg = d3.select("svg");
|
| 90 |
+
const zoomLayer = svg.append("g")
|
| 91 |
+
.attr("class", "zoom-layer")
|
| 92 |
+
.attr("transform", "translate(300, 300) scale(0.45)");
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
// Create legend outside zoom layer
|
| 96 |
+
const legendWrapper = svg.append("g")
|
| 97 |
+
.attr("class", "legend-wrapper")
|
| 98 |
+
.attr("transform", "translate(10, 400)");
|
| 99 |
+
const legendItemHeight = 24;
|
| 100 |
+
|
| 101 |
+
// Initialize zoom behavior
|
| 102 |
+
const zoom = d3.zoom()
|
| 103 |
+
.scaleExtent([0.01, 8])
|
| 104 |
+
.on("zoom", (event) => {
|
| 105 |
+
currentTransform = event.transform;
|
| 106 |
+
zoomLayer.attr("transform", currentTransform);
|
| 107 |
+
});
|
| 108 |
+
|
| 109 |
+
svg.call(zoom);
|
| 110 |
+
|
| 111 |
+
const simulation = d3.forceSimulation()
|
| 112 |
+
.force("link", d3.forceLink().id(d => d.id).distance(500))
|
| 113 |
+
.force("charge", d3.forceManyBody().strength(-185))
|
| 114 |
+
.force("center", d3.forceCenter(width / 2, (height / 1.5) - 200))
|
| 115 |
+
.force("collide", d3.forceCollide().radius(d => Math.sqrt(d.size) * 0.111))
|
| 116 |
+
.force("category", forceCluster(0.1))
|
| 117 |
+
.alphaDecay(0.15);
|
| 118 |
+
|
| 119 |
+
function forceCluster(strength = 89.05) {
|
| 120 |
+
function force(alpha) {
|
| 121 |
+
for (const d of simulation.nodes()) {
|
| 122 |
+
const centroid = categoryPositions[d.category] || categoryPositions.default;
|
| 123 |
+
d.vx += (centroid.x - d.x) * strength * alpha;
|
| 124 |
+
d.vy += (centroid.y - d.y) * strength * alpha;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
force.initialize = () => {};
|
| 128 |
+
return force;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
d3.json("json/co_occurrence_network.json").then(graph => {
|
| 132 |
+
fullGraph = graph;
|
| 133 |
+
render(+document.getElementById("tagSlider").value);
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
function initializeZoom() {
|
| 137 |
+
const bounds = zoomLayer.node().getBBox();
|
| 138 |
+
const contentWidth = bounds.width;
|
| 139 |
+
const contentHeight = bounds.height;
|
| 140 |
+
|
| 141 |
+
// Calculate scale to fit content with some padding
|
| 142 |
+
const scale = 0.4 / Math.max(
|
| 143 |
+
contentWidth / width,
|
| 144 |
+
contentHeight / height
|
| 145 |
+
);
|
| 146 |
+
|
| 147 |
+
// Calculate translation to center content
|
| 148 |
+
const translateX = (width - contentWidth * scale) / 3 - bounds.x * scale;
|
| 149 |
+
const translateY = (height - contentHeight * scale) / 2 - bounds.y * scale;
|
| 150 |
+
|
| 151 |
+
// Apply the transform smoothly
|
| 152 |
+
svg.transition()
|
| 153 |
+
.duration(1000)
|
| 154 |
+
.call(
|
| 155 |
+
zoom.transform,
|
| 156 |
+
d3.zoomIdentity
|
| 157 |
+
.translate(translateX, translateY)
|
| 158 |
+
.scale(scale)
|
| 159 |
+
);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
function render(nodeCount) {
|
| 163 |
+
zoomLayer.selectAll("*").remove();
|
| 164 |
+
|
| 165 |
+
zoomLayer.append("rect")
|
| 166 |
+
.attr("width", width)
|
| 167 |
+
.attr("height", height)
|
| 168 |
+
.attr("fill", getComputedStyle(document.documentElement).getPropertyValue('--svg-fill') || "#fff");
|
| 169 |
+
|
| 170 |
+
const topNodes = fullGraph.nodes.sort((a, b) => b.size - a.size).slice(0, nodeCount);
|
| 171 |
+
const nodeIds = new Set(topNodes.map(d => d.id));
|
| 172 |
+
const nodesWithPos = topNodes.map(d => {
|
| 173 |
+
const centroid = categoryPositions[d.category] || categoryPositions.default;
|
| 174 |
+
return { ...d, x: centroid.x + Math.random() * 100 - 50, y: centroid.y + Math.random() * 100 - 50 };
|
| 175 |
+
});
|
| 176 |
+
|
| 177 |
+
const nodeLookup = new Map(nodesWithPos.map(n => [n.id, n]));
|
| 178 |
+
|
| 179 |
+
const filteredLinks = fullGraph.links
|
| 180 |
+
.filter(l => nodeIds.has(l.source) && nodeIds.has(l.target))
|
| 181 |
+
.map(l => {
|
| 182 |
+
const source = nodeLookup.get(l.source);
|
| 183 |
+
const target = nodeLookup.get(l.target);
|
| 184 |
+
return {
|
| 185 |
+
...l,
|
| 186 |
+
source,
|
| 187 |
+
target,
|
| 188 |
+
dominant: source.size >= target.size ? source : target
|
| 189 |
+
};
|
| 190 |
+
});
|
| 191 |
+
|
| 192 |
+
const linkValueExtent = d3.extent(filteredLinks, d => d.value);
|
| 193 |
+
const linkStrokeScale = d3.scaleLinear().domain(linkValueExtent).range([0.5, 6]);
|
| 194 |
+
|
| 195 |
+
const link = zoomLayer.append("g").selectAll("line")
|
| 196 |
+
.data(filteredLinks)
|
| 197 |
+
.enter().append("line")
|
| 198 |
+
.attr("stroke", d => categoryColors[d.dominant.category] || categoryColors.default)
|
| 199 |
+
.attr("stroke-width", d => linkStrokeScale(d.value))
|
| 200 |
+
.attr("stroke-opacity", d => Math.min(0.4, d.value / 100));
|
| 201 |
+
|
| 202 |
+
const node = zoomLayer.append("g").selectAll("circle.main")
|
| 203 |
+
.data(nodesWithPos)
|
| 204 |
+
.enter().append("circle")
|
| 205 |
+
.attr("class", "node main")
|
| 206 |
+
.attr("r", d => Math.max(1, Math.sqrt(d.size) * 0.04))
|
| 207 |
+
.attr("fill", d => categoryColors[d.category] || categoryColors.default)
|
| 208 |
+
.attr("fill-opacity", d => Math.max(0.2, Math.min(1, d.size / 200)))
|
| 209 |
+
.call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));
|
| 210 |
+
|
| 211 |
+
const labelGroup = zoomLayer.append("g").selectAll("g.label")
|
| 212 |
+
.data(nodesWithPos)
|
| 213 |
+
.enter().append("g")
|
| 214 |
+
.attr("class", "label");
|
| 215 |
+
|
| 216 |
+
labelGroup.append("text")
|
| 217 |
+
.attr("text-anchor", "start")
|
| 218 |
+
.attr("dy", "0.35em")
|
| 219 |
+
.attr("fill", "black")
|
| 220 |
+
.attr("fill-opacity", 0.7)
|
| 221 |
+
.style("font-size", d => `${Math.max(11, Math.sqrt(d.size) * 0.04)}px`)
|
| 222 |
+
.text(d => d.id);
|
| 223 |
+
|
| 224 |
+
labelGroup.insert("rect", "text")
|
| 225 |
+
.attr("rx", 4)
|
| 226 |
+
.attr("ry", 4)
|
| 227 |
+
.attr("fill", "white")
|
| 228 |
+
.attr("fill-opacity", 0.7);
|
| 229 |
+
|
| 230 |
+
// Update legend
|
| 231 |
+
legendWrapper.selectAll("*").remove();
|
| 232 |
+
|
| 233 |
+
const legend = legendWrapper.selectAll(".legend")
|
| 234 |
+
.data(Object.keys(categoryColors))
|
| 235 |
+
.enter().append("g")
|
| 236 |
+
.attr("class", "legend")
|
| 237 |
+
.attr("transform", (d, i) => `translate(10,${i * legendItemHeight + 10})`);
|
| 238 |
+
|
| 239 |
+
legend.append("rect")
|
| 240 |
+
.attr("width", 12)
|
| 241 |
+
.attr("height", 12)
|
| 242 |
+
.attr("fill", d => categoryColors[d]);
|
| 243 |
+
|
| 244 |
+
legend.append("text")
|
| 245 |
+
.attr("x", 18)
|
| 246 |
+
.attr("y", 6)
|
| 247 |
+
.attr("dy", "0.35em")
|
| 248 |
+
.style("font-size", "14px")
|
| 249 |
+
.attr("fill", "black")
|
| 250 |
+
.text(d => categoryLabels[d] || d);
|
| 251 |
+
|
| 252 |
+
simulation.nodes(nodesWithPos).on("tick", () => {
|
| 253 |
+
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
|
| 254 |
+
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
|
| 255 |
+
|
| 256 |
+
node.attr("cx", d => d.x).attr("cy", d => d.y);
|
| 257 |
+
|
| 258 |
+
labelGroup.each(function(d) {
|
| 259 |
+
const text = d3.select(this).select("text");
|
| 260 |
+
const rect = d3.select(this).select("rect");
|
| 261 |
+
const bbox = text.node().getBBox();
|
| 262 |
+
const padding = 2;
|
| 263 |
+
const offset = Math.max(2, Math.sqrt(d.size) * 0.045 + padding);
|
| 264 |
+
|
| 265 |
+
d3.select(this).attr("transform", `translate(${d.x + offset},${d.y})`);
|
| 266 |
+
rect
|
| 267 |
+
.attr("x", bbox.x - padding)
|
| 268 |
+
.attr("y", bbox.y - padding)
|
| 269 |
+
.attr("width", bbox.width + padding * 2)
|
| 270 |
+
.attr("height", bbox.height + padding * 2);
|
| 271 |
+
});
|
| 272 |
+
});
|
| 273 |
+
|
| 274 |
+
simulation.force("link").links(filteredLinks);
|
| 275 |
+
simulation.alpha(1).restart();
|
| 276 |
+
|
| 277 |
+
// Initialize zoom after simulation stabilizes
|
| 278 |
+
//simulation.on("end", initializeZoom);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
function dragstarted(event, d) {
|
| 282 |
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
| 283 |
+
d.fx = d.x;
|
| 284 |
+
d.fy = d.y;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
function dragged(event, d) {
|
| 288 |
+
d.fx = event.x;
|
| 289 |
+
d.fy = event.y;
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
function dragended(event, d) {
|
| 293 |
+
if (!event.active) simulation.alphaTarget(0);
|
| 294 |
+
d.fx = null;
|
| 295 |
+
d.fy = null;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
function saveSvg() {
|
| 299 |
+
const svgNode = document.querySelector("svg");
|
| 300 |
+
const serializer = new XMLSerializer();
|
| 301 |
+
const source = serializer.serializeToString(svgNode);
|
| 302 |
+
const blob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
|
| 303 |
+
const url = URL.createObjectURL(blob);
|
| 304 |
+
const a = document.createElement("a");
|
| 305 |
+
a.href = url;
|
| 306 |
+
a.download = "graph.svg";
|
| 307 |
+
document.body.appendChild(a);
|
| 308 |
+
a.click();
|
| 309 |
+
document.body.removeChild(a);
|
| 310 |
+
URL.revokeObjectURL(url);
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
document.getElementById("tagSlider").addEventListener("input", function () {
|
| 314 |
+
const value = +this.value;
|
| 315 |
+
document.getElementById("tagCount").textContent = value;
|
| 316 |
+
render(value);
|
| 317 |
+
});
|
| 318 |
+
|
| 319 |
+
document.getElementById("modeToggle").addEventListener("change", function () {
|
| 320 |
+
document.documentElement.classList.toggle("light-mode");
|
| 321 |
+
render(+document.getElementById("tagSlider").value);
|
| 322 |
+
});
|
| 323 |
+
</script>
|
| 324 |
+
</body>
|
| 325 |
+
</html>
|
public/figure_5.html
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
| 6 |
+
<link rel="stylesheet" href="styles.css">
|
| 7 |
+
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<!-- Header Overlay -->
|
| 11 |
+
<div class="header-overlay">
|
| 12 |
+
<div class="visualization-container">
|
| 13 |
+
<h1>Tag Co-Occurrence Network</h1>
|
| 14 |
+
<h2 class="caption">Thematic clustering of the most frequent promotional tags used across all shared assets on CivitAI</h2>
|
| 15 |
+
|
| 16 |
+
<div class="nav-buttons">
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
</div>
|
| 24 |
+
<button class="nav-button" onclick="location.href='index.html'">Home</button>
|
| 25 |
+
<button class="nav-button" onclick="location.href='figure_15.html'">Next: Textual Training Data</button>
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
<div class="theme-toggle">
|
| 29 |
+
<label><input type="checkbox" id="modeToggle"> Toggle Light Mode</label>
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
<div class="controls">
|
| 33 |
+
<label>Top N Nodes:
|
| 34 |
+
<input type="range" id="nodeCountInput" value="30" min="1" max="300" />
|
| 35 |
+
<output id="nodeCountOutput">20</output>
|
| 36 |
+
</label><br/>
|
| 37 |
+
|
| 38 |
+
<label>Link Distance Scale:
|
| 39 |
+
<input type="range" id="linkScaleInput" value="1500" min="0" max="10000" />
|
| 40 |
+
<output id="linkScaleOutput">0</output>
|
| 41 |
+
</label><br/>
|
| 42 |
+
|
| 43 |
+
<label>Charge Strength:
|
| 44 |
+
<input type="range" id="chargeInput" value="10" min="0" max="50" />
|
| 45 |
+
<output id="chargeOutput">5</output>
|
| 46 |
+
</label><br/>
|
| 47 |
+
|
| 48 |
+
<label>Collide Strength:
|
| 49 |
+
<input type="range" id="collideInput" value="2" min="0" max="10" step="0.1" />
|
| 50 |
+
<output id="collideOutput">2</output>
|
| 51 |
+
</label><br/>
|
| 52 |
+
|
| 53 |
+
<label>Collide Base Radius:
|
| 54 |
+
<input type="range" id="collideBaseInput" value="22" min="0" max="35" />
|
| 55 |
+
<output id="collideBaseOutput">17</output>
|
| 56 |
+
</label><br/>
|
| 57 |
+
|
| 58 |
+
<label>Node Radius Scale:
|
| 59 |
+
<input type="range" id="radiusInput" value="65" min="5" max="80" />
|
| 60 |
+
<output id="radiusOutput">30</output>
|
| 61 |
+
</label><br/>
|
| 62 |
+
|
| 63 |
+
<label>Font Size Scale:
|
| 64 |
+
<input type="range" id="fontInput" value="25" min="1" max="50" />
|
| 65 |
+
<output id="fontOutput">25</output>
|
| 66 |
+
</label><br/>
|
| 67 |
+
|
| 68 |
+
<label>Link Thickness Base:
|
| 69 |
+
<input type="range" id="LinkStrokeIn" value="30" min="1" max="50" />
|
| 70 |
+
<output id="LinkStrokeOut">5</output>
|
| 71 |
+
</label><br/>
|
| 72 |
+
|
| 73 |
+
<label for="jsonSelector">Choose a JSON file:</label>
|
| 74 |
+
<select id="jsonSelector">
|
| 75 |
+
<option value="json/nodes_all.json">nodes_all.json</option>
|
| 76 |
+
<option value="json/nodes.json">nodes.json</option>
|
| 77 |
+
<option value="json/promo_tags_poi_true.json">promo_tags_poi_true.json</option>
|
| 78 |
+
<option value="json/promo_tags.json">promo_tags.json</option>
|
| 79 |
+
<option value="json/sunburst_data.json">sunburst_data.json</option>
|
| 80 |
+
<option value="json/tags_actor.json">tags_actor.json</option>
|
| 81 |
+
<option value="json/tags_actress.json">tags_actress.json</option>
|
| 82 |
+
<option value="json/tags_all_poi.json">tags_all_poi.json</option>
|
| 83 |
+
<option value="json/tags_american_poi.json">tags_american_poi.json</option>
|
| 84 |
+
<option value="json/tags_american.json">tags_american.json</option>
|
| 85 |
+
<option value="json/tags_asian_poi.json">tags_asian_poi.json</option>
|
| 86 |
+
<option value="json/tags_asian.json">tags_asian.json</option>
|
| 87 |
+
<option value="json/tags_canada.json">tags_canada.json</option>
|
| 88 |
+
<option value="json/tags_celebrity.json">tags_celebrity.json</option>
|
| 89 |
+
<option value="json/tags_character.json">tags_character.json</option>
|
| 90 |
+
<option value="json/tags_china.json">tags_china.json</option>
|
| 91 |
+
<option value="json/tags_chinese_poi.json">tags_chinese_poi.json</option>
|
| 92 |
+
<option value="json/tags_chinese.json">tags_chinese.json</option>
|
| 93 |
+
<option value="json/tags_german_poi.json">tags_german_poi.json</option>
|
| 94 |
+
<option value="json/tags_german.json">tags_german.json</option>
|
| 95 |
+
<option value="json/tags_germany.json">tags_germany.json</option>
|
| 96 |
+
<option value="json/tags_india.json">tags_india.json</option>
|
| 97 |
+
<option value="json/tags_indian_poi.json">tags_indian_poi.json</option>
|
| 98 |
+
<option value="json/tags_indian.json">tags_indian.json</option>
|
| 99 |
+
<option value="json/tags_instagram.json">tags_instagram.json</option>
|
| 100 |
+
<option value="json/tags_japan.json">tags_japan.json</option>
|
| 101 |
+
<option value="json/tags_japanese_poi.json">tags_japanese_poi.json</option>
|
| 102 |
+
<option value="json/tags_japanese.json">tags_japanese.json</option>
|
| 103 |
+
<option value="json/tags_korea.json">tags_korea.json</option>
|
| 104 |
+
<option value="json/tags_korean_poi.json">tags_korean_poi.json</option>
|
| 105 |
+
<option value="json/tags_korean.json">tags_korean.json</option>
|
| 106 |
+
<option value="json/tags_kpop.json">tags_kpop.json</option>
|
| 107 |
+
<option value="json/tags_man_poi.json">tags_man_poi.json</option>
|
| 108 |
+
<option value="json/tags_man.json">tags_man.json</option>
|
| 109 |
+
<option value="json/tags_russia.json">tags_russia.json</option>
|
| 110 |
+
<option value="json/tags_russian_poi.json">tags_russian_poi.json</option>
|
| 111 |
+
<option value="json/tags_russian.json">tags_russian.json</option>
|
| 112 |
+
<option value="json/tags_style_poi.json">tags_style_poi.json</option>
|
| 113 |
+
<option value="json/tags_style.json">tags_style.json</option>
|
| 114 |
+
<option value="json/tags_tiktok.json">tags_tiktok.json</option>
|
| 115 |
+
<option value="json/tags_uk.json">tags_uk.json</option>
|
| 116 |
+
<option value="json/tags_ukrainian.json">tags_ukrainian.json</option>
|
| 117 |
+
<option value="json/tags_woman_poi.json">tags_woman_poi.json</option>
|
| 118 |
+
<option value="json/tags_woman.json">tags_woman.json</option>
|
| 119 |
+
<option value="json/tags_youtuber.json">tags_youtuber.json</option>
|
| 120 |
+
<option value="json/tree_data.json">tree_data.json</option>
|
| 121 |
+
</select>
|
| 122 |
+
|
| 123 |
+
<button id="resetBtn">Reset</button>
|
| 124 |
+
<button id="downloadBtn">Download SVG</button>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
</div>
|
| 131 |
+
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
</div>
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
<!-- Main Content -->
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
<!-- Visualization -->
|
| 142 |
+
<!-- Change the SVG element to this -->
|
| 143 |
+
<svg viewBox="0 0 1600 1200" preserveAspectRatio="xMidYMid meet"></svg>
|
| 144 |
+
|
| 145 |
+
<!-- Theme Toggle -->
|
| 146 |
+
|
| 147 |
+
<!-- Navigation -->
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
<script>
|
| 151 |
+
const svgElement = document.querySelector("svg");
|
| 152 |
+
const width = 1600;
|
| 153 |
+
const height = 2000;
|
| 154 |
+
const NODE_BASE_RADIUS = 1;
|
| 155 |
+
const FONT_SIZE_BASE = 14;
|
| 156 |
+
|
| 157 |
+
let LINK_DISTANCE_SCALE = -200;
|
| 158 |
+
let CHARGE_STRENGTH = 80;
|
| 159 |
+
let COLLIDE_STRENGTH = 2;
|
| 160 |
+
let COLLIDE_BASE_RADIUS = 17;
|
| 161 |
+
let NODE_RADIUS_SCALE = 30;
|
| 162 |
+
let FONT_SIZE_SCALE = 25;
|
| 163 |
+
let LINK_THICKNESS_BASE = 5;
|
| 164 |
+
|
| 165 |
+
const svg = d3.select("svg");
|
| 166 |
+
const zoomLayer = svg.append("g").attr("class", "zoom-layer");
|
| 167 |
+
|
| 168 |
+
// Add zoom/pan behavior
|
| 169 |
+
svg.call(d3.zoom()
|
| 170 |
+
.scaleExtent([0.1, 8])
|
| 171 |
+
.on("zoom", (event) => {
|
| 172 |
+
zoomLayer.attr("transform", event.transform);
|
| 173 |
+
})
|
| 174 |
+
);
|
| 175 |
+
|
| 176 |
+
// Set initial background
|
| 177 |
+
zoomLayer.append("rect")
|
| 178 |
+
.attr("width", width)
|
| 179 |
+
.attr("height", height)
|
| 180 |
+
.attr("fill", getComputedStyle(document.documentElement).getPropertyValue('--svg-fill').trim());
|
| 181 |
+
|
| 182 |
+
let fullGraph = null;
|
| 183 |
+
|
| 184 |
+
function renderGraph(graph, nodeCount = 1) {
|
| 185 |
+
zoomLayer.selectAll("*").remove();
|
| 186 |
+
|
| 187 |
+
zoomLayer.append("rect")
|
| 188 |
+
.attr("width", width)
|
| 189 |
+
.attr("height", height)
|
| 190 |
+
.attr("fill", getComputedStyle(document.documentElement).getPropertyValue('--svg-fill').trim());
|
| 191 |
+
|
| 192 |
+
const nodeMap = new Map(graph.nodes.map(n => [n.id, n]));
|
| 193 |
+
|
| 194 |
+
graph.nodes.sort((a, b) => b.size - a.size);
|
| 195 |
+
const filteredNodes = graph.nodes.slice(0, nodeCount);
|
| 196 |
+
const topIds = new Set(filteredNodes.map(n => n.id));
|
| 197 |
+
const filteredLinks = graph.links
|
| 198 |
+
.filter(link => topIds.has(link.source) && topIds.has(link.target) && link.value >= 1)
|
| 199 |
+
.map(link => ({
|
| 200 |
+
...link,
|
| 201 |
+
source: nodeMap.get(link.source),
|
| 202 |
+
target: nodeMap.get(link.target)
|
| 203 |
+
}));
|
| 204 |
+
|
| 205 |
+
const [minNodeSize, maxNodeSize] = d3.extent(filteredNodes, n => n.size);
|
| 206 |
+
filteredNodes.forEach(n => {
|
| 207 |
+
n.normSize = (n.size - minNodeSize) / (maxNodeSize - minNodeSize || 1);
|
| 208 |
+
});
|
| 209 |
+
|
| 210 |
+
const [minLinkVal, maxLinkVal] = d3.extent(filteredLinks, d => d.value);
|
| 211 |
+
filteredLinks.forEach(d => {
|
| 212 |
+
d.normValue = (d.value - minLinkVal) / (maxLinkVal - minLinkVal || 1);
|
| 213 |
+
d.distance = 100 + d.normValue * LINK_DISTANCE_SCALE;
|
| 214 |
+
});
|
| 215 |
+
|
| 216 |
+
const edgeColor = d3.scaleLinear()
|
| 217 |
+
.domain([0, 0.3, 1])
|
| 218 |
+
.interpolate(d3.interpolateRgb)
|
| 219 |
+
.range(["#BC8F8F", "#FF7F50", "#800000"]);
|
| 220 |
+
|
| 221 |
+
const link = zoomLayer.append("g").selectAll("line")
|
| 222 |
+
.data(filteredLinks)
|
| 223 |
+
.enter().append("line")
|
| 224 |
+
.attr("stroke", d => edgeColor(d.normValue))
|
| 225 |
+
.attr("stroke-width", d => Math.max(0.1, d.value / maxLinkVal * LINK_THICKNESS_BASE))
|
| 226 |
+
.attr("stroke-opacity", d => d.normValue + 80)
|
| 227 |
+
|
| 228 |
+
const node = zoomLayer.append("g").selectAll("circle")
|
| 229 |
+
.data(filteredNodes)
|
| 230 |
+
.enter().append("circle")
|
| 231 |
+
.attr("r", d => d.size / maxNodeSize * NODE_RADIUS_SCALE)
|
| 232 |
+
.attr("fill", "#FF7F50")
|
| 233 |
+
.attr("stroke", "#800000")
|
| 234 |
+
.attr("stroke-width", 5)
|
| 235 |
+
.call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));
|
| 236 |
+
|
| 237 |
+
const labels = zoomLayer.append("g")
|
| 238 |
+
.attr("class", "label-group")
|
| 239 |
+
.selectAll("g")
|
| 240 |
+
.data(filteredNodes)
|
| 241 |
+
.enter().append("g")
|
| 242 |
+
.attr("class", "label");
|
| 243 |
+
|
| 244 |
+
labels.append("text")
|
| 245 |
+
.attr("text-anchor", "start")
|
| 246 |
+
.style("font-weight", "bold")
|
| 247 |
+
.style("font-size", d => `${FONT_SIZE_BASE + d.normSize * FONT_SIZE_SCALE}px`)
|
| 248 |
+
.attr("x", d => NODE_BASE_RADIUS + d.normSize * NODE_RADIUS_SCALE + 6)
|
| 249 |
+
.text(d => d.id);
|
| 250 |
+
|
| 251 |
+
const top10 = filteredNodes.slice(0, 3);
|
| 252 |
+
const insideLabels = zoomLayer.append("g")
|
| 253 |
+
.selectAll("text")
|
| 254 |
+
.data(top10)
|
| 255 |
+
.enter().append("text")
|
| 256 |
+
.attr("text-anchor", "middle")
|
| 257 |
+
.attr("dy", "0.35em")
|
| 258 |
+
.style("font-weight", "bold")
|
| 259 |
+
.style("fill", "#800000")
|
| 260 |
+
.style("pointer-events", "none")
|
| 261 |
+
.style("font-size", d => `${FONT_SIZE_BASE + d.normSize * FONT_SIZE_SCALE * 0.2}px`)
|
| 262 |
+
.text(d => d.size);
|
| 263 |
+
|
| 264 |
+
labels.each(function(d) {
|
| 265 |
+
const group = d3.select(this);
|
| 266 |
+
const text = group.select("text");
|
| 267 |
+
|
| 268 |
+
const fontSize = FONT_SIZE_BASE + d.normSize * FONT_SIZE_SCALE;
|
| 269 |
+
const paddingX = 4;
|
| 270 |
+
const paddingY = 2;
|
| 271 |
+
|
| 272 |
+
const labelX = NODE_BASE_RADIUS + d.normSize * NODE_RADIUS_SCALE + 6;
|
| 273 |
+
text
|
| 274 |
+
.attr("x", labelX)
|
| 275 |
+
.attr("y", 0)
|
| 276 |
+
.attr("dy", "0.35em")
|
| 277 |
+
.style("font-size", `${fontSize}px`);
|
| 278 |
+
|
| 279 |
+
const actualWidth = text.node().getComputedTextLength();
|
| 280 |
+
const actualHeight = fontSize;
|
| 281 |
+
|
| 282 |
+
group.insert("rect", "text")
|
| 283 |
+
.attr("x", labelX - paddingX)
|
| 284 |
+
.attr("y", -actualHeight / 2 - paddingY)
|
| 285 |
+
.attr("width", actualWidth + paddingX * 0.2)
|
| 286 |
+
.attr("height", actualHeight + paddingY * 2)
|
| 287 |
+
.attr("rx", 4)
|
| 288 |
+
.attr("ry", 4)
|
| 289 |
+
.attr("fill", "white")
|
| 290 |
+
.attr("fill-opacity", 0.7)
|
| 291 |
+
.attr("stroke", "#800000")
|
| 292 |
+
.attr("stroke-width", 4)
|
| 293 |
+
.attr("stroke-opacity", 1);
|
| 294 |
+
});
|
| 295 |
+
const simulation = d3.forceSimulation(filteredNodes)
|
| 296 |
+
.alphaDecay(0.05)
|
| 297 |
+
.alphaMin(0.001)
|
| 298 |
+
.force("link", d3.forceLink(filteredLinks).id(d => d.id).distance(d => d.distance))
|
| 299 |
+
.force("charge", d3.forceManyBody().strength(CHARGE_STRENGTH))
|
| 300 |
+
.force("center", d3.forceCenter(width / 2 -300, height / 2 - 310)) // Adjusted vertical center
|
| 301 |
+
.force("collide", d3.forceCollide().radius(d =>
|
| 302 |
+
COLLIDE_BASE_RADIUS + d.normSize * NODE_RADIUS_SCALE + (FONT_SIZE_BASE + d.normSize * FONT_SIZE_SCALE) * 0.1
|
| 303 |
+
).strength(COLLIDE_STRENGTH))
|
| 304 |
+
.on("tick", ticked);
|
| 305 |
+
function ticked() {
|
| 306 |
+
link.attr("x1", d => d.source.x).attr("y1", d => d.source.y)
|
| 307 |
+
.attr("x2", d => d.target.x).attr("y2", d => d.target.y);
|
| 308 |
+
node.attr("cx", d => d.x).attr("cy", d => d.y);
|
| 309 |
+
labels.attr("transform", d => `translate(${d.x}, ${d.y})`);
|
| 310 |
+
insideLabels.attr("x", d => d.x).attr("y", d => d.y);
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
function dragstarted(event, d) {
|
| 314 |
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
| 315 |
+
d.fx = d.x; d.fy = d.y;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
function dragged(event, d) {
|
| 319 |
+
d.fx = event.x; d.fy = event.y;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
function dragended(event, d) {
|
| 323 |
+
if (!event.active) simulation.alphaTarget(0);
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
// Rest of your existing JavaScript for controls, theme toggle, etc.
|
| 328 |
+
const sliders = {
|
| 329 |
+
nodeCount: document.getElementById("nodeCountInput"),
|
| 330 |
+
linkScale: document.getElementById("linkScaleInput"),
|
| 331 |
+
charge: document.getElementById("chargeInput"),
|
| 332 |
+
collide: document.getElementById("collideInput"),
|
| 333 |
+
collideBase: document.getElementById("collideBaseInput"),
|
| 334 |
+
radius: document.getElementById("radiusInput"),
|
| 335 |
+
font: document.getElementById("fontInput"),
|
| 336 |
+
linkThickness: document.getElementById("LinkStrokeIn"),
|
| 337 |
+
};
|
| 338 |
+
|
| 339 |
+
const outputs = {
|
| 340 |
+
nodeCount: document.getElementById("nodeCountOutput"),
|
| 341 |
+
linkScale: document.getElementById("linkScaleOutput"),
|
| 342 |
+
charge: document.getElementById("chargeOutput"),
|
| 343 |
+
collide: document.getElementById("collideOutput"),
|
| 344 |
+
collideBase: document.getElementById("collideBaseOutput"),
|
| 345 |
+
radius: document.getElementById("radiusOutput"),
|
| 346 |
+
font: document.getElementById("fontOutput"),
|
| 347 |
+
linkThickness: document.getElementById("LinkStrokeOut"),
|
| 348 |
+
};
|
| 349 |
+
|
| 350 |
+
function updateAndRender() {
|
| 351 |
+
LINK_DISTANCE_SCALE = +sliders.linkScale.value;
|
| 352 |
+
CHARGE_STRENGTH = +sliders.charge.value;
|
| 353 |
+
COLLIDE_STRENGTH = +sliders.collide.value;
|
| 354 |
+
COLLIDE_BASE_RADIUS = +sliders.collideBase.value;
|
| 355 |
+
NODE_RADIUS_SCALE = +sliders.radius.value;
|
| 356 |
+
FONT_SIZE_SCALE = +sliders.font.value;
|
| 357 |
+
LINK_THICKNESS_BASE = +sliders.linkThickness.value;
|
| 358 |
+
|
| 359 |
+
outputs.linkScale.textContent = LINK_DISTANCE_SCALE;
|
| 360 |
+
outputs.charge.textContent = CHARGE_STRENGTH;
|
| 361 |
+
outputs.collide.textContent = COLLIDE_STRENGTH;
|
| 362 |
+
outputs.collideBase.textContent = COLLIDE_BASE_RADIUS;
|
| 363 |
+
outputs.radius.textContent = NODE_RADIUS_SCALE;
|
| 364 |
+
outputs.font.textContent = FONT_SIZE_SCALE;
|
| 365 |
+
outputs.linkThickness.textContent = LINK_THICKNESS_BASE;
|
| 366 |
+
outputs.nodeCount.textContent = sliders.nodeCount.value;
|
| 367 |
+
|
| 368 |
+
if (fullGraph) renderGraph(fullGraph, +sliders.nodeCount.value);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
for (const key in sliders) {
|
| 372 |
+
sliders[key].addEventListener("input", updateAndRender);
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
document.getElementById("resetBtn").addEventListener("click", () => {
|
| 376 |
+
sliders.nodeCount.value = 50;
|
| 377 |
+
sliders.linkScale.value = -200;
|
| 378 |
+
sliders.charge.value = 80;
|
| 379 |
+
sliders.collide.value = 2;
|
| 380 |
+
sliders.collideBase.value = 17;
|
| 381 |
+
sliders.radius.value = 30;
|
| 382 |
+
sliders.font.value = 25;
|
| 383 |
+
sliders.linkThickness.value = 5;
|
| 384 |
+
updateAndRender();
|
| 385 |
+
});
|
| 386 |
+
|
| 387 |
+
document.getElementById("jsonSelector").addEventListener("change", function() {
|
| 388 |
+
loadJSON(this.value);
|
| 389 |
+
});
|
| 390 |
+
|
| 391 |
+
function updateSvgThemeStyles() {
|
| 392 |
+
const fill = getComputedStyle(document.documentElement).getPropertyValue('--svg-fill').trim();
|
| 393 |
+
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim();
|
| 394 |
+
const labelBg = getComputedStyle(document.documentElement).getPropertyValue('--label-bg').trim();
|
| 395 |
+
|
| 396 |
+
zoomLayer.select("rect").attr("fill", fill);
|
| 397 |
+
zoomLayer.selectAll("text").attr("fill", textColor);
|
| 398 |
+
zoomLayer.selectAll("g.label rect").attr("fill", labelBg);
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
const toggle = document.getElementById('modeToggle');
|
| 402 |
+
const root = document.documentElement;
|
| 403 |
+
|
| 404 |
+
root.classList.remove('light-mode');
|
| 405 |
+
|
| 406 |
+
toggle.addEventListener('change', () => {
|
| 407 |
+
root.classList.toggle('light-mode');
|
| 408 |
+
setTimeout(updateSvgThemeStyles, 50);
|
| 409 |
+
});
|
| 410 |
+
|
| 411 |
+
function loadJSON(filePath) {
|
| 412 |
+
d3.json(filePath).then(graph => {
|
| 413 |
+
fullGraph = graph;
|
| 414 |
+
updateAndRender();
|
| 415 |
+
});
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
const selector = document.getElementById("jsonSelector");
|
| 419 |
+
loadJSON(selector.value);
|
| 420 |
+
|
| 421 |
+
document.getElementById("downloadBtn").addEventListener("click", () => {
|
| 422 |
+
const svgNode = document.querySelector("svg");
|
| 423 |
+
const serializer = new XMLSerializer();
|
| 424 |
+
const source = serializer.serializeToString(svgNode);
|
| 425 |
+
const blob = new Blob([source], {type: "image/svg+xml;charset=utf-8"});
|
| 426 |
+
const url = URL.createObjectURL(blob);
|
| 427 |
+
|
| 428 |
+
const link = document.createElement("a");
|
| 429 |
+
link.href = url;
|
| 430 |
+
link.download = "graph-export.svg";
|
| 431 |
+
document.body.appendChild(link);
|
| 432 |
+
link.click();
|
| 433 |
+
document.body.removeChild(link);
|
| 434 |
+
URL.revokeObjectURL(url);
|
| 435 |
+
});
|
| 436 |
+
|
| 437 |
+
// Initial render
|
| 438 |
+
updateSvgThemeStyles();
|
| 439 |
+
updateAndRender();
|
| 440 |
+
</script>
|
| 441 |
+
</body>
|
| 442 |
+
</html>
|
public/img/booru_tags.svg
ADDED
|
|
public/img/japan.svg
ADDED
|
|
plots/grid10x120.png → public/img/sankey.png
RENAMED
|
File without changes
|
public/img/tag_clusters.png
ADDED
|
Git LFS Details
|
public/img/training_tags.png
ADDED
|
Git LFS Details
|
public/index.html
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
| 6 |
+
<link rel="stylesheet" href="styles.css">
|
| 7 |
+
<style>
|
| 8 |
+
.node { stroke: none; }
|
| 9 |
+
text { font-family: Arial, sans-serif; fill: black; }
|
| 10 |
+
.legend rect { stroke: black; stroke-width: 0.5px; }
|
| 11 |
+
</style>
|
| 12 |
+
</head>
|
| 13 |
+
<body>
|
| 14 |
+
<h1>Graph Visualizations</h1>
|
| 15 |
+
<h2 class="caption">Interactive networks and tag structures derived from user-generated content on CivitAI</h2>
|
| 16 |
+
<div class="description">
|
| 17 |
+
<p>
|
| 18 |
+
These interactive visualizations are part of the study <em>"Perpetual Misogyny: How Gendered Tropes Shape Text-To-Image AI Personalization"</em>. The research explores how user-driven content and tagging behavior on the model-sharing hub <strong>CivitAI</strong> has evolved over a two-year span.
|
| 19 |
+
</p>
|
| 20 |
+
<p>
|
| 21 |
+
The first network visualizes tag co-occurrence patterns—how users promote their models by grouping descriptive tags together. The second visualization maps semantic structures in the training data of over 11,000 LoRA models, revealing latent biases and conceptual clusters.
|
| 22 |
+
</p>
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<div class="theme-toggle" style="text-align: center; margin-bottom: 2rem;">
|
| 26 |
+
<label>
|
| 27 |
+
<input type="checkbox" id="modeToggle"> Toggle Light Mode
|
| 28 |
+
</label>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="card-grid">
|
| 31 |
+
<a class="card-link" href="figure_15.html">
|
| 32 |
+
<div class="preview-card">
|
| 33 |
+
<img src="img/booru_tags.svg" alt="Preview 1">
|
| 34 |
+
<div class="card-title">Training Tags Network</div>
|
| 35 |
+
<div class="card-footer">View Graph →</div>
|
| 36 |
+
</div>
|
| 37 |
+
</a>
|
| 38 |
+
|
| 39 |
+
<a class="card-link" href="figure_5.html">
|
| 40 |
+
<div class="preview-card">
|
| 41 |
+
<img src="img/japan.svg" alt="Preview 3">
|
| 42 |
+
<div class="card-title">Tag Groups</div>
|
| 43 |
+
<div class="card-footer">Explore Tags →</div>
|
| 44 |
+
</div>
|
| 45 |
+
</a>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
</div>
|
| 49 |
+
<script>
|
| 50 |
+
const toggle = document.getElementById('modeToggle');
|
| 51 |
+
const root = document.documentElement;
|
| 52 |
+
|
| 53 |
+
// Default to dark mode
|
| 54 |
+
root.classList.remove('light-mode');
|
| 55 |
+
|
| 56 |
+
toggle.addEventListener('change', () => {
|
| 57 |
+
root.classList.toggle('light-mode');
|
| 58 |
+
});
|
| 59 |
+
</script>
|
| 60 |
+
|
| 61 |
+
</body>
|
| 62 |
+
</html>
|