Source code for fact.instrument.camera

import pkg_resources as res
import numpy as np
from functools import lru_cache
import pandas as pd

from .constants import (
    FOCAL_LENGTH_MM, PINCUSHION_DISTORTION_SLOPE,
    PIXEL_SPACING_IN_MM, FOV_PER_PIXEL_DEG
)


[docs]def camera_distance_mm_to_deg(distance_mm): ''' Transform a distance in mm in the camera plane to it's approximate equivalent in degrees. ''' return distance_mm * FOV_PER_PIXEL_DEG / PIXEL_SPACING_IN_MM
pixel_mapping = np.genfromtxt( res.resource_filename('fact', 'resources/FACTmap111030.txt'), names=[ 'softID', 'hardID', 'geom_i', 'geom_j', 'G-APD', 'V_op', 'HV_B', 'HV_C', 'pos_X', 'pos_Y', 'radius' ], dtype=None, ) non_standard_pixel_chids = dict( dead=[927, 80, 873], crazy=[863, 297, 868], twins=[ # the signals of these pairs of pixels are the same (1093, 1094), (527, 528), (721, 722), ] ) GEOM_2_SOFTID = { (i, j): soft for i, j, soft in zip( pixel_mapping['geom_i'], pixel_mapping['geom_j'], pixel_mapping['softID'] )}
[docs]def reorder_softid2chid(array): ''' Returns view to the given array, remapped from softid order to chid order (e.g. MARS ordering to fact-tools ordering) ''' return array[CHID_2_SOFTID]
@lru_cache(maxsize=1)
[docs]def get_pixel_dataframe(): ''' return pixel mapping as pd.DataFrame ''' pm = pd.DataFrame(pixel_mapping) pm.sort_values('hardID', inplace=True) # after sorting the CHID is in principle the index # of pm, but I'll like to have it explicitely pm['CHID'] = np.arange(len(pm)) pm['trigger_patch_id'] = pm['CHID'] // 9 pm['bias_patch_id'] = pm['HV_B'] * 32 + pm['HV_C'] bias_patch_sizes = pm.bias_patch_id.value_counts().sort_index() pm['bias_patch_size'] = bias_patch_sizes[pm.bias_patch_id].values pm['x'] = -pm.pos_Y.values * PIXEL_SPACING_IN_MM pm['y'] = pm.pos_X.values * PIXEL_SPACING_IN_MM pm['x_angle'] = np.rad2deg( np.arctan(pm.x / FOCAL_LENGTH_MM) * (1 + PINCUSHION_DISTORTION_SLOPE) ) pm['y_angle'] = np.rad2deg( np.arctan(pm.y / FOCAL_LENGTH_MM) * (1 + PINCUSHION_DISTORTION_SLOPE) ) return pm
FOV_RADIUS = np.hypot( get_pixel_dataframe().x_angle, get_pixel_dataframe().y_angle ).max() patch_indices = get_pixel_dataframe()[[ 'trigger_patch_id', 'bias_patch_id', 'bias_patch_size', ]].drop_duplicates().reset_index(drop=True) @np.vectorize def geom2soft(i, j): return GEOM_2_SOFTID[(i, j)]
[docs]def softid2chid(softid): return hardid2chid(pixel_mapping['hardID'])[softid]
[docs]def softid2hardid(softid): return pixel_mapping['hardID'][softid]
[docs]def hardid2chid(hardid): crate = hardid // 1000 board = (hardid // 100) % 10 patch = (hardid // 10) % 10 pixel = (hardid % 10) return pixel + 9 * patch + 36 * board + 360 * crate
CHID_2_SOFTID = np.empty(1440, dtype=int) for softid in range(1440): hardid = pixel_mapping['hardID'][softid] chid = hardid2chid(hardid) CHID_2_SOFTID[chid] = softid
[docs]def chid2softid(chid): return CHID_2_SOFTID[chid]
[docs]def hardid2softid(hardid): return chid2softid(hardid2chid(hardid))
[docs]def get_pixel_coords(): ''' Calculate the pixel coordinates from the standard pixel-map file by default it gets rotated by 90 degrees clockwise to show the same orientation as MARS and fact-tools ''' df = get_pixel_dataframe() return df.x.values, df.y.values
@lru_cache(maxsize=1)
[docs]def bias_to_trigger_patch_map(): by_chid = pixel_mapping['hardID'].argsort() bias_channel = pixel_mapping[by_chid]['HV_B'] * 32 + pixel_mapping[by_chid]['HV_C'] _, idx = np.unique(bias_channel, return_index=True) return bias_channel[np.sort(idx)]
[docs]def combine_bias_patch_current_to_trigger_patch_current(bias_patch_currents): """ For this to work, you need to know that the calibrated currents in FACT which are delivered by the program FEEDBACK for all 320 bias patches are given as "uA per pixel per bias patch", not as "uA per bias patch" So if you want to combine these currents into one value given as: "uA per trigger patch" or "uA per pixel per trigger patch", you need to know the number of pixels in a bias patch. Luckily this is given by our patch_indices() DataFrame """ pi = patch_indices() fourers = pi[pi.bias_patch_size == 4].sort_values('trigger_patch_id') fivers = pi[pi.bias_patch_size == 5].sort_values('trigger_patch_id') b_c = bias_patch_currents # just to shorten the name t_c = b_c[fourers.bias_patch_id.values] * 4/9 + b_c[fivers.bias_patch_id.values] * 5/9 trigger_patch_currents = t_c # unshorten the name return trigger_patch_currents
[docs]def take_apart_trigger_values_for_bias_patches(trigger_rates): """ Assumption is you have 160 values from trigger patches, such as the trigger rates per patch, or the trigger threshold per patch or whatever else you cant find per patch. And for some reason you say: Well this is valid for the entire trigger patch really, but I believe it also correlates with the bias patch """ pi = patch_indices().sort_values('bias_patch_id') return trigger_rates[pi.trigger_patch_id.values]