from PIL import Image, ImageDraw, ImageFont, ImageOps, ExifTags
import os
import piexif
from datetime import datetime
# For generating watermarks
def correct_orientation(image):
"""
Correct the orientation of an image using its EXIF data.
"""
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
= image._getexif()
exif
if exif is not None and orientation in exif:
if exif[orientation] == 3:
= image.rotate(180, expand=True)
image elif exif[orientation] == 6:
= image.rotate(270, expand=True)
image elif exif[orientation] == 8:
= image.rotate(90, expand=True)
image
return image
def add_watermark(image_path, text, save_as=None):
= Image.open(image_path)
original = correct_orientation(original)
original = Image.new('RGBA', original.size, (255,255,255,0))
txt = ImageDraw.Draw(txt)
draw
# Scale the font size based on the image size
= 800 # Change this to your preferred base width
base_width = int((min(original.size) / base_width) * 20)
font_size = max(font_size, 10) # Set a minimum font size if needed
font_size
= ImageFont.truetype("arial.ttf", font_size)
font = original.width // 2
text_x = original.height - 30
text_y
= (text_x, text_y)
text_position =(255,255,255,128), font=font, anchor="mm")
draw.text(text_position, text, fill= Image.alpha_composite(original.convert('RGBA'), txt).convert('RGB')
watermarked
if save_as:
'JPEG')
watermarked.save(save_as, else:
'JPEG')
watermarked.save(image_path,
def get_camera_info(exif_data):
= {}
camera_info # Only add EXIF data to the dictionary if it is not the default 'Unknown'
if piexif.ImageIFD.Model in exif_data['0th']:
'model'] = exif_data['0th'][piexif.ImageIFD.Model].decode('utf-8')
camera_info[
if piexif.ExifIFD.ISOSpeedRatings in exif_data['Exif']:
'iso'] = str(exif_data['Exif'][piexif.ExifIFD.ISOSpeedRatings])
camera_info[
if piexif.ExifIFD.FNumber in exif_data['Exif']:
= exif_data['Exif'][piexif.ExifIFD.FNumber]
f_number 'fstop'] = f"{f_number[0] / f_number[1]}"
camera_info[
if piexif.ExifIFD.ExposureTime in exif_data['Exif']:
= exif_data['Exif'][piexif.ExifIFD.ExposureTime]
exposure_time 'shutterspeed'] = f"{exposure_time[0]}/{exposure_time[1]} sec"
camera_info[
if piexif.ExifIFD.DateTimeOriginal in exif_data['Exif']:
try:
'datetime'] = exif_data['Exif'][piexif.ExifIFD.DateTimeOriginal].decode('utf-8')
camera_info['year'] = datetime.strptime(camera_info['datetime'], '%Y:%m:%d %H:%M:%S').year
camera_info[except:
pass
return camera_info
def process_images(source_folder_path):
= '../images/watermark'
target_folder_path =True)
os.makedirs(target_folder_path, exist_ok
for filename in os.listdir(source_folder_path):
if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
= os.path.join(source_folder_path, filename)
image_path = piexif.load(image_path)
exif_data = get_camera_info(exif_data)
camera_info
# Construct watermark text
= [str(camera_info.get('year', ''))] # Start with the year
watermark_elements if 'model' in camera_info:
'model'])
watermark_elements.append(camera_info[if 'iso' in camera_info:
f"ISO {camera_info['iso']}")
watermark_elements.append(if 'shutterspeed' in camera_info:
'shutterspeed'])
watermark_elements.append(camera_info[
# Remove empty strings and join with ', '
= "(c) Charles Lehnen " + ', '.join(filter(None, watermark_elements))
watermark_text
= os.path.join(target_folder_path, f"{os.path.splitext(filename)[0]}_watermarked.jpg")
save_as_path =save_as_path)
add_watermark(image_path, watermark_text, save_as
'../images/no_watermark') process_images(
import os
from PIL import Image
# For generating list of files to copy/paste into photos.qmd
# Directory where the images are stored
= '../images/watermark/'
image_folder # Prefix for the path to use in the markdown output
= 'images/watermark/'
output_path_prefix
# Categorize images into portrait and landscape
= []
landscape_images = []
portrait_images
for filename in os.listdir(image_folder):
if filename.lower().endswith('.jpg'):
with Image.open(os.path.join(image_folder, filename)) as img:
if img.width > img.height:
landscape_images.append(filename)else:
portrait_images.append(filename)
# Combine the lists, placing landscape images first
= landscape_images + portrait_images
images
# Generate markdown output
= "::: {layout=\"[[1, 1], [1, 1]]\"}\n"
markdown_output for image in images:
= os.path.join(output_path_prefix, image).replace("\\", "/")
image_path += f"![]({image_path}){{group=\"my-gallery\"}}\n\n"
markdown_output += ":::"
markdown_output
print(markdown_output)
::: {layout="[[1, 1], [1, 1]]"}
![](images/watermark/IMG_3895_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4626_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4660_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4667_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4801_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4808_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4825_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_4844_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_5437_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_5444_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_5670_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_5868_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_7778_watermarked.jpg){group="my-gallery"}
![](images/watermark/PANO0009_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2662_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2666_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2674_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2703_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2735_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2740_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2743_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2777_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2783_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2784_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2796_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2810_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2821_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2825_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2842_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2909_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2914_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2946_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3004_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3016_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3025_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3044_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3079_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3089_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3095_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3163_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3183_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3195_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3208_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3219_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3224_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3232_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3254_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3255_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3258_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3263_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3312_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3322_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3324_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3330_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3349_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3354_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3358_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3369_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3378_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3414_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3433_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3444_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3451_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3458_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3459_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3467_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3476_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3480_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3482_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3484_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3485_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3490_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3494_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3495_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3503_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3506_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3521_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3525_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3542_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3544_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3552_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3558_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3586_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3590_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3594_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3608_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3637_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3666_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3675_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3685_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3690_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3692_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3726_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3737_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3759_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3823_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3879_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3886_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3889_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3938_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3941_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC4090_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC4093_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC4166_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC4169_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC4197_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_0110_watermarked.jpg){group="my-gallery"}
![](images/watermark/IMG_5902_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2707_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC2712_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3100_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3246_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3247_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3257_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3371_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3528_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC3587_watermarked.jpg){group="my-gallery"}
![](images/watermark/_DSC4231_watermarked.jpg){group="my-gallery"}
:::
import pandas as pd
try:
= pd.read_csv('../documents/projects.csv')
projects_df except UnicodeDecodeError:
# If a UnicodeDecodeError is encountered, try reading with 'latin1' encoding
= pd.read_csv('../documents/projects.csv', encoding='latin1')
projects_df
# Generate the server function script for the Shiny app using the DataFrame
# Template for a single marker in the server function
= """
marker_template addMarkers(
lng = {lng},
lat = {lat},
popup = "{popup}",
icon = {icon_name}Icon
) %>%
"""
# Initialize the base of the server function string
= "server <- function(input, output, session) {\n"
server_function_script
# Generate the makeIcon function calls for each unique icon
= """
icon_template {icon_name}Icon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/{icon_file}",
iconWidth = {icon_width}, iconHeight = {icon_height},
iconAnchorX = 25, iconAnchorY = 50
)
"""
# Assuming icons are 50x50 by default, you can add conditional logic for different sizes if necessary
for icon_file in projects_df['Icon'].unique():
= icon_file.split('.')[0] # Assuming the file name before the extension is the icon name
icon_name += icon_template.format(
server_function_script =icon_name,
icon_name=icon_file,
icon_file=50, # Default width
icon_width=50 # Default height
icon_height
)
# Add the leaflet rendering function call
+= " output$map <- renderLeaflet({\n leaflet() %>%\n addProviderTiles(\"Esri.NatGeoWorldMap\") %>%" # Added %>%
server_function_script
# Add markers for each project
for _, row in projects_df.iterrows():
= row['Icon'].split('.')[0]
icon_name += marker_template.format(
server_function_script =row['Longitude'],
lng=row['Latitude'],
lat=row['Description'].replace('"', '\\"'), # Escape double quotes in the description
popup=icon_name
icon_name
)
# Remove the extra %>% from the last marker_template
= server_function_script.rstrip("%>\n")
server_function_script
# Close the leaflet render function and the server function
+= "\n }) \n}"
server_function_script
# Output the generated script for copy/paste
print(server_function_script)
server <- function(input, output, session) {
COTOIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/COTO.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
blandingsIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/blandings.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
GalapTortIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/GalapTort.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
flyIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/fly.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
arthropodsIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/arthropods.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
elmIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/elm.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
JEPIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/JEP.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
mojaveIcon <- makeIcon(
iconUrl = "https://raw.githubusercontent.com/CharlesLehnen/portfolio/main/images/icons/mojave.png",
iconWidth = 50, iconHeight = 50,
iconAnchorX = 25, iconAnchorY = 50
)
output$map <- renderLeaflet({
leaflet() %>%
addProviderTiles("Esri.NatGeoWorldMap") %>%
addMarkers(
lng = -107.303802,
lat = 33.12007,
popup = "We evaluated the ecological significance of abandoned mines in New Mexico as habitats for bats to recommend them for closure or federal protection. This included the collection of habitat, LiDAR imagery, and disease metrics. Field techniques involved off-roading, hiking, and ropework, with additional avian surveys at mine closures.",
icon = COTOIcon
) %>%
addMarkers(
lng = -92.161371,
lat = 43.880424,
popup = "I independently monitored nesting sites of the endangered Blanding's Turtles at Murphy-Hanrehan Park, assisting with road crossings, health observations, photographic documentation, and egg translocation relocation since Blanding's exhibit natal homing",
icon = blandingsIcon
) %>%
addMarkers(
lng = -95.810074,
lat = -0.344018,
popup = "My doctoral research, in collaboration with the Galapagos Conservancy and Iniciativa Galápagos, explores the impact of introducing Española Giant Tortoises to Santa Fe Island to restore its ecosystem. Using drone imagery, isotope analysis, and camera traps, this project aims to take a mult-faceted approach to quantify the cascading ecological effects on this introduction.",
icon = GalapTortIcon
) %>%
addMarkers(
lng = -88.352343,
lat = -0.382326,
popup = "I spearheaded a 4-year comprehensive study on the natural history across five Galapagos islands, significantly contributing to the Philornis Working Group's efforts against the invasive Philornis downsi fly. This work, pivotal for understanding the ecology of Galapagos Diptera, informed P. downsi control strategies and non-target biocontrol testing. Previously, the reproductive ecology of Galapagos Diptera were largely unknown.",
icon = flyIcon
) %>%
addMarkers(
lng = -120.89189,
lat = 37.430757,
popup = "In collaboration with the Los Angeles Natural History Museum (NHM) and Cal State, we examined the impact of various factors on arthropod diversity across Los Angeles, analyzing topographical, abiotic, and anthropogenic drivers. We explored the complex responses within and between six arthropod groups, revealing high regional diversity and variable spatial composition. This research underscores the nuanced relationship between urban development and biodiversity, contributing valuable insights for urban ecology and land management practices.",
icon = arthropodsIcon
) %>%
addMarkers(
lng = -95.146029,
lat = 48.112037,
popup = "I assisted in research on Dutch elm disease, nursery techniques, and urban forestry, gaining expertise in plant propagation and tree care, from seed to adult tree. Collaborating closely with Saint Paul Parks and Recreation foresters, I collected seeds and collected data across study sites. I managed 8 acres of nursery and experimental plots.",
icon = elmIcon
) %>%
addMarkers(
lng = -116.4529,
lat = 33.978195,
popup = "I led the WonderKids virtual afterschool program, creating and implementing a semester long STEM curriculum for K-5 students that highlights diversity in science with guest speakers and virtual lab tours. This approach aimed to inspire students through hands-on experiments and exposure to a variety of specific scientific careers, emphasizing inclusivity in science education.",
icon = JEPIcon
) %>%
addMarkers(
lng = -112.123846,
lat = 40.359941,
popup = "For USFWS, I collected georeferenced biometric data on vulnerable Mojave desert tortoise populations across Nevada using line-distance sampling, adhering to sterile techniques. This included assessing body condition scores, checking for Mycoplasma infection, and taking morphological measurements, with strict protocols to prevent hypothermia, dehydration, and disease spread among tortoises.",
icon = mojaveIcon
)
})
}