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]