Perception models¶
This tutorial demostrates how to use ZenSVI to access prediction models to predict Urban Visual Perception of your street view images for the following indicators:
Safety
Lively
Wealthy
Beautiful
Boring
Depressing
Currently two prediction models are available. Liang et al. prediciton model is based on a ResNet50 architecture and Ouyang’s model is based on a Vision Transformer (ViT) archicture.
All values are float in the range of [0, 10].
Acknowledgement Liang, X., Chang, J. H., Gao, S., Zhao, T. & Biljecki, F. (2024). Evaluating human perception of building exteriors using street view imagery. Building and Environment, 263, 111875. https://doi.org/10.1016/j.buildenv.2024.111875
Ouyang, J.: Code repository for predicting human perception (2023)
BibTex:
@article{Liang.2024,
year = {2024},
title = {{Evaluating human perception of building exteriors using street view imagery}},
author = {Liang, Xiucheng and Chang, Jiat Hwee and Gao, Song and Zhao, Tianhong and Biljecki, Filip},
journal = {Building and Environment},
issn = {0360-1323},
doi = {10.1016/j.buildenv.2024.111875},
pages = {111875},
volume = {263},
}
@article{Ouyang.2023,
author = {Ouyang, Jiani},
title ={Code repository for predicting human perception},
year = {2023},
url = {https://github.com/strawmelon11/human-perception-place-pulse},
}
Download sample images¶
from huggingface_hub import HfApi, hf_hub_download
import os
def download_folder(repo_id, repo_type, folder_path, local_dir):
"""
Download an entire folder from a huggingface dataset repository.
repo_id : string
The ID of the repository (e.g., 'username/repo_name').
repo_type : string
Type of the repo, dataset or model.
folder_path : string
The path to the folder within the repository.
local_dir : string
Local folder to download the data. This mimics git behaviour
"""
api = HfApi()
# list all files in the repo, keep the ones within folder_path
all_files = api.list_repo_files(repo_id, repo_type=repo_type)
files_list = [f for f in all_files if f.startswith(folder_path)]
# download each of those files
for file_path in files_list:
hf_hub_download(repo_id=repo_id, repo_type=repo_type,
filename=file_path, local_dir=local_dir)
# Download entire data/ folder
repo_id = "NUS-UAL/zensvi_test_data" # you can replace this for other huggingface repos
repo_type = "dataset" # required by the API when the repo is a dataset
folder_path = "input/visualization/batch_images/batch_1" # replace the folder you want within the repo
local_dir = "./demo_data" # the local folder in your computer where it will be downloaded
if not os.path.exists(local_dir):
os.makedirs(local_dir)
# By default, huggingface download them to the .cache/huggingface folder
download_folder(repo_id, repo_type, folder_path, local_dir)
/Users/mquintana/miniforge3/envs/zensvi_311/lib/python3.11/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
from .autonotebook import tqdm as notebook_tqdm
Classification¶
from pathlib import Path
from zensvi.cv import ClassifierPerception
from zensvi.cv import ClassifierPerceptionViT
from zensvi.cv.classification.utils.Model_01 import Net
indicator = 'more boring'
dir_summary_output_1 = str(Path(local_dir) / "directory/summary_Liang")
dir_summary_output_2 = str(Path(local_dir) / "directory/summary_Ouyang")
input_folder = str(Path(local_dir) / folder_path)
Entire folder¶
# model by Liang et al
classifier = ClassifierPerception(perception_study = indicator)
classifier.classify(
input_folder,
dir_summary_output=dir_summary_output_1,
batch_size=3,
)
Using CPU
Using CPU
/Users/mquintana/Developer/forked__zensvi/src/zensvi/cv/classification/perception.py:134: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.
checkpoint = torch.load(checkpoint_path, map_location=self.device)
Evaluating human perception of study: more boring: 100%|██████████████████████████████████████████████████████| 34/34 [00:04<00:00, 7.83it/s]
[{'filename_key': '1158297185027486', 'more boring': 3.4220707416534424},
{'filename_key': '851588095803411', 'more boring': 4.156600475311279},
{'filename_key': '3078044802482414', 'more boring': 4.460619926452637},
{'filename_key': '600711734231518', 'more boring': 5.521065711975098},
{'filename_key': '515077153491310', 'more boring': 4.191150188446045},
{'filename_key': '5321359991242907', 'more boring': 4.283115386962891},
{'filename_key': '494572955764175', 'more boring': 4.368640899658203},
{'filename_key': '986257745381645', 'more boring': 4.948808670043945},
{'filename_key': '1160171564566431', 'more boring': 4.737444877624512},
{'filename_key': '384683626988506', 'more boring': 3.701953649520874},
{'filename_key': '628231478575219', 'more boring': 3.6941511631011963},
{'filename_key': '1455202961590851', 'more boring': 3.9634287357330322},
{'filename_key': '1201847686924949', 'more boring': 6.514774322509766},
{'filename_key': '285790924047947', 'more boring': 3.459219217300415},
{'filename_key': '769036347638490', 'more boring': 3.714940309524536},
{'filename_key': '442127337522246', 'more boring': 3.8784844875335693},
{'filename_key': '1060932214587523', 'more boring': 4.137831687927246},
{'filename_key': '309178287522328', 'more boring': 5.767539024353027},
{'filename_key': '4243461929044270', 'more boring': 3.8126723766326904},
{'filename_key': '780203909841888', 'more boring': 3.3381383419036865},
{'filename_key': '829854054614113', 'more boring': 4.967872619628906},
{'filename_key': '279187160565071', 'more boring': 3.166968822479248},
{'filename_key': '746859949850970', 'more boring': 3.949856996536255},
{'filename_key': '140739801908262', 'more boring': 3.203310251235962},
{'filename_key': '763642884785145', 'more boring': 3.3384649753570557},
{'filename_key': '153819143472034', 'more boring': 4.038415431976318},
{'filename_key': '740524730615854', 'more boring': 5.4163899421691895},
{'filename_key': '1132155588182622', 'more boring': 4.897639751434326},
{'filename_key': '466353571102919', 'more boring': 4.820302486419678},
{'filename_key': '178171651295060', 'more boring': 4.452124118804932},
{'filename_key': '2191380267663834', 'more boring': 5.485678672790527},
{'filename_key': '1433049487639971', 'more boring': 6.263242244720459},
{'filename_key': '455893955903565', 'more boring': 3.3749942779541016},
{'filename_key': '369828518560588', 'more boring': 4.751296520233154},
{'filename_key': '1239190920365188', 'more boring': 3.9415416717529297},
{'filename_key': '787864199235037', 'more boring': 3.196761131286621},
{'filename_key': '997069887637746', 'more boring': 6.66798734664917},
{'filename_key': '242686708680497', 'more boring': 4.6262431144714355},
{'filename_key': '1073136200058414', 'more boring': 3.6564416885375977},
{'filename_key': '372118695052994', 'more boring': 4.2638397216796875},
{'filename_key': '400343115498572', 'more boring': 2.935410261154175},
{'filename_key': '568163214952634', 'more boring': 3.247823715209961},
{'filename_key': '832645251205175', 'more boring': 6.1634907722473145},
{'filename_key': '1084443125613205', 'more boring': 4.674773216247559},
{'filename_key': '382118559876835', 'more boring': 4.022745609283447},
{'filename_key': '521600718998116', 'more boring': 4.809849739074707},
{'filename_key': '466596317888663', 'more boring': 3.407823324203491},
{'filename_key': '3273088799604749', 'more boring': 5.163775444030762},
{'filename_key': '175110354503057', 'more boring': 4.265507221221924},
{'filename_key': '293130315630690', 'more boring': 3.287019968032837},
{'filename_key': '368440941218058', 'more boring': 4.59842586517334},
{'filename_key': '360374115501372', 'more boring': 2.4474563598632812},
{'filename_key': '317344056477031', 'more boring': 3.2490713596343994},
{'filename_key': '1411457862599065', 'more boring': 3.5495786666870117},
{'filename_key': '1351343122024687', 'more boring': 3.5913236141204834},
{'filename_key': '177464108254007', 'more boring': 4.513995170593262},
{'filename_key': '893275082066597', 'more boring': 4.35598087310791},
{'filename_key': '1150400689211800', 'more boring': 5.398260116577148},
{'filename_key': '142884951426949', 'more boring': 3.2553606033325195},
{'filename_key': '132099122266773', 'more boring': 5.2104291915893555},
{'filename_key': '152342177384336', 'more boring': 3.9949471950531006},
{'filename_key': '644978016913808', 'more boring': 3.5999386310577393},
{'filename_key': '798932901298516', 'more boring': 6.6344828605651855},
{'filename_key': '1282744585815412', 'more boring': 2.9670517444610596},
{'filename_key': '511045037457458', 'more boring': 3.205746650695801},
{'filename_key': '3172519823016683', 'more boring': 4.630003929138184},
{'filename_key': '1143534366099128', 'more boring': 4.599789142608643},
{'filename_key': '929998750876650', 'more boring': 3.596707344055176},
{'filename_key': '1652222298294768', 'more boring': 3.42852520942688},
{'filename_key': '931251737725257', 'more boring': 4.933721542358398},
{'filename_key': '570248724481485', 'more boring': 4.066063404083252},
{'filename_key': '1090140864906446', 'more boring': 3.534109354019165},
{'filename_key': '5682964278411977', 'more boring': 4.355992794036865},
{'filename_key': '725417518697053', 'more boring': 3.7487385272979736},
{'filename_key': '182693114152610', 'more boring': 4.389308929443359},
{'filename_key': '1148421702811742', 'more boring': 6.727350234985352},
{'filename_key': '4020189094872009', 'more boring': 4.605851650238037},
{'filename_key': '1217856115704361', 'more boring': 3.6770095825195312},
{'filename_key': '128990276487694', 'more boring': 3.8086607456207275},
{'filename_key': '206022451136825', 'more boring': 6.730124473571777},
{'filename_key': '119736067293890', 'more boring': 3.2311599254608154},
{'filename_key': '293583242396497', 'more boring': 3.6974170207977295},
{'filename_key': '689758229497466', 'more boring': 3.4320437908172607},
{'filename_key': '758475608688555', 'more boring': 3.147540807723999},
{'filename_key': '1495040270927634', 'more boring': 3.9054298400878906},
{'filename_key': '208149790851091', 'more boring': 4.014562606811523},
{'filename_key': '450076786952826', 'more boring': 3.715214729309082},
{'filename_key': '752206072775246', 'more boring': 3.7525136470794678},
{'filename_key': '1269819240220850', 'more boring': 3.913250684738159},
{'filename_key': '2558106297827538', 'more boring': 3.3478240966796875},
{'filename_key': '753011575882126', 'more boring': 4.7493577003479},
{'filename_key': '308377623987060', 'more boring': 3.987757921218872},
{'filename_key': '507402617358382', 'more boring': 4.615646839141846},
{'filename_key': '151831933487222', 'more boring': 4.0581464767456055},
{'filename_key': '788111265235499', 'more boring': 3.763338565826416},
{'filename_key': '3257739417879181', 'more boring': 5.025196075439453},
{'filename_key': '728961341720924', 'more boring': 3.382528305053711},
{'filename_key': '202096538813995', 'more boring': 3.8208742141723633},
{'filename_key': '602785677836434', 'more boring': 3.874995470046997},
{'filename_key': '517455165915617', 'more boring': 4.339915752410889}]
# model by Ouyang
classifier = ClassifierPerceptionViT(perception_study = indicator)
classifier.classify(
input_folder,
dir_summary_output=dir_summary_output_2,
batch_size=3,
)
Using CPU
Using CPU
Fetching 2 files: 100%|███████████████████████████████████████████████████████████████████████████████████████| 2/2 [00:00<00:00, 1800.13it/s]
/Users/mquintana/Developer/forked__zensvi/src/zensvi/cv/classification/perception.py:283: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.
model = torch.load(checkpoint_path, map_location=self.device)
Evaluating human perception of study: more boring: 100%|██████████████████████████████████████████████████████| 34/34 [00:14<00:00, 2.38it/s]
[{'filename_key': '1158297185027486', 'more boring': 0.949999988079071},
{'filename_key': '851588095803411', 'more boring': 3.609999895095825},
{'filename_key': '3078044802482414', 'more boring': 5.889999866485596},
{'filename_key': '600711734231518', 'more boring': 3.0399999618530273},
{'filename_key': '515077153491310', 'more boring': 4.71999979019165},
{'filename_key': '5321359991242907', 'more boring': 1.6399999856948853},
{'filename_key': '494572955764175', 'more boring': 2.5399999618530273},
{'filename_key': '986257745381645', 'more boring': 3.640000104904175},
{'filename_key': '1160171564566431', 'more boring': 1.5399999618530273},
{'filename_key': '384683626988506', 'more boring': 7.619999885559082},
{'filename_key': '628231478575219', 'more boring': 2.75},
{'filename_key': '1455202961590851', 'more boring': 1.2100000381469727},
{'filename_key': '1201847686924949', 'more boring': 2.4200000762939453},
{'filename_key': '285790924047947', 'more boring': 4.389999866485596},
{'filename_key': '769036347638490', 'more boring': 2.0199999809265137},
{'filename_key': '442127337522246', 'more boring': 8.329999923706055},
{'filename_key': '1060932214587523', 'more boring': 6.849999904632568},
{'filename_key': '309178287522328', 'more boring': 6.730000019073486},
{'filename_key': '4243461929044270', 'more boring': 2.2699999809265137},
{'filename_key': '780203909841888', 'more boring': 3.740000009536743},
{'filename_key': '829854054614113', 'more boring': 6.769999980926514},
{'filename_key': '279187160565071', 'more boring': 7.320000171661377},
{'filename_key': '746859949850970', 'more boring': 1.5499999523162842},
{'filename_key': '140739801908262', 'more boring': 4.320000171661377},
{'filename_key': '763642884785145', 'more boring': 1.5299999713897705},
{'filename_key': '153819143472034', 'more boring': 3.740000009536743},
{'filename_key': '740524730615854', 'more boring': 0.9399999976158142},
{'filename_key': '1132155588182622', 'more boring': 2.690000057220459},
{'filename_key': '466353571102919', 'more boring': 2.1700000762939453},
{'filename_key': '178171651295060', 'more boring': 2.0199999809265137},
{'filename_key': '2191380267663834', 'more boring': 1.850000023841858},
{'filename_key': '1433049487639971', 'more boring': 8.729999542236328},
{'filename_key': '455893955903565', 'more boring': 4.21999979019165},
{'filename_key': '369828518560588', 'more boring': 1.350000023841858},
{'filename_key': '1239190920365188', 'more boring': 2.3299999237060547},
{'filename_key': '787864199235037', 'more boring': 2.0399999618530273},
{'filename_key': '997069887637746', 'more boring': 5.510000228881836},
{'filename_key': '242686708680497', 'more boring': 6.900000095367432},
{'filename_key': '1073136200058414', 'more boring': 2.5399999618530273},
{'filename_key': '372118695052994', 'more boring': 2.299999952316284},
{'filename_key': '400343115498572', 'more boring': 1.0099999904632568},
{'filename_key': '568163214952634', 'more boring': 1.9500000476837158},
{'filename_key': '832645251205175', 'more boring': 5.320000171661377},
{'filename_key': '1084443125613205', 'more boring': 3.8299999237060547},
{'filename_key': '382118559876835', 'more boring': 5.5},
{'filename_key': '521600718998116', 'more boring': 6.079999923706055},
{'filename_key': '466596317888663', 'more boring': 1.3700000047683716},
{'filename_key': '3273088799604749', 'more boring': 2.309999942779541},
{'filename_key': '175110354503057', 'more boring': 8.539999961853027},
{'filename_key': '293130315630690', 'more boring': 6.059999942779541},
{'filename_key': '368440941218058', 'more boring': 7.039999961853027},
{'filename_key': '360374115501372', 'more boring': 2.430000066757202},
{'filename_key': '317344056477031', 'more boring': 6.400000095367432},
{'filename_key': '1411457862599065', 'more boring': 1.5299999713897705},
{'filename_key': '1351343122024687', 'more boring': 1.5800000429153442},
{'filename_key': '177464108254007', 'more boring': 3.759999990463257},
{'filename_key': '893275082066597', 'more boring': 0.9300000071525574},
{'filename_key': '1150400689211800', 'more boring': 6.170000076293945},
{'filename_key': '142884951426949', 'more boring': 2.869999885559082},
{'filename_key': '132099122266773', 'more boring': 4.389999866485596},
{'filename_key': '152342177384336', 'more boring': 3.2899999618530273},
{'filename_key': '644978016913808', 'more boring': 3.6500000953674316},
{'filename_key': '798932901298516', 'more boring': 8.670000076293945},
{'filename_key': '1282744585815412', 'more boring': 1.9700000286102295},
{'filename_key': '511045037457458', 'more boring': 1.4500000476837158},
{'filename_key': '3172519823016683', 'more boring': 2.859999895095825},
{'filename_key': '1143534366099128', 'more boring': 8.380000114440918},
{'filename_key': '929998750876650', 'more boring': 1.1299999952316284},
{'filename_key': '1652222298294768', 'more boring': 2.25},
{'filename_key': '931251737725257', 'more boring': 4.059999942779541},
{'filename_key': '570248724481485', 'more boring': 1.3799999952316284},
{'filename_key': '1090140864906446', 'more boring': 1.4199999570846558},
{'filename_key': '5682964278411977', 'more boring': 6.690000057220459},
{'filename_key': '725417518697053', 'more boring': 6.480000019073486},
{'filename_key': '182693114152610', 'more boring': 1.7799999713897705},
{'filename_key': '1148421702811742', 'more boring': 5.820000171661377},
{'filename_key': '4020189094872009', 'more boring': 4.210000038146973},
{'filename_key': '1217856115704361', 'more boring': 7.190000057220459},
{'filename_key': '128990276487694', 'more boring': 5.710000038146973},
{'filename_key': '206022451136825', 'more boring': 1.1699999570846558},
{'filename_key': '119736067293890', 'more boring': 2.069999933242798},
{'filename_key': '293583242396497', 'more boring': 3.0399999618530273},
{'filename_key': '689758229497466', 'more boring': 1.690000057220459},
{'filename_key': '758475608688555', 'more boring': 2.430000066757202},
{'filename_key': '1495040270927634', 'more boring': 4.260000228881836},
{'filename_key': '208149790851091', 'more boring': 4.349999904632568},
{'filename_key': '450076786952826', 'more boring': 1.3300000429153442},
{'filename_key': '752206072775246', 'more boring': 2.319999933242798},
{'filename_key': '1269819240220850', 'more boring': 1.9600000381469727},
{'filename_key': '2558106297827538', 'more boring': 2.369999885559082},
{'filename_key': '753011575882126', 'more boring': 1.7100000381469727},
{'filename_key': '308377623987060', 'more boring': 4.21999979019165},
{'filename_key': '507402617358382', 'more boring': 3.75},
{'filename_key': '151831933487222', 'more boring': 0.9800000190734863},
{'filename_key': '788111265235499', 'more boring': 6.46999979019165},
{'filename_key': '3257739417879181', 'more boring': 2.940000057220459},
{'filename_key': '728961341720924', 'more boring': 2.4600000381469727},
{'filename_key': '202096538813995', 'more boring': 2.5},
{'filename_key': '602785677836434', 'more boring': 0.9200000166893005},
{'filename_key': '517455165915617', 'more boring': 0.9399999976158142}]
One image¶
# model by Liang et al
image_input = str(Path(input_folder) / "1060932214587523.png")
dir_summary_output = str(Path(local_dir) / "single/summary_Liang")
classifier = ClassifierPerception(perception_study = indicator)
classifier.classify(
image_input,
dir_summary_output=dir_summary_output,
)
Using CPU
Using CPU
/Users/mquintana/Developer/forked__zensvi/src/zensvi/cv/classification/perception.py:134: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.
checkpoint = torch.load(checkpoint_path, map_location=self.device)
Evaluating human perception of study: more boring: 100%|████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 19.37it/s]
[{'filename_key': '1060932214587523', 'more boring': 4.13783073425293}]
Examine output¶
import os
import math
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image
# Set the paths
output_folder_1 = dir_summary_output_1
output_folder_2 = dir_summary_output_2
indicator = 'more boring'
# Load results
results1_df = pd.read_csv(f'{output_folder_1}/results.csv')
results2_df = pd.read_csv(f'{output_folder_2}/results.csv')
image_ids = results1_df['filename_key']
# Number of rows and columns for the grid
cols = 2
rows = math.ceil(len(image_ids) / cols)
# Create a figure with subplots
fig, axes = plt.subplots(rows, cols, figsize=(10, 100)) # Adjust figsize as needed
axes = axes.flatten() # Flatten axes for easier indexing
# Sort image IDs to ensure consistent ordering
sorted_image_ids = sorted(image_ids)
# Iterate through each unique image ID
for i, img_uuid in enumerate(sorted_image_ids): # Limit to available images
img_path = os.path.join(input_folder, f"{img_uuid}.png") # Adjust if necessary
if os.path.exists(img_path): # Check if image exists
img = Image.open(img_path)
# Display the image
axes[i].imshow(img)
axes[i].axis('off') # Hide axes for the image
# Prepare to display classification outcomes
results_1 = results1_df[results1_df['filename_key'] == img_uuid][indicator].values[0]
text_1 = f'{round(results_1, 2)} - Liang'
results_2 = results2_df[results2_df['filename_key'] == img_uuid][indicator].values[0]
text_2 = f'{round(results_2, 2)} - Ouyang'
outcome_text = indicator + "\n" + text_1 + "\n" + text_2
# Overlay the classification outcomes at the bottom right corner of the image
axes[i].text(0.95, 0.05, outcome_text, fontsize=7, ha='right', va='bottom',
color='white', bbox=dict(facecolor='black', alpha=0.5), transform=axes[i].transAxes) # Add a semi-transparent background for better readability
else:
print(f"Image for UUID {img_uuid} not found.")
# Adjust layout and remove empty subplots
for j in range(len(sorted_image_ids), rows * cols):
axes[j].axis('off') # Hide any unused axes
plt.tight_layout()
plt.show()

From the above we can see that there are instances where both models agreed on similar scores and other where they differ. Each model has reported their own accuracies in their respective works and, although they are similar, discrepancies can be observed here. The ZenSVI package supports both models for benchmarking purposes and the development team encourage the submission of new models to contribute to this field.