Commit 2c4f3908 authored by Zolnierczuk, Piotr's avatar Zolnierczuk, Piotr
Browse files

new "home" for EPICS PVs

- for now top of nexus.py module
- added detector configuration PVs
parent a440e4d8
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
* release v2.1..

	put EPICS PV in one separate place (for now in nexus.py)
	- added detector config PVs

	new matrix_analysis module (pysen.echo.matrix_analysis)
	- uses methods from Hayter paper

	work on main plotting tab

	new utility "fix_nexus_value"
+6 −1
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ from numpy import (sqrt, log, log10, sin, cos, arccos, arctan2,
                     radians, degrees, linspace, logspace, isnan)
import numpy  as np

from .constants import KAPPA_N_NSA, HMN, ANGSTROM
from .constants import OMEGA_N, KAPPA_N_NSA, HMN, ANGSTROM
from .physics   import get_theta, get_q, q_binning, l_binning

INST_POSITIONS =  ['p1', 'p2', 'p3', 'p4']
@@ -73,6 +73,11 @@ NSE_FLIPPERS = { # type, position [m], thickness [m], turns_den
    "fpi22"  : dict(type='pi/2', position =+2.823903, thickness=0.010, turns_density=821.40), #w=0.28 h=0.28, i34
}

# 4c. phase angle sensitivity (field integral due to phase coil)
J5_PER_AMPERE = 74.658e-6 # [T*m/ampere] field integral per ampere for the phase coil (measured value)
PHASE_SENSITIVITY = J5_PER_AMPERE*OMEGA_N # ~3.5e10 [radians/Ampere/meter] phase angle sensitivity
                                          # or ~198 degrees/Ampere/Angstrom

# 5. Misc other data
ATTENUATOR_TABLE_08A = {
    'ERROR' :  1.0,
+97 −50
Original line number Diff line number Diff line
@@ -26,31 +26,71 @@ import numpy as np
import h5py

from .. import MICRO, ANGSTROM, tof2lambda
from ..config import Ltot, TDC_SHIFT, TDC_MAXCH
from ..config import Ltot, TDC_SHIFT, XDET, YDET, TDC_MAXCH, DEFAULT_ACCELERATOR_POWER, PHASE_SENSITIVITY

from .utils import get_nsefiletype

# FIXME (hard coded detector geometry)
X0_PIX    =   516 # center pixel
Y0_PIX    =   518
XWID2     =   813//2 # 410 # half-width
YWID2     =   821//2 # 410
# defaults for detector geometry (backwards compatibility)
_X0 = 516
_Y0 = 518
_X_SCALE = (813//2)/XDET*2*1e-3 # pixels/mm
_Y_SCALE = (821//2)/YDET*2*1e-3 # pixels/mm

PV_CONFIG_MAIN = {
# name        EPICS PV
'i00'  :     ('BL15:CAENELS00:ActualCurrent', 'BL15:Bruker:ReadOutCurrent'), # new name, old name
'phase':     ('BL15:CAENELS5:ActualCurrent',),
'qmin' :     ('BL15:Mot:Qmin'),
}

LTOT_P2 = Ltot.get('p2') # default value if mo_l not found
PV_CONFIG_LEGACY = {
# name   EPICS PV
'tau' : 'BL15:Legacy:Tau',
'j1'  : 'BL15:Legacy:J1' ,
'j2'  : 'BL15:Legacy:J2' ,
}

MOTORS    = ('mophi', 'mopsi', 'mo_z', 'moana', 'mo_l', 'moatt')

B_SENSORS = {'b_sample': 'sample'  ,
             'b_encl1' : 'fpi21'   ,
             'b_encl2' : 'fpi22'   ,
             'b_ext1'  : 'external',
             'b_ext2'  : 'external',
             'b_ext3'  : 'external',
             'b_ext4'  : 'external',
             'b_ext5'  : 'external'}
PV_CONFIG_AVERAGES = {
# name   EPICS PV           default value
'qmin' : ('BL15:Mot:Qmin' , 0.1),
'lmin' : ('BL15:Chop:Skf1:WavelengthSet' , None),
'lmax' : ('BL15:Chop:Skf4:WavelengthSet' , None),
#                                                    default value for 8 Angstrom neutrons
'phasesens':   ('BL15:Legacy:PhaseAngleSensitivity', np.degrees(PHASE_SENSITIVITY)*8*ANGSTROM),
'sample_temp': ('BL15:SE:LS1:SAMPLE_TEMP', 303.0),
'beam_power' : ('BeamPower', DEFAULT_ACCELERATOR_POWER),
#

# FIXME (hard coded detector geometry)
'x0_pix':   ('BL15:CS:Det:N1:CENTER_X', _X0),
'y0_pix':   ('BL15:CS:Det:N1:CENTER_Y', _Y0),
'x_scale' : ('BL15:CS:Det:N1:SCALE_X', _X_SCALE),
'y_scale' : ('BL15:CS:Det:N1:SCALE_Y', _Y_SCALE),
}

#
PV_MOTORS = {
# name   EPICS PV           default value
'mophi': ('BL15:Mot:mophi', None),
'mopsi': ('BL15:Mot:mopsi', None),
'mo_z':  ('BL15:Mot:mo_z' , None),
'moana': ('BL15:Mot:moana', None),
'mo_l' : ('BL15:Mot:mo_l' , Ltot.get('p2')), # default value if mo_l not found
'moatt': ('BL15:Mot:moatt', None),
}

PV_BSENSORS = {
#sensor     EPICS PV                type (location)
'b_sample': ("BL15:PLC:Mag:sample", "sample"),
'b_encl1' : ("BL15:PLC:Mag:encl1",  "fpi21"),
'b_encl2' : ("BL15:PLC:Mag:encl2",  "fpi22"),
'b_ext1'  : ("BL15:PLC:Mag:ext1",   "external"),
'b_ext2'  : ("BL15:PLC:Mag:ext2",   "external"),
'b_ext3'  : ("BL15:PLC:Mag:ext3",   "external"),
'b_ext4'  : ("BL15:PLC:Mag:ext4",   "external"),
'b_ext5'  : ("BL15:PLC:Mag:ext5",   "external"),
}


def pixel_decompose(pixel):
@@ -79,6 +119,28 @@ def time_format(ctime, fmt='%c'):
    ptime = dtm.datetime.strptime(ctime, '%Y-%m-%dT%H:%M:%S%z')
    return  ptime.strftime(fmt) #Fri Oct 24 05:26:54 2014

def get_average_value(nexus_file, vname, prefix="/entry/DASlogs", default_value=None):
    """
    get average value for a given nexus key, return default_value if not found

    nexus_file: nexus file object
    vname: pv name (without prefix)
    prefix: nexus key prefix (default: /entry/DASlogs)
    default_value: default value to return if key not found (default: None)
    """
    key = f"{prefix}/{vname}/average_value"
    try:
        value = nexus_file[key][0]
    except KeyError:
        basename = os.path.basename(nexus_file.filename)
        if default_value is None:
            logging.error("*** %s: cannot find nexus key %s", basename, key)
        else:
            logging.warning("*** %s: using default value for missing %s=%s",
                basename, key, default_value)
        value = default_value
    return value

def get_run_info(nxsfile):
    "get a base SNS-NSE run info as a dictionary"
    base = os.path.basename(nxsfile.filename)
@@ -101,38 +163,20 @@ def get_run_info(nxsfile):
    log.info("%s: %s, duration=%.1f min", base, res['time'], res['duration']/60)
    log.info("%s: %s %s", base, res['title'], res['notes'])

    def get_average_value(nexus_key, default_value):
        key = f"{nexus_key}/average_value"
        try:
            value = nxsfile[key][0]
        except KeyError:
            if default_value is None:
                log.error("*** %s: cannot find nexus key %s", base, key)
            else:
                log.warning("*** %s: using default value for missing %s=%s",
                    base, key, default_value)
            value = default_value
        return value
    # FIXME: put these in some confg list
    for motor in ('mophi', 'mopsi', 'mo_z', 'moana', 'mo_l', 'moatt'):
        default_value = LTOT_P2 if motor=='mo_l' else None
        res[motor] = get_average_value(f"/entry/DASlogs/BL15:Mot:{motor}", default_value)
    res['qmin' ] = get_average_value('/entry/DASlogs/BL15:Mot:Qmin', 0.1)
    res['lmin' ] = get_average_value('/entry/DASlogs/BL15:Chop:Skf1:WavelengthSet', None)
    res['lmax' ] = get_average_value('/entry/DASlogs/BL15:Chop:Skf4:WavelengthSet', None)
    #
    res['phasesens'] = get_average_value('/entry/DASlogs/BL15:Legacy:PhaseAngleSensitivity',1585.0)
    res['sample_temp'] = get_average_value('/entry/DASlogs/BL15:SE:LS1:SAMPLE_TEMP', 303.0)
    res['beam_power' ] = get_average_value('/entry/DASlogs/BeamPower', 1.7)
    #
    for name in PV_MOTORS:
        pvname, default_value = PV_MOTORS.get(name)
        res[name] = get_average_value(nxsfile, pvname, prefix='/entry/DASlogs', default_value=default_value)
    for name in PV_CONFIG_AVERAGES:
        pvname, default_value = PV_CONFIG_AVERAGES.get(name)
        res[name] = get_average_value(nxsfile, pvname, prefix='/entry/DASlogs', default_value=default_value)
    # legacy control values
    for pv in ('Tau', 'J1', 'J2'):
        key = pv.lower()
    for name in PV_CONFIG_LEGACY:
        pvname = PV_CONFIG_LEGACY.get(name)
        try:
            res[key] = timevalue_array(nxsfile, vname=f"BL15:Legacy:{pv}", prefix="/entry/DASlogs")
            res[name] = timevalue_array(nxsfile, vname=pvname, prefix="/entry/DASlogs")
        except KeyError:
            res[key] = None

            res[name] = None
    return res

def print_nexus_info(runinfo):
@@ -237,6 +281,7 @@ def process_nexus_scan(filename, **kwargs):
    pcharge = timevalue_array(nxsfile, 'proton_charge')
    events  = nxsfile['/entry/bank1_events']
    #
    values = {}
    try:
        main_sc_current = timevalue_array(nxsfile, 'BL15:CAENELS00:ActualCurrent')
    except KeyError:
@@ -247,8 +292,10 @@ def process_nexus_scan(filename, **kwargs):
        'phase': timevalue_array(nxsfile, 'BL15:CAENELS5:ActualCurrent'),
        'qmin' : timevalue_array(nxsfile, 'BL15:Mot:Qmin'),
    }
    for bsens in ('sample', 'encl1', 'encl2', 'ext1','ext2', 'ext3','ext4','ext5'):
        values['b_'+bsens] = timevalue_array(nxsfile, f"BL15:PLC:Mag:{bsens}")

    for bsens in PV_BSENSORS:
        pvname, _ = PV_BSENSORS.get(bsens)
        values[bsens] = timevalue_array(nxsfile, pvname)

    i, k = 0,0 # begin and end of scan
    in_scan = False
+25 −15
Original line number Diff line number Diff line
@@ -7,11 +7,10 @@ import logging

import numpy as np

from ..config import NXCHAN as NPIX, NTCHAN as NTOF
from .nexus   import process_nexus_scan, process_nexus_plain, print_nexus_info, X0_PIX, Y0_PIX, XWID2, YWID2

# BUNCH OF HARD CODED CONSTANTS
from ..config import NXCHAN as NPIX, NTCHAN as NTOF, XDET, YDET
from .nexus   import process_nexus_scan, process_nexus_plain, print_nexus_info

# FIXME: BUNCH OF HARD CODED CONSTANTS
TAU_IDX   = 1000
DN_IDX    =  100
UP_IDX    =  200
@@ -53,11 +52,7 @@ class BaseScan:
            return False
        self.info.update(**kwargs)
        #
        lmin = self.info.get('lmin', 5.0)
        lmax = self.info.get('lmax', 8.0)
        self.info['xbin'] = np.linspace(X0_PIX-XWID2, X0_PIX+XWID2, self.info['npix']+1)
        self.info['ybin'] = np.linspace(Y0_PIX-YWID2, Y0_PIX+YWID2, self.info['npix']+1)
        self.info['tbin'] = np.linspace(lmin, lmax, self.info['ntof']+1)
        self.get_detector_info()
        #
        if self.info['proton_charge']<=0:
            #self.log.warning("file %s has no proton_charge", nxsfilename)
@@ -72,6 +67,26 @@ class BaseScan:
        self.log.debug("nexus info: %s", print_nexus_info(self.info))
        return True

    def get_detector_info(self):
        "get detector info"
        ntof = self.info.get('ntof', NTOF)
        npix = self.info.get('npix', NPIX)

        lmin = self.info.get('lmin', 5.0)
        lmax = self.info.get('lmax', 8.0)

        x0 = self.info.get('x0_pix', 516)
        y0 = self.info.get('y0_pix', 518)
        x_scale = self.info.get('x_scale', 2.707)
        y_scale = self.info.get('y_scale', 2.733)

        xwid2 = int(x_scale*XDET*1e3/2)
        ywid2 = int(y_scale*YDET*1e3/2)

        self.info['xbin'] = np.linspace(x0-xwid2, x0+xwid2, npix+1)
        self.info['ybin'] = np.linspace(y0-ywid2, y0+ywid2, npix+1)
        self.info['tbin'] = np.linspace(lmin, lmax, ntof+1)

class TransmissionScan(BaseScan):
    """Read SNS-NSE EPICS/NeXus transmission scan file
    """
@@ -90,12 +105,7 @@ class TransmissionScan(BaseScan):
        self.info, self.data = _res
        self.info.update(**kwargs)
        #
        lmin = self.info.get('lmin', 5.0)
        lmax = self.info.get('lmax', 8.0)
        self.info['xbin'] = np.linspace(X0_PIX-XWID2, X0_PIX+XWID2, self.info['npix']+1)
        self.info['ybin'] = np.linspace(Y0_PIX-YWID2, Y0_PIX+YWID2, self.info['npix']+1)
        self.info['tbin'] = np.linspace(lmin, lmax, self.info['ntof']+1)
        #
        self.get_detector_info()
        # debug
        self.log.debug("nexus info: %s", print_nexus_info(self.info))
        return True
+6 −6
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ import numpy as np
from ..config import (  XDET, YDET, DEFAULT_PROTON_ENERGY as PROTON_ENERGY,
            PROTON_CHARGE_UNIT, get_attenuator_pos)
from ..constants import PICO, NANO, MICRO, ANGSTROM, GAUSS
from .nexus import MOTORS, B_SENSORS, time_format
from .nexus import PV_MOTORS, PV_BSENSORS, time_format
from .scans import EchoScan, DiffractionScan, TransmissionScan

# FIXME: BUNCH OF HARD CODED CONSTANTS
@@ -238,8 +238,8 @@ class EchoWriter(EchoScan):
        out.write(f"sample {samplename}\n")
        out.write( "* comment \n")
        out.write(f"automatic_date: {time_format(self.info['time'])}\n")
        out.write(f"{self.info['title']}, {ipts}\n")
        out.write(f"{samplename} {sampleid}\n")
        out.write(f"{self.info['title']}\n")
        out.write(f"{samplename}, {ipts}, ITEMS-{sampleid}\n")
        out.write( "\neof\n\n")
        return out.getvalue()

@@ -260,7 +260,7 @@ class EchoWriter(EchoScan):
        out.write(f"phase {phasedeg:g}\n")
        out.write(f"phase_current {phasecur:.6f} {phasecur:.6f}\n")
        #
        for bsens, btyp in B_SENSORS.items():
        for bsens, (_pvname, btyp) in PV_BSENSORS.items():
            b = res[f"{bsens}"]*MICRO/GAUSS
            out.write(f"Bfield {btyp:8s} {bsens:8s} {b:.6f} G 0.0 G 0.0 G\n")
        # proton charge
@@ -333,12 +333,12 @@ class EchoWriter(EchoScan):
                fd.write(f"*  i5 {i5:14.6f} {i5:14.6f}\n")
                #
                fd.write( "c bsensors (x,y,z) aimed | actual\n")
                for bsens in B_SENSORS:
                for bsens in PV_BSENSORS:
                    bfld = res2[bsens]*MICRO/GAUSS
                    fd.write(f"* {bsens:8s} 7.7 7.7 7.7 {bfld:14.6f} 0.0 0.0\n")
                #
                fd.write( "c motors\n")
                for mot in MOTORS:
                for mot in PV_MOTORS:
                    pos = self.info[mot]
                    if mot=='mo_l': # mo_l => convert to mm from m
                        pos = pos*1000.0
Loading