# Estimating heat exchange from thermal images ####
# The purpose of this file is to provide a brief introduction to using Thermimage
# It is assumed that the user has thermal imaging experience and has extracted
# data from thermal images already, or has a means to bring thermal image data
# into R. This package will not replace thermal image analysis software but it
# can help in automating some calculations associated with large datasets of thermal
# images.
# Getting Started: Install Thermimage ####
# if you don't have Thermimage installed, type: install.packages("Thermimage")
# which should download Thermimage from the CRAN repository and install it.
# then simply type library(Thermimage) to call the functions into the working
# environment
library(Thermimage)
# Minimum required information (units) ####
# Surface temperatures, Ts (oC - note: all temperature units are in oC)
# Ambient temperatures, Ta (oC)
# Characteristic dimension of the object or animal, L (m)
# Surface Area, A (m^2)
# Shape of object: choose from "sphere", "hcylinder", "vcylinder", "hplate", "vplate"
# Required if working outdoors with solar radiation ####
# Visible surface reflectance, rho, which could be measured or estimated (0-1)
# Solar radiation (SE=abbrev for solar energy), W/m2
# Can be estimated or provided ####
# Wind speed, V (m/s) - I tend to model heat exchange under different V (0.1 to 10 m/s)
# Ground Temperature, Tg (oC) - estimated from air temperature if not provided
# Incoming infrared radiation, Ld (will be estimated from Air Temperature)
# Incoming infrared radiation, Lu (will be estimated from Ground Temperature)
# Ground Temperature Estimation ####
# For Ground Temp, we derived a relationship based on data in Galapagos that describes
# Tg-Ta ~ Se, (N=516, based on daytime measurements)
# Thus, Tg = 0.0187128*SE + Ta
# Derived from daytime measurements within the ranges:
# Range of Ta: 23.7 to 34 C
# Range of SE: 6.5 to 1506.0 Watts/m2
# Or from published work by Bartlett et al. (2006) in the Tground() function, the
# relationship would be Tg = 0.0121*SE + Ta
# Make your Data frame ####
# Once you have decided on what variables you have or need to model, create a data
# frame with these values (Ta, Ts, Tg, SE, A, L, Shape, rho), where each row corresponds to
# an individual measurement. The data frame is not required for calling functions,
# but it will force you to assemble your data before proceeding with calculations.
# Other records such as size, date image captured, time of day, species, sex, etc...
# should also be stored in the data frame.
Ta<-rnorm(20, 25, sd=10)
Ts<-Ta+rnorm(20, 5, sd=1)
RH<-rep(0.5, length(Ta))
SE<-rnorm(20, 400, sd=50)
Tg<-Tground(Ta,SE)
A<-rep(0.4,length(Ta))
L<-rep(0.1, length(Ta))
V<-rep(1, length(Ta))
shape<-rep("hcylinder", length(Ta))
c<-forcedparameters(V=V, L=L, Ta=Ta, shape=shape)$c
n<-forcedparameters(V=V, L=L, Ta=Ta, shape=shape)$n
a<-freeparameters(L=L, Ts=Ts, Ta=Ta, shape=shape)$a
b<-freeparameters(L=L, Ts=Ts, Ta=Ta, shape=shape)$b
m<-freeparameters(L=L, Ts=Ts, Ta=Ta, shape=shape)$m
type<-rep("forced", length(Ta))
rho<-rep(0.1, length(Ta))
cloud<-rep(0, length(Ta))
d<-data.frame(Ta, Ts, Tg, SE, RH, rho, cloud, A, V, L, c, n, a, b, m, type, shape)
head(d)
# Basic calculations ####
# The basic approach to estimating heat loss is based on that outlined in
# Tattersall et al (2009)
# This involves breaking the object into component shapes, deriving the exposed areas
# of those shapes empirically, and calcuating Qtotal for each shape:
(Qtotal<-qrad() + qconv())
# Notice how the above example yielded an estimate. This is because there are default
# values in all the functions. In this case, the estimate is negative, meaning
# a net loss of heat to the environment. It's units are in W/m2.
# To convert the above measures into total heat flux, the Area of each part is required
Area1<-0.2 # units are in m2
Area2<-0.3 # units are in m2
Qtotal1<-qrad()*Area1 + qconv()*Area1
Qtotal2<-qrad()*Area2 + qconv()*Area2
QtotalAll<-Qtotal1 + Qtotal2
# This approach is used in animal thermal images, such that all component shapes sum to estimate entire
# body heat exchange: WholeBody = Qtotal1 + Qtotal2 + Qtotal3 ... Qtotaln
# Qtotal is made up of two components: qrad + qconv
# qrad is the net radiative heat flux (W/m2)
# qconv is the net convective heat flux (W/m2)
# qcond is usually ignored unless large contact areas between substrate. Additional
# information is required to accurately calculate conductive heat exchange and are not
# provided here.
# What is qabs ####
# Radiation is both absorbed and emitted by animals. I have broken this down into
# partially separate functions. qabs() is a function to estimate the area specific
# amount of solar and infrared radiation absorbed by the object from the environment:
# qabs() requires information on the air (ambient) temperature, ground temperature,
# relative humidity, emissivity of the object, reflectivity of the object,
# proportion cloud cover, and solar energy.
qabs(Ta = 20, Tg = NULL, RH = 0.5, E = 0.96, rho = 0.1, cloud = 0, SE = 400)
# compare to a shaded environment with lower SE:
qabs(Ta = 20, Tg = NULL, RH = 0.5, E = 0.96, rho = 0.1, cloud = 0, SE = 100)
# What is qrad ####
# Since the animal also emits radiation, qrad() provides the net radiative heat
# transfer.
qrad(Ts = 27, Ta = 20, Tg = NULL, RH = 0.5, E = 0.96, rho = 0.1, cloud = 0, SE = 100)
# Notice how the absorbed environmental radiation is ~440 W/m2, but the animal is also losing
# losing a similar amount, so once we account for the net radiative flux, it very nearly
# balances out at a slightly negative number (-1.486 W/m2)
# Ground temperature ####
# If you have measured ground temperature, then simply include it in the call to qrad:
qrad(Ts = 30, Ta = 25, Tg = 28, RH = 0.5, E = 0.96, rho = 0.1, cloud = 0, SE = 100)
# If you do not have ground temperature, but have measured Ta and SE, then let
# Tg=NULL. This will force a call to the Tground() function to estimate Tground
# It is likely better to assume that Tground is slightly higher than Ta, at least
# in the daytime. If using measurements obtained at night (SE=0), then you will have
# to provide both Ta and Tground, since Tground could be colder equal to Ta depending
# on cloud cover.
# What is hconv ####
# This is simply the convective heat coefficient. This is used in calculating the
# convective heat transfer and/or operative temperature but usually you will not need
# to call hconv() yourself
# What is qconv ####
# This is the function to calculate area specific convective heat transfer, analagous
# to qrad, except for convective heat transfer. Positive values mean heat is gained
# by convection, negative values mean heat is lost by convection. Included in the
# function is the ability to estimate free convection (which occurs at 0 wind speed)
# or forced convection (wind speed >=0.1 m/s). Unless working in a completely still
# environment, it is more appropriate to used "forced" convection down to 0.1 m/s
# wind speed. Typical wind speeds indoors are likely <0.5 m/s, but outside can
# vary wildly.
# In addition to needing surface temperature, air temperature, and velocity, you
# need information/estimates on shape. L is the critical dimension of the shape,
# which is usually the height of an object within the air stream. The diameter of
# a horizontal cylinder is its critical dimension. Finally, shape needs to be
# assigned. see help(qconv) for details.
qconv(Ts = 30, Ta = 20, V = 1, L = 0.1, type = "forced", shape="hcylinder")
qconv(Ts = 30, Ta = 20, V = 1, L = 0.1, type = "forced", shape="hplate")
qconv(Ts = 30, Ta = 20, V = 1, L = 0.1, type = "forced", shape="sphere")
# notice how the horizontal cylinder loses less than the horizontal plate which loses
# less than the sphere. Spherical objects lose ~1.8 times as much heat per area as
# cylinders.
# Which is higher: convection or radiation? ####
# Take a convection estimate at low wind speed:
qconv(Ts = 30, Ta = 20, V = 0.1, L = 0.1, type = "forced", shape="hcylinder")
# compare to a radiative estimate (without any solar absorption):
qrad(Ts = 30, Ta = 20, Tg = NULL, RH = 0.5, E = 0.96, rho = 0.1, cloud = 0, SE = 0)
# in this case, the net radiative heat loss is greater than convective heat loss
# if you decrease the critical dimension, however, the convective heat loss per m2
# is much greater. This is effectively how convective exchange works: small objects
# lose heat from convection more readily than large objects (e.g. frostbite on fingers)
# If L is 10 times smaller:
qconv(Ts = 30, Ta = 20, V = 0.1, L = 0.01, type = "forced", shape="hcylinder")
qrad(Ts = 30, Ta = 20, Tg = NULL, RH = 0.5, E = 0.96, rho = 0.1, cloud = 0, SE = 0)
# convection and radiative heat transfer are nearly the same.
# A safe conclusion here is that larger animals would rely more on radiative heat transfer
# than they would on convective heat transfer
# Sample Calculations ####
# Ideally, you have all parameters estimated or measured and put into a data frame.
# Using the dataframe, d we constructed earlier
(qrad.A<-with(d, qrad(Ts, Ta, Tg, RH, E=0.96, rho, cloud, SE)))
(qconv.free.A<-with(d, qconv(Ts, Ta, V, L, c, n, a, b, m, type="free", shape)))
(qconv.forced.A<-with(d, qconv(Ts, Ta, V, L, c, n, a, b, m, type, shape)))
qtotal<-A*(qrad.A + qconv.forced.A)
d<-data.frame(d, qrad=qrad.A*A, qconv=qconv.forced.A*A, qtotal=qtotal)
head(d)
# Test the equations out for consistency ####
# Toucan Proximal Bill at 10oC (from Tattersall et al 2009 spreadsheets)
A<-0.0097169
L<-0.0587
Ta<-10
Tg<-Ta
Ts<-15.59
SE<-0
rho<-0.1
E<-0.96
RH<-0.5
cloud<-1
V<-5
type="forced"
shape="hcylinder"
(qrad.A<-qrad(Ts=Ts, Ta=Ta, Tg=Tg, RH=RH, E=E, rho=rho, cloud=cloud, SE=SE))
# compare to calculated value of -28.7 W/m2
# the R calculations differ slightly from Tattersall et al (2009) since they did not use
# estimates of longwave radiation (Ld and Lu), but instead assumed a simpler, constant Ta
# environment.
(qrad.A<-qrad(Ts=Ts, Ta=Ta, Tg=Tg, RH=RH, E=E, rho=rho, cloud=0, SE=SE))
# but if cloud = 0, then the qrad values calculated here are much higher than calculated by
# Tattersall et al (2009) since they only estimated under simplifying, indoor conditions
# where background temperature = air temperature. In the outdoors, then, cloud presence
# would affect estimates of radiative heat loss
(qconv.forced.A<-qconv(Ts, Ta, V, L, type=type, shape=shape))
# compare to calculated value of -191.67 W/m2 - which is really close! The difference lies
# in estimates of air kinematic viscosity used
(qtotal.A<-(qrad.A + qconv.forced.A))
# Total area specific heat loss for the proximal area of the bill (Watts/m2)
qtotal.A*A
# Total heat loss for the proximal area of the bill (Watts)
# This lines up well with the published values in Tattersall et al (2009).
# This was confirmed in van de Van (2016) where they recalculated the area specifi
# heat flux from toucan bills to be ~65 W/m2:
qrad(Ts=Ts, Ta=Ta, Tg=Tg, RH=0.5, E=0.96, rho=rho, cloud=1, SE=0) +
qconv(Ts, Ta, V, L, type="free", shape=shape)
# Estimating Operative Temperature ####
# Operative environmental temperature is the expression of the "effective temperature" an
# object is experiencing, accounting for heat absorbed from radiation and heat lost to convection
# In other words, it is often used by some when trying to predict
# animal body temperature as a null expectation or reference point to determine whether
# active thermoregulation is being used. More often used in ectotherm studies, but as an
# initial estimate of what a freely moving animal temperature would be, it serves a useful
# reference. Usually, people would measure operative temperature with a model of an object
# placed into the environment, allowing wind, solar radiation and ambient temperature to
# influence its temperature. There are numerous formulations for it. The one here is from
# in Angilletta's book on Thermal Adaptations. Note: in the absence of sun or wind,
# operative temperature is simply ambient temperature.
# Operative temperature with varying reflectances:
Ts<-40
Ta<-30
SE<-seq(0,1100,100)
Toperative<-NULL
for(rho in seq(0, 1, 0.1)){
temp<-Te(Ts=Ts, Ta=Ta, Tg=NULL, RH=0.5, E=0.96, rho=rho, cloud=1, SE=SE, V=1,
L=0.1, type="forced", shape="hcylinder")
Toperative<-cbind(Toperative, temp)
}
rho<-seq(0, 1, 0.1)
Toperative<-data.frame(SE=seq(0,1100,100), Toperative)
colnames(Toperative)<-c("SE", seq(0,1,0.1))
matplot(Toperative$SE, Toperative[,-1], ylim=c(25, 50), type="l", xlim=c(0,1000),
main="Effects of Altering Reflectance from 0 to 1",
ylab="Operative Temperature (°C)", xlab="Solar Radiation (W/m2)", lty=1,
col=flirpal[rev(seq(1,380,35))])
for(i in 2:12){
ymax<-par()$yaxp[2]
xmax<-par()$xaxp[2]
x<-Toperative[,1]; y<-Toperative[,i]
lm1<-lm(y~x)
b<-coefficients(lm1)[1]; m<-coefficients(lm1)[2]
if(max(y)>ymax) {xpos<-(ymax-b)/m; ypos<-ymax}
if(max(y)<ymax) {xpos<-xmax; ypos<-y[which(x==1000)]}
text(xpos, ypos, labels=rho[(i-1)])
}
# Operative temperature with varying wind speeds
Ts<-40
Ta<-30
SE<-seq(0,1100,100)
Toperative<-NULL
for(V in seq(0, 10, 1)){
temp<-Te(Ts=Ts, Ta=Ta, Tg=NULL, RH=0.5, E=0.96, rho=0.1, cloud=1, SE=SE, V=V,
L=0.1, type="forced", shape="vcylinder")
Toperative<-cbind(Toperative, temp)
}
V<-seq(0, 10, 1)
Toperative<-data.frame(SE=seq(0,1100,100), Toperative)
colnames(Toperative)<-c("SE", seq(0,10,1))
matplot(Toperative$SE, Toperative[,-1], ylim=c(30, 50), type="l", xlim=c(0,1000),
main="Effects of Altering Wind Speed from 0 to 10 m/s",
ylab="Operative Temperature (°C)", xlab="Solar Radiation (W/m2)", lty=1,
col=flirpal[rev(seq(1,380,35))])
for(i in 2:12){
ymax<-par()$yaxp[2]
xmax<-par()$xaxp[2]
x<-Toperative[,1]; y<-Toperative[,i]
lm1<-lm(y~x)
b<-coefficients(lm1)[1]; m<-coefficients(lm1)[2]
if(max(y)>ymax) {xpos<-(ymax-b)/m; ypos<-ymax}
if(max(y)<ymax) {xpos<-xmax; ypos<-y[which(x==1000)]}
text(xpos, ypos, labels=V[(i-1)])
}
# Operative temperature with varying RH
Ts<-40
Ta<-30
SE<-seq(0,1100,100)
Toperative<-NULL
for(RH in seq(0, 1, 0.1)){
temp<-Te(Ts=Ts, Ta=Ta, Tg=NULL, RH=RH, E=0.96, rho=0.1, cloud=0.5, SE=SE, V=1,
L=0.1, type="forced", shape="hcylinder")
Toperative<-cbind(Toperative, temp)
}
RH<-seq(0, 1, 0.1)
Toperative<-data.frame(SE=seq(0,1100,100), Toperative)
colnames(Toperative)<-c("SE", seq(0,1,0.1))
matplot(Toperative$SE, Toperative[,-1], ylim=c(30, 50), type="l", xlim=c(0,1000),
main="Effects of changing RH from 0 to 1",
ylab="Operative Temperature (°C)", xlab="Solar Radiation (W/m2)", lty=1,
col=flirpal[rev(seq(1,380,35))])
for(i in 2:12){
ymax<-par()$yaxp[2]
xmax<-par()$xaxp[2]
x<-Toperative[,1]; y<-Toperative[,i]
lm1<-lm(y~x)
b<-coefficients(lm1)[1]; m<-coefficients(lm1)[2]
if(max(y)>ymax) {xpos<-(ymax-b)/m; ypos<-ymax}
if(max(y)<ymax) {xpos<-xmax; ypos<-y[which(x==1000)]}
text(xpos, ypos, labels=RH[(i-1)])
}
# Operative temperature with varying cloud cover
Ts<-40
Ta<-30
SE<-seq(0,1100,100)
Toperative<-NULL
for(cloud in seq(0, 1, 0.1)){
temp<-Te(Ts=Ts, Ta=Ta, Tg=NULL, RH=0.5, E=0.96, rho=0.5, cloud=cloud, SE=SE, V=1,
L=0.1, type="forced", shape="hcylinder")
Toperative<-cbind(Toperative, temp)
}
cloud<-seq(0, 1, 0.1)
Toperative<-data.frame(SE=seq(0,1100,100), Toperative)
colnames(Toperative)<-c("SE", seq(0,1,0.1))
matplot(Toperative$SE, Toperative[,-1], ylim=c(30, 50), type="l", xlim=c(0,1000),
main="Effects of changing RH from 0 to 1",
ylab="Operative Temperature (°C)", xlab="Solar Radiation (W/m2)", lty=1,
col=flirpal[rev(seq(1,380,35))])
for(i in 2:12){
ymax<-par()$yaxp[2]
xmax<-par()$xaxp[2]
x<-Toperative[,1]; y<-Toperative[,i]
lm1<-lm(y~x)
b<-coefficients(lm1)[1]; m<-coefficients(lm1)[2]
if(max(y)>ymax) {xpos<-(ymax-b)/m; ypos<-ymax}
if(max(y)<ymax) {xpos<-xmax; ypos<-y[which(x==1000)]}
text(xpos, ypos, labels=cloud[(i-1)])
}
A<-0.0097169
L<-0.0587
Ta<-30
SE<-1000
Tg<-Tground(Ta, SE)
Ts<-41
E<-0.96
RH<-0.5
V<-1
type="forced"
shape="hcylinder"
(qrad.A<-qrad(Ts=Ts, Ta=Ta, Tg=Tg, RH=RH, E=E, rho=0.03, cloud=1, SE=SE))
(qrad.A<-qrad(Ts=Ts, Ta=Ta, Tg=Tg, RH=RH, E=E, rho=0.07, cloud=1, SE=SE))
(qconv.forced.A<-qconv(Ts, Ta, V, L, type=type, shape=shape))
qconv(Ts, Ta, V, L, type="free", shape=shape)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.