Statistiques des images RAW

Pour analyser chaque image, on va utiliser deux méthodes à ce stade (on affinera plus tard) : la lecture des informations (header fits, fichier json) et les outils disponibles sous PySiril : bg, bgnoise et stat

Statistiques : images RAW APN

Avec les images RAW, les contenus des métadonnées sont très liées aux formats des fichiers et des constructeurs (voir les pages sur le sujet).
On va donc compléter les informations statiques (format Exif) par des fonctions statistiques déjà décrites.

# -*- coding: utf-8 -*-
"""
Created on Fri Jul  6 11:30:25 2018
This program analyzes APN raw images files :
@author: TTFonWeb
"""
import glob
import os
import datetime
import json
import sys
from pathlib import Path

from pysiril.siril   import *
from pysiril.wrapper import *
from pysiril.addons  import *

#rem exiftool.exe est déposé dans une subdir accessible par PATH

def ExtractExif(fnin,exe_path="i:\\$python\\"):
    stream = os.popen(exe_path+'exiftool '+fnin)
    output = stream.read()
    exif = {}
    lignes = output.splitlines()

    for ligne in lignes:
        elem = ligne.split(":")
        tag = elem[0].strip()
        value= elem[1]
        for v in range(2,len(elem)-1):
            value = value + ":"+str(v)
        exif[tag] = value.strip()
    
    stream.close()   
    return exif

def BuildHeader(exif,type):
    try:
        rec_header = "\n"+ '"' + type +" Filename" + '"' +";"
        rec_header = rec_header + "bg"+";"+"bgnoise"+";"
        rec_header = rec_header +"Canal"+";"+"Average"+";"+"Median"+";"+"Sigma"+";"+"AvgDev"+";"+"Min"+";"+"Max"+";"
        for tag in exif:
            rec_header = rec_header + '"' + tag + '"' +";"
        
    except Exception as e :
        print("\n**** ERROR *** " +  str(e) + "\n" )            
        print(rec_header, ">", exif) 

    return rec_header

def BuildRec(exif):
    try:
        rec=""
        for tag in exif:
            value = exif[tag].replace(".",",")
            value = value.replace(",ARW",".ARW")
            value = value.replace(",CRW",".CRW")
            value = value.replace(",CR2",".CR2")
            rec = rec + '"' + value + '"' + ";"

    except Exception as e :
        print("\n**** ERROR *** " +  str(e) + "\n" )            
        print(rec, ">", exif) 
    return rec

def BuildStats(file):
    try:
        rec=""
        image = os.path.basename(file)
        pathscan = os.path.dirname(file) 
        app.Execute("cd " + pathscan)
        app.Execute("load "+ image)
        _,bg = cmd.bg()
        rec = rec + str(bg.split("(")[0])+";"
        _,bgnoise = cmd.bgnoise()
        rec = rec + str(bgnoise[0][1]).replace(".",",")+";"
        _,stat = cmd.stat()
        for key, value in stat[0].items():
            rec = rec + str(value).replace(".",",")+";"

    except Exception as e :
        print("\n**** ERROR *** " +  str(e) + "\n" )            
        print(rec, ">", file) 
    return rec

#****************************************
#running parameters
Debug=True

if len(sys.argv[0])!=0:
    argmode=True 
else:
    argmode=False 
    
if argmode:
    try:
        inputMode = sys.argv[1]
        if inputMode in ('dir','file'):
            print ("argument mode found : ",inputMode)
            argmode = True
        else:
            argmode = False
    except:
        argmode = False
    try:
        if argmode:
            inputDir = sys.argv[2]
            print ("argument directory found : ",inputDir)
            argmode = True
    except:
        argmode = False

if argmode:
    scanmode    = inputMode
    path = inputDir
else:
    scanmode    = "dir"
    #scanmode    = "file"
    
    drive = "G:"
    rootdir    = "\\apn"
    startdir   = ""
    startfile  = ""
    path  = drive+rootdir

ARW_header = False
CRW_header = False
CR2_header = False

if scanmode == "dir":
    imagefiles_ARW =[]
    imagefiles_CRW =[]
    imagefiles_CR2 =[]

    #Global scan of subdirs to get informations
    for file in Path(path).rglob('*.ARW'):
        imagefiles_ARW.append(str(file))
        ARW_header = True

    for file in Path(path).rglob('*.CRW'):
        imagefiles_CRW.append(str(file))
        CRW_header = True

    for file in Path(path).rglob('*.CR2'):
        imagefiles_CR2.append(str(file))
        CR2_header = True

if scanmode == "file":
    if os.path.splitext(startfile).upper() == "ARW":
        imagefiles_ARW.append(startfile)
    if os.path.splitext(startfile).upper() == "CRW":
        imagefiles_CRW.append(startfile)
    if os.path.splitext(startfile).upper() == "CR2":
        imagefiles_CR2.append(startfile)

#open log file
now = datetime.datetime.now()
log=open(path+"\\TTFonWeb_Stats_APNReport_runlog"+ now.strftime("%Y%m%d_%H%M%S")+".csv","w")

print("StatusFunctions:begin")
app=Siril()
try:
    cmd=Wrapper(app)
    fct=Addons(app)
    app.Open()
    ARW_data =""
    CRW_data =""
    CR2_data =""

    #Process Json extraction and rename
    for file in imagefiles_ARW:
        exif = ExtractExif(file)
        if ARW_data != BuildHeader(exif,"ARW"):
            ARW_header=True
        if ARW_header:
            ARW_data = BuildHeader(exif,"ARW")
            log.write(ARW_data + "\n")
            ARW_header = False
        rec = BuildRec(exif)
        stats = BuildStats(file)
        log.write('"'+file+'"'+";"+stats+rec+"\n")

    for file in imagefiles_CRW:
        exif = ExtractExif(file)
        if CRW_data != BuildHeader(exif,"CRW"):
            CRW_header=True
        if CRW_header:
            CRW_data = BuildHeader(exif,"CRW")
            log.write(CRW_data + "\n")
            CRW_header = False
        rec = BuildRec(exif)
        stats = BuildStats(file)
        log.write('"'+file+'"'+";"+stats+rec+"\n")

    for file in imagefiles_CR2:
        exif = ExtractExif(file)
        if CR2_data != BuildHeader(exif,"CR2"):
            CR2_header=True
        if CR2_header:
            CR2_data = BuildHeader(exif,"CR2")
            log.write(CR2_data + "\n")
            CR2_header = False
        rec = BuildRec(exif)
        stats = BuildStats(file)
        log.write('"'+file+'"'+";"+stats+rec+"\n")

except Exception as e :
    print("\n**** ERROR *** " +  str(e) + "\n" )    

app.Close()
del app

log.close()

Ce programme analyse les format RAW de Sony (ARW) et deux formats RAW communs de Canon (CRW et CR2). C’est relativement facile d’en rajouter. A chaque version des métadonnées, une nouvelle section est produite dans la liste générée.

Exemple, pour les fichiers ARW, il fournira :

bg bgnoise Canal Average Median Sigma AvgDev Min Max ExifTool Version Number File Name Directory File Size File Modification Date/Time File Access Date/Time File Creation Date/Time File Permissions File Type File Type Extension MIME Type Exif Byte Order Image Description Orientation White Point Primary Chromaticities Image Width Image Height Bits Per Sample Compression Photometric Interpretation Samples Per Pixel Planar Configuration X Resolution Y Resolution Resolution Unit CFA Repeat Pattern Dim CFA Pattern 2 Sony Raw File Type Sony Tone Curve Strip Offsets Rows Per Strip Strip Byte Counts Chromatic Aberration Correction Distortion Correction Preview Image Start Preview Image Length Y Cb Cr Coefficients Y Cb Cr Positioning Exposure Time F Number ISO Sensitivity Type Recommended Exposure Index Exif Version Date/Time Original... Etc, etc... 

On remarque que les valeurs passées dans les zone de type “date” sont incorrectes :-(… C’est un effet (malheureux) de la version de Exiftool sous Windows. Il faudra voir à le corriger dans une prochaine version…

Statistiques : images RAW Fits

Dans ce cas, les metadonnées se trouve dans le “header fits” stockés avec l’image. Pour le reste des statistiques, l’appel est identique à celle du premier chapitre.

Mais hélas, le “header” Fits, si il est le plus répandu (surtout dans les outils de capture), est aussi celui qui permet le plus de variation possible ! En plus la moindre erreur de recherche (ou d’encodage à l’origine) provoque une erreur. Donc, on va “créer” une liste minimale et tester zone par zone sa présence et réagir selon le cas. Si il y a moyen de récolter les champs de manière plus certaine, on modifiera la prochaine version.

# -*- coding: utf-8 -*-
"""
Created on Fri Jul  6 11:30:25 2018

This program analyzes APN raw images files :

@author: TTFonWeb
"""

import glob
import os
import sys
import datetime
import json
from pathlib import Path

from astropy.io import fits

from pysiril.siril   import *
from pysiril.wrapper import *
from pysiril.addons  import *

#rem exiftool.exe est déposé dans une subdir accessible par PATH

def BuildHeader(hdu,type):
    try:
        rec_header = ""
        rec_header = "\n"+ '"' + type + " Filename" + '"' +";"
        rec_header = rec_header + "bg"+";"+"bgnoise"+";"
        rec_header = rec_header + "Canal"+";"+"Average"+";"+"Median"+";"+"Sigma"+";"+"AvgDev"+";"+"Min"+";"+"Max"+";"
        rec_header = rec_header + 'SIMPLE'  
        rec_header = rec_header + 'BITPIX'  
        rec_header = rec_header + 'NAXIS '  
        rec_header = rec_header + 'NAXIS1'  
        rec_header = rec_header + 'NAXIS2'  
        rec_header = rec_header + 'BZERO '  
        rec_header = rec_header + 'EXTEND'  
        rec_header = rec_header + 'IMAGETYP'
        rec_header = rec_header + 'EXPOSURE'
        rec_header = rec_header + 'EXPTIME '
        rec_header = rec_header + 'DATE-LOC'
        rec_header = rec_header + 'DATE-OBS'
        rec_header = rec_header + 'XBINNING'
        rec_header = rec_header + 'YBINNING'
        rec_header = rec_header + 'GAIN    '
        rec_header = rec_header + 'OFFSET  '
        rec_header = rec_header + 'EGAIN   '
        rec_header = rec_header + 'XPIXSZ  '
        rec_header = rec_header + 'YPIXSZ  '
        rec_header = rec_header + 'INSTRUME'
        rec_header = rec_header + 'SET-TEMP'
        rec_header = rec_header + 'CCD-TEMP'
        rec_header = rec_header + 'BAYERPAT'
        rec_header = rec_header + 'TELESCOP'
        rec_header = rec_header + 'FOCALLEN'
        rec_header = rec_header + 'RA      '
        rec_header = rec_header + 'DEC     '
        rec_header = rec_header + 'CENTALT '
        rec_header = rec_header + 'CENTAZ  '
        rec_header = rec_header + 'SITEELEV'
        rec_header = rec_header + 'SITELAT '
        rec_header = rec_header + 'SITELONG'
        rec_header = rec_header + 'OBJECT  '
        rec_header = rec_header + 'OBJCTRA '
        rec_header = rec_header + 'OBJCTDEC'
        rec_header = rec_header + 'OBJCTROT'
        
    except Exception as e :
        print("\n**** ERROR *** " +  str(e) + "\n" )            
        print(rec_header, ">", hdu.header) 

    return rec_header

def BuildRec(hdu):
    try:
        rec_data = ""
        try: 
            rec_data = rec_data +  str(hdu.header['SIMPLE'])+";"  
        except:
            rec_data = rec_data +  ";"
        try: 
            rec_data = rec_data +  str(hdu.header['BITPIX'])+";"  
        except:
            rec_data = rec_data +  ";"
        try: 
            rec_data = rec_data +  str(hdu.header['NAXIS '])+";"  
        except:
            rec_data = rec_data +  ";"
        try: 
            rec_data = rec_data +  str(hdu.header['NAXIS1'])+";"  
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['NAXIS2'])+";"  
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['BZERO '])+";"  
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['EXTEND'])+";"  
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['IMAGETYP'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['EXPOSURE'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['EXPTIME '])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['DATE-LOC'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['DATE-OBS'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['XBINNING'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['YBINNING'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['GAIN'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['OFFSET'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['EGAIN'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['XPIXSZ'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['YPIXSZ'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['INSTRUME'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['SET-TEMP'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['CCD-TEMP'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['BAYERPAT'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['TELESCOP'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['FOCALLEN'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['RA'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['DEC'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['CENTALT'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['CENTAZ'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['SITEELEV'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['SITELAT'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['SITELONG'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['OBJECT'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['OBJCTRA'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['OBJCTDEC'])+";"
        except:
            rec_data = rec_data +  ";"  
        try: 
            rec_data = rec_data +  str(hdu.header['OBJCTROT'])+";"
        except:
            rec_data = rec_data +  ";"  

    except Exception as e :
        print("\n**** ERROR *** " +  str(e) + "\n" )            
        print(rec, ">", exif) 
    return rec_data

def BuildStats(file):
    try:
        rec=""
        image = os.path.basename(file)
        pathscan = os.path.dirname(file) 
        app.Execute("cd " + pathscan)
        app.Execute("load "+ image)
        _,bg = cmd.bg()
        rec = rec + str(bg.split("(")[0])+";"
        _,bgnoise = cmd.bgnoise()
        rec = rec + str(bgnoise[0][1]).replace(".",",")+";"
        _,stat = cmd.stat()
        for key, value in stat[0].items():
            rec = rec + str(value).replace(".",",")+";"

    except Exception as e :
        print("\n**** ERROR *** " +  str(e) + "\n" )            
        print(rec, ">", file) 
    return rec

#****************************************
#running parameters
Debug=True

if len(sys.argv[0])!=0:
    argmode=True 
else:
    argmode=False 
    
if argmode:
    try:
        inputMode = sys.argv[1]
        if inputMode in ('dir','file'):
            print ("argument mode found : ",inputMode)
            argmode = True
        else:
            argmode = False
    except:
        argmode = False
    try:
        if argmode:
            inputDir = sys.argv[2]
            print ("argument directory found : ",inputDir)
            argmode = True
    except:
        argmode = False

if argmode:
    scanmode    = inputMode
    path = inputDir
else:
    scanmode    = "dir"
    #scanmode    = "file"
    
    drive = "G:"
    rootdir    = "\\apn"
    startdir   = ""
    startfile  = ""
    path  = drive+rootdir

header = False

if scanmode == "dir":
    imagefiles=[]

    #Global scan of subdirs to get informations
    for file in Path(path).rglob('*.fits'):
        imagefiles.append(str(file))
        header = True

if scanmode == "file":
    if os.path.splitext(startfile).upper() == "FITS":
        imagefiles.append(startfile)

#open log file
now = datetime.datetime.now()
log=open(path+"\\TTFonWeb_Stats_APNReport_runlog"+ now.strftime("%Y%m%d_%H%M%S")+".csv","w")

print("StatusFunctions:begin")
app=Siril()
try:
    cmd=Wrapper(app)
    fct=Addons(app)
    app.Open()
    data =""

    rec_header = ""
    #Process Json extraction and rename
    for file in imagefiles:
        try:
            hdulist = fits.open(file)
            hdu = hdulist[0]
            rec_header_new = BuildHeader(hdu,'FITS')
            if rec_header != rec_header_new :
                rec_header = rec_header_new
                log.write(rec_header+"\n")
    
            rec_data = BuildRec(hdu)
            stats = BuildStats(file)
            log.write('"'+file+'"'+";"+stats+rec_data+"\n")
        except:    
            print('Cannot process file :',file)

except Exception as e :
    print("\n**** ERROR *** " +  str(e) + "\n" )    

app.Close()
del app

log.close()

Exemples de résultats

Le plus pratique est de générer une sub-directory de test…

2021-12-21_03-35-13__-14.80_120.00s_0004.fits
albi_00001.fits
albi_00002.fits
albi_00003.fits
albi_00004.fits
albi_00005.fits
CRW_7310.CRW
IMG_2177.CR2
new-image.fits
Surface
ttf001_2022-01-05T05-38-35.786_Resolved_M-57
_DSC0204.ARW

Qui représente un mélange des différents formats… En faisant fonctionner les trois programmes sur la même sub-dir, on obtient :

Programme dédié Evscope (voir page liée)

Sortie de statistiques Evscope dans un tableur

Programme généraliste pour APN RAW

Sortie de statistiques APN ouverte dans un tableur

Programme minimal pour images FITS

Sortie de statistiques FITS ouverte dans un tableur