Evscope : Inventaire et statistiques des images

Logique générale : on

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

Dans ce cas précis, les “download” issus de Unistellar ont tous la même structure :

  • Une sub-dir par image de type “enhanced”
    • avec une image issue du “Live stacking” (stacksum)
    • et son fichier descriptif lié (format json)
    • Une sub-dir “Stackinput” contenant les images individuelles
      • chaque image (fits) et son fichier descriptif (json) lié
  • Une sub-dir pour les “Darkfame” (en fait, un master dark)
    • Une image dark (fits) et son fichier descriptif lié (json)

Fichier descriptif de type “stacksum”

{
"cameragain": "250cb",
"expotime": "3.971754s",
"focallength": "450mm",
"fovradecdeg": "114.44004, 38.77056",
"fovradecdms": "07h37m45.6096s, +38d46m14.016s",
"framePurpose": "StackSum",
"frameseqid": 469,
"imagedepth": 16,
"pipelinemode": "EnhancedVision",
"sensormodel": "IMX224",
"serialnumber": "xxxxxx",
"size": "1296x976",
"timestamp": "2022-03-19T21-15-50.180",
"version": 1.5
}

Dans ce cas de figure, le contenu de la zone “frameseqid” contient le nombre d’images empilées en mode “Live stacking”. Dans l’exemple ci-dessus, l’image est le résultat de 469 x 3.97sec, soit 1862 sec (31 min).

Quand aux coordonnées, comme déjà analysé, elles fournissent les coordonnées finales d’observation. On remarque aussi que l’image finale est de 1296×976, résolution basique du IMX224.

Fichier descriptif de type “stackinput”

{
"cameragain": "250cb",
"expotime": "3.971754s",
"focallength": "450mm",
"fovradecdeg": "114.44004, 38.77056",
"fovradecdms": "07h37m45.6096s, +38d46m14.016s",
"framePurpose": "StackInput",
"frameseqid": 0,
"imagedepth": 16,
"pipelinemode": "EnhancedVision",
"sensormodel": "IMX224",
"serialnumber": "xxxxxx",
"size": "1304x976",
"timestamp": "2022-03-19T20-39-38.397",
"version": 1.5
}

Dans ce cas de figure, le contenu de la zone “frameseqid” contient le numéro d’ordre de l’image dans l’empilement. Et la taille de l’image est “native”, à savoir 1304×976 (8 pixels de plus en largeur).

Fichier descriptif de type “Darkframemean”

{
"cameragain": "250cb",
"expotime": "3.971754s",
"focallength": "450mm",
"fovradecdeg": "97.21215, 33.25594",
"fovradecdms": "06h28m50.916s, +33d15m21.384s",
"framePurpose": "DarkframeMean",
"frameseqid": 20,
"imagedepth": 16,
"pipelinemode": "EnhancedVision",
"sensormodel": "IMX224",
"serialnumber": "xxxxxx",
"size": "1304x976",
"timestamp": "2021-12-08T03-54-19.543",
"version": 1.4
}

Dans ce cas de figure, le contenu de la zone “frameseqid” contient le nombre d’images utilisées pour la création du Master Dark. On remarque que quelque soit le nombre d’image prises ensuite, le “master dark” n’est créé que sur base de 20 images.

bg : Renvoie le niveau du fond de l’image chargée en mémoire.

bgnoise : Renvoie le niveau de bruit de fond de l’image chargée en mémoire

stat : Renvoie la statistique globale de l’image actuelle. Ex: B&W layer: Mean: 5458.6, Median: 5568.0, Sigma: 1159.7, AvgDev: 831.5, Min: 1408.0, Max: 65520.0

On va cumuler cela dans une ligne (format csv Excel) et donner, dans l’ordre : Filename; cameragain; expotime; focallength; fovradecdeg; fovradecdms; imagedepth; framePurpose; frameseqid; TotExpotime; pipelinemode; sensormodel; size; timestamp; bg; bgnoise; Canal; Average; Median; Sigma; AvgDev; Min; Max;’

L’invocation du programme, et des autres qui suivront, pourront se faire de deux manières : en statique (les sub-dir examinées sont fixées dans le programme) ou dynamique (précisées lors de l’appel).

python <programme> <mode> <directory>
<mode> : dir / file
<directory> : le nom de la sub-dir principale

Evscope Scan library V1.1

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

This program analyzes eVscope raw images files :

@author: TTFonWeb
"""

import glob
import os
import sys
import datetime
import json

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

#****************************************
#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

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

if scanmode == "dir":
    listsubdir=[]
    jsonfiles =[]
    start=False

    #Global scan of subdirs to get informations
    for file in glob.glob(path + "\\*"):
        if startdir != "":
            if file.find(startdir) >= 0 or start: 
                start=True
                if file.find("_EnhancedVision") >= 0:
                    listsubdir.append(file)
                if file.find("_Resolved") >= 0:
                    listsubdir.append(file)
                if file.find("_DarkframeMean") >= 0:
                    listsubdir.append(file)
        else:
            if file.find("_EnhancedVision") >= 0:
                listsubdir.append(file)
            if file.find("_Resolved") >= 0:
                listsubdir.append(file)
            if file.find("_DarkframeMean") >= 0:
                listsubdir.append(file)

if scanmode == "file":
    listsubdir=[]
    jsonfiles =[]
    jsonfiles.append(startfile)

rec = "Filename"+";"+ 'cameragain'+";"+'expotime'+";"+'focallength'+";"
rec = rec +'fovradecdeg'+";"+'fovradecdms'+";"+'imagedepth'+";"+'framePurpose'+";"
rec = rec +'frameseqid'+";"+'TotExpotime'+";"+'pipelinemode'+";"
rec = rec +'sensormodel'+";"+'size'+";"+'timestamp'+";"+"bg"+";"+"bgnoise"+";"
rec = rec +"Canal"+";"+"Average"+";"+"Median"+";"+"Sigma"+";"+"AvgDev"+";"+"Min"+";"+"Max"+";"
log.write(rec+"\n")

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

    #Process Json extraction and rename
    for file in listsubdir:
        jsonfiles=[]
        
        if file.find("_Resolved") >= 0 or file.find("_EnhancedVision") >= 0:
            for jsonfile in glob.glob(file + "\\*.json"):
                jsonfiles.append(jsonfile)
                
            for jsonfile in glob.glob(file + "\\stackinput\\*.json"):
                jsonfiles.append(jsonfile)
                        
            for jsonfile in jsonfiles:
                imagefile = jsonfile.replace(".json",".fits")
                with open(jsonfile,"r") as json_file:
                    jsondata = json.load(json_file)
                    expo=float(jsondata['expotime'].split("s")[0])
                    totexpo = jsondata['frameseqid'] * expo 
        
                    rec = file +";"+ jsondata['cameragain'].replace('.',',')+";"
                    rec = rec +jsondata['expotime'].replace('.',',')+";"
                    rec = rec +jsondata['focallength'].replace('.',',')+";"
                    rec = rec +'"'+jsondata['fovradecdeg']+'"'+";"
                    rec = rec +'"'+jsondata['fovradecdms']+'"'+";"
                    rec = rec +str(jsondata['imagedepth'])+";"
                    rec = rec +jsondata['framePurpose']+";"
                    rec = rec +str(jsondata['frameseqid'])+";"
                    rec = rec +str(totexpo).replace('.',',')+";"
                    rec = rec +jsondata['pipelinemode']+";"
                    rec = rec +jsondata['sensormodel']+";"+jsondata['size']+";"+jsondata['timestamp']+";"
                    
                    image = os.path.basename(imagefile)
                    pathscan = os.path.dirname(imagefile) 
                    
                    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(".",",")+";"
                             
                    log.write(rec+"\n")

        if file.find("_DarkframeMean") >= 0:
            jsonfiles = glob.glob(file + "\\*.json")

            if len(jsonfiles)>0:
                for jsonfile in jsonfiles:
                    with open(jsonfile,"r") as json_file:
                        jsondata = json.load(json_file)
                        expo=float(jsondata['expotime'].split("s")[0])
                        if jsondata['framePurpose'] != "StackInput":
                            totexpo = jsondata['frameseqid'] * expo 
                        else:
                            totexpo = expo 
            
                        rec = file +";"+ jsondata['cameragain'].replace('.',',')+";"
                        rec = rec +jsondata['expotime'].replace('.',',')+";"
                        rec = rec +jsondata['focallength'].replace('.',',')+";"
                        rec = rec +'"'+jsondata['fovradecdeg']+'"'+";"
                        rec = rec +'"'+jsondata['fovradecdms']+'"'+";"
                        rec = rec +str(jsondata['imagedepth'])+";"
                        rec = rec +jsondata['framePurpose']+";"
                        rec = rec +str(jsondata['frameseqid'])+";"
                        rec = rec +str(totexpo).replace('.',',')+";"
                        rec = rec +jsondata['pipelinemode']+";"
                        rec = rec +jsondata['sensormodel']+";"+jsondata['size']+";"+jsondata['timestamp']+";"
    
                        image = os.path.basename(jsonfile).replace('.json','.fits')
                        pathscan = os.path.dirname(jsonfile)
                        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(".",",")+";"
                        log.write(rec+"\n")
                 
    #    app.Script("basic.ssf")
except Exception as e :
    print("\n**** ERROR *** " +  str(e) + "\n" )    

app.Close()
del app

log.close()

Exécuté, par exemple sur la sub-directory principale des images issues des “download” de Unistellar, cela permet d’obtenir facilement (quoique, il faut du temps pour exécuter…) une statistique globale sur les images :

Statistiques bg et bgnoise sur 39000 images de Evscope…

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 le programme sur la sub-dir, on obtient :

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

Sortie de statistiques Evscope dans un tableur