From cd6e7b963cf56b2aaee19dbcaea1bfb3b7f6d6b1 Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Wed, 22 Jun 2022 22:25:24 +1000 Subject: [PATCH] Added folder_stats.py --- README.md | 13 ++++ coastsnap/folder_stats.py | 104 ++++++++++++++++++++++++++ coastsnap/generate_statistics_csv.bat | 3 + coastsnap/spotteron_batch_download.py | 9 +-- statistics.csv | 35 +++++++++ workflow.pptx | Bin 285219 -> 285285 bytes 6 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 coastsnap/folder_stats.py create mode 100644 coastsnap/generate_statistics_csv.bat create mode 100644 statistics.csv diff --git a/README.md b/README.md index a94b01b..43e5a7a 100644 --- a/README.md +++ b/README.md @@ -72,4 +72,17 @@ Script Logic: For every site in oneDrive CoastSnap directory, iterate through th * Starts tagging from the most recent image and stops for the site when an image has already been tagged. This way, the user can manually remove bad registered/tagged images, and they will not be automatically replaced. * Retrieves tide data for the site from the .mat file specified in Database/CoastSnapDB.xlsx +### Statistics +Run `generate_statistics_csv.bat` + +Generates `statistics.csv` which contains information about the Images directory. Columns include: + +site | # processed | # photoshop | # registered | stability | Most recent image deleted + +* stability = # registered / # processed. This formula is based on the assumption that someone will manually remove poorly registered images in `Images/Registered`. Thus stability represents the percentage of images that had good registration. + +## Future Improvements + +### Image metadata +Currently (22/6/22) it would appear that images downloaded from Spotteron do not retain the images' metadata. This is based on looking in windows file explorer image->properties, as well as using the exif python package. Note: The metadata presented in file explorer is IPTC data. There is a python package to interact with this data, but I had issues with it. diff --git a/coastsnap/folder_stats.py b/coastsnap/folder_stats.py new file mode 100644 index 0000000..785fcad --- /dev/null +++ b/coastsnap/folder_stats.py @@ -0,0 +1,104 @@ +import os +import pandas as pd +from pathlib import Path +from time import strptime + +code_dir = str(Path(os.getcwd()).parent) +sites_csv_path = os.path.join(code_dir, "coastsnap_sites.csv") +coastsnap_sites_csv = pd.read_csv(sites_csv_path) +images_parent_dir = coastsnap_sites_csv.parent_directory[0] +images_dir = os.path.join(images_parent_dir, "Images") + +stats_csv = pd.DataFrame(columns = ['site','# processed', '# photoshop', '# registered', 'stability', 'most recently deleted'] ) + + +for site in os.listdir(images_dir): # Loop through SITES + i=0 + to_append = [site, 0, 0, 0, 0, 0] + processed = False + photoshop = False + registered = False + latest_image_found = False + site_path = os.path.join(images_dir, site) + processed_path = os.path.join(site_path,'Processed') + photoshop_path = os.path.join(site_path,'Photoshop') + registered_path = os.path.join(site_path, 'Registered') + try: # Check if site contains 'Processed' directory + processed_years_list = os.listdir(processed_path) + processed_years_list = [x for x in processed_years_list if len(x) == 4] # remove files that aren't years + processed_years_list.reverse() + processed = True + except: + continue + + try: # Check if site contains 'Processed' directory + photoshop_years_list = os.listdir(photoshop_path) + photoshop_years_list = [x for x in photoshop_years_list if len(x) == 4] # remove files that aren't years + photoshop_years_list.reverse() + photoshop = True + except: + continue + + try: # Check if site contains 'Processed' directory + registered_years_list = os.listdir(registered_path) + registered_years_list = [x for x in registered_years_list if len(x) == 4] # remove files that aren't years + registered_years_list.reverse() + registered = True + except: + continue + + if processed: + i=0 + for year in processed_years_list: # Loop through YEARS + processed_year_path = os.path.join(processed_path, year) + processed_image_list = os.listdir(processed_year_path) + processed_image_list.reverse() + for image_filename in processed_image_list: # Loop through IMAGES + i += 1 + to_append[1] = i + + if photoshop: + i=0 + for year in photoshop_years_list: # Loop through YEARS + year_path = os.path.join(photoshop_path, year) + image_list = os.listdir(year_path) + image_list.reverse() + for image_filename in image_list: # Loop through IMAGES + + year_path = year_path.replace('Photoshop', 'Registered') + registered_image_path = year_path + '/' + image_filename[:-4] + '_registered.jpg' + if image_filename.endswith('.jpg') and not os.path.isfile(registered_image_path) and not latest_image_found: + latest_image_found = True + filename_list = image_filename.split(".") + date = filename_list[3].split("_") + image_date = date[0] + '-' + '{:02d}'.format(strptime(filename_list[2],'%b').tm_mon) +'-'+ filename_list[5] + + to_append[5] = image_date + print(site) + print(image_filename) + i += 1 + to_append[2] = i + + if registered: + i=0 + for year in registered_years_list: # Loop through YEARS + registered_year_path = os.path.join(registered_path, year) + registered_image_list = os.listdir(registered_year_path) + registered_image_list.reverse() + for image_filename in registered_image_list: # Loop through IMAGES + i += 1 + to_append[3] = i + + stats_csv_length = len(stats_csv) + stats_csv.loc[stats_csv_length] = to_append + + +# Add site stability data (# registered / # processed) +for i, row in stats_csv.iterrows(): + stability = "{0:.0%}".format(stats_csv.at[i, '# registered'] / stats_csv.at[i, '# processed']) + stats_csv.at[i,'stability'] = stability + +stats_csv.set_index('site', inplace = True) + +output_file_path = os.path.join(code_dir, 'statistics.csv') +stats_csv.to_csv(output_file_path) diff --git a/coastsnap/generate_statistics_csv.bat b/coastsnap/generate_statistics_csv.bat new file mode 100644 index 0000000..9316b29 --- /dev/null +++ b/coastsnap/generate_statistics_csv.bat @@ -0,0 +1,3 @@ +call activate coastsnap +python "%~dp0folder_stats.py" +call conda deactivate \ No newline at end of file diff --git a/coastsnap/spotteron_batch_download.py b/coastsnap/spotteron_batch_download.py index 928fbcf..85ed5e8 100644 --- a/coastsnap/spotteron_batch_download.py +++ b/coastsnap/spotteron_batch_download.py @@ -6,7 +6,7 @@ import datetime import re from pathlib import Path from urllib.parse import urljoin -from os import path, makedirs +from os import path, makedirs, getcwd import attr import pytz @@ -17,10 +17,9 @@ from loguru import logger from timezonefinder import TimezoneFinder from werkzeug.utils import secure_filename -#app = typer.Typer() -coastsnap_sites = pd.read_csv("C:/Users/z5079346/OneDrive - UNSW/Projects/Coastsnap_test/CoastSnap_Sites.csv") - - +code_images_dir = str(Path(getcwd()).parent) +coastsnap_sites_path = path.join(code_images_dir, "coastsnap_sites.csv") +coastsnap_sites = pd.read_csv(coastsnap_sites_path) @attr.s() class SpotteronImage: diff --git a/statistics.csv b/statistics.csv new file mode 100644 index 0000000..9628e84 --- /dev/null +++ b/statistics.csv @@ -0,0 +1,35 @@ +site,# processed,# photoshop,# registered,stability,most recently deleted +alex,75,74,73,97%,25-08-2021 +birubi,65,64,0,0%,12-06-2022 +blacksmiths,1581,1577,1381,87%,30-04-2022 +broulee,190,191,93,49%,14-06-2022 +buddina,117,114,85,73%,15-06-2022 +burleigh,245,242,0,0%,17-06-2022 +byron,1255,1329,676,54%,13-06-2022 +cathieillaroo,97,103,66,68%,16-06-2022 +cathielagoon,74,75,73,99%,10-04-2022 +coolum,80,36,22,28%,13-06-2022 +cooya,55,46,0,0%,15-11-2021 +cowbay,34,34,0,0%,16-06-2022 +era,56,56,45,80%,14-04-2022 +fourmile,244,247,115,47%,01-06-2022 +frankston,191,189,157,82%,16-06-2022 +garie,48,48,0,0%,13-06-2022 +hungry,128,128,92,72%,20-05-2022 +macsnth,73,72,0,0%,13-06-2022 +macssth,60,59,0,0%,13-06-2022 +manly,1167,1262,1122,96%,14-06-2022 +moffat,114,131,215,189%,09-03-2022 +newell,23,34,0,0%,27-04-2022 +nthnarra,2315,2444,1097,47%,16-06-2022 +queenscliff,85,79,0,0%,14-06-2022 +rainbow,50,50,0,0%,24-04-2022 +seaford,69,57,0,0%,11-06-2022 +shortpoint,222,222,0,0%,06-06-2022 +stockton1,262,261,33,13%,24-05-2022 +stockton2,214,213,62,29%,11-05-2022 +stockton3,257,256,69,27%,15-05-2022 +tomakin,211,211,142,67%,13-06-2022 +tugun,288,289,132,46%,01-06-2022 +wamberal,372,366,0,0%,15-06-2022 +wonga,46,46,0,0%,30-05-2022 diff --git a/workflow.pptx b/workflow.pptx index 2a49ae12a245f56a3520f045610c5a8a6a48d058..cadca3c2645309888366987bd58ed55dcd18fe6f 100644 GIT binary patch delta 3989 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 delta 3942 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