Source code for pyradi.ry3dnoise

################################################################
# 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 MS Willers,
# Portions created by MS Willers are Copyright (C) 2006-2012
# All Rights Reserved.

# Contributor(s): _______________________________________________.
################################################################
"""
This module provides a set of functions to aid in the calculation of 3D noise parameters from
noise images. The functions are based on the work done by John D'Agostino and Curtis Webb.
For details see "3-D Analysis Framwork and Measurement Methodology for Imaging System
Nioise" p110-121 in "Infrared Imaging Systems: Design, Analysis, Modelling, and Testing II",
Holst, G. C., ed., Volume 1488, SPIE (1991).

See the __main__ function for examples of use.

This package was partly developed to provide additional material in support of students 
and readers of the book Electro-Optical System Analysis and Design: A Radiometry 
Perspective,  Cornelius J. Willers, ISBN 9780819495693, SPIE Monograph Volume
PM236, SPIE Press, 2013.  http://spie.org/x648.html?product_id=2021423&origin_id=x646
"""




__version__ = '0.1.0'
__author__='MS Willers'
__all__=['oprDT', 'oprDV', 'oprDH', 'oprSDT', 'oprSDV','oprSDH', 'getS','getNT','getNH',
    'getNV', 'getNVH','getNTV','getNTH', 'getNTVH', 'getTotal']

import sys
import numpy as np


################################################################
##
[docs] def oprDV(imgSeq): """ Operator DV averages over rows for each pixel. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | numpy array of dimensions (frames,1,cols) Raises: | No exception is raised. """ frames, rows, cols = imgSeq.shape im = np.mean(imgSeq, axis=1, dtype=np.float64) imShaped = im.reshape(frames*cols) return imShaped.reshape(frames, 1, cols)
################################################################ ##
[docs] def oprDH(imgSeq): """ Operator DH averages over columns for each pixel. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | numpy array of dimensions (frames,rows,1) Raises: | No exception is raised. """ frames, rows, cols = imgSeq.shape im = np.mean(imgSeq, axis=2, dtype=np.float64) imShaped = im.reshape(frames*rows) return imShaped.reshape(frames, rows, 1)
################################################################ ##
[docs] def oprDT(imgSeq): """ Operator DT averages over frames for each pixel. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | numpy array of dimensions (1,rows,cols) Raises: | No exception is raised. """ frames, rows, cols = imgSeq.shape im = np.mean(imgSeq, axis=0, dtype=np.float64) imShaped = im.reshape(rows*cols) return imShaped.reshape(1, rows, cols)
################################################################ ##
[docs] def oprSDT(imgSeq): """ Operator SDT first averages over frames for each pixel. The result is subtracted from all images. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | numpy array of dimensions (frames,rows,cols) Raises: | No exception is raised. """ return imgSeq - oprDT(imgSeq)
################################################################ ##
[docs] def oprSDH(imgSeq): """ Operator SDH first averages over columns for each pixel. The result is subtracted from all images. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | numpy array of dimensions (frames,rows,cols) Raises: | No exception is raised. """ return imgSeq - oprDH(imgSeq)
################################################################ ##
[docs] def oprSDV(imgSeq): """ Operator SDV first averages over rows for each pixel. The result is subtracted from all images. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | numpy array of dimensions (frames,rows,cols) Raises: | No exception is raised. """ return imgSeq - oprDV(imgSeq)
################################################################ ##
[docs] def getS(imgSeq): """ Average over all pixels. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): average of all pixels Raises: | No exception is raised. """ im = oprDT(oprDV(oprDH(imgSeq))) return im[0, 0, 0]
################################################################ ##
[docs] def getNT(imgSeq): """ Average for all pixels as a function of time/frames. Represents noise which consists of fluctuations in the temporal direction affecting the mean of each frame. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): frame-to-frame intensity variation Raises: | No exception is raised. """ im = oprDV(oprDH(oprSDT(imgSeq))) # ddof=1: unbiased variance estimator (divides by N-1). # ddof=0: maximum-likelihood estimate (divides by N). return (np.std(im, dtype=np.float64, ddof=1), im)
################################################################ ##
[docs] def getNVH(imgSeq): """ Average over all frames, for each pixel. Represents non-uniformity spatial noise that does not change from frame-to-frame. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): fixed spatial noise Raises: | No exception is raised. """ im = oprDT(oprSDV(oprSDH(imgSeq))) return (np.std(im, dtype=np.float64, ddof=1),im)
################################################################ ##
[docs] def getNTV(imgSeq): """ Average for each row and frame over all columns. Represents variations in row averages that change from frame-to-frame. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): row temporal noise Raises: | No exception is raised. """ im = oprDH(oprSDT(oprSDV(imgSeq))) return (np.std(im, dtype=np.float64, ddof=1), im)
################################################################ ##
[docs] def getNTH(imgSeq): """ Average for each column and frame over all rows. Represents variations in column averages that change from frame-to-frame. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): column temporal noise Raises: | No exception is raised. """ im = oprDV(oprSDT(oprSDH(imgSeq))) return (np.std(im, dtype=np.float64, ddof=1), im)
################################################################ ##
[docs] def getNV(imgSeq): """ Average for each column over all frames and rows. Represents variations in row averages that are fixed in time. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): fixed row noise Raises: | No exception is raised. """ im = oprDT(oprDH(oprSDV(imgSeq))) return (np.std(im, dtype=np.float64, ddof=1), im)
################################################################ ##
[docs] def getNH(imgSeq): """ Average for each row over all frames and cols. Represents variations in column averages that are fixed in time. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): fixed column noise Raises: | No exception is raised. """ im = oprDT(oprDV(oprSDH(imgSeq))) return (np.std(im, dtype=np.float64, ddof=1), im)
################################################################ ##
[docs] def getNTVH(imgSeq): """ Noise for each row, frame & column. Represents random noise in the detector and electronics. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): temporal pixel noise Raises: | No exception is raised. """ im = oprSDT(oprSDV(oprSDH(imgSeq))) return (np.std(im, dtype=np.float64, ddof=1), im)
################################################################ ##
[docs] def getTotal(imgSeq): """ Total system noise. Args: | imgSeq (np.ndarray): numpy array of dimensions (frames,rows,cols) Returns: | noise (double): total system noise Raises: | No exception is raised. """ nt,im = getNT(imgSeq) nvh,im = getNVH(imgSeq) ntv,im = getNTV(imgSeq) nth,im = getNTH(imgSeq) nv,im = getNV(imgSeq) nh,im = getNH(imgSeq) ntvh,im = getNTVH(imgSeq) return np.sqrt(nt**2 + nvh**2 + ntv**2 + nth**2 + nv**2 + nh**2 + ntvh**2)
################################################################ ## if __name__ == '__main__': import os as _os _clutter = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), 'clutter') _os.makedirs(_clutter, exist_ok=True) def _c(fname): """Redirect an output filename into the clutter folder.""" return _os.path.join(_clutter, _os.path.basename(str(fname))) import pyradi.ryfiles as ryfiles import pyradi.ryplot as ryplot import pyradi.ryptw as ryptw import pyradi.ryutils as ryutils #--------------------- simulated sensor noise data from raw format ------------------- rows = 100 cols = 100 vartype = np.uint16 imagefile = 'data/sensornoise.raw' outfilename = 'sensornoise.txt' # load images framesToLoad = list(range(1, 21, 1)) frames, img = ryfiles.readRawFrames(imagefile, rows, cols, vartype, framesToLoad) if frames > 0: # show something P = ryplot.Plotter(1, 1, 1, 'Simulated noise', figsize=(12, 8)) P.showImage(1, img[0]) P.saveFig(_c('rawframe0.png')) with open(_c(outfilename), 'w') as outfile: outfile.write(f'\n{frames} Frames read from {imagefile}\n') outfile.write(f'\nImage average S : {getS(img):10.3f} \n') outfile.write(f'Total system noise : {getTotal(img):10.3f} \n\n') outfile.write('Fixed/spatial noise | Temporal noise | Variation effect\n') outfile.write('---------------------|---------------------|-----------------\n') outfile.write(f'Nh : {getNH(img)[0]:10.3f} | Nth : {getNTH(img)[0]:10.3f} | Column \n') outfile.write(f'Nv : {getNV(img)[0]:10.3f} | Ntv : {getNTV(img)[0]:10.3f} | Row \n') outfile.write(f'Nvh : {getNVH(img)[0]:10.3f} | Ntvh : {getNTVH(img)[0]:10.3f} | Pixel \n') outfile.write(f' | Nt : {getNT(img)[0]:10.3f} | Frame \n') print(f'\n({frames}, {rows}, {cols}) (frames, rows, cols) processed from {imagefile} - see results in {outfilename}\n') else: print('Error in reading noise images data') NH = getNH(img)[1].reshape(cols).reshape(1,cols) NTH = getNTH(img)[1].reshape(frames*cols).reshape(frames,cols) NV = getNV(img)[1].reshape(rows).reshape(rows,1) NTV = getNTV(img)[1].reshape(frames*rows).reshape(rows,frames) NVH = getNVH(img)[1].reshape(rows*cols).reshape(rows,cols) NTVH = getNTVH(img)[1][0,:,:] NT = getNT(img)[1].reshape(frames).reshape(frames,1) P = ryplot.Plotter(1, 3, 3,'Simulated Image Sequence 3-D Noise Components', figsize=(12, 12)) P.showImage(1, NH, ptitle=f'Nh: Fixed Column: Avg(vt) {ryutils.intify_tuple(NH.shape)}', titlefsize=10) P.showImage(2, NTH, ptitle=f'Nth: Temporal Column: Avg(v) {ryutils.intify_tuple(NTH.shape)}', titlefsize=10) P.showImage(4, NV, ptitle=f'Nv: Fixed Row: Avg(ht) {ryutils.intify_tuple(NV.shape)}', titlefsize=10) P.showImage(5, NTV, ptitle=f'Ntv: Temporal Row: Avg(h) {ryutils.intify_tuple(NTV.shape)}', titlefsize=10) P.showImage(8, NVH, ptitle=f'Nvh: Fixed Pixel: Avg(t)\nPixel Non-uniformity {ryutils.intify_tuple(NVH.shape)}', titlefsize=10) P.showImage(9, NTVH, ptitle=f'Ntvh: Temporal: Avg()\nRandom noise {ryutils.intify_tuple(NTVH.shape)}', titlefsize=10) P.showImage(7, NT, ptitle=f'Nt: Temporal Frame: Avg(hv) {ryutils.intify_tuple(NT.shape)}', titlefsize=10) P.showImage(3, img[0,:,:], ptitle=f'First Frame {ryutils.intify_tuple(img[0, :, :].shape)}', titlefsize=10) P.showImage(6, img[-1,:,:], ptitle=f'Last Frame {ryutils.intify_tuple(img[-1, :, :].shape)}', titlefsize=10) P.getPlot().show() P.saveFig(_c('simnoise.png')) #--------------------- Jade ptw file with noise data -------------------------------- ptwfile = 'data/PyradiSampleMWIR.ptw' outfilename = 'PyradiSampleMWIR.txt' header = ryptw.readPTWHeader(ptwfile) rows = header.h_Rows cols = header.h_Cols framesToLoad = list(range(1, 101, 1)) frames = len(framesToLoad) data, _ = ryptw.getPTWFrame(header, framesToLoad[0]) for frame in framesToLoad[1:]: f, _ = ryptw.getPTWFrame(header, frame) data = np.concatenate((data, f)) img = data.reshape(frames, rows, cols) # show something P = ryplot.Plotter(1, 1, 1, 'Measured MWIR noise', figsize=(12, 8)) P.showImage(1, img[0]) P.getPlot().show() P.saveFig(_c('ptwframe0.png')) with open(_c(outfilename), 'w') as outfile: outfile.write(f'\n{frames} Frames read from {ptwfile}\n') outfile.write(f'\nImage average S : {getS(img):10.3f} \n') outfile.write(f'Total system noise : {getTotal(img):10.3f} \n\n') outfile.write('Fixed/spatial noise | Temporal noise | Variation effect\n') outfile.write('---------------------|---------------------|-----------------\n') outfile.write(f'Nh : {getNH(img)[0]:10.3f} | Nth : {getNTH(img)[0]:10.3f} | Column \n') outfile.write(f'Nv : {getNV(img)[0]:10.3f} | Ntv : {getNTV(img)[0]:10.3f} | Row \n') outfile.write(f'Nvh : {getNVH(img)[0]:10.3f} | Ntvh : {getNTVH(img)[0]:10.3f} | Pixel \n') outfile.write(f' | Nt : {getNT(img)[0]:10.3f} | Frame \n') print(f'\n({frames}, {rows}, {cols}) (frames, rows, cols) processed from {ptwfile} - see results in {outfilename}\n') NH = getNH(img)[1].reshape(cols).reshape(1,cols) NTH = getNTH(img)[1].reshape(frames*cols).reshape(frames,cols) NV = getNV(img)[1].reshape(rows).reshape(rows,1) NTV = getNTV(img)[1].reshape(frames*rows).reshape(frames,rows) NVH = getNVH(img)[1].reshape(rows*cols).reshape(rows,cols) NTVH = getNTVH(img)[1][0,:,:] NT = getNT(img)[1].reshape(frames).reshape(frames,1) P = ryplot.Plotter(1, 3, 3,'PTW Image Sequence 3-D Noise Components', figsize=(12, 12)) P.showImage(1, NH, ptitle=f'Nh: Fixed Column: Avg(vt) {ryutils.intify_tuple(NH.shape)}', titlefsize=10) P.showImage(2, NTH, ptitle=f'Nth: Temporal Column: Avg(v) {ryutils.intify_tuple(NTH.shape)}', titlefsize=10) P.showImage(4, NV, ptitle=f'Nv: Fixed Row: Avg(ht) {ryutils.intify_tuple(NV.shape)}', titlefsize=10) P.showImage(5, NTV, ptitle=f'Ntv: Temporal Row: Avg(h) {ryutils.intify_tuple(NTV.shape)}', titlefsize=10) P.showImage(8, NVH, ptitle=f'Nvh: Fixed Pixel: Avg(t)\nPixel Non-uniformity {ryutils.intify_tuple(NVH.shape)}', titlefsize=10) P.showImage(9, NTVH, ptitle=f'Ntvh: Temporal: Avg()\nRandom noise {ryutils.intify_tuple(NTVH.shape)}', titlefsize=10) P.showImage(7, NT, ptitle=f'Nt: Temporal Frame: Avg(hv) {ryutils.intify_tuple(NT.shape)}', titlefsize=10) P.showImage(3, img[0,:,:], ptitle=f'First Frame {ryutils.intify_tuple(img[0, :, :].shape)}', titlefsize=10) P.showImage(6, img[-1,:,:], ptitle=f'Last Frame {ryutils.intify_tuple(img[-1, :, :].shape)}', titlefsize=10) P.getPlot().show() P.saveFig(_c('ptwnoise.png')) print('ry3dnoise done!')