REU Calibration

This page contains the raw calibration data and analysis tools used to characterize the contribution of ion and thermal leakage to the instrumental background of the Cassini INMS as discussed in the supplement to Waite et al. 2017. The raw data was generated through a series of laboratory experiments with the Cassini INMS Refurbished Engineering Unit (REU). The experimental parameters for these measurements were designed to approximate the conditions seen during the E21 flyby of Enceladus. The analysis code is used to extract the raw data and organize it according to measurement type. The integrated results are presented in the spreadsheet file.

Click to Expand
Actions

Download All
(49mb zipped)
Click to Expand


Download All Images
(1.42mb zipped)

INMS_DA_scratch.py


figure()
sttl = 'Background - R1017'
cts = msc349bkg
ttl = 'IP=1000ms, 20 scan summation, H2counts={:.0f}, H2/H20={:.2f}%'
ttl = ttl.format(cts[1], (cts[1]/cts[14])*100)
plot_MS_bar(massBkg, cts, sttl, ttl)

#==========Generating cross talk plots from data====================#

#specific case for 2015-349 containing data for both H2 and water
#the script did not terminate D003 correctly
#raw D003 contains H2 OSNB CS-off, H2O bkgc, H2O bkgo, H2O OSNB CS-On
ds349 = get_datasets(sorted(msd349.keys()))
ds349['D003']['srange'] = (101, 140)
ds349['D000bkgc'] = ds349.pop('D000')
_ = ds349.pop('D004')
_ = ds349.pop('D005')

ds349h2o = {'D000bkgc': {'srange': (151, 170)},
            'D000bkgo': {'srange': (171, 190)},
            'D003': {'srange': (196, 235)},
            'D004': {'srange': (236, 275)},
            'D005': {'srange': (276, 315)}}

#ds349 uses the same OSNB background dataset as ds349h2o
ds349['D000bkgo']['sum'] = ds349h2o['D000bkgo'].copy()

#include 'H2O' in the ds349h2o sttl
for k in ds349h2o.keys():
    ds349h2o[k]['sttl'] = ds349h2o[k]['sttl']+' H2O'
    print ds349h2o[k]['sttl']

#the background scans in ds349 are masses 1-50 -> truncate to 1-23
dst = ds349
for k in dst.keys():
    if len(dst[k]['masses']) > 19:
        dst[k]['masses'] = masses
        dst[k]['sum'] = dst[k]['sum'][:19]

#the background scans in ds349h2o are masses 1-50 -> truncate to 1-23
dst = ds349h2o
for k in dst.keys():
    if len(dst[k]['masses']) > 19:
        dst[k]['masses'] = masses
        dst[k]['sum'] = dst[k]['sum'][:19]


#specific case for ds356
ds356['D004']['srange'] = (121, 145)
ds356['D001bkgc'] = ds356.pop('D001')
ds356['D002bkgo'] = ds356.pop('D002')

#specific case for ds015
ds015['D003']['sttl'] = 'OSNB - OS Off - R1023'
ds015['D001bkgc'] = ds015.pop('D001')
ds015['D002bkgo'] = ds015.pop('D002')

#specific case for ds018
ds018['D001bkgc'] = ds018.pop('D001')
ds018['D002bkgo'] = ds018.pop('D002')

#specific case for ds027
ds027['D001bkgc'] = ds027.pop('D001')
ds027['D002bkgo'] = ds027.pop('D002')

#specific case for ds028
_ = ds028.pop('D000')
ds028['D001bkgc'] = ds028.pop('D001')
ds028['D002bkgo'] = ds028.pop('D002')

#special case for ds046
#some of the scans in D000 occur between D002 and D003.
#Therefore need to sort on integer value of scan number
msc046.sort(key=lambda ms: int(ms[2]))
_ = ds046.pop('D000')
ds046['D001bkgc'] = ds046.pop('D001')
ds046['D002bkgo'] = ds046.pop('D002')

#specific case for ds047
#Observed evolution of outgassing products as filament warms up
#R1031-D003was moved to 2016-047 directory from 2016-048
#plot evolution of mass species

#specific case for ds048
#must be sorted similar to ds046
msc048.sort(key=lambda ms: int(ms[2]))
_ = ds048.pop('D000')
ds048['D004bkgc'] = ds048.pop('D004')
ds048['D005bkgo'] = ds048.pop('D005')


#specific case for 2015-352
dsets = get_datasets(msf)
msc352 = get_INMS_scan_data(msd352, id0='mass', sfields=sfld, mfields=mfld)
ds352 = {k:{'srange':(int(dsets[k][0][1:]),int(dsets[k][-1][1:]))}
			for k in dsets.keys()[1:4]}
ds352['D000bkgo'] = {'srange':(57,96)}
ds352['D000bkgc'] = {'srange':(17,56)}
_ = ds352.pop('D000')

plot_crosstalk(ds352, msc352)
dkeys = sorted(ds352.keys())
dsum = [ds352[k]['sum'] for k in dkeys]
dsum[2] = dsum[2]-dsum[1]
dsum[3] = dsum[3]-dsum[0]
dsum[4] = dsum[4]-dsum[1]

for i in range(2,5):
    sttl = ds352[dkeys[i]]['sttl']+' Bkg Subtract'
    cts = dsum[i]
    ttl = ds352[dkeys[i]]['ttl']
    plot_MS_bar(masses, cts, sttl, ttl)

cstoff = dsum[4]/dsum[3]*100
sttl = 'Crosstalk Ratio, 8.112kms, CS Off - R1020'
cts = cstoff
ttl = 'IP=1000ms, 40 scan summation, KE=0.675eV, H2 ratio={:.3f}%'.format(cts[1])
plot_MS_bar(masses, cts, sttl, ttl, yl='Percent')

cston = dsum[2]/dsum[3]*100
sttl = 'Crosstalk Ratio, 8.112kms, CS On - R1020'
cts = cston
ttl = 'IP=1000ms, 40 scan summation, KE=0.675eV, H2 ratio={:.3f}%'.format(cts[1])
plot_MS_bar(masses, cts, sttl, ttl)

#all data sets
dsAll = [ds352, ds349, ds356, ds349h2o]
dsp = [[ds[s]['sttl'], ds[s]['h2cts'].mean(), ds[s]['wcts'].mean() ]
		for ds in dsAll for s in sorted(ds.keys())]


#=================Consolidating H2/H2O leakag data============================#
# gather all datasets into one list
# iterate the list and

#H2 Thermal leakage
thermalH2Data = [ds349, ds352, ds356, ds018, ds028]

#=====================plotting SID Data for R1003============================#
dpath = inmsDataDir+'2015-272'
msSID = get_INMS_data(dpath, '*_MS_*')
mscSID = get_INMS_scan_data(msSID, id0='mass')

BKG = [0]
SID1 = range(1,8,2)
THM1 = range(2,9,2)
SID2 = range(9,16,2)
THM2 = range(10,17,2)
SID3 = range(17,25,2)
THM3 = range(18,25,2)

sid272 = {'SID1':SID1, 'THM1':THM1,
            'SID2':SID2, 'THM2':THM2,
            'SID3':SID3, 'THM3':THM3,
            'BKG':BKG}

ds272 = get_datasets(sid272)
sum_sets(ds272, mscSID)
dkeys = sorted(ds272.keys())

for k in sid272.keys():
    sid272[k] = sum(array([ds272[dkeys[i]]['sum'] for i in sid272[k]]),0)

M = mscSID[0][-1]
for k, s in sid272.iteritems():
    plot_MS_bar(M, s, k, 'R1003')

SID_1 = sid272['SID1']-sid272['THM1']
SID_2 = sid272['SID2']-sid272['THM2']
SID_3 = sid272['SID3']-sid272['THM3']

#=====================Plotting Simulted Energy Scan Data from GM=====================#
os.chdir(inmsDataDir)
simfile = glob.glob('*.dat')[0]
with open(simfile) as df:
        dlist = [item.strip('\r\n').split(',')
        			for item in df.readlines()]
simQL = array([d[0] for d in dlist], float)
simCnts = array([d[1] for d in dlist], float)
figure()
plot(simQL, simCnts/simCnts.max())
ylim(0, 1.1)
ylabel('Counts')
xlabel('QL3 Voltage')
title('Simulated Energy Scan 11.6eV CO2', fontsize=16)
draw()
savefig('Simulated Energy Scan 11.6eV CO2.png')
close()
        

INMS_REU_Data_Analysis.py


#------------------------------------------------------------------------#
# Title: 	INMSAngularResponseDA.py
# Purpose:  Provide tools for reading, analyzing, and plotting INMS data.
#
#------------------------------------------------------------------------#
#
# How to use this software:
# Must have Python v2.7.5 or later and IPython installed and running.
# Open the IPython interpreter - QtConsole and run the following commands
#
# cd /Users/jgrimes/Documents/Ion Gun/data or relevant user directory
# from INMS_REU_Data_Analysis import *
# ion()
#
# All required scripts and packages have now been loaded and the directory
# has been set to the data location to access files. To run methods from
# the interpreter prompt type
# ...: functionName(arg1, arg2, ..., argN)
#------------------------------------------------------------------------#

#begin import packages
#from pylab import * #all of numpy and matplotlib
from numpy import *
from scipy import *
from datetime import datetime
from datetime import timedelta
from matplotlib.pyplot import *
from matplotlib.dates import DateFormatter
from scipy.interpolate import griddata

import os
import csv
import json
import glob

inmsDataDir = '/Users/jgrimes/Documents/Cassini/INMS Data/'
formatter = DateFormatter('%H:%M')

sfld = ['run', 'dataset', 'scan', 'mode',
        'initial_time', 'final_time']

mfld = ['ip','cs_fil1_emis_iset','cs_fil1_emis_imon', 'cs_anode1_imon',
        'cs_ionrep_imon', 'os_fil1_emis_imon', 'chamber_ion_pmon']

def get_INMS_data(datapath, srchstr):

    os.chdir(datapath)
    #make a list of appropriate files
    esFiles = glob.glob(srchstr)

    #read each file in the list
    esData = {}
    for dfile in esFiles:
        esData[dfile] = read_INMS_scan_file(dfile)

    return esData

def get_INMS_scan_data(data, id0='lens_vset', sfields='', mfields=''):
#
    if sfields == '':
        sfields = ['run', 'dataset', 'scan',
                    'initial_time', 'final_time']
    if mfields == '':
        mfields = ['cs_fil1_emis_imon', 'cs_anode1_imon',
                     'cs_ionrep_imon', 'chamber_ion_pmon']
    scanData = []
    for k in sorted(data.keys()):
        try:
            scan = data[k]['Scan']
            oneScan = [scan[item] for item in sfields]
            monitors = data[k]['Monitors']
            oneScan.extend([monitors[item] for item in mfields])
            titl = data[k]['Data'][0]
            cnts = data[k]['Data'][1][:, titl.index('counter1')]
            d = data[k]['Data'][1][:, titl.index(id0)]
            oneScan.extend([cnts, d])
            scanData.append(oneScan)
        except TypeError:
            print(k)
            pass
    return scanData

#===============DA tools for INMS Mass Scan Data============================#

def get_MS_scan_times(jn_file):
    tBkg, tSID, tTherm, SIDgrps = [],[],[],[]
    dset = 1
    with open(jn_file) as jn:
        for line in jn:
            if 'Scan' in line:
                jln0, jln1 = line.strip('[').split(']')
                if 'Background' in jln1:
                    tBkg.append(jln0)
                    SIDgrps.append({'Bkg':tBkg})
                elif 'SID' in jln1:
                    tSID.append(jln0)
                elif 'Thermal' in jln1:
                    tTherm.append(jln0)
            elif 'SID measurement completed' in line:
                tEnd = line.strip('[').split(']')[0]
                SIDgrps.append({'SID':tSID, 'Therm':tTherm, 'End':tEnd})
                tSID, tTherm = [], []
    return SIDgrps

def get_datasets(msFiles):
    dsets = {}
    for msf in msFiles:
        _,_,d,s = msf.strip('.txt').split('_')[2:]
        try:
            dsets[d].append(s)
        except KeyError:
            dsets[d] = [s]
    return dsets


def scale_ms_data(data, aveP=0, avEmis=0):
    if aveP==0:
        #if no average pressure provided, calculate for this set
        aveP = array([d[8] for d in data], dtype=float).mean()

    if avEmis==0:
        #if no average emission provided, calculate for this set
        avEmis = array([d[5] for d in data], dtype=float).mean()

    numscans = len(data)
    press = array([d[8] for d in data], dtype=float)
    normP = aveP/press
    emis = array([d[5] for d in data], dtype=float)
    normE = avEmis/emis
    scans = array([d[-2] for d in data])
    scaled = [scans[i]*normP[i]*normE[i] for i in range(0, numscans)]
    return scaled

def plot_MS_bar(M, D, sttl, ttl, yl='Counts', save=False, inline=True):
    figure()
    bar(M-.5, D)
    suptitle(sttl, fontsize=16)
    gca().set_title(ttl, fontsize=12)
    xlabel('Mass per Charge')
    ylabel(yl)
    xlim(.5, 25.5)
    ylim(0, 1.05*max(D))
    draw()
    savefig(sttl+'.png')

def plot_crosstalk(dsets, mscans):
    sum_sets(dsets, mscans)
    for s in dsets:
        plot_MS_bar(s['masses'], s['sum'], s['sttl'], s['ttl'])


def get_ds_ranges(dst):
    for k in dst.keys():
        sc = sorted(dst[k])
        i, f = int(sc[0][1:]), int(sc[-1][1:])
        dst[k] = {'srange':(i,f)}


def sum_sets(dsets, mscans):
    # sum the mass scans to create one composite mass scan for the dataset
    # extract all items relevant to scale and compare mass scan data
    for s in sorted(dsets.keys()):
        i, f = dsets[s]['srange']
        scanSum = sum(array([ms[-2] for ms in mscans[i-1: f]]),0)
        h2cts = array([ms[-2][1] for ms in mscans[i-1:f]])
        wcts = array([ms[-2][14] for ms in mscans[i-1:f]])
        csEmis = array([ms[8] for ms in mscans[i-1:f]])
        osEmis = array([ms[11] for ms in mscans[i-1:f]])
        chPress = array([ms[12] for ms in mscans[i-1:f]])
        run, mode, ip = mscans[f-1][0:7:3]
        masses = mscans[f-1][-1]
        numscans = f-(i-1)

        sttl = '{} - R{}'
        if 'bkg' in s:
            sttl =  '{} Background - R{}'
        elif mode=='OSNB':
            if float(mscans[f-1][7]) >= 1:
                sttl = '{} - CS On - R{}'
            else:
                sttl = '{} - CS Off - R{}'
        sttl = sttl.format(mode, run)
        if 'v_comp' in dsets[s].keys():
            sttl = sttl + ' {}kms'.format(dsets[s]['v_comp'])

        ttl = 'IP={}ms, {} scan summation, H2counts={:.0f}'
        ttl = ttl.format(ip, numscans, scanSum[1])

        dsets[s]['sum']=scanSum
        dsets[s]['h2cts']=h2cts
        dsets[s]['wcts'] = wcts
        dsets[s]['masses'] = masses
        dsets[s]['cemis'] = csEmis
        dsets[s]['oemis'] = osEmis
        dsets[s]['chPress'] = chPress
        dsets[s]['sttl']=sttl
        dsets[s]['ttl']=ttl


def compute_stats(dsets):
    pfmat = '{:<28}: sum={:12.0f}, std={:12.2f}, var={:12.2f}'
    hfmat = '{:<28}: {:>12}{:>12}{:>12}'
    print(hfmat.format('Dataset','H2 Sum','Avg. H2','Std. Dev.', 'Variance'))
    for dk in sorted(dsets.keys()):
        if 'h2cts' in dsets[dk].keys():
            ds = dsets[dk]
            ds['h2sum'] = ds['h2cts'].sum()
            ds['mean'] = ds['h2cts'].mean()
            ds['std'] = ds['h2cts'].std()
            ds['var'] = ds['h2cts'].var()
            pp = pfmat.format(ds['sttl'],ds['h2sum'],ds['mean'],ds['std'],ds['var'])
            print(pp)


def compute_h2ratios(dsets):
    #dsets must be a specific grouping of 5 data sets including, in order,
    #CSN background, OSNB background, OSNB-filOn, CSN, and OSNB-filOff.
    if len(dsets)==5:
        skeys = sorted(dsets.keys())

        #asign the variables
        bkgc, bkgo, osnb1, csn, osnb2 = [dsets[sk] for sk in skeys]

        #subtract the background
        csnb = csn['sum'] - bkgc['sum']
        osnb1b = osnb1['sum'] - bkgo['sum']
        osnb2b = osnb2['sum'] - bkgo['sum']

        #compute the ratios for all species
        osnb1r = osnb1b/csnb
        osnb2r = osnb2b/csnb

        #compute the uncertainty
        m1, m2, mc = osnb1['mean'], osnb2['mean'], csn['mean']
        m1r = m1/mc
        m2r = m2/mc
        sig1 = (m1/mc)*sqrt((1/m1)+(1/mc))
        sig2 = (m2/mc)*sqrt((1/m2)+(1/mc))

        #attach results to appropriate data sets
        osnb1['ratio'] = m1r
        osnb1['sigma'] = sig1

        osnb2['ratio'] = m2r
        osnb2['sigma'] = sig2

        #print results
        pfmat = '\tCS{}: \tR(H2)={:.2e}, uncertainty={:.2e}'
        print('\tH2 ratio OSNB/{}'.format(csn['sttl']))
        print(pfmat.format('On', m1r, sig1))
        print(pfmat.format('Off', m2r, sig2))

    else:
        print('Compute_h2ratios requires exactly 5 data sets.')


def compute_water_ratios(dsets):
    skeys = sorted(dsets.keys())
    mh1, mhc, mh2 = [dsets[sk]['mean'] for sk in skeys[2:]]
    mw1, mwc, mw2 = [dsets[sk]['sum'][14]/40 for sk in skeys[2:]]
    mhw1, mhw2 = mh1/mw1, mh2/mw2
    sig1 = (mh1/mw1)*sqrt((1/mh1)+(1/mw1))
    sig2 = (mh2/mw2)*sqrt((1/mh2)+(1/mw2))
    sig3 = mhc/mwc*sqrt((1/mhc)+(1/mwc))
    sig4 = mh1/mwc*sqrt((1/mh1)+(1/mhc))
    sig5 = mh2/mwc*sqrt((1/mh2)+(1/mhc))
    pfmat = '\t{}: \tR({})={:.2e}, uncertainty={:.2e}'
    print('\tH2/H2O ratio OSNB v_comp = 8.506 km/s')
    print(pfmat.format('OSNB CS-On', 'H2/H2O', mhw1, sig1))
    print(pfmat.format('OSNB CS-Off', 'H2/H2O',mhw2, sig2))
    print(pfmat.format('CS Only', 'H2/H2O', mhc/mwc, sig3))
    print(pfmat.format('OSNB CS_On', 'H2(OSNB)/H2O(CSN)', mh1/mwc, sig4))
    print(pfmat.format('OSNB CS_Off', 'H2(OSNB)/H2O(CSN)', mh2/mwc, sig5))

sfld = ['run', 'dataset', 'scan', 'mode',
        'initial_time', 'final_time']

mfld = ['ip','cs_fil1_emis_iset','cs_fil1_emis_imon', 'cs_anode1_imon',
        'cs_ionrep_imon', 'os_fil1_emis_imon', 'chamber_ion_pmon']


def get_crosstalk_data(dpath):
    msd = get_INMS_data(dpath, '*_MS_*')
    msc = get_INMS_scan_data(msd, id0='mass', sfields=sfld, mfields=mfld)
    dst = get_datasets(msd)
    get_ds_ranges(dst)
    sum_sets(dst, msc)
    compute_stats(dst)

    return dst, msd, msc


#sttl = 'CSN Background - R{}'
#ttlc = 'IP=1000ms, 40 scan summation, H2counts={:.0f}'.format(cts[1])

#sttl = 'OSNB Background - R{}'
#ttlc = 'IP=1000ms, 40 scan summation, H2counts={:.0f}'.format(cts[1])

#sttl = 'OSNB - CS On - R{}'
#ttlc = 'IP=1000ms, 40 scan summation, H2counts={:.0f}'.format(cts[1])

#sttl = 'CSN - R{}'
#ttlc = 'IP=1000ms, 40 scan summation, H2counts={:.0f}'.format(cts[1])

#sttl = 'OSNB - CS Off - R{}'
#ttlc = 'IP=1000ms, 40 scan summation, H2counts={:.0f}'.format(cts[1])

#sttl = 'Crosstalk Ratio, CS On - R{}'
#ttlr = 'IP=1000ms, 40 scan summation, H2 ratio = {:.3f}'.format(cts[1])

#sttl = 'Crosstalk Ratio, CS Off - R{}'
#ttlr = 'IP=1000ms, 40 scan summation, H2 ratio = {:.3f}'.format(cts[1])


#ct352 = [{dset:'bkgc', rrange}]

Download
(142KB zipped)

Filename Size
INMS Source Crosstalk and H2.docx 92 KB
INMS_DA_scratch.py 5 KB
INMS_REU_Data_Analysis.py 10 KB
OSNB CSN Crosstalk Counting Statistics.xlsx 59 KB