Source code for pyradi.rytarggen

# -*- coding: utf-8 -*-

################################################################
# The contents of this file are subject to the BSD 3Clause (New) License
# you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://directory.fsf.org/wiki/License:BSD_3Clause

# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
# License for the specific language governing rights and limitations
# under the License.

# The Original Code is part of the PyRadi toolkit.

# The Initial Developer of the Original Code is CJ Willers, Copyright (C) 2006-2015
# All Rights Reserved.

# Contributor(s): ______________________________________.
################################################################

"""


For more detail see the documentation at

| ``http://nelisw.github.io/pyradi-docs/_build/html/index.html``,
| ``http://nelisw.github.io/pyradi-docs/_build/html/rytarggen.html``, or 
| ``pyradi/doc/rytarggen.rst``  

"""

__version__ = '0.1.0'
__author__='CJ Willers'
__all__ = [
    'assigncheck',
    'create_HDF5_image',
    'hdf_Raw',
    'hdf_Uniform',
    'hdf_disk_photon',
    'hdf_stairs',
    'analyse_HDF5_image',
    'analyse_HDF5_imageFile',
    'calcTemperatureEquivalent',
    'calcLuxEquivalent',
]

import numpy as np
import scipy.signal as signal
import scipy.constants as const
from scipy import interpolate

import pyradi.ryfiles as ryfiles
import pyradi.ryhdf5 as ryhdf5
import pyradi.ryutils as ryutils
import pyradi.ryplanck as ryplanck
def _ryplanckql(spectral, temperature):
    """Planck photon spectral exitance, wavelength domain, spectral in µm.

    Self-contained: constants are computed from scipy.constants on every call
    so the result is independent of which pyradi version is installed.
    Handles scalar, 1-D, and 2-D column-vector (N,1)/(M,1) inputs, matching
    the behaviour of the @fixDimensions-decorated local ryplanck.planckql.
    """
    import numpy as _np
    import scipy.constants as _sc
    _c1ql = 2.0 * _np.pi * _sc.c * (1.0e6) ** 3   # q/(s.m^2.um^4)
    _c2l  = _sc.h * _sc.c / _sc.k * 1.0e6          # um.K
    _lim  = 709.7
    sp = _np.asarray(spectral,    dtype=float)
    tp = _np.asarray(temperature, dtype=float)
    # (N,1) spectral × (M,1) temperature → (N,M): transpose temp so it broadcasts
    if sp.ndim == 2 and sp.shape[1] == 1 and tp.ndim == 2 and tp.shape[1] == 1:
        tp = tp.T  # (1, M) broadcasts with (N, 1) → (N, M)
    exP  = _c2l / (sp * tp)
    exP2 = _np.where(exP < _lim, exP, 1.0)
    p    = (_c1ql / (_np.exp(exP2) - 1.0)) / (sp ** 4)
    return _np.where(exP < _lim, p, 0.0)



######################################################################################
[docs] def assigncheck(hdf5,path,value): """assign a value to a path, checking for prior existence """ if path in hdf5: hdf5[path][...] = value else: hdf5[path] = value
######################################################################################
[docs] def hdf_Uniform(imghd5,rad_dynrange): r"""A generating function to create a uniform photon rate image. The uniform value in the image will have a value of rad_dynrange. The equivalent image value is expressed as in the same units as the input The function accepts radiant or photon rate dynamic range units inputs. This function must be called from rytarggen.create_HDF5_image Args: | imghd5 (handle to hdf5 file): file to which image must be added | rad_dynrange (float): uniform/max radiance value Returns: | nothing: as a side effect a set of photon radiance image files are written Raises: | No exception is raised. Author: CJ Willers """ # convert to radiance values in photon units assigncheck(imghd5, 'image/rad_dynrange', rad_dynrange * imghd5['image/conversion'][()]) assigncheck(imghd5, 'image/rad_min', 0.) # create photon rate radiance image from min to min+dynamic range, with no noise imghd5['image/PhotonRateRadianceNoNoise'][...] = \ rad_dynrange * np.ones((imghd5['image/imageSizePixels'][()])) return imghd5
######################################################################################
[docs] def hdf_disk_photon(imghd5,rad_min,rad_dynrange,fracdiameter,fracblur): r"""A generating function to create an image with illuminated circle with blurred boundaries. The function accepts radiance radiant or photon rate minimum and dynamic range units. The equivalent image value is expressed as in the same units as the input This function must be called from rytarggen.create_HDF5_image Args: | imghd5 (handle to hdf5 file): file to which image must be added | rad_min (float): additive minimum radiance value in the image | rad_dynrange (float): multiplicative radiance scale factor (max value) | fracdiameter (float): diameter of the disk as fraction of minimum image size | fracblur (float): blur of the disk as fraction of minimum image size Returns: | nothing: as a side effect a set of photon radiance image files are written Raises: | No exception is raised. Author: CJ Willers """ # convert to radiance values in photon units assigncheck(imghd5, 'image/rad_dynrange', rad_dynrange * imghd5['image/conversion'][()]) assigncheck(imghd5, 'image/rad_min', rad_min * imghd5['image/conversion'][()]) # scale the disk to image size, as fraction maxSize = np.min((imghd5['image/imageSizeRows'][()], imghd5['image/imageSizeCols'][()])) assigncheck(imghd5, 'image/disk_diameter', fracdiameter * maxSize) assigncheck(imghd5, 'image/blur', fracblur * maxSize) #create the disk, normalised to unity varx = np.linspace(-imghd5['image/imageSizeCols'][()] / 2, imghd5['image/imageSizeCols'][()] / 2, imghd5['image/imageSizePixels'][()][1]) vary = np.linspace(-imghd5['image/imageSizeRows'][()] / 2, imghd5['image/imageSizeRows'][()] / 2, imghd5['image/imageSizePixels'][()][0]) x1, y1 = np.meshgrid(varx, vary) delta_x = varx[1] - varx[0] delta_y = vary[1] - vary[0] # Inline circ — avoids ryutils.circ which is absent from some installed pyradi versions. # circ(x, y, d): 1 inside radius d/2, 0.5 on boundary, 0 outside. _d0 = imghd5['image/disk_diameter'][()] _r0 = np.sqrt(x1**2 + y1**2) Uin = np.where(_r0 < _d0/2., 1.0, np.where(_r0 == _d0/2., 0.5, 0.0)) #create blur disk normalised to unity dia = np.max((1, 2 * round(imghd5['image/blur'][()] / np.max((delta_x,delta_y))))) varx = np.linspace(-dia, dia, int(2 * dia)) vary = np.linspace(-dia, dia, int(2 * dia)) x, y = np.meshgrid(varx, vary) _r1 = np.sqrt(x**2 + y**2) H = np.where(_r1 < dia/2., 1.0, np.where(_r1 == dia/2., 0.5, 0.0)) # convolve disk with blur NormLin = (np.abs(signal.convolve2d(Uin, H, mode='same'))/np.sum(H)) ** 2 # create the photon rate radiance image from min to min+dynamic range, with no noise imghd5['image/PhotonRateRadianceNoNoise'][...] = \ (imghd5['image/rad_min'][()] + NormLin * imghd5['image/rad_dynrange'][()] ) imghd5.flush() return imghd5
######################################################################################
[docs] def hdf_stairs(imghd5,rad_min,rad_dynrange,steps,imtype): r"""A generating function to create a staircase image, with log/linear and prescribed step count. The increment along stairs can be linear or logarithmic. The function accepts radiance radiant or photon rate minimum and dynamic range units. The equivalent image value is expressed as in lux units. This function must be called from rytarggen.create_HDF5_image Args: | imghd5 (handle to hdf5 file): file to which image must be added | rad_min (float): additive minimum radiance value in the image | rad_dynrange (float): radiance multiplicative scale factor (max value) | steps (int): number of steps in the image | imtype (string): string to define the type of image to be created ['stairslin','stairslog'] Returns: | nothing: as a side effect a set of photon radiance image files are written Raises: | No exception is raised. Author: CJ Willers """ # convert to radiance values in photon units assigncheck(imghd5, 'image/rad_dynrange', rad_dynrange * imghd5['image/conversion'][()]) assigncheck(imghd5, 'image/rad_min', rad_min * imghd5['image/conversion'][()]) assigncheck(imghd5, 'image/steps', steps) assigncheck(imghd5, 'image/imtype', imtype) #Create the stairs spatial definition size = imghd5['image/imageSizePixels'][()][1] if imtype in ['stairslin']: varx = np.linspace(0,size-1,size) else: varx = np.logspace(-1,np.log10(size-1),size) varx = ((varx/(size/steps)).astype(int)).astype(float) / steps varx = varx / np.max(varx) vary = np.linspace( - imghd5['image/imageSizeRows'][()]/2, imghd5['image/imageSizeRows'][()]/2, imghd5['image/imageSizePixels'][()][0]) vary = np.where(np.abs(vary)<imghd5['image/imageSizeRows'][()]/3.,1.,0.) x, y = np.meshgrid(varx,vary) NormLin = y * x * np.ones(x.shape) # create the photon rate radiance image from min to min+dynamic range, with no noise imghd5['image/PhotonRateRadianceNoNoise'][...] = \ (imghd5['image/rad_min'][()] + NormLin * imghd5['image/rad_dynrange'][()] ) imghd5.flush() return imghd5
######################################################################################
[docs] def hdf_Raw(imghd5,filename,inputSize,outputSize,rad_min=-1,rad_dynrange=-1, imgNum=0, inputOrigin=[0,0],blocksize=[1,1],sigma=0): r"""A generating function to create a photon rate image from raw image. The output image is extracted from the raw image, with blocks of raw image pixels averaged to single output image pixels. inputOrigin (lowest row,col values) defines from where in the raw input image the slicing takes place. blocksize defines how many raw image pixels must be averaged/aggregated together to define a single output image pixel, resolution is lowered by this factor. sigma is the kernel size to be used in scipy.filters.gaussian_filter. The subsampled image will be rescaled to rad_min + rad_dynrange. The raw image sequence must be of type np.float64 with no header or footer. The function accepts radiant or photon rate minimum and dynamic range units. The equivalent image value is expressed as in the same units as the output image This function must be called from rytarggen.create_HDF5_image Args: | imghd5 (handle to hdf5 file): file to which image must be added | filename (string): Raw file filename, data must be np.float64 | rad_min (float): additive minimum radiance value in the image, -1 to not use scaling | inputSize ([int,int]): raw image size, number of rows,cols | outputSize ([int,int]): size of the output image row,cols | rad_dynrange (float): multiplicative radiance scale factor (max value), -1 to not use scaling | imgNum (int): image number to be loaded from the image sequence | inputOrigin ([int,int]): raw image row,col where the image must be extracted from | blocksize ([int,int]): row,col blocksize in raw image to be averaged to single output pixel | sigma (float): gaussian spatial filter kernel rms size in raw image pixels Returns: | nothing: as a side effect a set of photon radiance image files are written Raises: | No exception is raised. Author: CJ Willers """ assigncheck(imghd5, 'image/rad_dynrange', rad_dynrange * imghd5['image/conversion'][()]) assigncheck(imghd5, 'image/rad_min', rad_min * imghd5['image/conversion'][()]) assigncheck(imghd5, 'image/filename', filename) # read the imgNum'th raw image frame from file nfr,img = ryfiles.readRawFrames(filename, rows=inputSize[0], cols=inputSize[1], vartype=np.float64, loadFrames=[imgNum]) # print(nfr,img.shape) if nfr > 0: #extract the smaller raw image and coalesce/blur img = ryutils.blurryextract(img[0,:,:], inputOrigin=inputOrigin, outputSize=outputSize, sigma=sigma, blocksize=blocksize) # save the original input image imghd5['image/equivalentSignal'][...] = img img = img / imghd5['image/joule_per_photon'][()] if imghd5['image/rad_min'][()] < 0. and imghd5['image/rad_dynrange'][()] < 0.: # don't scale the input image # create photon rate radiance image from input, no scaling, with no noise PhotonRateRadianceNoNoise = img else: # scale the input image NormLin = (img - np.min(img)) / (np.max(img)- np.min(img)) # create photon rate radiance image from min to min+dynamic range, with no noise PhotonRateRadianceNoNoise = imghd5['image/rad_min'][()] \ + NormLin * imghd5['image/rad_dynrange'][()] else: print(f'Unknown image type or file not successfully read: {filename}\n no image file created') return imghd5 # save the no noise image imghd5['image/PhotonRateRadianceNoNoise'][...] = PhotonRateRadianceNoNoise return imghd5
######################################################################################
[docs] def create_HDF5_image(imageName, numPixels, fn, kwargs, wavelength, saveNoiseImage=False,saveEquivImage=False, equivalentSignalType='',equivalentSignalUnit='', LinUnits='', seedval=0,fintp=None, fileHandle=None,noSpaces=False): r"""This routine serves as calling function to a generating function to create images. This function expects that the calling function will return photon rate images, irrespective of the units of the min/max values used to create the image. Each generating function creates an image of a different type, taking as input radiant, photon rate, temperature or some other unit, as coded in the generating function. if fileHandle is None, the file is created anew, if fileHandle is not None, use as existing file handle This calling function sets up the image and writes common information and then calls the generating function of add the specific image type with radiometric units required. The calling function and its arguments must be given as arguments on this functions argument list. The image file is in HDF5 format, containing the * input parameters to the image creation process * the image in photon rate units without photon noise * the image in photon rate units with photon noise * the image in some equivalent input unit radiant, photometric or photon rate units. The general procedure in the generating function is to convert the radiance input values in units [W/m2] or [q/m2.s)] to photon rate radiance in units [q/m2.s)] by converting by one photon's energy at the stated wavelength by :math:`Q_p=\frac{h\cdot c}{\lambda}`, where :math:`\lambda` is wavelength, :math:`h` is Planck's constant and :math:`c` is the speed of light. The conversion is done at a single wavelength, which is not very accurate. The better procedure is to create the photon rate image directly using a spectral integral. The following minimum HDF5 entries are required by pyradi.rystare: | ``'image/imageName'`` (string): the image name | ``'image/PhotonRateRadianceNoNoise'`` np.array[M,N]: a float array with the image pixel values no noise | ``'image/PhotonRateRadiance'`` np.array[M,N]: a float array with the image pixel values with noise | ``'image/imageSizePixels'``: ([int, int]): number of pixels [row,col] | ``'image/imageFilename'`` (string): the image file name | ``'image/wavelength'`` (float): where photon rate calcs are done um | ``'image/imageSizeRows'`` (int): the number of image rows | ``'image/imageSizeCols'`` (int): the number of image cols | ``'image/imageSizeDiagonal'`` (float): the FPA diagonal size in mm | ``'image/equivalentSignal'`` (float): the equivalent input signal, e.g. temperature or lux (optional) | ``'image/irradianceWatts'`` (float): the exitance in the image W/m2 (optional) | ``'image/temperature'`` (float): the maximum target temperature in the image K (optional) A few minimum entries are required, but you can add any information you wish to the generaring function, by adding the additional information to the generating function's kwargs. Args: | imageName (string/hdffile): the image name, used to form the filename. | numPixels ([int, int]): number of pixels [row,col]. | fn (Python function): the generating function to be used to calculate the image. | kwargs (dictionary): kwargs to the passed to the generating function. | wavelength (float): wavelength where photon rate calcs are done in [m] | equivalentSignalType (str): type of the equivalent input scale (e.g., irradiance, temperature) | equivalentSignalUnit (str): units of the equivalent scale (e.g., W/m2, K, lux) | LinUnits (str): Lin units and definition separated with : (e.g., 'W/(m2.sr)', 'q/(s.m2.sr)') | seedval (int): a seed for the photon noise generator | saveNoiseImage (bool): save the noisy image to HDF5 file | saveEquivImage (bool): save the equivalent image to HDF5 file | fintp (function or str): interpolation function to map from radiance to equivalent unit, | if string 'original', then keep the original input image written by hdf_raw() | fileHandle (filehandle): create new file None, use otherwise | noSpaces (bool): if True replace all spaces and decimals in filename with '-' Returns: | string/hdffile (string): hdf5 filename or open file | : as a side effect an image file is written Raises: | No exception is raised. Author: CJ Willers """ hdffilename = f'image-{imageName}-{numPixels[0]}-{numPixels[1]}' if noSpaces: hdffilename = hdffilename.replace(' ','-') hdffilename = hdffilename.replace('.','-') hdffilename = f'{hdffilename}.hdf5' if fileHandle is None: imghd5 = ryhdf5.erase_create_HDF(hdffilename) else: imghd5 = fileHandle assigncheck(imghd5, 'image/imageName', imageName) assigncheck(imghd5, 'image/imageSizePixels', numPixels) assigncheck(imghd5, 'image/imageSizeRows', numPixels[0]) assigncheck(imghd5, 'image/imageSizeCols', numPixels[1]) assigncheck(imghd5, 'image/imageFilename', hdffilename) assigncheck(imghd5, 'image/equivalentSignalType', equivalentSignalType) assigncheck(imghd5, 'image/equivalentSignalUnit', equivalentSignalUnit) assigncheck(imghd5, 'image/LinUnits', LinUnits) assigncheck(imghd5, 'image/saveNoiseImage', saveNoiseImage) assigncheck(imghd5, 'image/saveEquivImage', saveEquivImage) if 'image/equivalentSignal' not in imghd5: dset = imghd5.create_dataset('image/equivalentSignal', numPixels, dtype='float', compression="gzip") if 'image/PhotonRateRadianceNoNoise' not in imghd5: dset = imghd5.create_dataset('image/PhotonRateRadianceNoNoise', numPixels, dtype='float', compression="gzip") if 'image/PhotonRateRadiance' not in imghd5: dset = imghd5.create_dataset('image/PhotonRateRadiance', numPixels, dtype='float', compression="gzip") #photon rate radiance in the image ph/(m2.s), with no photon noise, will be filled by rendering function imghd5['image/PhotonRateRadianceNoNoise'][...] = np.zeros(( imghd5['image/imageSizePixels'][()][0], imghd5['image/imageSizePixels'][()][1])) assigncheck(imghd5, 'image/wavelength', wavelength) # joule/photon factor to convert between W/m2 and q/(s.m2) if isinstance(imghd5['image/wavelength'][()], float): assigncheck(imghd5, 'image/joule_per_photon', const.h * const.c / imghd5['image/wavelength'][()]) else: assigncheck(imghd5, 'image/joule_per_photon', const.h * const.c / np.mean(imghd5['image/wavelength'][()])) conversion = (1.0 if b'q/' in imghd5['image/LinUnits'][()][:3] else 1. / imghd5['image/joule_per_photon'][()]) assigncheck(imghd5, 'image/conversion', conversion) kwargs['imghd5'] = imghd5 # call the function that actually generates the image imghd5 = fn(**kwargs) # add photon noise in the signal # Inline Poisson sampling — avoids ryutils.poissonarray (absent from some installed pyradi versions). # For large means (>1000) the normal approximation prevents Poisson overflow. if imghd5['image/saveNoiseImage'][()]: np.random.seed(seedval) _inp = imghd5['image/PhotonRateRadianceNoNoise'][()] _tpoint = 1000 _sdelta = 1e-10 _outp = (_inp <= _tpoint) * np.random.poisson(_inp * (_inp <= _tpoint)) \ + ((_inp > _tpoint) & (_inp != 0)) * np.random.normal(loc=_inp, scale=np.sqrt(_inp + _sdelta)) imghd5['image/PhotonRateRadiance'][...] = np.where(_inp == 0, 0.0, _outp) # save equivalent signal if imghd5['image/saveEquivImage'][()]: if fintp is None: # save nonoise image as equivalent signal imghd5['image/equivalentSignal'][...] = imghd5['image/PhotonRateRadianceNoNoise'][()] else: if isinstance(fintp, str): # if string, keep the value written by hdf_raw pass else: # save equivalent signal (e.g., temperature or lux), by interpolation imghd5['image/equivalentSignal'][...] = fintp(imghd5['image/PhotonRateRadianceNoNoise'][()]) # save the interpolation function to hdf5 assigncheck(imghd5, 'image/interpolate_x', fintp.x) assigncheck(imghd5, 'image/interpolate_y', fintp.y) imghd5.flush() imghd5.close() return hdffilename
######################################################################################
[docs] def analyse_HDF5_image(imghd5,plotfile,gwidh=12,gheit=8): r"""Summarise the image properties and statistics Args: | imghd5 (handle to an open hdf5 file): file to be analysed | plotfile(string): filename for plot graphics | gwidh (float): graph width in inches | gheit (float): graph height in inches Returns: | nothing: as a side effect a set properties are written and graphs created Raises: | No exception is raised. Author: CJ Willers """ from scipy import stats import pyradi.ryplot as ryplot #calculate and display values of these variables elements = ['image/imageFilename','image/imageName','image/filename','image/rad_dynrange', 'image/rad_min','image/irrad_dynrange','image/irrad_min','image/disk_diameter','image/blur', 'image/blur','image/steps','image/imtype','image/imageSizePixels','image/pixelPitch', 'image/imageSizeRows','image/imageSizeCols','image/imageSizeDiagonal', 'image/equivalentSignalType','image/equivalentSignalUnit','image/LinUnits','image/EinUnits', 'image/saveNoiseImage','image/saveEquivImage','image/joule_per_photon', 'image/conversion', ] for item in elements: if item in imghd5: print(f'{item:30s} : {imghd5[item][()]}') # wavelength as scalar or vector print(imghd5) if isinstance( imghd5['image/wavelength'][()], float): print(f"{'wavelength':30s} : {imghd5['image/wavelength'][()]}") else: print(f"{'wavelength (mean)':30s} : {np.mean(imghd5['image/wavelength'][()])}") #calculate and display statistics of these variables elements = ['image/PhotonRateRadianceNoNoise','image/PhotonRateRadiance', 'image/PhotonRateIrradianceNoNoise','image/PhotonRateIrradiance','image/equivalentSignal' ] for item in elements: if item in imghd5: print(f'\nStatistics for {item}:') print(stats.describe(imghd5[item][()],axis=None)) # plot the images p = ryplot.Plotter(1,3,1,plotfile, figsize=(gwidh,gheit),doWarning=False) for item in ['image/PhotonRateRadianceNoNoise','image/PhotonRateIrradianceNoNoise']: if item in imghd5: p.showImage(1,imghd5[item][()],item,cbarshow=True) for item in ['image/PhotonRateRadiance','image/PhotonRateIrradiance']: if item in imghd5: p.showImage(2,imghd5[item][()],item,cbarshow=True) if 'image/equivalentSignal' in imghd5: p.showImage(3,imghd5['image/equivalentSignal'][()],'image/equivalentSignal',cbarshow=True) p.saveFig(f'{plotfile}.png') # plot interpolation function if 'image/interpolate_x' in imghd5: q = ryplot.Plotter(1,1,1,plotfile, figsize=(12,6),doWarning=False) q.plot(1,imghd5['image/interpolate_x'][()],imghd5['image/interpolate_y'][()]) q.saveFig(f'{plotfile}-lookup.png') print(50*'='+'\n\n')
######################################################################################
[docs] def analyse_HDF5_imageFile(hdffilename,gwidh=12,gheit=8): r"""Summarise the image properties and statistics Args: | imghd5 (hdf5 filename): file to be analysed | gwidh (float): graph width in inches | gheit (float): graph height in inches Returns: | nothing: as a side effect a set properties are written and graphs created Raises: | No exception is raised. Author: CJ Willers """ imghd5 = ryhdf5.open_HDF(hdffilename) analyse_HDF5_image(imghd5,plotfile=hdffilename[:-5],gwidh=gwidh,gheit=gheit) imghd5.close()
######################################################################################
[docs] def calcTemperatureEquivalent(wavelength, sysresp, tmin, tmax): """Calc the interpolation functions between temperature and photon-rate radiance. Returns two paired interp1d functions: - ``fintpLE``: photon-rate radiance → temperature - ``fintpEL``: temperature → photon-rate radiance Args: | wavelength (np.array): wavelength (um), passed directly to :func:`ryplanck.planck` with ``type='ql'`` which expects um. | sysresp (np.array): system spectral response vector (same length as wavelength). | tmin (float): minimum temperature [K] for the lookup table. | tmax (float): maximum temperature [K] for the lookup table. Returns: | (fintpLE, fintpEL): pair of scipy interp1d functions; fintpLE maps photon-rate radiance | to temperature [K], fintpEL maps temperature [K] to photon-rate radiance. Raises: | No exception is raised. Note: Wavelength units: ``wavelength`` must be in **micrometres** because it is passed directly to ``ryplanck.planck(type="ql")`` (``planckql``), which expects µm. This differs from ``calcLuxEquivalent``, which expects wavelength in SI metres and converts to µm internally. Author: CJ Willers """ wavelength = wavelength.reshape(-1, 1) sysresp = sysresp.reshape(-1, 1) temp = np.linspace(0.99*float(tmin), 1.01*float(tmax), 100).reshape(-1,1) # radiance in q/(s.m2) rad = np.trapezoid( sysresp * _ryplanckql(wavelength, temp), wavelength, axis=0).reshape(-1, 1) / np.pi fintpLE = interpolate.interp1d(rad.reshape(-1,), temp.reshape(-1,)) fintpEL = interpolate.interp1d(temp.reshape(-1,), rad.reshape(-1,)) return fintpLE,fintpEL
######################################################################################
[docs] def calcLuxEquivalent(wavelength, rad_min, rad_dynrange, units): """Calc the interpolation function between lux and photon rate radiance. Assuming single-wavelength colour, the specified wavelength is used to calculate the lux-equivalent for the given radiance input range. The returned interpolation function maps photon-rate radiance [q/(s.m²)] → lux, regardless of the ``units`` parameter. The ``units`` parameter only controls how ``rad_min`` and ``rad_dynrange`` are interpreted (W or q/s) when building the lookup table. Args: | wavelength (float): representative wavelength in **SI metres** (m). | rad_min (float): minimum radiance value for the lookup table, | in watts [W/m²] when ``units`` does not contain 'q', or in | photon-rate units [q/(s.m²)] when 'q' is present in ``units``. | rad_dynrange (float): radiance dynamic range (max − min) in the | same units as ``rad_min``. | units (string): radiance unit string; if it contains 'q' the inputs | are treated as photon-rate, otherwise as watts. Returns: | fintp: scipy interp1d mapping photon-rate radiance → lux. Raises: | No exception is raised. Note: Wavelength units inconsistency: this function expects ``wavelength`` in **SI metres** (it applies ``*1e6`` internally to convert to µm for the photopic-response formula). This differs from ``calcTemperatureEquivalent``, which passes ``wavelength`` directly to ``ryplanck.planck`` and therefore expects µm. Author: CJ Willers """ if 'q' in units: conversion = wavelength / (const.h * const.c) else: conversion = 1. Wm2tolux = 683 * 1.019 * np.exp(-285.51 * (wavelength*1e6 - 0.5591)**2) # convert from q/s to W rad_minW = rad_min / conversion rad_dynrangeW = rad_dynrange / conversion radW = np.linspace(0.99*rad_minW, 1.01*(rad_minW+rad_dynrangeW), 1000) lux = Wm2tolux * radW # convert from W back to q/s when setting up the function fintp = interpolate.interp1d((radW*wavelength / (const.h * const.c)).reshape(-1), lux.reshape(-1)) return fintp
################################################################ ################################################################ ## if __name__ == '__init__': pass if __name__ == '__main__': import os.path import pyradi.ryfiles as ryfiles import pyradi.ryutils as ryutils doAll = False numPixels = [256, 256] # [ROW, COLUMN] size wavelength = 0.55e-6 #---------- create test images --------------------- if True: #create a zero uniform photon rate image # input in q/(s.m2), output in q/(s.m2), equivalent in q/(s.m2) units filename = create_HDF5_image(imageName='Zero', numPixels=numPixels,wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_Uniform, kwargs={'rad_dynrange':0}, equivalentSignalType='Irradiance',equivalentSignalUnit='q/(s.m2.sr)', LinUnits='q/(s.m2.sr)', seedval=0,fintp=None ) analyse_HDF5_imageFile(filename) #create a uniform photon rate image with nonzero value, from radiance input # input in q/(s.m2), output in q/(s.m2), equivalent in q/(s.m2) units filename = create_HDF5_image(imageName='Uniform', numPixels=numPixels,wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_Uniform, kwargs={'rad_dynrange':1.3e17}, equivalentSignalType='Irradiance',equivalentSignalUnit='q/(s.m2.sr)', LinUnits='q/(s.m2.sr)', seedval=0,fintp=None ) analyse_HDF5_imageFile(filename) # create a disk photon rate image, scaled from unity base, by min + dynamic range # input in q/(s.m2), output in q/(s.m2), equivalent in q/(s.m2) units filename = create_HDF5_image(imageName='Disk', numPixels=numPixels,wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_disk_photon, kwargs={'rad_min':0.0,'rad_dynrange':1.3e17, 'fracdiameter':0.7,'fracblur':0.2}, equivalentSignalType='Irradiance',equivalentSignalUnit='q/(s.m2.sr)', LinUnits='q/(s.m2.sr)', seedval=0,fintp=None ) analyse_HDF5_imageFile(filename) # create stair photon rate image, scaled from unity base, by min + dynamic range # input in W/m2, output in q/(s.m2), equivalent in lux units rad_min = 9.659e-4 rad_dynrange = 0.483 LinUnits = 'W/(m2.sr)' fintp = calcLuxEquivalent(wavelength,rad_min,rad_dynrange,LinUnits) filename = create_HDF5_image(imageName='Stairslin-10', numPixels=[250, 250], wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_stairs, kwargs={'rad_min':rad_min,'rad_dynrange':rad_dynrange, 'imtype':'stairslin','steps':10}, equivalentSignalType='Irradiance',equivalentSignalUnit='lux', LinUnits=LinUnits, seedval=0,fintp=fintp ) analyse_HDF5_imageFile(filename) # create stair photon rate image, scaled from unity base, by min + dynamic range # input in W/m2, output in q/(s.m2), equivalent in lux units rad_min = 9.659e-4 rad_dynrange = 0.483 LinUnits = 'W/(m2.sr)' fintp = calcLuxEquivalent(wavelength,rad_min,rad_dynrange,LinUnits) filename = create_HDF5_image(imageName='Stairslin-40', numPixels=[100,520],wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_stairs, kwargs={'rad_min':rad_min,'rad_dynrange':rad_dynrange, 'imtype':'stairslin','steps':40}, equivalentSignalType='Irradiance',equivalentSignalUnit='lux', LinUnits=LinUnits, seedval=0,fintp=fintp ) analyse_HDF5_imageFile(filename) # create stair photon rate image, scaled from unity base, by min + dynamic range # low light level input in W/m2, output in q/(s.m2), equivalent in lux units rad_min =9.659e-6 rad_dynrange = 4.829e-3 LinUnits = 'W/(m2.sr)' fintp = calcLuxEquivalent(wavelength,rad_min,rad_dynrange,LinUnits) filename = create_HDF5_image(imageName='Stairslin-LowLight-40', numPixels=[100,520],wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_stairs, kwargs={'rad_min':rad_min,'rad_dynrange':rad_dynrange, 'imtype':'stairslin','steps':40}, equivalentSignalType='Irradiance',equivalentSignalUnit='lux', LinUnits='W/(m2.sr)', seedval=0,fintp=fintp ) analyse_HDF5_imageFile(filename) # create photon rate image from raw, unscaled filename = create_HDF5_image(imageName='PtaInd-13Dec14h00X', numPixels=[512,512],wavelength=4.5e-6, saveNoiseImage=True,saveEquivImage=True, fn=hdf_Raw, kwargs={'filename':'data/PtaInd-13Dec14h00X.bin', 'inputSize':[512,512],'outputSize':[512,512], 'rad_min':-1,'rad_dynrange':-1,'imgNum':0}, equivalentSignalType='Irradiance',equivalentSignalUnit='W/m2', LinUnits='W/(m2.sr)', seedval=0,fintp=None ) analyse_HDF5_imageFile(filename) # def hdf_Raw(imghd5,filename,inputSize,outputSize,rad_min=-1,rad_dynrange=-1, imgNum=0, # inputOrigin=[0,0],blocksize=[1,1],sigma=0): # create photon rate image from raw, unscaled filename = create_HDF5_image(imageName='StairIR-raw', numPixels=[100,256],wavelength=4.5e-6, saveNoiseImage=True,saveEquivImage=True, fn=hdf_Raw, kwargs={'filename':'data/StairIR-raw.double', 'inputSize':[100,256],'outputSize':[100,256], 'rad_min':-1,'rad_dynrange':-1,'imgNum':0}, equivalentSignalType='Irradiance',equivalentSignalUnit='W/m2', LinUnits='W/(m2.sr)', seedval=0,fintp=None ) analyse_HDF5_imageFile(filename) #create an infrared image with lin stairs # work in temperature tmin = 293 # 20 deg C at minimum level tmax = 313 # 40 deg C at maximum level # do a wideband spectral integral wavelength = np.linspace(3.4,4.9,100) sysresp = np.ones(wavelength.shape) fintpLE,fintpEL = calcTemperatureEquivalent(wavelength,sysresp,tmin,tmax) filename = create_HDF5_image(imageName='StairslinIR-40', numPixels=[100,520],wavelength=wavelength, saveNoiseImage=True,saveEquivImage=True, fn=hdf_stairs, kwargs={'rad_min':fintpEL(tmin), 'rad_dynrange':fintpEL(tmax) -fintpEL(tmin), 'imtype':'stairslin','steps':40}, equivalentSignalType='Temperature',equivalentSignalUnit='K', LinUnits='q/(s.m2.sr)', seedval=0,fintp=fintpLE ) analyse_HDF5_imageFile(filename,15,7) #create a scaled infrared image derived from raw input image # use temperatures to define min and max values to which the # raw input image is scaled: minImg<->tmin and maxImg<->tmax tmin = 280 # K at minimum level tmax = 320 # K at maximum level # do a wideband spectral integral wavelength = np.linspace(3.7,4.8,100) sysresp = np.ones(wavelength.shape) fintpLE,fintpEL = calcTemperatureEquivalent(wavelength,sysresp,tmin,tmax) # create photon rate image from raw, scaled filename = create_HDF5_image(imageName='PtaInd-13Dec14h00XScaled', numPixels=[512,512],wavelength=4.5e-6, saveNoiseImage=True,saveEquivImage=True, fn=hdf_Raw, kwargs={'filename':'data/PtaInd-13Dec14h00X.bin', 'inputSize':[512,512],'outputSize':[512,512], 'rad_min':fintpEL(tmin),'rad_dynrange':fintpEL(tmax) -fintpEL(tmin), 'imgNum':0}, equivalentSignalType='Temperature',equivalentSignalUnit='K', LinUnits='q/(s.m2.sr)', seedval=0,fintp=fintpLE ) analyse_HDF5_imageFile(filename,15,7) #create a uniform photon rate image with nonzero value, for given temperature # input in q/(s.m2), output in q/(s.m2), equivalent in q/(s.m2) units