diff --git a/colorpy/ColorPy.html b/colorpy/ColorPy.html index 23dbb8e..a5de76a 100644 --- a/colorpy/ColorPy.html +++ b/colorpy/ColorPy.html @@ -5,9 +5,9 @@ ColorPy @@ -22,33 +22,33 @@

ColorPy - A Python package for handling physical descriptions of color and l

Introduction and Motivation

-ColorPy is a Python package that can convert physical descriptions of light - -spectra of light intensity vs. wavelength - into RGB colors that can be drawn on -a computer screen.  It provides a nice set of attractive plots that you can -make of such spectra, and some other color related functions as well.  All +ColorPy is a Python package that can convert physical descriptions of light - +spectra of light intensity vs. wavelength - into RGB colors that can be drawn on +a computer screen.  It provides a nice set of attractive plots that you can +make of such spectra, and some other color related functions as well.  All of the plots in this documentation were created with ColorPy.

-ColorPy is free software.  ('Free' as in speech and beer.)  It is -released under the GNU Lesser GPL license.  You are free to use ColorPy -for any application that you like, including commercial applications.  If -you modify ColorPy, you should release the source code for your modifications.  +ColorPy is free software.  ('Free' as in speech and beer.)  It is +released under the GNU Lesser GPL license.  You are free to use ColorPy +for any application that you like, including commercial applications.  If +you modify ColorPy, you should release the source code for your modifications.  You have no obligation to release any source for your products that just use ColorPy, however.

-Several years ago, I developed some C++ code to do these kinds of physical color -calculations.  Recently, I decided to port the code to Python, and publish -the library as open source under the GNU LGPL license.  I decided to make -use of (and assume the existence of) NumPy and MatPlotLib for this.  These -libraries make it easy to make some nice, attractive, and informative, plots of +Several years ago, I developed some C++ code to do these kinds of physical color +calculations.  Recently, I decided to port the code to Python, and publish +the library as open source under the GNU LGPL license.  I decided to make +use of (and assume the existence of) NumPy and MatPlotLib for this.  These +libraries make it easy to make some nice, attractive, and informative, plots of spectra.  Besides, Python is just more fun than C++.

-So what can ColorPy do?  The short answer, is to scan this document, and -examine the various plots of spectra and their colors.  You can use ColorPy -to make the same kinds of plots, for whatever spectra you have and are -interested in.  ColorPy also provides conversions between several important -three-dimensional 'color spaces', specifically RGB, XYZ, Luv, and Lab.  -(There can be many different RGB spaces, depending on the particular display -used to view the results.  By default, ColorPy uses the sRGB space, but you +So what can ColorPy do?  The short answer, is to scan this document, and +examine the various plots of spectra and their colors.  You can use ColorPy +to make the same kinds of plots, for whatever spectra you have and are +interested in.  ColorPy also provides conversions between several important +three-dimensional 'color spaces', specifically RGB, XYZ, Luv, and Lab.  +(There can be many different RGB spaces, depending on the particular display +used to view the results.  By default, ColorPy uses the sRGB space, but you can configure it to use other RGB spaces if you like.)

@@ -79,58 +79,58 @@

License

Prerequisites

-To use ColorPy, you must have installed the following:  -Python, -NumPy, and +To use ColorPy, you must have installed the following:  +Python, +NumPy, and MatPlotLib.   Typically, SciPy -is installed along with NumPy and MatPlotLib.  -ColorPy doesn't use SciPy explicitly, although MatPlotLib may require SciPy.  -(I am not sure.)  ColorPy is a 'pure' Python distribution, so you do not -need any extra software to build it.  I have tested ColorPy both on Windows -XP and Ubuntu Linux, and it should run on any system where you can install the -prerequisites.  If, for some reason, you can only install NumPy but not -MatPlotLib, you should still be able to do many of the calculations, but will +is installed along with NumPy and MatPlotLib.  +ColorPy doesn't use SciPy explicitly, although MatPlotLib may require SciPy.  +(I am not sure.)  ColorPy is a 'pure' Python distribution, so you do not +need any extra software to build it.  I have tested ColorPy both on Windows +XP and Ubuntu Linux, and it should run on any system where you can install the +prerequisites.  If, for some reason, you can only install NumPy but not +MatPlotLib, you should still be able to do many of the calculations, but will not be able to make any of the nice plots.

Types and Units

-ColorPy generally uses wavelengths measured in nanometers (nm), 10-9 m.  -Otherwise, typical metric units are used.  For descriptions of spectra, -ColorPy uses two-dimensional NumPy arrays, with two columns and an arbitrary -number of rows.  Each row of these arrays represents the light intensity -for one wavelength, with the value in the first column being the wavelength in -nm, and the value in the second column being the light intensity at that -wavelength.  ColorPy can provide a blank spectrum array, via -colorpy.ciexyx.empty_spectrum(), which will have rows for each wavelength from 360 nm to -830 nm, at 1 nm increments.  (Wavelengths outside this range are generally -ignored, as the eye cannot see them.)  However, you can create your own spectrum +ColorPy generally uses wavelengths measured in nanometers (nm), 10-9 m.  +Otherwise, typical metric units are used.  For descriptions of spectra, +ColorPy uses two-dimensional NumPy arrays, with two columns and an arbitrary +number of rows.  Each row of these arrays represents the light intensity +for one wavelength, with the value in the first column being the wavelength in +nm, and the value in the second column being the light intensity at that +wavelength.  ColorPy can provide a blank spectrum array, via +colorpy.ciexyx.empty_spectrum(), which will have rows for each wavelength from 360 nm to +830 nm, at 1 nm increments.  (Wavelengths outside this range are generally +ignored, as the eye cannot see them.)  However, you can create your own spectrum arrays with any set of wavelengths you like.

-Color values are represented as three-component NumPy vectors.  -(One-dimensional arrays).  Typically, these are vectors of floats, with the -exception of displayable irgb colors, which are arrays of integers (in the range +Color values are represented as three-component NumPy vectors.  +(One-dimensional arrays).  Typically, these are vectors of floats, with the +exception of displayable irgb colors, which are arrays of integers (in the range 0 - 255).

Fundamentals - Mapping spectra to three-dimensional color values

-We are interested in working with physical descriptions of light spectra, that -is, functions of intensity vs. wavelength.  However, color is perceived as -a three-dimensional quantity, as there are three sets of color receptors in the -eye, which respond approximately to red, green and blue light.  So how do +We are interested in working with physical descriptions of light spectra, that +is, functions of intensity vs. wavelength.  However, color is perceived as +a three-dimensional quantity, as there are three sets of color receptors in the +eye, which respond approximately to red, green and blue light.  So how do we reduce a function of intensity vs. wavelength to a three-dimensional value?

-This fundamental step is done by integrating the intensity function with a set -of three matching functions.  The standard matching functions were defined -by the Commission Internationale de l'Eclairage -(CIE), based on experiments with viewers matching the color of single wavelength -lights.  The matching functions generally used in computer graphics are -those developed in 1931, which used a 2 degree field of view.  (There is -also a set of matching functions developed in 1964, covering a field of view of -10 degrees, but the larger field of view does not correspond to typical -conditions in viewing computer graphics.)  So the mapping is done as +This fundamental step is done by integrating the intensity function with a set +of three matching functions.  The standard matching functions were defined +by the Commission Internationale de l'Eclairage +(CIE), based on experiments with viewers matching the color of single wavelength +lights.  The matching functions generally used in computer graphics are +those developed in 1931, which used a 2 degree field of view.  (There is +also a set of matching functions developed in 1964, covering a field of view of +10 degrees, but the larger field of view does not correspond to typical +conditions in viewing computer graphics.)  So the mapping is done as follows:

@@ -138,54 +138,54 @@

Fundamentals - Mapping spectra to three-dimensional color values

Y = ∫ I (λ) * CIE-Y (λ) * dλ
Z = ∫ I (λ) * CIE-Z (λ) * dλ

-where I (λ) is the spectrum of light intensity vs. wavelength, and CIE-X (λ), -CIE-Y (λ) and CIE-Z (λ) are the matching functions.  The CIE matching -functions are defined over the interval of 360 nm to 830 nm, and are zero for -all wavelengths outside this interval, so these are the bounds for the +where I (λ) is the spectrum of light intensity vs. wavelength, and CIE-X (λ), +CIE-Y (λ) and CIE-Z (λ) are the matching functions.  The CIE matching +functions are defined over the interval of 360 nm to 830 nm, and are zero for +all wavelengths outside this interval, so these are the bounds for the integrals.

-So what do these matching functions look like?  Let's take a look at a plot +So what do these matching functions look like?  Let's take a look at a plot (made with ColorPy, of course.)


Figure 1 - The 1931 CIE XYZ matching functions.

-This plot shows the three matching functions vs. wavelength.  The colors -underneath the curve, at each wavelength, are the (approximate) colors that the -human eye will perceive for a pure spectral line at that wavelength, of constant -intensity.  The apparent brightness of the color at each wavelength -indicates how strongly the eye perceives that wavelength - the intensity for -each wavelength is the same.  (The next section will explain how we get the +This plot shows the three matching functions vs. wavelength.  The colors +underneath the curve, at each wavelength, are the (approximate) colors that the +human eye will perceive for a pure spectral line at that wavelength, of constant +intensity.  The apparent brightness of the color at each wavelength +indicates how strongly the eye perceives that wavelength - the intensity for +each wavelength is the same.  (The next section will explain how we get the RGB values for the colors.)

-Each of the three plots was generated via colorpy.plots.spectrum_subplot (spectrum), +Each of the three plots was generated via colorpy.plots.spectrum_subplot (spectrum), where spectrum is the value of the matching function vs. wavelength.

-All three of the matching functions are zero or positive everywhere.  Since -the light intensity at any wavelength is never negative, this means that the -resulting XYZ color values are never negative.  Also, the Y matching -function corresponds exactly to the luminous efficiency of the eye - the eye's -response to light of constant luminance.  (These facts are some of the +All three of the matching functions are zero or positive everywhere.  Since +the light intensity at any wavelength is never negative, this means that the +resulting XYZ color values are never negative.  Also, the Y matching +function corresponds exactly to the luminous efficiency of the eye - the eye's +response to light of constant luminance.  (These facts are some of the reasons that make this particular set of matching functions so useful.)

-So now we can map a spectrum of intensity vs. wavelength into a -three-dimensional value.  Before we consider how to convert this into an -RGB color value that we can draw, we will first discuss some typical scaling +So now we can map a spectrum of intensity vs. wavelength into a +three-dimensional value.  Before we consider how to convert this into an +RGB color value that we can draw, we will first discuss some typical scaling operations on XYZ colors.

-Often, it is useful to consider the 'chromaticity' of a color, that is, the hue -and saturation, independent of the intensity.  This is typically done by -scaling the XYZ values so that their sum is 1.0.  The resulting scaled -values are conventionally written as lower case letters x,y,z.  With this -scaling, x+y+z = 1.0.  The chromaticity can be -specified by the resulting x and y values, and the z component can be reconstructed as z = 1.0 - x - y.  -It is also common to specify colors with their chromaticity (x and y), as well -as the total brightness (Y).  Occasionally, one also wants to scale an XYZ +Often, it is useful to consider the 'chromaticity' of a color, that is, the hue +and saturation, independent of the intensity.  This is typically done by +scaling the XYZ values so that their sum is 1.0.  The resulting scaled +values are conventionally written as lower case letters x,y,z.  With this +scaling, x+y+z = 1.0.  The chromaticity can be +specified by the resulting x and y values, and the z component can be reconstructed as z = 1.0 - x - y.  +It is also common to specify colors with their chromaticity (x and y), as well +as the total brightness (Y).  Occasionally, one also wants to scale an XYZ color so that the resulting Y value is 1.0.

-ColorPy represents XYZ colors (and other types of colors) as three-component -vectors.  There are some 'constructor' like functions to create such +ColorPy represents XYZ colors (and other types of colors) as three-component +vectors.  There are some 'constructor' like functions to create such arrays, and perform these kinds of scaling:

@@ -195,187 +195,187 @@

Fundamentals - Mapping spectra to three-dimensional color values

colorpy.colormodels.xyz_normalize_Y1 (xyz)

-Notice that color types are generally specified in ColorPy with lower case -letters, as this is more readable.  (I.e., xyz_color instead of XYZ_color.)  -The user must keep track of the particular normalization that applies in each +Notice that color types are generally specified in ColorPy with lower case +letters, as this is more readable.  (I.e., xyz_color instead of XYZ_color.)  +The user must keep track of the particular normalization that applies in each situation.

Fundamentals - Converting XYZ colors to RGB colors

-So how do we convert one of these XYZ colors to an RGB color that I can draw on +So how do we convert one of these XYZ colors to an RGB color that I can draw on my computer?

-The short answer, is to call colorpy.colormodels.irgb_from_xyz (xyz), where xyz is the -XYZ color vector.  This will return a three element integer vector, with -each component in the range 0 - 255.  There is also a function -colorpy.colormodels.irgb_string_from_xyz (xyz) that will return a hex string, such as +The short answer, is to call colorpy.colormodels.irgb_from_xyz (xyz), where xyz is the +XYZ color vector.  This will return a three element integer vector, with +each component in the range 0 - 255.  There is also a function +colorpy.colormodels.irgb_string_from_xyz (xyz) that will return a hex string, such as '#FF0000' for red.

-There are several subtleties and approximations in the behavior of these +There are several subtleties and approximations in the behavior of these functions, which are important to understand what is happening.

-The first step in the conversion, is to convert the XYZ color to a 'linear' RGB -color.  By 'linear', we mean that the light intensity is proportional to -the numerical color values.  ColorPy represents such linear RGB values as -floats, with the nominal range of 0.0 - 1.0 covering the range of intensity that the -monitor display can produce.  (This implies an assumption as to the -physical brightness of the display.)  The conversion from XYZ to linear RGB -is done by multiplication by a 3x3 element array.  So, which array to use?  -The specific values of the array depend on the physical display in question, -specifically the chromaticities of the monitor phosphors.  Not all displays -have the exact same red, green and blue monitor primaries, and so any conversion -matrix cannot apply to all displays.  This can be a considerable -complication, but fortunately, there is a specification of monitor -chromaticities that we can assume, part of the sRGB standard, and are likely to -be a close match to most actual displays.  ColorPy uses this assumption by -default, although you can change the assumed monitor chromaticities to nearly +The first step in the conversion, is to convert the XYZ color to a 'linear' RGB +color.  By 'linear', we mean that the light intensity is proportional to +the numerical color values.  ColorPy represents such linear RGB values as +floats, with the nominal range of 0.0 - 1.0 covering the range of intensity that the +monitor display can produce.  (This implies an assumption as to the +physical brightness of the display.)  The conversion from XYZ to linear RGB +is done by multiplication by a 3x3 element array.  So, which array to use?  +The specific values of the array depend on the physical display in question, +specifically the chromaticities of the monitor phosphors.  Not all displays +have the exact same red, green and blue monitor primaries, and so any conversion +matrix cannot apply to all displays.  This can be a considerable +complication, but fortunately, there is a specification of monitor +chromaticities that we can assume, part of the sRGB standard, and are likely to +be a close match to most actual displays.  ColorPy uses this assumption by +default, although you can change the assumed monitor chromaticities to nearly anything you like.

-So for now, let's assume the standard sRGB chromaticities, which gives us the +So for now, let's assume the standard sRGB chromaticities, which gives us the correct 3x3 matrix, and so we can convert our XYZ colors to linear RGB colors.

-We then come to the next obstacle...  The RGB values that we get from this -process are often out of range - meaning that they are either greater than 1.0, -or even that they are negative!  The first case is fairly straightforward, -it means that the color is too bright for the display.  The second case -means that the color is too saturated and vivid for the display.  The -display must compose all colors from some combination of positive amounts of the -colors of its red, green and blue phosphors.  The colors of these phosphors -are not perfectly saturated, they are washed out, mixed with white, to some -extent.  So not all colors can be displayed accurately.  As an -example, the colors of pure spectral lines, all have some negative component.  -Something must be done to put these values into the 0.0 - 1.0 range that can +We then come to the next obstacle...  The RGB values that we get from this +process are often out of range - meaning that they are either greater than 1.0, +or even that they are negative!  The first case is fairly straightforward, +it means that the color is too bright for the display.  The second case +means that the color is too saturated and vivid for the display.  The +display must compose all colors from some combination of positive amounts of the +colors of its red, green and blue phosphors.  The colors of these phosphors +are not perfectly saturated, they are washed out, mixed with white, to some +extent.  So not all colors can be displayed accurately.  As an +example, the colors of pure spectral lines, all have some negative component.  +Something must be done to put these values into the 0.0 - 1.0 range that can actually be displayed, known as color clipping.

-In the first case, values larger than 1.0, ColorPy scales the color so that the -maximum component is 1.0.  This reduces the brightness without changing the -chromaticity.  The second case requires some change in chromaticity.  -By default, ColorPy will add white to the color, just enough to make all of the -components non-negative.  (You can also have ColorPy clamp the negative -values to zero.  My personal, qualitative, assessment is that adding white -produces somewhat better results.  There is also the potential to develop a +In the first case, values larger than 1.0, ColorPy scales the color so that the +maximum component is 1.0.  This reduces the brightness without changing the +chromaticity.  The second case requires some change in chromaticity.  +By default, ColorPy will add white to the color, just enough to make all of the +components non-negative.  (You can also have ColorPy clamp the negative +values to zero.  My personal, qualitative, assessment is that adding white +produces somewhat better results.  There is also the potential to develop a better clipping function.)

-So now we have linear RGB values in the range 0.0 - 1.0.  The next subtlety -in the conversion process, is that the intensity of colors on the display is not -simply proportional to the color values given to the hardware.  This -situation is known as 'gamma correction', and is particularly significant for -CRT displays.  The voltage on the electron gun in the CRT display is -proportional to the RGB values given to the hardware to display, but the -intensity of the resulting light is *not* proportional to this voltage, in fact -the relationship is a power law.  The particular correction for this -depends on the physical display in question.  LCD displays add another -complication, as it is not clear (at least to me) what the correct conversion is -in this case.  Again, we rely on the sRGB standard to decide what to do.  -That standard assumes a physical 'gamma' exponent of about 2.2, and ColorPy -applies this correction by default.  You can change this to a different +So now we have linear RGB values in the range 0.0 - 1.0.  The next subtlety +in the conversion process, is that the intensity of colors on the display is not +simply proportional to the color values given to the hardware.  This +situation is known as 'gamma correction', and is particularly significant for +CRT displays.  The voltage on the electron gun in the CRT display is +proportional to the RGB values given to the hardware to display, but the +intensity of the resulting light is *not* proportional to this voltage, in fact +the relationship is a power law.  The particular correction for this +depends on the physical display in question.  LCD displays add another +complication, as it is not clear (at least to me) what the correct conversion is +in this case.  Again, we rely on the sRGB standard to decide what to do.  +That standard assumes a physical 'gamma' exponent of about 2.2, and ColorPy +applies this correction by default.  You can change this to a different exponent if you like.

-The final step after gamma correction, is to convert the RGB components from the -range 0.0 - 1.0 to 0 - 255, which is the typical range needed to pass to the -hardware.  This is done with simple scaling and rounding.  The final -result of all of these conversions, RGB color values in the range 0 - 255, is -referred to as an irgb_color.  This is the color type that can be passed to +The final step after gamma correction, is to convert the RGB components from the +range 0.0 - 1.0 to 0 - 255, which is the typical range needed to pass to the +hardware.  This is done with simple scaling and rounding.  The final +result of all of these conversions, RGB color values in the range 0 - 255, is +referred to as an irgb_color.  This is the color type that can be passed to drawing functions.

-Summarizing of these conversions, with the functions that ColorPy uses +Summarizing of these conversions, with the functions that ColorPy uses internally:

-colorpy.colormodels.rgb_from_xyz (xyz) - Converts an XYZ color to a linear RGB color, -with components in the nominal range 0.0 - 1.0, but possibly out of range -(greater than 1.0, or negative).  The resulting linear RGB color cannot be +colorpy.colormodels.rgb_from_xyz (xyz) - Converts an XYZ color to a linear RGB color, +with components in the nominal range 0.0 - 1.0, but possibly out of range +(greater than 1.0, or negative).  The resulting linear RGB color cannot be directly passed to drawing functions.

-colorpy.colormodels.irgb_from_rgb (rgb) - Converts a linear RGB color in the nominal -range 0.0 - 1.0 to a displayable irgb color, definitely in the range 0 - 255.  -Color clipping may be applied (intensity as well as chromaticity), and gamma -correction is accounted for.  This result can be passed to drawing +colorpy.colormodels.irgb_from_rgb (rgb) - Converts a linear RGB color in the nominal +range 0.0 - 1.0 to a displayable irgb color, definitely in the range 0 - 255.  +Color clipping may be applied (intensity as well as chromaticity), and gamma +correction is accounted for.  This result can be passed to drawing functions.


-With all of this, let's plot some real colors.  First, consider the pure -spectral lines - that is, spectra that are all black (zero -intensity), except at a single wavelength.  We consider all the wavelengths -from 360 nm to 830 nm, which covers the range of human vision (and the range of +With all of this, let's plot some real colors.  First, consider the pure +spectral lines - that is, spectra that are all black (zero +intensity), except at a single wavelength.  We consider all the wavelengths +from 360 nm to 830 nm, which covers the range of human vision (and the range of the CIE XYZ matching functions.)

-The two-part plot below shows the result.  The top section, shows the best -colors that ColorPy can draw for each wavelength.  The amount of light -intensity for each wavelength is the same.  But since the human eye has -different sensitivity to different wavelengths, the apparent brightness looks different -for different colors.  For example, the color for 750 nm is quite dark, -while the color for 550 nm is quite bright.  They represent lines with the -same physical luminance, however.  The bottom section shows the -linear RGB values corresponding to each wavelength.  You can see that there -are negative RGB values on this plot.  In fact, there is a negative -component at every wavelength - none of the pure spectral lines can be displayed -with full saturation.  (The overall intensity scale is arbitrary, and has +The two-part plot below shows the result.  The top section, shows the best +colors that ColorPy can draw for each wavelength.  The amount of light +intensity for each wavelength is the same.  But since the human eye has +different sensitivity to different wavelengths, the apparent brightness looks different +for different colors.  For example, the color for 750 nm is quite dark, +while the color for 550 nm is quite bright.  They represent lines with the +same physical luminance, however.  The bottom section shows the +linear RGB values corresponding to each wavelength.  You can see that there +are negative RGB values on this plot.  In fact, there is a negative +component at every wavelength - none of the pure spectral lines can be displayed +with full saturation.  (The overall intensity scale is arbitrary, and has been chosen so that the largest RGB component for any wavelength is 1.0.)


Figure 2 - RGB values for the pure spectral lines.

-This specific plot was made with colorpy.plots.visible_spectrum_plot (), and the real -work was done with colorpy.plots.color_vs_param_plot (param_list, rgb_colors, title, -filename, tight=False, plotfunc=pylab.plot, xlabel='param', ylabel='RGB Color').  -This function accepts two lists, one of an arbitrary parameter (wavelength in -this case), and one of linear RGB colors.  (The two lists must be of the -same size.)  You also must supply a title and filename for the plot.  -Optional arguments include a request that the x-axis be 'tightened' to only -include the range of the parameters, a different plotting function from the -default, and different labels for the axes.  This is a very handy function, +This specific plot was made with colorpy.plots.visible_spectrum_plot (), and the real +work was done with colorpy.plots.color_vs_param_plot (param_list, rgb_colors, title, +filename, tight=False, plotfunc=pylab.plot, xlabel='param', ylabel='RGB Color').  +This function accepts two lists, one of an arbitrary parameter (wavelength in +this case), and one of linear RGB colors.  (The two lists must be of the +same size.)  You also must supply a title and filename for the plot.  +Optional arguments include a request that the x-axis be 'tightened' to only +include the range of the parameters, a different plotting function from the +default, and different labels for the axes.  This is a very handy function, useful for many other plots besides this one.

-You can see that there are negative RGB values for these colors, and those +You can see that there are negative RGB values for these colors, and those actually drawn have been clipped to something displayable.

-Another way to understand the limited color gamut (range of displayable colors) of physical displays, is to consider -the 'shark fin' CIE chromaticity diagram.  On this plot, we draw the chromaticities of the pure -spectral lines.  These trace out a fin shaped region.  The low -wavelength colors start at the lower left corner of the fin, and as the -wavelength increases, moves up on the plot towards green, and then down and -to the right towards yellow and red.  The longest wavelength corresponds to -the red corner at the far right.  The straight line connecting the long -wavelength red to the short wavelength blue is not composed of pure spectral -lines, rather these 'purples' are a linear combination of the extreme red and -blue colors.  The outer boundary of this diagram represents the spectrally -pure colors.  Just inside this boundary, we draw the best color match for +Another way to understand the limited color gamut (range of displayable colors) of physical displays, is to consider +the 'shark fin' CIE chromaticity diagram.  On this plot, we draw the chromaticities of the pure +spectral lines.  These trace out a fin shaped region.  The low +wavelength colors start at the lower left corner of the fin, and as the +wavelength increases, moves up on the plot towards green, and then down and +to the right towards yellow and red.  The longest wavelength corresponds to +the red corner at the far right.  The straight line connecting the long +wavelength red to the short wavelength blue is not composed of pure spectral +lines, rather these 'purples' are a linear combination of the extreme red and +blue colors.  The outer boundary of this diagram represents the spectrally +pure colors.  Just inside this boundary, we draw the best color match for each wavelength.

-The triangle inside the fin represents the range (gamut) of colors that the -physical display can show.  The vertices labeled Red, Green and Blue -represent the chromaticities of the monitor primaries, and the point labeled -White represents the white point with all primaries at full strength.  -(This plot assumes the standard sRGB primaries and white point.)  The -points inside the inner triangle are the only colors that the display can render -accurately.  (This figure -could use a little work.  It would be nice to label the outer boundary -of the fin with the corresponding wavelength.)  The points outside the -inner triangle are colors that must be approximated.  (Points outside the +The triangle inside the fin represents the range (gamut) of colors that the +physical display can show.  The vertices labeled Red, Green and Blue +represent the chromaticities of the monitor primaries, and the point labeled +White represents the white point with all primaries at full strength.  +(This plot assumes the standard sRGB primaries and white point.)  The +points inside the inner triangle are the only colors that the display can render +accurately.  (This figure +could use a little work.  It would be nice to label the outer boundary +of the fin with the corresponding wavelength.)  The points outside the +inner triangle are colors that must be approximated.  (Points outside the outer 'fin' do not correspond to any color at all.)

-You can see that the standard monitor is much more limited in displaying greens -than blues and reds.  If someone is able to invent a much purer green -colored phosphor, with low persistence so it is suitable for animations and -hence real displays, then the world of computer graphics will get significantly -more richly colored!  Also notice that there is no possible set of of three -monitor phosphor chromaticities that can cover the entire visible gamut.  -For any three points inside the 'fin', the enclosed triangle must necessarily -exclude some of the pure spectral colors, even if the monitor phosphors were +You can see that the standard monitor is much more limited in displaying greens +than blues and reds.  If someone is able to invent a much purer green +colored phosphor, with low persistence so it is suitable for animations and +hence real displays, then the world of computer graphics will get significantly +more richly colored!  Also notice that there is no possible set of of three +monitor phosphor chromaticities that can cover the entire visible gamut.  +For any three points inside the 'fin', the enclosed triangle must necessarily +exclude some of the pure spectral colors, even if the monitor phosphors were perfectly spectrally pure.


Figure 3 - CIE chromaticity diagram of the visible gamut.
-Colors inside -the inner triangle can be accurately drawn on the display, points outside (but +Colors inside +the inner triangle can be accurately drawn on the display, points outside (but inside the fin) must be approximated.

-This figure is drawn with colorpy.plots.shark_fin_plot().  This is kind of a +This figure is drawn with colorpy.plots.shark_fin_plot().  This is kind of a specialized figure, probably not that useful for other things.  Now, let's consider some more examples.

@@ -388,24 +388,24 @@

MacBeth ColorChecker Chart


Figure 4 - MacBeth ColorChecker Chart.

-The simplest way that ColorPy can be used to display colors, is to display a set -of known XYZ (or RGB) colors.  As an example, we use the MacBeth ColorChecker Chart.  -This is a set of standard reference colors that is used in photography and -video.  (You can buy the physical chart from photographic suppliers.  -It is not particularly cheap.)  It is used to verify that colors are being -reproduced accurately on film.  [Hall, p. 119] provides a set of xy colors for -the patches on this chart (which must assume some particular lighting -environment), and ColorPy can convert these into displayable colors.  This -serves as a test that the xyz to rgb conversions are operating correctly, which +The simplest way that ColorPy can be used to display colors, is to display a set +of known XYZ (or RGB) colors.  As an example, we use the MacBeth ColorChecker Chart.  +This is a set of standard reference colors that is used in photography and +video.  (You can buy the physical chart from photographic suppliers.  +It is not particularly cheap.)  It is used to verify that colors are being +reproduced accurately on film.  [Hall, p. 119] provides a set of xy colors for +the patches on this chart (which must assume some particular lighting +environment), and ColorPy can convert these into displayable colors.  This +serves as a test that the xyz to rgb conversions are operating correctly, which is analogous to the sort of thing the real chart is used for.

-This 'patch' plot is made with colorpy.plots.xyz_patch_plot (xyz_colors, color_names, +This 'patch' plot is made with colorpy.plots.xyz_patch_plot (xyz_colors, color_names, title, filename, patch_gap=0.05, num_across=6), where xyz_colors and color_names -are two lists, the first with the XYZ color values to draw, and the second with -names to draw on the plot.  The two lists must be of the same size, but you -can pass None for the second list to skip the labels.  You also must supply -a title and filename, and there are optional arguments to fine-tune the size and -arrangement of the patches.  There is also a similar function +are two lists, the first with the XYZ color values to draw, and the second with +names to draw on the plot.  The two lists must be of the same size, but you +can pass None for the second list to skip the labels.  You also must supply +a title and filename, and there are optional arguments to fine-tune the size and +arrangement of the patches.  There is also a similar function colorpy.plots.rgb_patch_plot() when you have known RGB values that you want to draw.

@@ -415,121 +415,121 @@

MacBeth ColorChecker Chart

Blackbody Radiation

-For a more interesting example, one that involves physical light spectra, consider the colors of thermal -blackbodies.  A 'blackbody' is an object that is in thermal equilibrium -with its environment, at some temperature T.  Such an object will radiate -light energy, with a particular spectrum of intensity vs. wavelength.  This -spectrum depends only on the temperature of the blackbody, and is completely -independent of the composition of the blackbody.  The theory of blackbodies -was important in the development of quantum mechanics, the first application of -quantum mechanics was by Max Planck in deriving the blackbody spectrum.  Many real light +For a more interesting example, one that involves physical light spectra, consider the colors of thermal +blackbodies.  A 'blackbody' is an object that is in thermal equilibrium +with its environment, at some temperature T.  Such an object will radiate +light energy, with a particular spectrum of intensity vs. wavelength.  This +spectrum depends only on the temperature of the blackbody, and is completely +independent of the composition of the blackbody.  The theory of blackbodies +was important in the development of quantum mechanics, the first application of +quantum mechanics was by Max Planck in deriving the blackbody spectrum.  Many real light sources are approximately blackbodies.

-Blackbody theory shows that the 'monochromatic specific intensity' -Bλ(T), the power at wavelength λ radiated per unit wavelength +Blackbody theory shows that the 'monochromatic specific intensity' +Bλ(T), the power at wavelength λ radiated per unit wavelength per unit solid angle, is [Shu p. 78]:

Bλ(T) = (2hc2)/(λ5) * (1 / (exp(hc/λkT) - 1))

-where h = Planck's constant, c = speed of light, k +where h = Planck's constant, c = speed of light, k = Boltzman's constant, λ = wavelength, and T = temperature.

-Using this relation, we can construct a spectrum in ColorPy, and then determine -the rgb color of the blackbody radiator.  The module blackbody provides the +Using this relation, we can construct a spectrum in ColorPy, and then determine +the rgb color of the blackbody radiator.  The module blackbody provides the appropriate functions.  First, the function blackbody.blackbody_specific_intensity (wl_nm, T_K) -calculates the Bλ(T) above, for any wavelength and temperature.  This is -then converted into a spectrum of intensity vs. wavelength.  The function -ciexyz.empty_spectrum() is called to get a NumPy array to hold the -spectrum.  This array has one row for each wavelength to be used, -from 360 to 830 nm, with an increment of 1 nm.  The first column is already -filled in with the wavelength (in nm), the second column is to be filled with -the intensity, and is initially zero.  Since Bλ(T) represents the power per unit wavelength, this -must be multiplied by the width of the interval, which is 1 nm.  This -resulting spectrum is then converted into an xyz color with -ciexyz.xyz_from_spectrum().  This can then be converted to a -displayable irgb color and -drawn.  The whole process is performed by colorpy.blackbody.blackbody_spectrum(), +calculates the Bλ(T) above, for any wavelength and temperature.  This is +then converted into a spectrum of intensity vs. wavelength.  The function +ciexyz.empty_spectrum() is called to get a NumPy array to hold the +spectrum.  This array has one row for each wavelength to be used, +from 360 to 830 nm, with an increment of 1 nm.  The first column is already +filled in with the wavelength (in nm), the second column is to be filled with +the intensity, and is initially zero.  Since Bλ(T) represents the power per unit wavelength, this +must be multiplied by the width of the interval, which is 1 nm.  This +resulting spectrum is then converted into an xyz color with +ciexyz.xyz_from_spectrum().  This can then be converted to a +displayable irgb color and +drawn.  The whole process is performed by colorpy.blackbody.blackbody_spectrum(), which is listed below.

def blackbody_spectrum (T_K):
    '''Get the spectrum of a blackbody, as a numpy array.'''
    spectrum = ciexyz.empty_spectrum()
    (num_rows, num_cols) = spectrum.shape
-    for i in xrange (0, num_rows):
+    for i in range (0, num_rows):
        specific_intensity = blackbody_specific_intensity (spectrum [i][0], T_K)
        # scale by size of wavelength interval
        spectrum [i][1] = specific_intensity * ciexyz.delta_wl_nm * 1.0e-9
    return spectrum

-So let's look at some results of these calculations, which also introduce a new -type of ColorPy plot, the spectrum plot.  First, consider a -blackbody with a temperature of 5778 K.  This is the surface temperature of -the Sun [Wikipedia], and this spectrum approximates that of the sun.  Figure 5 below -shows both the overall color of the resulting spectrum, with a graph of the -spectrum itself below the color patch.  The overall color is nearly white.  -The spectrum shows that the peak intensity is in the green region, around 500 nm -or so, but with significant contributions from both lower and higher -wavelengths.  The colors in the spectrum plot are indicate the extent to -which the eye is sensitive to the particular wavelength.  Each color band -has the same amount of luminance, so the apparent brightness of the color indicates the -extent to which the eye is sensitive to that wavelength.  The eye has a -very low sensitivity to wavelengths below 400 nm or so, and to wavelengths above -700 nm or so.  Thus, the resulting color in the spectrum plot is nearly -black.  With a wavelength of 550 nm, on the other hand, the eye is quite -sensitive to this wavelength, and the resulting color is therefore bright -(green).  This way of drawing the spectrum is intended to help show the -contributions of each wavelength in the spectrum to the overall color.  For -example, in this case, there is a considerable amount of power at wavelengths -from 700 nm to 830 nm.  However, the eye has a low sensitivity to these, +So let's look at some results of these calculations, which also introduce a new +type of ColorPy plot, the spectrum plot.  First, consider a +blackbody with a temperature of 5778 K.  This is the surface temperature of +the Sun [Wikipedia], and this spectrum approximates that of the sun.  Figure 5 below +shows both the overall color of the resulting spectrum, with a graph of the +spectrum itself below the color patch.  The overall color is nearly white.  +The spectrum shows that the peak intensity is in the green region, around 500 nm +or so, but with significant contributions from both lower and higher +wavelengths.  The colors in the spectrum plot are indicate the extent to +which the eye is sensitive to the particular wavelength.  Each color band +has the same amount of luminance, so the apparent brightness of the color indicates the +extent to which the eye is sensitive to that wavelength.  The eye has a +very low sensitivity to wavelengths below 400 nm or so, and to wavelengths above +700 nm or so.  Thus, the resulting color in the spectrum plot is nearly +black.  With a wavelength of 550 nm, on the other hand, the eye is quite +sensitive to this wavelength, and the resulting color is therefore bright +(green).  This way of drawing the spectrum is intended to help show the +contributions of each wavelength in the spectrum to the overall color.  For +example, in this case, there is a considerable amount of power at wavelengths +from 700 nm to 830 nm.  However, the eye has a low sensitivity to these, and so these wavelengths do not contribute much to the overall color.

-These kinds of plots are made with colorpy.plots.spectrum_plot (spectrum, title, -filename, xlabel, ylabel), where spectrum is the numpy array with the spectrum +These kinds of plots are made with colorpy.plots.spectrum_plot (spectrum, title, +filename, xlabel, ylabel), where spectrum is the numpy array with the spectrum data, and the other parameters are the title, filename, and axis labels.


-Figure 5 - Color of a 5778 K blackbody.  This approximates the spectrum of +Figure 5 - Color of a 5778 K blackbody.  This approximates the spectrum of the Sun.

-Since the temperature is just a parameter to the function calls, we can do the -same thing for any other temperature that we like.  The nearby star Proxima -Centauri is much cooler than the sun, it has a surface temperature of about 3000 -K. -[Wikipedia].  The spectrum of a 3000 K blackbody is shown below, -with the same kind of plot.  -The overall color in this case is orange, and the spectrum is concentrated at +Since the temperature is just a parameter to the function calls, we can do the +same thing for any other temperature that we like.  The nearby star Proxima +Centauri is much cooler than the sun, it has a surface temperature of about 3000 +K. +[Wikipedia].  The spectrum of a 3000 K blackbody is shown below, +with the same kind of plot.  +The overall color in this case is orange, and the spectrum is concentrated at longer wavelengths.


Figure 6 - Spectrum of 3000 K blackbody.  This approximates the spectrum of Proxima Centauri.

-We can also evaluate this at higher temperatures.  The star Rigel, in the -constellation Orion, has a -surface temperature of 11000 K [Wikipedia], and the resulting blackbody spectrum is shown -below.  The overall color is now blue-white, and the intensity is +We can also evaluate this at higher temperatures.  The star Rigel, in the +constellation Orion, has a +surface temperature of 11000 K [Wikipedia], and the resulting blackbody spectrum is shown +below.  The overall color is now blue-white, and the intensity is concentrated at low wavelengths.


-Figure 7 - Spectrum of 11000 K blackbody.  This approximates the spectrum +Figure 7 - Spectrum of 11000 K blackbody.  This approximates the spectrum of the star Rigel.

-But why limit ourselves to just a handful of temperatures?  We can -calculate the blackbody spectrum for very many temperatures, and ColorPy allows -us to plot the resulting color vs. temperature, while also showing a plot of the -rgb color values.  In the figure below, we have calculated the blackbody -color for temperatures ranging from 1200 K to 16000 K.  The top subplot -shows the resulting color, as a function of temperature, while the lower plot +But why limit ourselves to just a handful of temperatures?  We can +calculate the blackbody spectrum for very many temperatures, and ColorPy allows +us to plot the resulting color vs. temperature, while also showing a plot of the +rgb color values.  In the figure below, we have calculated the blackbody +color for temperatures ranging from 1200 K to 16000 K.  The top subplot +shows the resulting color, as a function of temperature, while the lower plot shows the linear RGB values.  -For low temperatures, the blackbody is red to orange, and as the temperature +For low temperatures, the blackbody is red to orange, and as the temperature increases, the color becomes white, and then blue.

-This style plot was generated with colorpy.plots.color_vs_param_plot (param_list, -rgb_colors, title, filename, tight, plotfunc, xlabel, ylabel).  The -arguments tight, plotfunc, xlabel and ylabel are optional, in this case we set -tight=True and plotfunc=pylab.semilogy to obtain the semilog plot, which is +This style plot was generated with colorpy.plots.color_vs_param_plot (param_list, +rgb_colors, title, filename, tight, plotfunc, xlabel, ylabel).  The +arguments tight, plotfunc, xlabel and ylabel are optional, in this case we set +tight=True and plotfunc=pylab.semilogy to obtain the semilog plot, which is needed for the very large range of color values covered.

@@ -537,45 +537,45 @@

Blackbody Radiation


Figure 8 - Color of blackbody vs. temperature.

-Let's also consider similar plots over different temperature ranges.  -First, consider the range 950 K to 1200 K, shown below.  The colors are all +Let's also consider similar plots over different temperature ranges.  +First, consider the range 950 K to 1200 K, shown below.  The colors are all nearly red, and are brighter for higher temperatures.

-This provides another example to discuss the color intensity scaling that ColorPy -uses.  ColorPy attempts to calculate a color value that, when displayed on a +This provides another example to discuss the color intensity scaling that ColorPy +uses.  ColorPy attempts to calculate a color value that, when displayed on a computer monitor, will match the physical brightness of the spectrum.  -In this plot, the red value reaches 1.0 at about 1150 K.  This means -(approximately) that an 1150 K blackbody is as bright as the monitor at full -intensity.  Similarly, a 950 K blackbody is about 1.5% as bright as the -monitor.  This, by the way, suggests an experimental test of whether the -display brightness assumed by ColorPy is correct - a 1150 K blackbody is +In this plot, the red value reaches 1.0 at about 1150 K.  This means +(approximately) that an 1150 K blackbody is as bright as the monitor at full +intensity.  Similarly, a 950 K blackbody is about 1.5% as bright as the +monitor.  This, by the way, suggests an experimental test of whether the +display brightness assumed by ColorPy is correct - a 1150 K blackbody is predicted to be as bright as the display monitor at full red.

-Also notice that there is no blue line on this plot.  For temperatures as -low as 1200 K (and all lower), the correct blue value is negative.  (This -means that the monitor is not capable of displaying the color at full saturation -- it is necessarily washed out.)  Since negative values cannot be plotted -on a log axis, there is no blue on this plot.  The green value also becomes +Also notice that there is no blue line on this plot.  For temperatures as +low as 1200 K (and all lower), the correct blue value is negative.  (This +means that the monitor is not capable of displaying the color at full saturation +- it is necessarily washed out.)  Since negative values cannot be plotted +on a log axis, there is no blue on this plot.  The green value also becomes negative near the left edge of the plot.


Figure 9 - Colors of relatively cool blackbodies.

-Now consider a high temperature range, 10000 K to 40000 K, shown in the plot -below.  In this situation, the colors are far in excess of that -displayable on the monitor.  A 40000 K blackbody is far far brighter than -any computer monitor!  In fact, the intensity is on the order of 109 times -that of what the monitor can display.  When displaying very bright colors -such as this, ColorPy will scale them to the brightest color, of the same hue -and saturation, that can be displayed.  In this case, where all the colors -are scaled like this, the resulting RGB values all have similar brightness.  -So, all the colors on this chart are of similar brightness.  However, there -is actually a large range in physical brightness - a 40000 K blackbody is much -brighter than a 10000 K blackbody.  Note how this contrasts with the -situation on the cool blackbody plot, where the intensity of the displayed -colors varied with temperature.  There is a physical variation in intensity -in both cases, but ColorPy can only show this when the colors are in the range +Now consider a high temperature range, 10000 K to 40000 K, shown in the plot +below.  In this situation, the colors are far in excess of that +displayable on the monitor.  A 40000 K blackbody is far far brighter than +any computer monitor!  In fact, the intensity is on the order of 109 times +that of what the monitor can display.  When displaying very bright colors +such as this, ColorPy will scale them to the brightest color, of the same hue +and saturation, that can be displayed.  In this case, where all the colors +are scaled like this, the resulting RGB values all have similar brightness.  +So, all the colors on this chart are of similar brightness.  However, there +is actually a large range in physical brightness - a 40000 K blackbody is much +brighter than a 10000 K blackbody.  Note how this contrasts with the +situation on the cool blackbody plot, where the intensity of the displayed +colors varied with temperature.  There is a physical variation in intensity +in both cases, but ColorPy can only show this when the colors are in the range displayable by the monitor.

@@ -589,61 +589,61 @@

Blackbody Radiation

Example 2 - Rayleigh Scattering

-For a second example, consider Rayleigh scattering.  This concerns the -scattering of light by small particles (much smaller than the wavelength of the -light.)  In this situation, the amount of scattering is inversely -proportional to the fourth power of the wavelength.  To compute the -spectrum resulting from Rayleigh scattering, we need a description of the -original light source, the 'Illuminant', as the power emitted as a function of -wavelength.  Then, the spectrum resulting from Rayleigh scattering is [van de Hulst, +For a second example, consider Rayleigh scattering.  This concerns the +scattering of light by small particles (much smaller than the wavelength of the +light.)  In this situation, the amount of scattering is inversely +proportional to the fourth power of the wavelength.  To compute the +spectrum resulting from Rayleigh scattering, we need a description of the +original light source, the 'Illuminant', as the power emitted as a function of +wavelength.  Then, the spectrum resulting from Rayleigh scattering is [van de Hulst, p. 65]:

Scattered Intensity (λ) = Illuminant (λ) * a * (1 / λ4)

-where a is a proportionality constant.  (In the plots below, we have -arbitrarily taken the constant a so that the value of the Rayleigh scattering +where a is a proportionality constant.  (In the plots below, we have +arbitrarily taken the constant a so that the value of the Rayleigh scattering term at 550 nm is 1.0.)

-Often, the intensity of the illuminant is arbitrary.  The illuminants -provided by ColorPy are scaled so that they have Y = 1.0, which means that they -are about as bright as the monitor at full white.  One obvious choice for -an illuminant is a blackbody.  ColorPy will provide an illuminant for a -blackbody of any temperature you like, with colorpy.illuminants.get_blackbody_illuminant (T_K).  -(Remember that these illuminants are scaled to have a Y = 1.0, rather than the +Often, the intensity of the illuminant is arbitrary.  The illuminants +provided by ColorPy are scaled so that they have Y = 1.0, which means that they +are about as bright as the monitor at full white.  One obvious choice for +an illuminant is a blackbody.  ColorPy will provide an illuminant for a +blackbody of any temperature you like, with colorpy.illuminants.get_blackbody_illuminant (T_K).  +(Remember that these illuminants are scaled to have a Y = 1.0, rather than the (typically) much larger brightness of a true blackbody.)

-A familiar illuminant is the one for a 5778 K blackbody, which approximates the -illumination of the Sun.  The plot below shows the effect of Rayleigh -scattering of this illuminant.  The overall color is sky blue, with the -spectrum concentrated at low wavelengths.  This result is as expected, as -Rayleigh scattering is the basic physical +A familiar illuminant is the one for a 5778 K blackbody, which approximates the +illumination of the Sun.  The plot below shows the effect of Rayleigh +scattering of this illuminant.  The overall color is sky blue, with the +spectrum concentrated at low wavelengths.  This result is as expected, as +Rayleigh scattering is the basic physical reason that the sky is blue.


Figure 11 - Rayleigh scattering of a 5778 K blackbody.  Why the sky is blue.

-But why limit ourselves to the color of our Sun?  With ColorPy, we can do -the same calculations for a blackbody of any temperature we like.  So we -repeat the process, first for a temperature of 3000 K (Proxima Centauri), and -then for a temperature of 11000 K (Rigel).  The plots below show the -results.  If we lived on a planet around Proxima Centauri, the sky would be -nearly white.  On the other hand, if we lived on a planet near Rigel, the +But why limit ourselves to the color of our Sun?  With ColorPy, we can do +the same calculations for a blackbody of any temperature we like.  So we +repeat the process, first for a temperature of 3000 K (Proxima Centauri), and +then for a temperature of 11000 K (Rigel).  The plots below show the +results.  If we lived on a planet around Proxima Centauri, the sky would be +nearly white.  On the other hand, if we lived on a planet near Rigel, the sky would also be blue, but a deeper shade of blue than we have on Earth.


-Figure 12 - Rayleigh scattering of a 3000 K blackbody.  The color of the sky +Figure 12 - Rayleigh scattering of a 3000 K blackbody.  The color of the sky from near Proxima Centauri.


-Figure 13 - Rayleigh scattering of a 11000 K blackbody.  The color of the +Figure 13 - Rayleigh scattering of a 11000 K blackbody.  The color of the sky from near Rigel.

-We can also make a plot for many temperatures, and get a plot of the color of -the sky vs. blackbody illuminant temperature, similar to the plot we made of the -color of the blackbody itself vs. temperature.  The range of sky colors is -about the same as the range of blackbody colors, but the sky colors are bluer +We can also make a plot for many temperatures, and get a plot of the color of +the sky vs. blackbody illuminant temperature, similar to the plot we made of the +color of the blackbody itself vs. temperature.  The range of sky colors is +about the same as the range of blackbody colors, but the sky colors are bluer than the blackbody colors, for any given temperature.


@@ -656,17 +656,17 @@

Example 2 - Rayleigh Scattering

Illuminants

-

ColorPy provides several different 'illuminant' functions that can be used as -light sources.  In addition to the blackbody illuminants, ColorPy provides -the CIE standard Illuminant D65.  Illuminant D65 is intended to provide a -good approximation for normal daylight.  Illuminant D65 is recommended as -the general-purpose, default, illumination for daytime conditions (on Earth only +

ColorPy provides several different 'illuminant' functions that can be used as +light sources.  In addition to the blackbody illuminants, ColorPy provides +the CIE standard Illuminant D65.  Illuminant D65 is intended to provide a +good approximation for normal daylight.  Illuminant D65 is recommended as +the general-purpose, default, illumination for daytime conditions (on Earth only however!)

-

Illuminant D65 is given by a table of intensity vs. wavelength, rather than -a mathematical formula.  ColorPy provides this illuminant, normalized so -that Y = 1.0, as usual.  The spectrum of D65 is shown below.  Note -that the overall color is white.  In fact, D65 is used as the white point -for determining the xyz to rgb conversion matrix, so D65 is in fact the very +

Illuminant D65 is given by a table of intensity vs. wavelength, rather than +a mathematical formula.  ColorPy provides this illuminant, normalized so +that Y = 1.0, as usual.  The spectrum of D65 is shown below.  Note +that the overall color is white.  In fact, D65 is used as the white point +for determining the xyz to rgb conversion matrix, so D65 is in fact the very definition of white!


@@ -678,22 +678,22 @@

Illuminants

Example 3 - Thin Film Interference

-In this example, we will calculate the colors produced by interference films, -such as a soap bubble, or an oil slick on water.  This time, we will use +In this example, we will calculate the colors produced by interference films, +such as a soap bubble, or an oil slick on water.  This time, we will use D65 as the illuminant.

-The physical situation can be described by illumination from above, passing -through a dielectric medium with some index of refraction n1.  As the wave -propagates, it meets an interface where the index of refraction changes to a new -value n2.  At the interface, part of the original wave is reflected, and -part continues into the new medium.  The material n2 is considered to be in -a thin layer.  After passing through this thin layer, the wave meets a -second interface, where the index of refraction changes from n2 to n3.  -Again, part of the incident wave is reflected from the interface, and part +The physical situation can be described by illumination from above, passing +through a dielectric medium with some index of refraction n1.  As the wave +propagates, it meets an interface where the index of refraction changes to a new +value n2.  At the interface, part of the original wave is reflected, and +part continues into the new medium.  The material n2 is considered to be in +a thin layer.  After passing through this thin layer, the wave meets a +second interface, where the index of refraction changes from n2 to n3.  +Again, part of the incident wave is reflected from the interface, and part continues to propagate.

-We will assume that the regions where the index is n1 and n3 are infinite in -extent, while the region where the index is n2 is limited to a thin layer, with +We will assume that the regions where the index is n1 and n3 are infinite in +extent, while the region where the index is n2 is limited to a thin layer, with thickness t.

Some typical indices of refraction of real materials are:
@@ -703,70 +703,70 @@

Example 3 - Thin Film Interference

n = 1.44   - Oil
n = 1.50   - Glass

-The total reflected wave from the system is a combination of the wave reflected -from the first interface (n1 to n2), and the wave reflected from the second +The total reflected wave from the system is a combination of the wave reflected +from the first interface (n1 to n2), and the wave reflected from the second interface (n2 to n3).

-There may be multiple reflections - e.g. the wave reflected from -the second interface will not fully pass through the (now reversed) interface -from n2 to n1, rather only part will pass, while some will reflect again and -head back to the interface from n2 to n3.  The calculations in ColorPy +There may be multiple reflections - e.g. the wave reflected from +the second interface will not fully pass through the (now reversed) interface +from n2 to n1, rather only part will pass, while some will reflect again and +head back to the interface from n2 to n3.  The calculations in ColorPy consider all numbers of multiple reflections, not just a single reflection.

-What makes the interesting colors, is that the two waves travel through a -different path length, and this results in them being out of phase.  The -exact change in phase depends on both the wavelength of the light, the thickness -of the layer, and the index of refraction of the layer.  Depending on the -specifics, the two waves may constructively interfere, resulting in a large -amplitude of the reflected wave, or the waves may destructively interfere, -resulting in a small (or zero) amplitude of the reflected wave, or something in +What makes the interesting colors, is that the two waves travel through a +different path length, and this results in them being out of phase.  The +exact change in phase depends on both the wavelength of the light, the thickness +of the layer, and the index of refraction of the layer.  Depending on the +specifics, the two waves may constructively interfere, resulting in a large +amplitude of the reflected wave, or the waves may destructively interfere, +resulting in a small (or zero) amplitude of the reflected wave, or something in between.

-Whether the interference is constructive or destructive depends on the -wavelength, and for thin films, part of the spectrum will be reduced from -destructive interference, and part enhanced from constructive interference, +Whether the interference is constructive or destructive depends on the +wavelength, and for thin films, part of the spectrum will be reduced from +destructive interference, and part enhanced from constructive interference, resulting in a significant color shift.

-First, consider a soap bubble.  In this situation, material 1 is air, -material 2 is a solution of soap in water, while material 3 is air again.  -(The inside of the bubble.)  So, n1 = 1.003, n2 = 1.33, and n3 = 1.003.  -Calculating the color of the total reflection, with an illuminant of D65, as a -function of the thickness of the layer, results in the plot below.  Note +First, consider a soap bubble.  In this situation, material 1 is air, +material 2 is a solution of soap in water, while material 3 is air again.  +(The inside of the bubble.)  So, n1 = 1.003, n2 = 1.33, and n3 = 1.003.  +Calculating the color of the total reflection, with an illuminant of D65, as a +function of the thickness of the layer, results in the plot below.  Note that the RGB components oscillate as the thickness is varied. 

-The phase relationship between the red, green and blue components affects the +The phase relationship between the red, green and blue components affects the resulting color.

-For the most -part, these stay within the displayable range of 0.0 to 1.0, but there are a few -places where the red component becomes negative, in the most vivid green -regions.  These vivid green colors cannot be properly displayed on the -monitor, the true color is more saturated than what is shown.  Most of the +For the most +part, these stay within the displayable range of 0.0 to 1.0, but there are a few +places where the red component becomes negative, in the most vivid green +regions.  These vivid green colors cannot be properly displayed on the +monitor, the true color is more saturated than what is shown.  Most of the other colors can be displayed properly.


Figure 16 - Color of reflections from a soap bubble.  The illuminant is D65.

-For a particular thickness of the film, we can plot the reflectance spectrum.  -The plots below show the spectrum for some of the particularly vivid colors, for -thicknesses (not wavelengths!) of 400 nm and 500 nm.  For these plots, we -used an illuminant that is constant over wavelength, rather than D65.  The -only reason is to avoid the jaggedness of the D65 curve from making the plot +For a particular thickness of the film, we can plot the reflectance spectrum.  +The plots below show the spectrum for some of the particularly vivid colors, for +thicknesses (not wavelengths!) of 400 nm and 500 nm.  For these plots, we +used an illuminant that is constant over wavelength, rather than D65.  The +only reason is to avoid the jaggedness of the D65 curve from making the plot more confusing.


-Figure 17 - Spectrum of soap bubble reflection for a thickness of 400 nm.  +Figure 17 - Spectrum of soap bubble reflection for a thickness of 400 nm.  The illuminant is constant over wavelength.


-Figure 18 - Spectrum of soap bubble reflection for a thickness of 500 nm.  +Figure 18 - Spectrum of soap bubble reflection for a thickness of 500 nm.  The illuminant is constant over wavelength.

-We can do the same thing for an oil slick floating on water.  In this case, -medium 1 is air (n = 1.003), medium 2 is oil (n = 1.44), and medium 3 is water -(n = 1.33).  The result is shown below.  Note that the colors are not -as saturated and vivid as for the reflection from a soap bubble.  Since -these colors are less saturated than the soap bubble reflections, all of them +We can do the same thing for an oil slick floating on water.  In this case, +medium 1 is air (n = 1.003), medium 2 is oil (n = 1.44), and medium 3 is water +(n = 1.33).  The result is shown below.  Note that the colors are not +as saturated and vivid as for the reflection from a soap bubble.  Since +these colors are less saturated than the soap bubble reflections, all of them are properly displayable on the computer.

@@ -779,44 +779,44 @@

Example 3 - Thin Film Interference

(Nearly) Perceptually Uniform Color Spaces - Luv and Lab

-ColorPy also provides some color manipulation functions, that are not directly -related to physical color calculations.  The most important of these are -routines to convert colors into the nearly perceptually uniform color spaces Luv +ColorPy also provides some color manipulation functions, that are not directly +related to physical color calculations.  The most important of these are +routines to convert colors into the nearly perceptually uniform color spaces Luv and Lab.

-The common color spaces rgb and xyz are not very perceptually uniform.  -This means that, if you calculate the distance between two colors +The common color spaces rgb and xyz are not very perceptually uniform.  +This means that, if you calculate the distance between two colors C1 and C2, using the usual Euclidean metric, namely:

-Distance = √ ((Red2 - Red1)2 + -(Green2 - Green1)2 + +Distance = √ ((Red2 - Red1)2 + +(Green2 - Green1)2 + (Blue2 - Blue1)2)

-the calculated distance values do not agree well with the psychological apparent -differences in the colors.  I.e., the mind may see two colors as very -different, but where they have a small distance, or alternately the mind may see -two colors as similar, but where they have a large color distance.  This -causes difficulties in calculating the 'closest color'.  (Since xyz and rgb -values are linearly related, the same distance issues apply to both of those +the calculated distance values do not agree well with the psychological apparent +differences in the colors.  I.e., the mind may see two colors as very +different, but where they have a small distance, or alternately the mind may see +two colors as similar, but where they have a large color distance.  This +causes difficulties in calculating the 'closest color'.  (Since xyz and rgb +values are linearly related, the same distance issues apply to both of those spaces.)

-The color spaces Luv and Lab are designed to be a much more perceptually uniform -color space than rgb or xyz.  If colors are transformed into Luv or Lab -space, then mathematical distance calculations, using the Euclidean metric, on -Luv and Lab values will provide a much better assessment of how different the +The color spaces Luv and Lab are designed to be a much more perceptually uniform +color space than rgb or xyz.  If colors are transformed into Luv or Lab +space, then mathematical distance calculations, using the Euclidean metric, on +Luv and Lab values will provide a much better assessment of how different the colors appear.

-These perceptually uniform color spaces are not perfect.  (The fact -that there are two of them, is a clear sign that they are imperfect.)  -However, they should do a substantially better job in measuring the apparent +These perceptually uniform color spaces are not perfect.  (The fact +that there are two of them, is a clear sign that they are imperfect.)  +However, they should do a substantially better job in measuring the apparent differences in colors.

-ColorPy provides routines to transform color values from xyz into both Luv and -Lab, and also routines to convert back to xyz.  Coupled with the -conversions between xyz and rgb, one can convert between any of the desired -color spaces.  Most descriptions of the Luv and Lab models only provide the -transformation from xyz to Luv and Lab, but ColorPy also provides the inverses.  +ColorPy provides routines to transform color values from xyz into both Luv and +Lab, and also routines to convert back to xyz.  Coupled with the +conversions between xyz and rgb, one can convert between any of the desired +color spaces.  Most descriptions of the Luv and Lab models only provide the +transformation from xyz to Luv and Lab, but ColorPy also provides the inverses.  The conversion routines are:

colorpy.colormodels.luv_from_xyz (xyz)
@@ -824,19 +824,19 @@

(Nearly) Perceptually Uniform Color Spaces - Luv and Lab

colorpy.colormodels.lab_from_xyz (xyz)
colorpy.colormodels.xyz_from_lab (Lab)

-The Luv and Lab conversions depend on the definition of the white point.  -By default, the white point used in specifying the rgb to xyz conversions is -used, this is CIE D65 by default.  You can change this, by passing the +The Luv and Lab conversions depend on the definition of the white point.  +By default, the white point used in specifying the rgb to xyz conversions is +used, this is CIE D65 by default.  You can change this, by passing the desired chromaticity of the white point, to:

colorpy.colormodels.init_Luv_Lab_white_point (white_point)

-One potential application of these spaces, would be an improvement in the -color clipping algorithm.  When ColorPy needs to clip an undisplayable -color value (with rgb values either negative or greater than 1.0), the best -action would probably be to choose the displayable color that is perceptually -closest to the desired color.  If this was done with these color spaces, -rather than the current clipping algorithm, a better selection of out-of-gamut +One potential application of these spaces, would be an improvement in the +color clipping algorithm.  When ColorPy needs to clip an undisplayable +color value (with rgb values either negative or greater than 1.0), the best +action would probably be to choose the displayable color that is perceptually +closest to the desired color.  If this was done with these color spaces, +rather than the current clipping algorithm, a better selection of out-of-gamut colors might result.


@@ -859,8 +859,8 @@

Download ColorPy

Installation:

-If you are installing from the Windows binary distribution, all you need to do -is double-click the executable, and follow the installation prompts.  +If you are installing from the Windows binary distribution, all you need to do +is double-click the executable, and follow the installation prompts.  Otherwise, you must first unpack the distribution, and then install.

@@ -868,9 +868,9 @@

Download ColorPy

Windows -
-Unzip the .zip distribution. Recent versions of Windows (XP or later), will -unpack the directory automatically, you can simply enter the directory in -Windows Explorer. You will probably need to copy the uncompressed files into +Unzip the .zip distribution. Recent versions of Windows (XP or later), will +unpack the directory automatically, you can simply enter the directory in +Windows Explorer. You will probably need to copy the uncompressed files into another directory.

@@ -888,13 +888,13 @@

Download ColorPy

    python setup.py install

-It is possible that you may need to supply a path to the Python executable.  +It is possible that you may need to supply a path to the Python executable.  You will probably need administrator privileges to do this.  This should complete the installation.

-After downloading and installing, I recommend that you run the test cases, and -then create the sample figures.  These will provide a check that the module +After downloading and installing, I recommend that you run the test cases, and +then create the sample figures.  These will provide a check that the module is working correctly.

@@ -906,7 +906,7 @@

Download ColorPy

import colorpy.figures
colorpy.figures.figures()

-This will generate the sample figures (typically .png files), including all +This will generate the sample figures (typically .png files), including all those in this documentation, as well as several others.


@@ -924,29 +924,29 @@

Module Reference


The models are:

-xyz - CIE XYZ color space, based on the 1931 matching functions for a 2 degree +xyz - CIE XYZ color space, based on the 1931 matching functions for a 2 degree field of view.
-    Spectra are converted to xyz color values by integrating with the matching +    Spectra are converted to xyz color values by integrating with the matching functions in ciexyz.py.

-    xyz colors are often handled as absolute values, conventionally written with +    xyz colors are often handled as absolute values, conventionally written with uppercase letters XYZ,
-    or as scaled values (so that X+Y+Z = 1.0), conventionally written with lowercase +    or as scaled values (so that X+Y+Z = 1.0), conventionally written with lowercase letters xyz.

This is the fundamental color model around which all others are based.

-rgb - Colors expressed as red, green and blue values, in the nominal range 0.0 - +rgb - Colors expressed as red, green and blue values, in the nominal range 0.0 - 1.0.
-    These are linear color values, meaning that doubling the number implies a +    These are linear color values, meaning that doubling the number implies a doubling of the light intensity.
-    rgb color values may be out of range (greater than 1.0, or negative), and do not +    rgb color values may be out of range (greater than 1.0, or negative), and do not account for gamma correction.
    They should not be drawn directly.

-irgb - Displayable color values expressed as red, green and blue values, in the +irgb - Displayable color values expressed as red, green and blue values, in the range 0 - 255.
-    These have been adjusted for gamma correction, and have been clipped into the +    These have been adjusted for gamma correction, and have been clipped into the displayable range 0 - 255.
    These color values can be drawn directly.

@@ -955,7 +955,7 @@

Module Reference

Lab - Another nearly perceptually uniform color space.

As far as I know, the Luv and Lab spaces are of similar quality.
-Neither is perfect, so perhaps try each, and see what works best for your +Neither is perfect, so perhaps try each, and see what works best for your application.

The models store color values as 3-element NumPy vectors.
@@ -1029,11 +1029,11 @@

Module Reference

    Convert a color hex string (like '#AB13D2') into a displayable irgb color.

irgb_from_rgb (rgb) -
-    Convert a (linear) rgb value (range 0.0 - 1.0) into a 0-255 displayable integer +    Convert a (linear) rgb value (range 0.0 - 1.0) into a 0-255 displayable integer irgb value (range 0 - 255).

rgb_from_irgb (irgb) -
-    Convert a displayable (gamma corrected) irgb value (range 0 - 255) into a linear +    Convert a displayable (gamma corrected) irgb value (range 0 - 255) into a linear rgb value (range 0.0 - 1.0).

irgb_string_from_rgb (rgb) -
@@ -1082,7 +1082,7 @@

Module Reference

    irgb color with values in the range (0 - 255), clipping as necessary.

    The return value is a tuple, the first element is the clipped irgb color,
-    and the second element is a tuple indicating which (if any) clipping processes +    and the second element is a tuple indicating which (if any) clipping processes were used.

Initialization functions:
@@ -1094,7 +1094,7 @@

Module Reference

white_point = SRGB_White)
-

    Setup the conversions between CIE XYZ and linear RGB spaces.
-    Also do other initializations (gamma, conversions with Luv and Lab spaces, +    Also do other initializations (gamma, conversions with Luv and Lab spaces, clipping model).
    The default arguments correspond to the sRGB standard RGB space.
    The conversion is defined by supplying the chromaticities of each of
@@ -1134,15 +1134,15 @@

Module Reference


References:

-Foley, van Dam, Feiner and Hughes. Computer Graphics: Principles and Practice, +Foley, van Dam, Feiner and Hughes. Computer Graphics: Principles and Practice, 2nd edition,
    Addison Wesley Systems Programming Series, 1990. ISBN 0-201-12110-7.

-Roy Hall, Illumination and Color in Computer Generated Imagery. Monographs in +Roy Hall, Illumination and Color in Computer Generated Imagery. Monographs in Visual Communication,
    Springer-Verlag, New York, 1989. ISBN 0-387-96774-5.

-Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and +Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and Formulae, 2nd edition,
    John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN 0-471-39918-3.

@@ -1156,7 +1156,7 @@

Module Reference


sRGB - http://www.color.org/sRGB.xalter - (accessed 15 Sep 2008)
    A Standard Default Color Space for the Internet: sRGB,
-    Michael Stokes (Hewlett-Packard), Matthew Anderson (Microsoft), Srinivasan +    Michael Stokes (Hewlett-Packard), Matthew Anderson (Microsoft), Srinivasan Chandrasekar (Microsoft),
    Ricardo Motta (Hewlett-Packard), Version 1.10, November 5, 1996.

@@ -1164,48 +1164,48 @@

Module Reference


-colorpy.ciexyz.py - Spectral response curves for 1931 CIE XYZ 2 degree field of view +colorpy.ciexyz.py - Spectral response curves for 1931 CIE XYZ 2 degree field of view matching functions.

Description:

This module provides the CIE standard XYZ color matching functions.
-The 1931 tabulation, for a 2 degree field of view, is used in preference to the +The 1931 tabulation, for a 2 degree field of view, is used in preference to the 10 degree 1964 set,
as is conventional in computer graphics.

-The matching functions are stored internally at 1 nm increments, and linear +The matching functions are stored internally at 1 nm increments, and linear interpolation is
used for any wavelength in between.

ColorPy attempts to scale the matching functions so that:
-A spectrum, constant with wavelength, over the range 360 nm to 830 nm, with a +A spectrum, constant with wavelength, over the range 360 nm to 830 nm, with a total intensity
-equal to the (assumed) physical intensity of the monitor, will sample with Y = +equal to the (assumed) physical intensity of the monitor, will sample with Y = 1.0.

-This scaling corresponds with that in colormodels.py, which assumes Y = 1.0 at +This scaling corresponds with that in colormodels.py, which assumes Y = 1.0 at full white.

-NOTE - I suspect that the scaling is not quite correct. I think it is at least +NOTE - I suspect that the scaling is not quite correct. I think it is at least close.

-Ideally, we would like the spectrum of the actual monitor display, at full +Ideally, we would like the spectrum of the actual monitor display, at full white, which is not
independent of wavelength, to sample to Y = 1.0.

Constants and Functions:

-start_wl_nm, end_wl_nm - Default starting and ending range of wavelengths, in +start_wl_nm, end_wl_nm - Default starting and ending range of wavelengths, in nm, as integers.
delta_wl_nm - Default wavelength spacing, in nm, as a float.

-DEFAULT_DISPLAY_INTENSITY - Default assumed intensity of monitor display, in +DEFAULT_DISPLAY_INTENSITY - Default assumed intensity of monitor display, in W/m^2

init (monitor_intensity = DEFAULT_DISPLAY_INTENSITY)
-    Initialization of color matching curves. Called at module startup with default +    Initialization of color matching curves. Called at module startup with default arguments.
    This can be called again to change the assumed display intensity.

@@ -1214,7 +1214,7 @@

Module Reference

    This is a 2D numpy array, with one row for each wavelength in the visible range,
    360 nm to 830 nm, with a spacing of delta_wl_nm (1.0 nm), and two columns.
    The first column is filled with the wavelength [nm].
-    The second column is filled with 0.0. It should later be filled with the +    The second column is filled with 0.0. It should later be filled with the intensity.
    The result can be passed to xyz_from_spectrum() to convert to an xyz color.

@@ -1233,24 +1233,24 @@

Module Reference

num_purples = 0,
dwl_angstroms = 10)
    Get an array of xyz colors covering the visible spectrum.
-    Optionally add a number of 'purples', which are colors interpolated between the +    Optionally add a number of 'purples', which are colors interpolated between the color
    of the lowest wavelength (violet) and the highest (red).
-    brightness - Desired maximum rgb component of each color. Default 1.0. (Maxiumum +    brightness - Desired maximum rgb component of each color. Default 1.0. (Maxiumum displayable brightness)
-    num_purples - Number of colors to interpolate in the 'purple' range. Default 0. +    num_purples - Number of colors to interpolate in the 'purple' range. Default 0. (No purples)
-    dwl_angstroms - Wavelength separation, in angstroms (0.1 nm). Default 10 A. (1 +    dwl_angstroms - Wavelength separation, in angstroms (0.1 nm). Default 10 A. (1 nm spacing)

References:

-Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and +Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and Formulae,
-2nd edition, John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN +2nd edition, John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN 0-471-39918-3.

-CVRL Color and Vision Database - http://cvrl.ioo.ucl.ac.uk/index.htm - (accessed +CVRL Color and Vision Database - http://cvrl.ioo.ucl.ac.uk/index.htm - (accessed 17 Sep 2008)
Color and Vision Research Laboratories.
Provides a set of data sets related to color vision.
@@ -1259,15 +1259,15 @@

Module Reference


CIE Standards - http://cvrl.ioo.ucl.ac.uk/cie.htm - (accessed 17 Sep 2008)
CIE standards as maintained by CVRL.
-The 1931 CIE XYZ and D65 tables that ColorPy uses were obtained from the +The 1931 CIE XYZ and D65 tables that ColorPy uses were obtained from the following files, linked here:
http://cvrl.ioo.ucl.ac.uk/database/data/cmfs/ciexyz31_1.txt
http://cvrl.ioo.ucl.ac.uk/database/data/cie/Illuminantd65.txt

-CIE International Commission on Illumination - http://www.cie.co.at/ - (accessed +CIE International Commission on Illumination - http://www.cie.co.at/ - (accessed 17 Sep 2008)
Official website of the CIE.
-There are tables of the standard functions (matching functions, illuminants) +There are tables of the standard functions (matching functions, illuminants) here:
http://www.cie.co.at/main/freepubs.html
http://www.cie.co.at/publ/abst/datatables15_2004/x2.txt
@@ -1317,7 +1317,7 @@

Module Reference

    Get the spectrum of a blackbody at the given temperature, normalized to Y = 1.0.

get_constant_illuminant () -
-    Get an illuminant, with spectrum constant over wavelength, normalized to Y = +    Get an illuminant, with spectrum constant over wavelength, normalized to Y = 1.0.

scale_illuminant (illuminant, scaling) -
@@ -1325,12 +1325,12 @@

Module Reference


References:

-Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and +Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and Formulae,
-2nd edition, John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN +2nd edition, John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN 0-471-39918-3.

-CVRL Color and Vision Database - http://cvrl.ioo.ucl.ac.uk/index.htm - (accessed +CVRL Color and Vision Database - http://cvrl.ioo.ucl.ac.uk/index.htm - (accessed 17 Sep 2008)
Color and Vision Research Laboratories.
Provides a set of data sets related to color vision.
@@ -1339,15 +1339,15 @@

Module Reference


CIE Standards - http://cvrl.ioo.ucl.ac.uk/cie.htm - (accessed 17 Sep 2008)
CIE standards as maintained by CVRL.
-The 1931 CIE XYZ and D65 tables that ColorPy uses were obtained from the +The 1931 CIE XYZ and D65 tables that ColorPy uses were obtained from the following files, linked here:
http://cvrl.ioo.ucl.ac.uk/database/data/cmfs/ciexyz31_1.txt
http://cvrl.ioo.ucl.ac.uk/database/data/cie/Illuminantd65.txt

-CIE International Commission on Illumination - http://www.cie.co.at/ - (accessed +CIE International Commission on Illumination - http://www.cie.co.at/ - (accessed 17 Sep 2008)
Official website of the CIE.
-There are tables of the standard functions (matching functions, illuminants) +There are tables of the standard functions (matching functions, illuminants) here:
http://www.cie.co.at/main/freepubs.html
http://www.cie.co.at/publ/abst/datatables15_2004/x2.txt
@@ -1373,7 +1373,7 @@

Module Reference

    between y0 and y1. The first value will be y0, the last y1.

tighten_x_axis (x_list) -
-    Tighten the x axis (only) of the current plot to match the given range of x +    Tighten the x axis (only) of the current plot to match the given range of x values.
    The y axis limits are not affected.

@@ -1401,10 +1401,10 @@

Module Reference

    Plot a spectrum, with x-axis the wavelength, and y-axis the intensity.
    The curve is colored at that wavelength by the (approximate) color of a
    pure spectral color at that wavelength, with intensity constant over wavelength.
-    (This means that dark looking colors here mean that wavelength is poorly viewed +    (This means that dark looking colors here mean that wavelength is poorly viewed by the eye.
    This is not a complete plotting function, e.g. no file is saved, etc.
-    It is assumed that this function is being called by one that handles those +    It is assumed that this function is being called by one that handles those things.

spectrum_plot (
@@ -1462,7 +1462,7 @@

Module Reference

    Plot the CIE XYZ matching functions, as three spectral subplots.

shark_fin_plot () -
-    Draw the 'shark fin' CIE chromaticity diagram of the pure spectral lines (plus +    Draw the 'shark fin' CIE chromaticity diagram of the pure spectral lines (plus purples) in xy space.

@@ -1524,7 +1524,7 @@

Module Reference


Description:

-Calculation of the scattering by very small particles (compared to the +Calculation of the scattering by very small particles (compared to the wavelength).
Also known as Rayleigh scattering.
The scattering intensity is proportional to 1/wavelength^4.
@@ -1539,7 +1539,7 @@

Module Reference

    The scattering is scaled so that the factor for wl_nm = 555.0 is 1.0.

rayleigh_scattering_spectrum () -
-    Get the Rayleigh scattering spectrum (independent of illuminant), as a numpy +    Get the Rayleigh scattering spectrum (independent of illuminant), as a numpy array.

rayleigh_illuminated_spectrum (illuminant) -
@@ -1554,7 +1554,7 @@

Module Reference

    Make a patch plot of the Rayleigh scattering color for each illuminant.

rayleigh_color_vs_illuminant_temperature_plot (T_list, title, filename) -
-    Make a plot of the Rayleigh scattered color vs. temperature of blackbody +    Make a plot of the Rayleigh scattered color vs. temperature of blackbody illuminant.

rayleigh_spectrum_plot (illuminant, title, filename) -
@@ -1573,17 +1573,17 @@

Module Reference


Description:

-Reflection from a thin film, as a function of wavelength, thickness, and index +Reflection from a thin film, as a function of wavelength, thickness, and index of refraction of materials.

-Note that film thicknesses are given in nm instead of m, as this is a more +Note that film thicknesses are given in nm instead of m, as this is a more convenient unit in this case.

We consider incident light from a medium of index of refraction n1,
-striking a thin film of index n2, with a third medium of index n3 behind the +striking a thin film of index n2, with a third medium of index n3 behind the film.

-The total reflection from the film, back towards the incident light, is +The total reflection from the film, back towards the incident light, is calculated.

Some sample values of the index of refraction:
@@ -1618,16 +1618,16 @@

Module Reference


Plots:

-thinfilm_patch_plot (n1, n2, n3, thickness_nm_list, illuminant, title, filename) +thinfilm_patch_plot (n1, n2, n3, thickness_nm_list, illuminant, title, filename) -
    Make a patch plot of the color of the film for each thickness [nm].

-thinfilm_color_vs_thickness_plot (n1, n2, n3, thickness_nm_list, illuminant, +thinfilm_color_vs_thickness_plot (n1, n2, n3, thickness_nm_list, illuminant, title, filename) -
    Plot the color of the thin film for the specfied thicknesses [nm].

thinfilm_spectrum_plot (n1, n2, n3, thickness_nm, illuminant, title, filename) -
-    Plot the spectrum of the reflection from a thin film for the given thickness +    Plot the spectrum of the reflection from a thin film for the given thickness [nm].

References:
@@ -1636,7 +1636,7 @@

Module Reference

McGraw-Hill Book Company, 1968. Library of Congress 64-66016.

M. Minnaert, The nature of light and color in the open air,
-translation H.M. Kremer-Priest, Dover Publications, New York, 1954. ISBN +translation H.M. Kremer-Priest, Dover Publications, New York, 1954. ISBN 486-20196-1. p. 208-209.

@@ -1649,21 +1649,21 @@

Module Reference


Some miscellaneous plots.

-colorstring_patch_plot (colorstrings, color_names, title, filename, num_across=6) +colorstring_patch_plot (colorstrings, color_names, title, filename, num_across=6) -
    Color patch plot for colors specified as hex strings.

MacBeth_ColorChecker_patch_plot () -
    MacBeth ColorChecker Chart.
-    The xyz values are from Hall p. 119. I do not know for what lighting conditions +    The xyz values are from Hall p. 119. I do not know for what lighting conditions this applies.

chemical_solutions_patch_plot () -
    Colors of some chemical solutions.
-    Darren L. Williams et. al., 'Beyond lambda-max: Transforming Visible Spectra +    Darren L. Williams et. al., 'Beyond lambda-max: Transforming Visible Spectra into 24-bit Color Values'.
    Journal of Chemical Education, Vol 84, No 11, Nov 2007, p1873-1877.
-    A student laboratory experiment to measure the transmission spectra of some +    A student laboratory experiment to measure the transmission spectra of some common chemical solutions,
    and determine the rgb values.

@@ -1672,9 +1672,9 @@

Module Reference

    Karl Glazebrook and Ivan Baldry
    http://www.pha.jhu.edu/~kgb/cosspec/ (accessed 17 Sep 2008)
    The color of the sum of all light in the universe.
-    This originally caused some controversy when the (correct) xyz color was incorrectly +    This originally caused some controversy when the (correct) xyz color was incorrectly reported as light green.
-    The authors also consider several other white points, here we just use the +    The authors also consider several other white points, here we just use the default (normally D65).

spectral_colors_patch_plot () -
@@ -1684,12 +1684,12 @@

Module Reference

    Colors of the pure spectral lines plus purples.

perceptually_uniform_spectral_colors () -
-    Patch plot of (nearly) perceptually equally spaced colors, covering the pure +    Patch plot of (nearly) perceptually equally spaced colors, covering the pure spectral lines plus purples.

spectral_line_555nm_plot () -
    Plot a spectrum that has mostly only a line at 555 nm.
-    It is widened a bit only so the plot looks nicer, otherwise the black curve +    It is widened a bit only so the plot looks nicer, otherwise the black curve covers up the color.

@@ -1702,7 +1702,7 @@

Module Reference


Creates the sample figures.

-This can also create the figures with some non-default initialization +This can also create the figures with some non-default initialization conditions.

Functions:
@@ -1738,40 +1738,40 @@

Issues

-ColorPy is certainly not perfect.  Some of the problems it likely has, and +ColorPy is certainly not perfect.  Some of the problems it likely has, and so possible avenues for future improvements, are as follows:

-I am not sure that the (assumed) physical luminance of the monitor display is -correct.  I am pretty sure that it is close, but things are confusing -enough that this may not be quite right.  In most cases, this should not -matter, as the intensities are typically scaled by an arbitrary factor.  In -any case, attempting to scale to the physical display brightness might not be -the best course of action in many cases.  For example, for a plot of very -bright colors (such as hot blackbodies), all the colors are clamped to the +I am not sure that the (assumed) physical luminance of the monitor display is +correct.  I am pretty sure that it is close, but things are confusing +enough that this may not be quite right.  In most cases, this should not +matter, as the intensities are typically scaled by an arbitrary factor.  In +any case, attempting to scale to the physical display brightness might not be +the best course of action in many cases.  For example, for a plot of very +bright colors (such as hot blackbodies), all the colors are clamped to the maximum brightness, when there is a large range of luminance in the data.

-There are many places where the Python code is not well 'vectorized', that is, a -single Python call might be able to operate on an entire spectrum, for example, -but the current code requires a Python call for each wavelength.  This will -certainly degrade performance, and ColorPy is definitely slower than the +There are many places where the Python code is not well 'vectorized', that is, a +single Python call might be able to operate on an entire spectrum, for example, +but the current code requires a Python call for each wavelength.  This will +certainly degrade performance, and ColorPy is definitely slower than the original C++ code.  Still, I think the performance is acceptable.

-The default gamma correction may not be ideal for LCD displays.  As these -are getting more and more common, this is becoming the most important case, +The default gamma correction may not be ideal for LCD displays.  As these +are getting more and more common, this is becoming the most important case, rather than CRT displays.

-The color clipping method can probably be improved, it is likely that the Luv +The color clipping method can probably be improved, it is likely that the Luv and Lab color models could help with this.

-There are some standard illuminants (especially D55 and D75) that would be nice +There are some standard illuminants (especially D55 and D75) that would be nice to add.

-ColorPy does not have support for HSV (Hue-Saturation-Value) and HLS -(Hue-Lightness-Saturation) color models.  These are not particularly -relevant for physically based color modeling, but they are reasonably common in +ColorPy does not have support for HSV (Hue-Saturation-Value) and HLS +(Hue-Lightness-Saturation) color models.  These are not particularly +relevant for physically based color modeling, but they are reasonably common in the computing world, and it would be nice to add this support for that reason.

-There are also surely bugs that I have not found, and also things that could be +There are also surely bugs that I have not found, and also things that could be more conveniently arranged for many uses.

@@ -1796,7 +1796,7 @@

Literature References

Wyszecki - Wyszecki and Stiles, Color Science: Concepts and Methods, Quantitative Data and Formulae, 2nd edition, John Wiley, 1982. Wiley Classics Library Edition 2000. ISBN 0-471-39918-3.

-

Judd - Judd and Wyszecki, Color in Business, Science and +

Judd - Judd and Wyszecki, Color in Business, Science and Industry, 1975.

Waves - Frank S. Crawford, Jr., Waves: Berkeley Physics Course - Volume 3, @@ -1808,14 +1808,14 @@

Literature References

Minnaert - M. Minnaert, The nature of light and color in the open air, translation H.M. Kremer-Priest, Dover Publications, New York, 1954. ISBN 486-20196-1.

-

Kasson - Kasson and Plouffe, An Analysis of Selected Computer Interchange Color +

Kasson - Kasson and Plouffe, An Analysis of Selected Computer Interchange Color Spaces, ACM Transactions on Graphics, Vol. 11, No. 4, October 1992.

Poynton - Frequently asked questions about Gamma and Color, posted to comp.graphics.algorithms, 25 Jan 1995.

-sRGB - http://www.color.org/sRGB.xalter - +sRGB - http://www.color.org/sRGB.xalter - (accessed 15 Sep 2008)
'A Standard Default Color Space for the Internet: sRGB'., Michael Stokes (Hewlett-Packard), Matthew Anderson (Microsoft), @@ -1824,16 +1824,16 @@

Literature References

-CVRL Color and Vision Database - +CVRL Color and Vision Database - http://cvrl.ioo.ucl.ac.uk/index.htm - (accessed 17 Sep 2008)
Color and Vision Research Laboratories. Provides a set of data sets related to color vision. ColorPy uses the tables from this site for the 1931 CIE XYZ matching functions, and for Illuminant D65, both at 1 nm wavelength increments.

- +

-CIE Standards maintained by CVRL - +CIE Standards maintained by CVRL - http://cvrl.ioo.ucl.ac.uk/cie.htm - (accessed 17 Sep 2008)
The 1931 CIE XYZ and D65 tables that ColorPy uses were obtained from the following files, linked here:
http://cvrl.ioo.ucl.ac.uk/database/data/cmfs/ciexyz31_1.txt
@@ -1841,7 +1841,7 @@

Literature References

-CIE International Commission on Illumination - +CIE International Commission on Illumination - http://www.cie.co.at/ - (accessed 17 Sep 2008)
Official website of the CIE. There are tables of the standard functions (matching functions, illuminants) here:
@@ -1858,7 +1858,7 @@

Literature References

Karl Glazebrook and Ivan Baldry - Average color of the light in the universe.

-

Williams et. al. - +

Williams et. al. - Darren L. Williams et. al., 'Beyond lambda-max: Transforming Visible Spectra into 24-bit Color Values'.
Journal of Chemical Education, Vol 84, No 11, Nov 2007, p1873-1877.
A student laboratory experiment to measure the transmission spectra of some common chemical solutions, @@ -1867,4 +1867,4 @@

Literature References

- \ No newline at end of file + diff --git a/colorpy/blackbody.py b/colorpy/blackbody.py index 7909fcd..86871c2 100644 --- a/colorpy/blackbody.py +++ b/colorpy/blackbody.py @@ -104,7 +104,7 @@ def blackbody_spectrum (T_K): '''Get the spectrum of a blackbody, as a numpy array.''' spectrum = ciexyz.empty_spectrum() (num_rows, num_cols) = spectrum.shape - for i in xrange (0, num_rows): + for i in range (0, num_rows): specific_intensity = blackbody_specific_intensity (spectrum [i][0], T_K) # scale by size of wavelength interval spectrum [i][1] = specific_intensity * ciexyz.delta_wl_nm * 1.0e-9 @@ -135,7 +135,7 @@ def blackbody_color_vs_temperature_plot (T_list, title, filename): '''Draw a color vs temperature plot for the given temperature range.''' num_T = len (T_list) rgb_list = numpy.empty ((num_T, 3)) - for i in xrange (0, num_T): + for i in range (0, num_T): T_i = T_list [i] xyz = blackbody_color (T_i) rgb_list [i] = colormodels.rgb_from_xyz (xyz) diff --git a/colorpy/ciexyz.py b/colorpy/ciexyz.py index da554d7..8c23d6e 100644 --- a/colorpy/ciexyz.py +++ b/colorpy/ciexyz.py @@ -653,13 +653,13 @@ def init (display_intensity = DEFAULT_DISPLAY_INTENSITY): _wavelengths [create_table_size-1] = end_wl_nm + 1 _xyz_colors [create_table_size-1] = colormodels.xyz_color (0.0, 0.0, 0.0) # fill in the middle rows from the source data - for i in xrange (0, len (_CIEXYZ_1931_table)): + for i in range (0, len (_CIEXYZ_1931_table)): (wl,x,y,z) = _CIEXYZ_1931_table [i] _wavelengths [i+1] = wl _xyz_colors [i+1] = colormodels.xyz_color (x,y,z) # get the integrals of each curve integral = numpy.zeros (3) - for i in xrange (0, create_table_size-1): + for i in range (0, create_table_size-1): d_integral = 0.5 * (_xyz_colors [i] + _xyz_colors [i+1]) * delta_wl_nm integral += d_integral # scale the sampling curves so that: @@ -672,7 +672,7 @@ def init (display_intensity = DEFAULT_DISPLAY_INTENSITY): scaling = num_wl / (integral [1] * display_intensity) _xyz_colors *= scaling # now calculate all the deltas - for i in xrange (0, create_table_size-1): + for i in range (0, create_table_size-1): _xyz_deltas [i] = _xyz_colors [i+1] - _xyz_colors [i] _xyz_deltas [create_table_size-1] = colormodels.xyz_color (0.0, 0.0, 0.0) @@ -688,10 +688,10 @@ def empty_spectrum (): The result can be passed to xyz_from_spectrum() to convert to an xyz color. ''' - wl_nm_range = xrange (start_wl_nm, end_wl_nm + 1) + wl_nm_range = range (start_wl_nm, end_wl_nm + 1) num_wl = len (wl_nm_range) spectrum = numpy.zeros ((num_wl, 2)) - for i in xrange (0, num_wl): + for i in range (0, num_wl): spectrum [i][0] = float (wl_nm_range [i]) return spectrum @@ -720,7 +720,7 @@ def xyz_from_spectrum (spectrum): assert num_col == 2, 'Expecting 2D array with each row: wavelength [nm], specific intensity [W/unit solid angle]' # integrate rtn = colormodels.xyz_color (0.0, 0.0, 0.0) - for i in xrange (0, num_wl): + for i in range (0, num_wl): wl_nm_i = spectrum [i][0] specific_intensity_i = spectrum [i][1] xyz = xyz_from_wavelength (wl_nm_i) @@ -740,7 +740,7 @@ def get_normalized_spectral_line_colors ( dwl_angstroms - Wavelength separation, in angstroms (0.1 nm). Default 10 A. (1 nm spacing) ''' # get range of wavelengths, in angstroms, so that we can have finer resolution than 1 nm - wl_angstrom_range = xrange (10*start_wl_nm, 10*(end_wl_nm + 1), dwl_angstroms) + wl_angstrom_range = range (10*start_wl_nm, 10*(end_wl_nm + 1), dwl_angstroms) # get total point count num_spectral = len (wl_angstrom_range) num_points = num_spectral + num_purples @@ -756,7 +756,7 @@ def get_normalized_spectral_line_colors ( # interpolate from end point to start point (filling in the purples) first_xyz = xyzs [0] last_xyz = xyzs [num_spectral - 1] - for ipurple in xrange (0, num_purples): + for ipurple in range (0, num_purples): t = float (ipurple) / float (num_purples - 1) omt = 1.0 - t xyz = t * first_xyz + omt * last_xyz @@ -764,7 +764,7 @@ def get_normalized_spectral_line_colors ( xyzs [i] = xyz i += 1 # scale each color to have the max rgb component equal to the desired brightness - for i in xrange (0, num_points): + for i in range (0, num_points): rgb = colormodels.brightest_rgb_from_xyz (xyzs [i], brightness) xyzs [i] = colormodels.xyz_from_rgb (rgb) # done @@ -784,7 +784,7 @@ def get_normalized_spectral_line_colors_annotated ( dwl_angstroms - Wavelength separation, in angstroms (0.1 nm). Default 10 A. (1 nm spacing) ''' # get range of wavelengths, in angstroms, so that we can have finer resolution than 1 nm - wl_angstrom_range = xrange (10*start_wl_nm, 10*(end_wl_nm + 1), dwl_angstroms) + wl_angstrom_range = range (10*start_wl_nm, 10*(end_wl_nm + 1), dwl_angstroms) # get total point count num_spectral = len (wl_angstrom_range) num_points = num_spectral + num_purples @@ -803,7 +803,7 @@ def get_normalized_spectral_line_colors_annotated ( # interpolate from end point to start point (filling in the purples) first_xyz = xyzs [0] last_xyz = xyzs [num_spectral - 1] - for ipurple in xrange (0, num_purples): + for ipurple in range (0, num_purples): t = float (ipurple) / float (num_purples - 1) omt = 1.0 - t xyz = t * first_xyz + omt * last_xyz @@ -813,7 +813,7 @@ def get_normalized_spectral_line_colors_annotated ( names.append (name) i += 1 # scale each color to have the max rgb component equal to the desired brightness - for i in xrange (0, num_points): + for i in range (0, num_points): rgb = colormodels.brightest_rgb_from_xyz (xyzs [i], brightness) xyzs [i] = colormodels.xyz_from_rgb (rgb) # done diff --git a/colorpy/colormodels.py b/colorpy/colormodels.py index a5e517b..ec15962 100644 --- a/colorpy/colormodels.py +++ b/colorpy/colormodels.py @@ -134,8 +134,8 @@ simple_gamma_correct (x) - Simple power law for gamma correction. Not used by default. - -srgb_gamma_invert (x) - + +srgb_gamma_invert (x) - sRGB standard for gamma inverse correction. This is used by default. @@ -159,7 +159,7 @@ phosphor_green = SRGB_Green, phosphor_blue = SRGB_Blue, white_point = SRGB_White) - - + Setup the conversions between CIE XYZ and linear RGB spaces. Also do other initializations (gamma, conversions with Luv and Lab spaces, clipping model). The default arguments correspond to the sRGB standard RGB space. @@ -175,7 +175,7 @@ display_from_linear_function = srgb_gamma_invert, linear_from_display_function = srgb_gamma_correct, gamma = STANDARD_GAMMA) - - + Setup gamma correction. The functions used for gamma correction/inversion can be specified, as well as a gamma value. @@ -187,7 +187,7 @@ into a linear component [proportional to light intensity]. The choices for the functions: display_from_linear_function - - srgb_gamma_invert [default] - sRGB standard + srgb_gamma_invert [default] - sRGB standard simple_gamma_invert - simple power function, can specify gamma. linear_from_display_function - srgb_gamma_correct [default] - sRGB standard @@ -220,7 +220,7 @@ sRGB - http://www.color.org/sRGB.xalter - (accessed 15 Sep 2008) A Standard Default Color Space for the Internet: sRGB, Michael Stokes (Hewlett-Packard), Matthew Anderson (Microsoft), Srinivasan Chandrasekar (Microsoft), - Ricardo Motta (Hewlett-Packard), Version 1.10, November 5, 1996. + Ricardo Motta (Hewlett-Packard), Version 1.10, November 5, 1996. License: @@ -300,7 +300,7 @@ def luv_color (L, u, v): '''Construct a Luv color from components.''' rtn = numpy.array ([L, u, v]) return rtn - + def lab_color (L, a, b): '''Construct a Lab color from components.''' rtn = numpy.array ([L, a, b]) @@ -332,7 +332,7 @@ def lab_color (L, a, b): SMPTE_Red = xyz_color (0.630, 0.340) SMPTE_Green = xyz_color (0.310, 0.595) SMPTE_Blue = xyz_color (0.155, 0.070) -# use D65 as white point for SMPTE +# use D65 as white point for SMPTE # NTSC phosphors [original standard for TV, but no longer used in TV sets] # From Hall p. 119 and Foley/Van Dam p. 589 @@ -422,9 +422,9 @@ def init ( white_point = SRGB_White): '''Setup the conversions between CIE XYZ and linear RGB spaces. Also do other initializations (gamma, conversions with Luv and Lab spaces, clipping model). - + The default arguments correspond to the sRGB standard RGB space. - + The conversion is defined by supplying the chromaticities of each of the monitor phosphors, as well as the resulting white color when all of the phosphors are at full strength. @@ -451,8 +451,8 @@ def init ( phosphor_blue * intensities [2])) # invert to get rgb_from_xyz matrix rgb_from_xyz_matrix = numpy.linalg.inv (xyz_from_rgb_matrix) - #print 'xyz_from_rgb', str (xyz_from_rgb_matrix) - #print 'rgb_from_xyz', str (rgb_from_xyz_matrix) +# print ('xyz_from_rgb %s' % str (xyz_from_rgb_matrix)) +# print ('rgb_from_xyz %s' % str (rgb_from_xyz_matrix)) # conversions between the (almost) perceptually uniform # spaces (Luv, Lab) require the definition of a white point. @@ -467,7 +467,7 @@ def init ( def rgb_from_xyz (xyz): '''Convert an xyz color to rgb.''' return numpy.dot (rgb_from_xyz_matrix, xyz) - + def xyz_from_rgb (rgb): '''Convert an rgb color to xyz.''' return numpy.dot (xyz_from_rgb_matrix, rgb) @@ -669,7 +669,7 @@ def xyz_from_lab (Lab): # Gamma correction # -# Non-gamma corrected rgb values, also called non-linear rgb values, +# Non-gamma corrected rgb values, also called non-linear rgb values, # correspond to palette register entries [although here they are kept # in the range 0.0 to 1.0.] The numerical values are not proportional # to the amount of light energy present. @@ -691,9 +691,9 @@ def xyz_from_lab (Lab): STANDARD_GAMMA = 2.2 # Although NTSC specifies a gamma of 2.2 as standard, this is designed -# to account for the dim viewing environments typical of TV, but not -# computers. Well-adjusted CRT displays have a true gamma in the range -# 2.35 through 2.55. We use the physical gamma value here, not 2.2, +# to account for the dim viewing environments typical of TV, but not +# computers. Well-adjusted CRT displays have a true gamma in the range +# 2.35 through 2.55. We use the physical gamma value here, not 2.2, # thus not correcting for a dim viewing environment. # [Poynton, Gamma FAQ p.5, p.9, Hall, p. 121] POYNTON_GAMMA = 2.45 @@ -743,7 +743,7 @@ def init_gamma_correction ( '''Setup gamma correction. The functions used for gamma correction/inversion can be specified, as well as a gamma value. - + The specified display_from_linear_function should convert a linear (rgb) component [proportional to light intensity] into displayable component [proportional to palette values]. @@ -754,7 +754,7 @@ def init_gamma_correction ( The choices for the functions: display_from_linear_function - - srgb_gamma_invert [default] - sRGB standard + srgb_gamma_invert [default] - sRGB standard simple_gamma_invert - simple power function, can specify gamma. linear_from_display_function - srgb_gamma_correct [default] - sRGB standard @@ -826,8 +826,8 @@ def clip_rgb_color (rgb_color): rgb [2] = scaling * (rgb [2] - rgb_min); clipped_chromaticity = True else: - raise ValueError, 'Invalid color clipping method %s' % (str(_clip_method)) - + raise ValueError ('Invalid color clipping method %s' % str(_clip_method)) + # clip intensity if needed (rgb values > 1.0) by scaling rgb_max = max (rgb) # we actually don't overflow until 255.0 * intensity > 255.5, so instead of 1.0 use ... @@ -839,7 +839,7 @@ def clip_rgb_color (rgb_color): clipped_intensity = True # gamma correction - for index in xrange (0, 3): + for index in range (0, 3): rgb [index] = display_from_linear_component (rgb [index]) # scale to 0 - 255 @@ -851,7 +851,7 @@ def clip_rgb_color (rgb_color): ig = min (255, max (0, ig)) ib = min (255, max (0, ib)) irgb = irgb_color (ir, ig, ib) - return (irgb, (clipped_chromaticity, clipped_intensity)) + return (irgb, (clipped_chromaticity, clipped_intensity)) # # Conversions between linear rgb colors (range 0.0 - 1.0, values proportional to light intensity) @@ -863,7 +863,7 @@ def clip_rgb_color (rgb_color): def irgb_string_from_irgb (irgb): '''Convert a displayable irgb color (0-255) into a hex string.''' # ensure that values are in the range 0-255 - for index in xrange (0,3): + for index in range (0,3): irgb [index] = min (255, max (0, irgb [index])) # convert to hex string irgb_string = '#%02X%02X%02X' % (irgb [0], irgb [1], irgb [2]) @@ -873,9 +873,9 @@ def irgb_from_irgb_string (irgb_string): '''Convert a color hex string (like '#AB13D2') into a displayable irgb color.''' strlen = len (irgb_string) if strlen != 7: - raise ValueError, 'irgb_string_from_irgb(): Expecting 7 character string like #AB13D2' + raise ValueError ('irgb_string_from_irgb(): Expecting 7 character string like #AB13D2') if irgb_string [0] != '#': - raise ValueError, 'irgb_string_from_irgb(): Expecting 7 character string like #AB13D2' + raise ValueError ('irgb_string_from_irgb(): Expecting 7 character string like #AB13D2') irs = irgb_string [1:3] igs = irgb_string [3:5] ibs = irgb_string [5:7] @@ -903,7 +903,7 @@ def rgb_from_irgb (irgb): b = linear_from_display_component (b0) rgb = rgb_color (r, g, b) return rgb - + def irgb_string_from_rgb (rgb): '''Clip the rgb color, convert to a displayable color, and convert to a hex string.''' return irgb_string_from_irgb (irgb_from_rgb (rgb)) @@ -922,6 +922,6 @@ def irgb_string_from_xyz (xyz): # Initialization - Initialize to sRGB at module startup. # If a different rgb model is needed, then the startup can be re-done to set the new conditions. # - + init() # Default conversions setup on module load diff --git a/colorpy/data/massage_CIEXYZ.py b/colorpy/data/massage_CIEXYZ.py index 202fe75..6f925f9 100644 --- a/colorpy/data/massage_CIEXYZ.py +++ b/colorpy/data/massage_CIEXYZ.py @@ -12,7 +12,7 @@ Provides a set of data sets related to color vision. ColorPy uses the tables from this site for the 1931 CIE XYZ matching functions, and for Illuminant D65, both at 1 nm wavelength increments. - + CIE Standards - http://cvrl.ioo.ucl.ac.uk/cie.htm - (accessed 17 Sep 2008) CIE standards as maintained by CVRL. The 1931 CIE XYZ and D65 tables that ColorPy uses were obtained from the following files, linked here: @@ -88,7 +88,7 @@ def create_CIE_XYZ_1931_table_5nm (): keys = dict_x.keys() # all should be the same keys.sort() msgs.append ('_CIEXYZ_1931_table = [\n') - for i in xrange (0, len (keys)): + for i in range (0, len (keys)): ikey = keys [i] wl_nm = ikey x = dict_x [ikey] @@ -106,7 +106,7 @@ def doit_CIE_XYZ_1931_5nm (): '''Create tables from the official CIE data.''' msgs = create_CIE_XYZ_1931_table_5nm() for i in msgs: - print i, + print (i), f = open ('CIE_XYZ_1931_5nm.txt', 'w') f.writelines (msgs) f.close() @@ -123,7 +123,7 @@ def create_CVRL_XYZ_1931_table_1nm (): lines = f.readlines() f.close() msgs.append ('_CIEXYZ_1931_table = [\n') - for i in xrange (0, len (lines)): + for i in range (0, len (lines)): iline = lines [i].rstrip() sep = ',' if i == len (lines)-1: @@ -135,7 +135,7 @@ def create_CVRL_XYZ_1931_table_1nm (): def doit_CVRL_XYZ_1931_table_1nm (): msgs = create_CVRL_XYZ_1931_table_1nm() for i in msgs: - print i, + print (i), f = open ('CVRL_XYZ_1931_1nm.txt', 'w') f.writelines (msgs) f.close() @@ -149,7 +149,7 @@ def create_CVRL_D65_table_1nm (): lines = f.readlines() f.close() msgs.append ('_Illuminant_D65_table = [\n') - for i in xrange (0, len (lines)): + for i in range (0, len (lines)): iline = lines [i].rstrip() sep = ',' if i == len (lines)-1: @@ -161,7 +161,7 @@ def create_CVRL_D65_table_1nm (): def doit_CVRL_D65_table_1nm (): msgs = create_CVRL_D65_table_1nm() for i in msgs: - print i, + print (i), f = open ('CVRL_D65_1nm.txt', 'w') f.writelines (msgs) f.close() @@ -173,4 +173,3 @@ def main (): doit_CIE_XYZ_1931_5nm () doit_CVRL_XYZ_1931_table_1nm () doit_CVRL_D65_table_1nm () - diff --git a/colorpy/illuminants.py b/colorpy/illuminants.py index e3cac54..94615b3 100644 --- a/colorpy/illuminants.py +++ b/colorpy/illuminants.py @@ -646,7 +646,7 @@ def init (): global _Illuminant_D65 _Illuminant_D65 = ciexyz.empty_spectrum() (num_wl, num_cols) = _Illuminant_D65.shape - for i in xrange (0, num_wl): + for i in range (0, num_wl): _Illuminant_D65 [i][1] = _Illuminant_D65_table [first_index + i][1] # normalization - illuminant is scaled so that Y = 1.0 xyz = ciexyz.xyz_from_spectrum (_Illuminant_D65) @@ -690,7 +690,7 @@ def get_constant_illuminant (): '''Get an illuminant, with spectrum constant over wavelength, normalized to Y = 1.0.''' illuminant = ciexyz.empty_spectrum() (num_wl, num_cols) = illuminant.shape - for i in xrange (0, num_wl): + for i in range (0, num_wl): illuminant [i][1] = 1.0 xyz = ciexyz.xyz_from_spectrum (illuminant) if xyz [1] != 0.0: diff --git a/colorpy/misc.py b/colorpy/misc.py index 9198877..e5968b9 100644 --- a/colorpy/misc.py +++ b/colorpy/misc.py @@ -280,12 +280,12 @@ def perceptually_uniform_spectral_colors ( # convert colors to a nearly perceptually uniform space uniforms = numpy.empty ((num_colors, 3)) - for i in xrange (0, num_colors): + for i in range (0, num_colors): uniforms [i] = uniform_from_xyz (xyzs [i]) # determine spacing sum_ds = 0.0 dss = numpy.empty ((num_colors, 1)) - for i in xrange (0, num_colors-1): + for i in range (0, num_colors-1): dri = uniforms [i+1] - uniforms [i] dsi = math.sqrt (numpy.dot (dri, dri)) dss [i] = dsi diff --git a/colorpy/plots.py b/colorpy/plots.py index d8e71af..b9ad9d0 100644 --- a/colorpy/plots.py +++ b/colorpy/plots.py @@ -134,7 +134,7 @@ def log_interpolate (y0, y1, num_values): between y0 and y1. The first value will be y0, the last y1.''' rtn = [] if num_values <= 0: - raise ValueError, 'Invalid number of divisions %s in log_interpolate' % (str (num_values)) + raise ValueError ('Invalid number of divisions %s in log_interpolate' % str (num_values)) if num_values == 1: # can't use both endpoints, too constrained yi = math.sqrt (y0 * y1) @@ -142,7 +142,7 @@ def log_interpolate (y0, y1, num_values): else: # normal case beta = math.log (y1 / y0) / float (num_values - 1) - for i in xrange (0, num_values): + for i in range (0, num_values): yi = y0 * math.exp (beta * float (i)) rtn.append (yi) return rtn @@ -185,7 +185,7 @@ def draw_patch (x0, y0, color, name, patch_gap): # make plot with each color with one patch pylab.clf() num_colors = len (rgb_colors) - for i in xrange (0, num_colors): + for i in range (0, num_colors): (iy, ix) = divmod (i, num_across) # get color as a displayable string colorstring = colormodels.irgb_string_from_rgb (rgb_colors [i]) @@ -196,7 +196,7 @@ def draw_patch (x0, y0, color, name, patch_gap): draw_patch (float (ix), float (-iy), colorstring, name, patch_gap) pylab.axis ('off') pylab.title (title) - print 'Saving plot %s' % str (filename) + print ('Saving plot %s' % str (filename)) pylab.savefig (filename) def xyz_patch_plot ( @@ -228,7 +228,7 @@ def spectrum_subplot (spectrum): (num_wl, num_cols) = spectrum.shape # get rgb colors for each wavelength rgb_colors = numpy.empty ((num_wl, 3)) - for i in xrange (0, num_wl): + for i in range (0, num_wl): wl_nm = spectrum [i][0] xyz = ciexyz.xyz_from_wavelength (wl_nm) rgb_colors [i] = colormodels.rgb_from_xyz (xyz) @@ -237,7 +237,7 @@ def spectrum_subplot (spectrum): scaling = 1.0 / rgb_max rgb_colors *= scaling # draw color patches (thin vertical lines matching the spectrum curve) in color - for i in xrange (0, num_wl-1): # skipping the last one here to stay in range + for i in range (0, num_wl-1): # skipping the last one here to stay in range x0 = spectrum [i][0] x1 = spectrum [i+1][0] y0 = spectrum [i][1] @@ -292,7 +292,7 @@ def spectrum_plot ( pylab.xlabel (xlabel) pylab.ylabel (ylabel) # done - print 'Saving plot %s' % str (filename) + print ('Saving plot %s' % str (filename)) pylab.savefig (filename) # @@ -327,7 +327,7 @@ def color_vs_param_plot ( pylab.title (title) # no xlabel, ylabel in upper plot num_points = len (param_list) - for i in xrange (0, num_points-1): + for i in range (0, num_points-1): x0 = param_list [i] x1 = param_list [i+1] y0 = 0.0 @@ -348,7 +348,7 @@ def color_vs_param_plot ( tighten_x_axis (param_list) pylab.xlabel (xlabel) pylab.ylabel (ylabel) - print 'Saving plot %s' % str (filename) + print ('Saving plot %s' % str (filename)) pylab.savefig (filename) # @@ -361,7 +361,7 @@ def visible_spectrum_plot (): (num_wl, num_cols) = spectrum.shape # get rgb colors for each wavelength rgb_colors = numpy.empty ((num_wl, 3)) - for i in xrange (0, num_wl): + for i in range (0, num_wl): xyz = ciexyz.xyz_from_wavelength (spectrum [i][0]) rgb = colormodels.rgb_from_xyz (xyz) rgb_colors [i] = rgb @@ -386,7 +386,7 @@ def cie_matching_functions_plot (): spectrum_y = ciexyz.empty_spectrum() spectrum_z = ciexyz.empty_spectrum() (num_wl, num_cols) = spectrum_x.shape - for i in xrange (0, num_wl): + for i in range (0, num_wl): wl_nm = spectrum_x [i][0] xyz = ciexyz.xyz_from_wavelength (wl_nm) spectrum_x [i][1] = xyz [0] @@ -414,7 +414,7 @@ def cie_matching_functions_plot (): tighten_x_axis (spectrum_x [:,0]) # done filename = 'CIEXYZ_Matching' - print 'Saving plot %s' % str (filename) + print ('Saving plot %s' % str (filename)) pylab.savefig (filename) def scattered_visual_brightness (): @@ -422,7 +422,7 @@ def scattered_visual_brightness (): # get 'spectra' for y matching functions and multiply by 1/wl^4 spectrum_y = ciexyz.empty_spectrum() (num_wl, num_cols) = spectrum_y.shape - for i in xrange (0, num_wl): + for i in range (0, num_wl): wl_nm = spectrum_y [i][0] rayleigh = math.pow (550.0 / wl_nm, 4) xyz = ciexyz.xyz_from_wavelength (wl_nm) @@ -435,7 +435,7 @@ def scattered_visual_brightness (): tighten_x_axis (spectrum_y [:,0]) # done filename = 'Visual_scattering' - print 'Saving plot %s' % str (filename) + print ('Saving plot %s' % str (filename)) pylab.savefig (filename) def shark_fin_plot (): @@ -445,7 +445,7 @@ def shark_fin_plot (): # get normalized colors xy_list = xyz_list.copy() (num_colors, num_cols) = xy_list.shape - for i in xrange (0, num_colors): + for i in range (0, num_colors): colormodels.xyz_normalize (xy_list [i]) # get phosphor colors and normalize red = colormodels.PhosphorRed @@ -470,7 +470,7 @@ def get_direc_to_white (xyz): # draw best attempt at pure spectral colors on inner edge of shark fin s = 0.025 # distance in xy plane towards white point - for i in xrange (0, len (xy_list)-1): + for i in range (0, len (xy_list)-1): x0 = xy_list [i][0] y0 = xy_list [i][1] x1 = xy_list [i+1][0] @@ -500,10 +500,10 @@ def fill_gamut_slice (v0, v1, v2): num_s, num_t = 50, 50 dv10 = v1 - v0 dv21 = v2 - v1 - for i_s in xrange (num_s): + for i_s in range (num_s): s_a = float (i_s) / float (num_s) s_b = float (i_s+1) / float (num_s) - for i_t in xrange (num_t): + for i_t in range (num_t): t_a = float (i_t) / float (num_t) t_b = float (i_t+1) / float (num_t) # vertex coords @@ -543,7 +543,7 @@ def fill_gamut_slice (v0, v1, v2): pylab.ylabel (r'CIE $y$') pylab.title (r'CIE Chromaticity Diagram') filename = 'ChromaticityDiagram' - print 'Saving plot %s' % (str (filename)) + print ('Saving plot %s' % str (filename)) pylab.savefig (filename) # Special figures @@ -571,7 +571,7 @@ def visible_spectrum_table (filename='visible_spectrum.html'): # get rgb colors for each wavelength rgb_colors_1 = numpy.empty ((num_wl, 3)) rgb_colors_2 = numpy.empty ((num_wl, 3)) - for i in xrange (0, num_wl): + for i in range (0, num_wl): xyz = ciexyz.xyz_from_wavelength (spectrum [i][0]) rgb_1 = colormodels.rgb_from_xyz (xyz) rgb_2 = colormodels.brightest_rgb_from_xyz (xyz) @@ -611,7 +611,7 @@ def write_link (f, url, text): f.write ('\n') # each row - for i in xrange (0, num_wl): + for i in range (0, num_wl): irgb_1 = colormodels.irgb_from_rgb (rgb_colors_1 [i]) irgb_2 = colormodels.irgb_from_rgb (rgb_colors_2 [i]) red = irgb_2 [0] @@ -668,4 +668,3 @@ def vst (): if __name__ == '__main__': figures() - diff --git a/colorpy/rayleigh.py b/colorpy/rayleigh.py index d70baeb..e5384b8 100644 --- a/colorpy/rayleigh.py +++ b/colorpy/rayleigh.py @@ -84,7 +84,7 @@ def rayleigh_scattering_spectrum (): '''Get the Rayleigh scattering spectrum (independent of illuminant), as a numpy array.''' spectrum = ciexyz.empty_spectrum() (num_rows, num_cols) = spectrum.shape - for i in xrange (0, num_rows): + for i in range (0, num_rows): spectrum [i][1] = rayleigh_scattering (spectrum [i][0]) return spectrum @@ -92,7 +92,7 @@ def rayleigh_illuminated_spectrum (illuminant): '''Get the spectrum when illuminated by the specified illuminant.''' spectrum = rayleigh_scattering_spectrum() (num_wl, num_col) = spectrum.shape - for i in xrange (0, num_wl): + for i in range (0, num_wl): spectrum [i][1] *= illuminant [i][1] return spectrum @@ -120,7 +120,7 @@ def rayleigh_color_vs_illuminant_temperature_plot (T_list, title, filename): '''Make a plot of the Rayleigh scattered color vs. temperature of blackbody illuminant.''' num_T = len (T_list) rgb_list = numpy.empty ((num_T, 3)) - for i in xrange (0, num_T): + for i in range (0, num_T): T_i = T_list [i] illuminant = illuminants.get_blackbody_illuminant (T_i) xyz = rayleigh_illuminated_color (illuminant) diff --git a/colorpy/test_blackbody.py b/colorpy/test_blackbody.py index 2b2a425..54d0d50 100644 --- a/colorpy/test_blackbody.py +++ b/colorpy/test_blackbody.py @@ -32,7 +32,7 @@ def blackbody_total_intensity (T_K, start_wl_nm, end_wl_nm): '''Get the sum of the specific intensity, at 1 nm increments, from start_wl_nm to end_wl_nm.''' total = 0.0 - for wl_nm in xrange (start_wl_nm, end_wl_nm+1): + for wl_nm in range (start_wl_nm, end_wl_nm+1): specific = blackbody.blackbody_specific_intensity (wl_nm, T_K) total += specific return total @@ -53,7 +53,7 @@ def test_Wyszecki_p29 (): '''Calculations re Wyszecki p29 table.''' T = 1336.0 # 'Gold' point xyz = blackbody.blackbody_color (T) - print 'blackbody color at gold point', str (xyz) + print ('blackbody color at gold point %s' % str (xyz)) # this source is supposed to be 0.11 cd/cm^2 = 1100 cd/m^2 # whereas monitors are c. 80 cd/m^2 to 300 cd/m^2 @@ -62,18 +62,18 @@ def test_stefan_boltzman (verbose=1): NOTE - This currently does not match. I am not sure what the situation is.''' T = 100.0 sb_test0 = blackbody_total_intensity_stefan_boltzman (T) - print 'sb_test0', sb_test0 + print ('sb_test0 %' % sb_test0) sb_test1 = blackbody_total_intensity (T, 0, 1000) - print 'sb_test1', sb_test1 + print ('sb_test1 %' % sb_test1) sb_test2 = blackbody_total_intensity (T, 0, 10000) - print 'sb_test2', sb_test2 + print ('sb_test2 %' % sb_test2) sb_test3 = blackbody_total_intensity (T, 0, 100000) - print 'sb_test3', sb_test3 + print ('sb_test3 %' % sb_test3) # following start to get slow... #sb_test4 = blackbody_total_intensity (T, 0, 1000000) - #print 'sb_test4', sb_test4 +# print ('sb_test4' % sb_test4) #sb_test5 = blackbody_total_intensity (T, 0, 10000000) - #print 'sb_test5', sb_test5 +# print ('sb_test5' % sb_test5) # compare the computed result with the stefan-boltzman formula # TODO - these do not match, although the T^4 behavior is observed... @@ -88,13 +88,13 @@ def test_stefan_boltzman (verbose=1): ratio = total4 / total_sb #if verbose >= 1: if True: - print 'T', T - print 'total_sb', total_sb - print 'total1', total1 - print 'total2', total2 - print 'total3', total3 - print 'total4', total4 - print 'ratio', ratio + print ('T %s' % T) + print ('total_sb %s' % total_sb) + print ('total1 %s' % total1) + print ('total2 %s' % total2) + print ('total3 %s' % total3) + print ('total4 %s' % total4) + print ('ratio %s' % ratio) def test_blackbody (verbose=0): '''Test the blackbody functions.''' @@ -111,17 +111,17 @@ def test_blackbody (verbose=0): # determine the color for several temperatures - 10000.0 is a particularly good range temp_ranges = [100.0, 1000.0, 10000.0, 100000.0, 1000000.0 ] - for T0 in temp_ranges: - for i in xrange (0, 20): + for T0 in temp_ranges: + for i in range (0, 20): T_K = T0 * random.random() xyz = blackbody.blackbody_color (T_K) if verbose >= 1: - print 'T = %g K, xyz = %s' % (T_K, str (xyz)) + print ('T = %g K, xyz = %s' % (T_K, str (xyz))) num_passed += 1 # didn't exception msg = 'test_blackbody() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Table of xy chromaticities for blackbodies # From (I think): Judd and Wyszecki, Color in Business, Science and Industry, 1975, p. 164 @@ -151,14 +151,14 @@ def test_blackbody (verbose=0): [ 8500.0, 0.2908, 0.3000], [10000.0, 0.2807, 0.2884], [30000.0, 0.2501, 0.2489]]) - + def test_book (verbose=1): '''Test that the computed chromaticities match an existing table, from Judd and Wyszecki.''' num_passed = 0 num_failed = 0 - + (num_rows, num_cols) = book_chrom_table.shape - for i in xrange (0, num_rows): + for i in range (0, num_rows): T = book_chrom_table [i][0] book_x = book_chrom_table [i][1] book_y = book_chrom_table [i][2] @@ -177,10 +177,10 @@ def test_book (verbose=1): msg = 'test_book() : T = %g : calculated x,y = %g,%g : book values x,y = %g,%g : errors = %g,%g' % ( T, xyz [0], xyz [1], book_x, book_y, dx, dy) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) if passed: num_passed += 1 else: @@ -188,7 +188,7 @@ def test_book (verbose=1): msg = 'test_book() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Tests @@ -197,4 +197,3 @@ def test (verbose=0): test_blackbody (verbose=verbose) test_book (verbose=verbose) #test_stefan_boltzman (verbose=verbose) - diff --git a/colorpy/test_ciexyz.py b/colorpy/test_ciexyz.py index 771841a..ecb4a18 100644 --- a/colorpy/test_ciexyz.py +++ b/colorpy/test_ciexyz.py @@ -28,17 +28,17 @@ def test (verbose=0): '''Test the CIE XYZ conversions. Mainly call some functions.''' - for i in xrange (0, 100): + for i in range (0, 100): wl_nm = 1000.0 * random.random() xyz = ciexyz.xyz_from_wavelength (wl_nm) if verbose >= 1: - print 'wl_nm = %g, xyz = %s' % (wl_nm, str (xyz)) - for i in xrange (0, 10): + print ('wl_nm = %g, xyz = %s' % (wl_nm, str (xyz))) + for i in range (0, 10): empty = ciexyz.empty_spectrum () xyz = ciexyz.xyz_from_spectrum (empty) if verbose >= 1: - print 'black = %s' % (str (xyz)) + print ('black = %s' % str (xyz)) xyz_555 = ciexyz.xyz_from_wavelength (555.0) if verbose >= 1: - print '555 nm = %s' % (str (xyz_555)) - print 'test_ciexyz.test() passed.' + print ('555 nm = %s' % str (xyz_555)) + print ('test_ciexyz.test() passed.') diff --git a/colorpy/test_colormodels.py b/colorpy/test_colormodels.py index 51a8c9d..acdb96f 100644 --- a/colorpy/test_colormodels.py +++ b/colorpy/test_colormodels.py @@ -46,16 +46,16 @@ def test_A (xyz0, tolerance=1.0e-10, verbose=1): msg = 'test_xyz_rgb.test_A() : xyz0 = %s, rgb(xyz0) = %s, xyz(rgb(xyz0)) = %s, rgb(xyz(rgb(xyz0))) = %s, errors = (%g, %g), %s' % ( str (xyz0), str (rgb0), str (xyz1), str (rgb1), error_rgb, error_xyz, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed num_passed = 0 num_failed = 0 - for i in xrange (0, 100): + for i in range (0, 100): x0 = 10.0 * random.random() y0 = 10.0 * random.random() z0 = 10.0 * random.random() @@ -77,11 +77,11 @@ def test_A (xyz0, tolerance=1.0e-10, verbose=1): msg = 'test_xyz_rgb() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) def test_xyz_irgb (verbose=1): '''Test the direct conversions from xyz to irgb.''' - for i in xrange (0, 100): + for i in range (0, 100): x0 = 10.0 * random.random() y0 = 10.0 * random.random() z0 = 10.0 * random.random() @@ -90,13 +90,13 @@ def test_xyz_irgb (verbose=1): colormodels.rgb_from_xyz (xyz0)) irgb1 = colormodels.irgb_from_xyz (xyz0) if (irgb0[0] != irgb1[0]) or (irgb0[1] != irgb1[1]) or (irgb0[2] != irgb1[2]): - raise ValueError + raise ValueError () irgbs0 = colormodels.irgb_string_from_rgb ( colormodels.rgb_from_xyz (xyz0)) irgbs1 = colormodels.irgb_string_from_xyz (xyz0) if irgbs0 != irgbs1: - raise ValueError - print 'Passed test_xyz_irgb()' + raise ValueError () + print ('Passed test_xyz_irgb()') # # Color model conversions to (nearly) perceptually uniform spaces Luv and Lab. @@ -110,7 +110,7 @@ def calc_L_LUM_C (): '''L_LUM_C should be ideally chosen so that the two models in L_luminance() agree exactly at the cutoff point. This is where the extra digits in L_LUM_C, over Kasson, come from.''' wanted = (colormodels.L_LUM_A * math.pow (colormodels.L_LUM_CUTOFF, 1.0/3.0) - colormodels.L_LUM_B) / colormodels.L_LUM_CUTOFF - print 'optimal L_LUM_C = %.16e' % (wanted) + print ('optimal L_LUM_C = %.16e' % wanted) def test_L_luminance (verbose=1): '''Test that L_luminance() and L_luminance_inverse() are really inverses.''' @@ -134,10 +134,10 @@ def test_A (y0, tolerance=1.0e-13, verbose=1): msg = 'test_L_luminance.test_A() : y0 = %g (%s), L(y0) = %g, y(L(y0)) = %g, error = %g, %s' % ( y0, range_info, L0, y1, error, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed # Test B - Check that L_luminance() is the inverse of L_luminance_inverse() @@ -160,26 +160,26 @@ def test_B (L0, tolerance=1.0e-10, verbose=1): msg = 'test_L_luminance.test_B() : L0 = %g (%s), y(L0) = %g, L(y(L0)) = %g, error = %g, %s' % ( L0, range_info, y0, L1, error, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed num_passed = 0 num_failed = 0 # Test A for fairly small y values (to ensure coverage of linear range) - for i in xrange (0, 100): + for i in range (0, 100): y0 = 0.1 * random.random() passed = test_A (y0, tolerance=1.0e-13, verbose=verbose) if passed: num_passed += 1 else: num_failed += 1 - + # Test A for fairly large y values - for i in xrange (0, 100): + for i in range (0, 100): y0 = 10.0 * random.random() passed = test_A (y0, tolerance=1.0e-13, verbose=verbose) if passed: @@ -188,16 +188,16 @@ def test_B (L0, tolerance=1.0e-10, verbose=1): num_failed += 1 # Test B for fairly small L values (to ensure coverage of linear range) - for i in xrange (0, 100): + for i in range (0, 100): L0 = 50.0 * random.random() passed = test_B (L0, tolerance=1.0e-10, verbose=verbose) if passed: num_passed += 1 else: num_failed += 1 - + # Test B for fairly large L values - for i in xrange (0, 100): + for i in range (0, 100): L0 = 1000.0 * random.random() passed = test_B (L0, tolerance=1.0e-10, verbose=verbose) if passed: @@ -207,7 +207,7 @@ def test_B (L0, tolerance=1.0e-10, verbose=1): msg = 'test_L_luminance() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Utility function for Luv @@ -228,10 +228,10 @@ def test_A (xyz0, tolerance=0.0, verbose=1): msg = 'test_uv_primes.test_A() : xyz0 = %s, (up,vp) = (%g,%g), xyz(up,vp) = %s, error = %g, %s' % ( str (xyz0), up0, vp0, str(xyz1), error, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed def test_B (up0, vp0, y0, tolerance=0.0, verbose=1): @@ -249,17 +249,17 @@ def test_B (up0, vp0, y0, tolerance=0.0, verbose=1): msg = 'test_uv_primes.test_B() : (up0,vp0,y0) = (%g,%g,%g), xyz (up0,vp0,y0) = %s, (up,vp)(xyz) = (%g,%g), error = %g, %s' % ( up0, vp0, y0, str (xyz0), up1, vp1, error, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed - + num_passed = 0 num_failed = 0 # Test A - for i in xrange (0, 100): + for i in range (0, 100): x0 = 10.0 * random.random() y0 = 10.0 * random.random() z0 = 10.0 * random.random() @@ -269,9 +269,9 @@ def test_B (up0, vp0, y0, tolerance=0.0, verbose=1): num_passed += 1 else: num_failed += 1 - + # Test black case explicitly - xyz0 = colormodels.xyz_color (0.0, 0.0, 0.0) + xyz0 = colormodels.xyz_color (0.0, 0.0, 0.0) passed = test_A (xyz0, tolerance=0.0, verbose=verbose) if passed: num_passed += 1 @@ -279,7 +279,7 @@ def test_B (up0, vp0, y0, tolerance=0.0, verbose=1): num_failed += 1 # Test B - for i in xrange (0, 100): + for i in range (0, 100): up0 = 4.0 * (2.0 * random.random() - 1.0) vp0 = 9.0 * (2.0 * random.random() - 1.0) y0 = 10.0 * random.random() @@ -298,7 +298,7 @@ def test_B (up0, vp0, y0, tolerance=0.0, verbose=1): msg = 'test_uv_primes() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Utility function for Lab # See [Kasson p.399] for details. @@ -309,7 +309,7 @@ def calc_LAB_F_A (): '''LAB_F_A should be ideally chosen so that the two models in Lab_f() agree exactly at the cutoff point. This is where the extra digits in LAB_F_A, over Kasson, come from.''' wanted = (math.pow (colormodels.L_LUM_CUTOFF, 1.0/3.0) - colormodels.LAB_F_B) / colormodels.L_LUM_CUTOFF - print 'optimal LAB_F_A = %.16e' % (wanted) + print ('optimal LAB_F_A = %.16e' % wanted) def test_Lab_f (verbose=1): '''Test that Lab_f() and Lab_f_inverse() are really inverses.''' @@ -333,10 +333,10 @@ def test_A (t0, tolerance=1.0e-13, verbose=1): msg = 'test_Lab_f.test_A() : t0 = %g (%s), f(t0) = %g, t(f(t0)) = %g, error = %g, %s' % ( t0, range_info, f0, t1, error, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueErro (msg) return passed # Test B - Check that Lab_f() is the inverse of Lab_f_inverse() @@ -359,17 +359,17 @@ def test_B (f0, tolerance=1.0e-10, verbose=1): msg = 'test_Lab_f.test_B() : f0 = %g (%s), t(f0) = %g, f(t(f0)) = %g, error = %g, %s' % ( f0, range_info, t0, f1, error, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed num_passed = 0 num_failed = 0 # Test A for fairly small y values (to ensure coverage of linear range) - for i in xrange (0, 100): + for i in range (0, 100): y0 = 0.025 * random.random() passed = test_A (y0, tolerance=1.0e-13, verbose=verbose) if passed: @@ -378,7 +378,7 @@ def test_B (f0, tolerance=1.0e-10, verbose=1): num_failed += 1 # Test A for fairly large y values - for i in xrange (0, 100): + for i in range (0, 100): y0 = 10.0 * random.random() passed = test_A (y0, tolerance=1.0e-13, verbose=verbose) if passed: @@ -387,16 +387,16 @@ def test_B (f0, tolerance=1.0e-10, verbose=1): num_failed += 1 # Test B for fairly small L values (to ensure coverage of linear range) - for i in xrange (0, 100): + for i in range (0, 100): L0 = 0.25 * random.random() passed = test_B (L0, tolerance=1.0e-10, verbose=verbose) if passed: num_passed += 1 else: num_failed += 1 - + # Test B for fairly large L values - for i in xrange (0, 100): + for i in range (0, 100): L0 = 1000.0 * random.random() passed = test_B (L0, tolerance=1.0e-10, verbose=verbose) if passed: @@ -406,7 +406,7 @@ def test_B (f0, tolerance=1.0e-10, verbose=1): msg = 'test_Lab_f() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Conversions between standard device independent color space (CIE XYZ) # and the almost perceptually uniform space Luv. @@ -432,16 +432,16 @@ def test_A (xyz0, tolerance=1.0e-10, verbose=1): msg = 'test_xyz_luv.test_A() : xyz0 = %s, luv(xyz0) = %s, xyz(luv(xyz0)) = %s, luv(xyz(luv(xyz0))) = %s, errors = (%g, %g), %s' % ( str (xyz0), str (luv0), str (xyz1), str (luv1), error_luv, error_xyz, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed num_passed = 0 num_failed = 0 - for i in xrange (0, 100): + for i in range (0, 100): x0 = 10.0 * random.random() y0 = 10.0 * random.random() z0 = 10.0 * random.random() @@ -462,7 +462,7 @@ def test_A (xyz0, tolerance=1.0e-10, verbose=1): msg = 'test_xyz_luv() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Conversions between standard device independent color space (CIE XYZ) # and the almost perceptually uniform space Lab. @@ -488,16 +488,16 @@ def test_A (xyz0, tolerance=1.0e-10, verbose=1): msg = 'test_xyz_lab.test_A() : xyz0 = %s, lab(xyz0) = %s, xyz(lab(xyz0)) = %s, lab(xyz(lab(xyz0))) = %s, errors = (%g, %g), %s' % ( str (xyz0), str (lab0), str (xyz1), str (lab1), error_lab, error_xyz, status) if verbose >= 1: - print msg + print (msg) if not passed: pass - raise ValueError, msg + raise ValueError (msg) return passed num_passed = 0 num_failed = 0 - for i in xrange (0, 100): + for i in range (0, 100): x0 = 10.0 * random.random() y0 = 10.0 * random.random() z0 = 10.0 * random.random() @@ -518,17 +518,17 @@ def test_A (xyz0, tolerance=1.0e-10, verbose=1): msg = 'test_xyz_lab() : %d tests passed, %d tests failed' % ( num_passed, num_failed) - print msg + print (msg) # Gamma correction def test_gamma (verbose=1): if verbose >= 1: - print 'Testing gamma corrections...' + print ('Testing gamma corrections...') def test_gamma_corrections (): # test individual component gamma - for i in xrange (0, 100): + for i in range (0, 100): x = 10.0 * (2.0 * random.random() - 1.0) a = colormodels.linear_from_display_component (x) b = colormodels.display_from_linear_component (a) @@ -538,40 +538,40 @@ def test_gamma_corrections (): rel1 = math.fabs (err1 / (b + x)) err2 = math.fabs (c - a) rel2 = math.fabs (err2 / (c + a)) - #print 'x = %g, b = %g, err = %g, rel = %g' % (x, b, err1, rel1) - #print 'a = %g, c = %g, err = %g, rel = %g' % (a, c, err2, rel2) +# print ('x = %g, b = %g, err = %g, rel = %g' % (x, b, err1, rel1)) +# print ('a = %g, c = %g, err = %g, rel = %g' % (a, c, err2, rel2)) tolerance = 1.0e-14 if rel1 > tolerance: - raise ValueError + raise ValueError() if rel2 > tolerance: - raise ValueError + raise ValueError() # test default sRGB component (cannot supply exponent) if verbose >= 1: - print 'testing sRGB gamma' + print ()'testing sRGB gamma') colormodels.init_gamma_correction ( display_from_linear_function = colormodels.srgb_gamma_invert, linear_from_display_function = colormodels.srgb_gamma_correct) test_gamma_corrections() - # test simple power law gamma (can supply exponent) + # test simple power law gamma (can supply exponent) gamma_set = [0.1, 0.5, 1.0, 1.1, 1.5, 2.0, 2.2, 2.5, 10.0] for gamma in gamma_set: if verbose >= 1: - print 'testing gamma', gamma + print ('testing gamma %s' % gamma) colormodels.init_gamma_correction ( display_from_linear_function = colormodels.simple_gamma_invert, linear_from_display_function = colormodels.simple_gamma_correct, gamma = gamma) test_gamma_corrections() - print 'Passed test_gamma()' + print ('Passed test_gamma()') # Linear (0.0-1.0) rgb to/from displayable (0-255) irgb def test_irgb_string (verbose=1): '''Convert back and forth from irgb and irgb_string.''' - for i in xrange (0, 100): + for i in range (0, 100): ir = random.randrange (0, 256) ig = random.randrange (0, 256) ib = random.randrange (0, 256) @@ -581,16 +581,16 @@ def test_irgb_string (verbose=1): irgb_string2 = colormodels.irgb_string_from_irgb (irgb2) if (irgb[0] != irgb2[0]) or (irgb[1] != irgb2[1]) or (irgb[2] != irgb2[2]): msg = 'irgb %s and irgb2 %s do not match' % (str (irgb), str (irgb2)) - raise ValueError, msg + raise ValueError (msg) if (irgb_string != irgb_string2): msg = 'irgb_string %s and irgb_string2 %s do not match' % (irgb_string, irgb_string2) - raise ValueError, msg + raise ValueError (msg) if verbose >= 1: - print 'Passed test_irgb_string()' + print ('Passed test_irgb_string()') def test_rgb_irgb (verbose=1): '''Test that conversions between rgb and irgb are invertible.''' - for i in xrange (0, 100): + for i in range (0, 100): ir = random.randrange (0, 256) ig = random.randrange (0, 256) ib = random.randrange (0, 256) @@ -600,7 +600,7 @@ def test_rgb_irgb (verbose=1): rgb1 = colormodels.rgb_from_irgb (irgb1) if (irgb0[0] != irgb1[0]) or (irgb0[1] != irgb1[1]) or (irgb0[2] != irgb1[2]): msg = 'irgb0 %s and irgb1 %s do not match' % (str (irgb0), str (irgb1)) - raise ValueError, msg + raise ValueError (msg) tolerance = 1.0e-14 err_rgb = rgb1 - rgb0 err_r = math.fabs (err_rgb [0]) @@ -608,38 +608,38 @@ def test_rgb_irgb (verbose=1): err_b = math.fabs (err_rgb [2]) if (err_r > tolerance) or (err_g > tolerance) or (err_b > tolerance): msg = 'rgb0 %s and rgb1 %s differ by %g' % (str (rgb0), str (rgb1), max (err_r,err_g,err_b)) - raise ValueError, msg + raise ValueError (msg) if verbose >= 1: - print 'Passed test_rgb_irgb()' + print ('Passed test_rgb_irgb()') # Clipping def test_clipping (verbose=1): '''Test the various color clipping methods.''' xyz_colors = ciexyz.get_normalized_spectral_line_colors () - #print 'xyz_colors', xyz_colors +# print ('xyz_colors %s' % xyz_colors) (num_wl, num_cols) = xyz_colors.shape # get rgb values for standard clipping colormodels.init_clipping (colormodels.CLIP_ADD_WHITE) rgb_add_white = [] - for i in xrange (0, num_wl): + for i in range (0, num_wl): color = colormodels.irgb_string_from_rgb ( colormodels.rgb_from_xyz (xyz_colors [i])) rgb_add_white.append (color) # get rgb values for clamp clipping colormodels.init_clipping (colormodels.CLIP_CLAMP_TO_ZERO) rgb_clamp = [] - for i in xrange (0, num_wl): + for i in range (0, num_wl): color = colormodels.irgb_string_from_rgb ( colormodels.rgb_from_xyz (xyz_colors [i])) rgb_clamp.append (color) # compare if verbose >= 1: - print 'colors from add white, colors from clamp' - for i in xrange (0, num_wl): - print rgb_add_white [i], rgb_clamp [i] - print 'Passed test_clipping()' - + print ('colors from add white, colors from clamp') + for i in range (0, num_wl): + print ('%s %s' % (rgb_add_white [i], rgb_clamp [i])) + print ('Passed test_clipping()') + # # Main test routine for the conversions # diff --git a/colorpy/test_illuminants.py b/colorpy/test_illuminants.py index 3a0cbae..4edcf6d 100644 --- a/colorpy/test_illuminants.py +++ b/colorpy/test_illuminants.py @@ -28,22 +28,21 @@ def test (verbose=0): '''Mainly call some functions.''' D65 = illuminants.get_illuminant_D65() if verbose >= 1: - print 'Illuminant D65' - print str (D65) + print ('Illuminant D65') + print (str (D65)) A = illuminants.get_illuminant_A() if verbose >= 1: - print 'Illuminant A' - print str (A) + print ('Illuminant A') + print (str (A)) const = illuminants.get_constant_illuminant() if verbose >= 1: - print 'Constant Illuminant' - print str (const) + print ('Constant Illuminant') + print (str (const)) T_list = [0.0, 1.0, 100.0, 1000.0, 5778.0, 10000.0, 100000.0] for T in T_list: bb = illuminants.get_blackbody_illuminant (T) if verbose >= 1: - print 'Blackbody Illuminant : %g K' % (T) - print str (bb) - print 'test_illuminants.test() passed.' - + print ('Blackbody Illuminant : %g K' % T) + print (str (bb)) + print ('test_illuminants.test() passed.') diff --git a/colorpy/test_rayleigh.py b/colorpy/test_rayleigh.py index 8fbe2ef..a3b5d44 100644 --- a/colorpy/test_rayleigh.py +++ b/colorpy/test_rayleigh.py @@ -29,12 +29,11 @@ def test (): '''Mainly call some functions.''' - for i in xrange (0, 100): + for i in range (0, 100): wl_nm = 1000.0 * random.random() rayleigh.rayleigh_scattering (wl_nm) rayleigh.rayleigh_scattering_spectrum() illum = illuminants.get_illuminant_D65() rayleigh.rayleigh_illuminated_spectrum (illum) rayleigh.rayleigh_illuminated_color (illum) - print 'test_rayleigh.test() passed.' # didnt exception - + print ('test_rayleigh.test() passed.') # didnt exception diff --git a/colorpy/test_thinfilm.py b/colorpy/test_thinfilm.py index 1ec63e2..9482867 100644 --- a/colorpy/test_thinfilm.py +++ b/colorpy/test_thinfilm.py @@ -30,17 +30,16 @@ def test (): '''Module test. Mainly call some functions.''' illuminant = illuminants.get_illuminant_D65() - for j in xrange (0, 100): + for j in range (0, 100): n1 = 5.0 * random.random() n2 = 5.0 * random.random() n3 = 5.0 * random.random() thickness_nm = 10000.0 * random.random() film = thinfilm.thin_film (n1, n2, n3, thickness_nm) - for k in xrange (0, 100): + for k in range (0, 100): wl_nm = 1000.0 * random.random() film.get_interference_reflection_coefficient (wl_nm) film.reflection_spectrum () film.illuminated_spectrum (illuminant) film.illuminated_color (illuminant) - print 'test_thinfilm.test() passed.' # no exceptions - + print ('test_thinfilm.test() passed.') # no exceptions diff --git a/colorpy/thinfilm.py b/colorpy/thinfilm.py index b03141f..8a4d4ed 100644 --- a/colorpy/thinfilm.py +++ b/colorpy/thinfilm.py @@ -148,7 +148,7 @@ def reflection_spectrum (self): '''Get the reflection spectrum (independent of illuminant) for the thin film.''' spectrum = ciexyz.empty_spectrum() (num_rows, num_cols) = spectrum.shape - for i in xrange (0, num_rows): + for i in range (0, num_rows): wl_nm = spectrum [i][0] spectrum [i][1] = self.get_interference_reflection_coefficient (wl_nm) return spectrum @@ -157,7 +157,7 @@ def illuminated_spectrum (self, illuminant): '''Get the spectrum when illuminated by the specified illuminant.''' spectrum = self.reflection_spectrum() (num_wl, num_col) = spectrum.shape - for i in xrange (0, num_wl): + for i in range (0, num_wl): spectrum [i][1] *= illuminant [i][1] return spectrum @@ -184,7 +184,7 @@ def thinfilm_color_vs_thickness_plot (n1, n2, n3, thickness_nm_list, illuminant, '''Plot the color of the thin film for the specfied thicknesses [nm].''' num_thick = len (thickness_nm_list) rgb_list = numpy.empty ((num_thick, 3)) - for i in xrange (0, num_thick): + for i in range (0, num_thick): film = thin_film (n1, n2, n3, thickness_nm_list [i]) xyz = film.illuminated_color (illuminant) rgb_list [i] = colormodels.rgb_from_xyz (xyz) @@ -210,15 +210,15 @@ def thinfilm_spectrum_plot (n1, n2, n3, thickness_nm, illuminant, title, filenam def figures (): '''Draw some thin film plots.''' # simple patch plot - thickness_nm_list = xrange (0, 1000, 10) + thickness_nm_list = range (0, 1000, 10) illuminant = illuminants.get_illuminant_D65() illuminants.scale_illuminant (illuminant, 9.50) thinfilm_patch_plot (1.500, 1.003, 1.500, thickness_nm_list, illuminant, 'ThinFilm Patch Plot', 'ThinFilm-Patch') # plot the colors of films vs thickness. # we scale the illuminant to get a better range of color. - #thickness_nm_list = xrange (0, 1000, 2) # faster - thickness_nm_list = xrange (0, 1000, 1) # nicer + #thickness_nm_list = range (0, 1000, 2) # faster + thickness_nm_list = range (0, 1000, 1) # nicer # gap in glass/plastic illuminant = illuminants.get_illuminant_D65() illuminants.scale_illuminant (illuminant, 4.50)