def convert_geometry_hex1d_to_rect2d(geom, signal, key=None, add_rot=0): """converts the geometry object of a camera with a hexagonal grid into a square grid by slanting and stretching the 1D arrays of pixel x and y positions and signal intensities are converted to 2D arrays. If the signal array contains a time-dimension it is conserved. Parameters ---------- geom : CameraGeometry object geometry object of hexagonal cameras signal : ndarray 1D (no timing) or 2D (with timing) array of the pmt signals key : (default: None) arbitrary key to store the transformed geometry in a buffer add_rot : int/float (default: 0) parameter to apply an additional rotation of `add_rot` times 60° Returns ------- new_geom : CameraGeometry object geometry object of the slanted picture now with a rectangular grid and a 2D grid for the pixel positions. contains now a 2D masking array signifying which of the pixels came from the original geometry and which are simply fillers from the rectangular grid rot_img : ndarray 2D (no timing) or 3D (with timing) the rectangular signal image """ if key in rot_buffer: # if the conversion with this key was done before and stored, # just read it in (geom, new_geom, hex_to_rect_map) = rot_buffer[key] else: # otherwise, we have to do the conversion first now, # skew all the coordinates of the original geometry # extra_rot is the angle to get back to aligned hexagons with flat # tops. Note that the pixel rotation angle brings the camera so that # hexagons have a point at the top, so need to go 30deg back to # make them flat extra_rot = geom.pix_rotation - 30 * u.deg # total rotation angle: rot_angle = (add_rot * 60 * u.deg) - extra_rot logger.debug("geom={}".format(geom)) logger.debug("rot={}, extra={}".format(rot_angle, extra_rot)) rot_x, rot_y = unskew_hex_pixel_grid(geom.pix_x, geom.pix_y, cam_angle=rot_angle) # with all the coordinate points, we can define the bin edges # of a 2D histogram x_edges, y_edges, x_scale = get_orthogonal_grid_edges(rot_x, rot_y) # this histogram will introduce bins that do not correspond to # any pixel from the original geometry. so we create a mask to # remember the true camera pixels by simply throwing all pixel # positions into numpy.histogramdd: proper pixels contain the # value 1, false pixels the value 0. square_mask = np.histogramdd([rot_y, rot_x], bins=(y_edges, x_edges))[0].astype(bool) # to be consistent with the pixel intensity, instead of saving # only the rotated positions of the true pixels (rot_x and # rot_y), create 2D arrays of all x and y positions (also the # false ones). grid_x, grid_y = np.meshgrid((x_edges[:-1] + x_edges[1:]) / 2., (y_edges[:-1] + y_edges[1:]) / 2.) ids = [] # instead of blindly enumerating all pixels, let's instead # store a list of all valid -- i.e. picked by the mask -- 2D # indices for i, row in enumerate(square_mask): for j, val in enumerate(row): if val is True: ids.append((i, j)) # the area of the pixels (note that this is still a deformed # image) pix_area = np.ones_like(grid_x) \ * (x_edges[1] - x_edges[0]) * (y_edges[1] - y_edges[0]) # creating a new geometry object with the attributes we just determined new_geom = CameraGeometry( cam_id=geom.cam_id + "_rect", pix_id=ids, # this is a list of all the valid coordinate pairs now pix_x=grid_x * u.m, pix_y=grid_y * u.m, pix_area=pix_area * u.m ** 2, neighbors=geom.neighbors, pix_type='rectangular', apply_derotation=False) # storing the pixel mask for later use new_geom.mask = square_mask # create a transfer map by enumerating all pixel positions in a 2D histogram hex_to_rect_map = np.histogramdd([rot_y, rot_x], bins=(y_edges, x_edges), weights=np.arange(len(signal)))[0].astype(int) # bins that do not correspond to the original image get an entry of `-1` hex_to_rect_map[~square_mask] = -1 if signal.ndim > 1: long_map = [] for i in range(signal.shape[-1]): tmp_map = hex_to_rect_map + i * (np.max(hex_to_rect_map) + 1) tmp_map[~square_mask] = -1 long_map.append(tmp_map) hex_to_rect_map = np.array(long_map) if key is not None: # if a key is given, store the essential objects in a buffer rot_buffer[key] = (geom, new_geom, hex_to_rect_map) # done `if key in rot_buffer` # create the rotated rectangular image by applying `hex_to_rect_map` to the flat, # extended input image # `input_img_ext` is the flattened input image extended by one entry that contains NaN # since `hex_to_rect_map` contains `-1` for "fake" pixels, it maps this extra NaN # value at the last array position to any bin that does not correspond to a pixel of # the original image input_img_ext = np.full(np.prod(signal.shape) + 1, np.nan) # the way the map is produced, it has the time dimension as axis=0; # but `signal` has it as axis=-1, so we need to roll the axes back and forth a bit. # if there is no time dimension, `signal` is a 1d array and `rollaxis` has no effect. input_img_ext[:-1] = np.rollaxis(signal, axis=-1, start=0).ravel() # now apply the transfer map rot_img = input_img_ext[hex_to_rect_map] # if there is a time dimension, roll the time axis back to the last position try: rot_img = np.rollaxis(rot_img, 0, 3) except ValueError: pass return new_geom, rot_img
def convert_geometry_1d_to_2d(geom, signal, key=None, add_rot=0): """converts the geometry object of a camera with a hexagonal grid into a square grid by slanting and stretching the 1D arrays of pixel x and y positions and signal intensities are converted to 2D arrays. If the signal array contains a time-dimension it is conserved. Parameters ---------- geom : CameraGeometry object geometry object of hexagonal cameras signal : ndarray 1D (no timing) or 2D (with timing) array of the pmt signals key : (default: None) arbitrary key to store the transformed geometry in a buffer add_rot : int/float (default: 0) parameter to apply an additional rotation of @add_rot times 60° Returns ------- new_geom : CameraGeometry object geometry object of the slanted picture now with a rectangular grid and a 2D grid for the pixel positions contains now a 2D masking array signifying which of the pixels came from the original geometry and which are simply fillers from the rectangular grid square_img : ndarray 2D (no timing) or 3D (with timing) array of the pmt signals """ if key in rot_buffer: # if the conversion with this key was done and stored before, # just read it in (rot_x, rot_y, x_edges, y_edges, new_geom, x_scale) = rot_buffer[key] else: # otherwise, we have to do the conversion now first, skey all # the coordinates of the original geometry # extra_rot is the angle to get back to aligned hexagons with flat # tops. Note that the pixel rotation angle brings the camera so that # hexagons have a point at the top, so need to go 30deg past that to # make them flat extra_rot = geom.pix_rotation + 30*u.deg # total rotation angle: rot_angle = (add_rot * 60 * u.deg) - extra_rot # if geom.cam_id.startswith("NectarCam")\ # or geom.cam_id.startswith("LSTCam"): # rot_angle += geom.cam_rotation + 90 * u.deg logger.debug("geom={}".format(geom)) logger.debug("rot={}, extra={}".format(rot_angle, extra_rot)) rot_x, rot_y = unskew_hex_pixel_grid(geom.pix_x, geom.pix_y, rot_angle) # with all the coordinate points, we can define the bin edges # of a 2D histogram x_edges, y_edges, x_scale = get_orthogonal_grid_edges(rot_x, rot_y) # this histogram will introduce bins that do not correspond to # any pixel from the original geometry. so we create a mask to # remember the true camera pixels by simply throwing all pixel # positions into numpy.histogramdd: proper pixels contain the # value 1, false pixels the value 0. square_mask = np.histogramdd([rot_y, rot_x], bins=(y_edges, x_edges))[0].astype(bool) # to be consistent with the pixel intensity, instead of saving # only the rotated positions of the true pixels (rot_x and # rot_y), create 2D arrays of all x and y positions (also the # false ones). grid_x, grid_y = np.meshgrid((x_edges[:-1] + x_edges[1:]) / 2., (y_edges[:-1] + y_edges[1:]) / 2.) ids = [] # instead of blindly enumerating all pixels, let's instead # store a list of all valid -- i.e. picked by the mask -- 2D # indices for i, row in enumerate(square_mask): for j, val in enumerate(row): if val is True: ids.append((i, j)) # the area of the pixels (note that this is still a deformed # image) pix_area = np.ones_like(grid_x) \ * (x_edges[1] - x_edges[0]) * (y_edges[1] - y_edges[0]) # creating a new geometry object with the attributes we just determined new_geom = CameraGeometry( cam_id=geom.cam_id+"_rect", pix_id=ids, # this is a list of all the valid coordinate pairs now pix_x=grid_x * u.m, pix_y=grid_y * u.m, pix_area=pix_area * u.m ** 2, neighbors=geom.neighbors, cam_rotation=-geom.pix_rotation, pix_type='rectangular', apply_derotation=False) # storing the pixel mask and camera rotation for later use new_geom.mask = square_mask if key is not None: # if a key is given, store the essential objects in a buffer rot_buffer[key] = (rot_x, rot_y, x_edges, y_edges, new_geom, x_scale) # resample the signal array to correspond to the square grid -- # for signal arrays containing time slices (ndim > 1) or not # approach is the same as used for the mask only with the signal # as bin-weights if signal.ndim > 1: t_dim = signal.shape[1] square_img = np.histogramdd([np.repeat(rot_y, t_dim), np.repeat(rot_x, t_dim), [a for a in range(t_dim)] * len(rot_x)], bins=(y_edges, x_edges, range(t_dim + 1)), weights=signal.ravel())[0] else: square_img = np.histogramdd([rot_y, rot_x], bins=(y_edges, x_edges), weights=signal)[0] return new_geom, square_img
def convert_geometry_1d_to_2d(geom, signal, key=None, add_rot=0): """converts the geometry object of a camera with a hexagonal grid into a square grid by slanting and stretching the 1D arrays of pixel x and y positions and signal intensities are converted to 2D arrays. If the signal array contains a time-dimension it is conserved. Parameters ---------- geom : CameraGeometry object geometry object of hexagonal cameras signal : ndarray 1D (no timing) or 2D (with timing) array of the pmt signals key : (default: None) arbitrary key to store the transformed geometry in a buffer add_rot : int/float (default: 0) parameter to apply an additional rotation of @add_rot times 60° Returns ------- new_geom : CameraGeometry object geometry object of the slanted picture now with a rectangular grid and a 2D grid for the pixel positions contains now a 2D masking array signifying which of the pixels came from the original geometry and which are simply fillers from the rectangular grid square_img : ndarray 2D (no timing) or 3D (with timing) array of the pmt signals """ if key in rot_buffer: # if the conversion with this key was done and stored before, # just read it in (rot_x, rot_y, x_edges, y_edges, new_geom, rot_angle, pix_rotation, x_scale) = rot_buffer[key] else: # otherwise, we have to do the conversion now first, skew all # the coordinates of the original geometry # extra_rot is the angle to get back to aligned hexagons with flat # tops. Note that the pixel rotation angle brings the camera so that # hexagons have a point at the top, so need to go 30deg back to # make them flat extra_rot = geom.pix_rotation - 30 * u.deg # total rotation angle: rot_angle = (add_rot * 60 * u.deg) - extra_rot # if geom.cam_id.startswith("NectarCam")\ # or geom.cam_id.startswith("LSTCam"): # rot_angle += geom.cam_rotation + 90 * u.deg logger.debug("geom={}".format(geom)) logger.debug("rot={}, extra={}".format(rot_angle, extra_rot)) rot_x, rot_y = unskew_hex_pixel_grid(geom.pix_x, geom.pix_y, cam_angle=rot_angle) # with all the coordinate points, we can define the bin edges # of a 2D histogram x_edges, y_edges, x_scale = get_orthogonal_grid_edges(rot_x, rot_y) # this histogram will introduce bins that do not correspond to # any pixel from the original geometry. so we create a mask to # remember the true camera pixels by simply throwing all pixel # positions into numpy.histogramdd: proper pixels contain the # value 1, false pixels the value 0. square_mask = np.histogramdd([rot_y, rot_x], bins=(y_edges, x_edges))[0].astype(bool) # to be consistent with the pixel intensity, instead of saving # only the rotated positions of the true pixels (rot_x and # rot_y), create 2D arrays of all x and y positions (also the # false ones). grid_x, grid_y = np.meshgrid((x_edges[:-1] + x_edges[1:]) / 2., (y_edges[:-1] + y_edges[1:]) / 2.) ids = [] # instead of blindly enumerating all pixels, let's instead # store a list of all valid -- i.e. picked by the mask -- 2D # indices for i, row in enumerate(square_mask): for j, val in enumerate(row): if val is True: ids.append((i, j)) # the area of the pixels (note that this is still a deformed # image) pix_area = np.ones_like(grid_x) \ * (x_edges[1] - x_edges[0]) * (y_edges[1] - y_edges[0]) # creating a new geometry object with the attributes we just determined new_geom = CameraGeometry( cam_id=geom.cam_id + "_rect", pix_id=ids, # this is a list of all the valid coordinate pairs now pix_x=grid_x * u.m, pix_y=grid_y * u.m, pix_area=pix_area * u.m ** 2, neighbors=geom.neighbors, pix_type='rectangular', apply_derotation=False) # storing the pixel mask and camera rotation for later use new_geom.mask = square_mask if key is not None: # if a key is given, store the essential objects in a buffer rot_buffer[key] = RotBuffer(rot_x, rot_y, x_edges, y_edges, new_geom, rot_angle, geom.pix_rotation, x_scale) # resample the signal array to correspond to the square grid -- # for signal arrays containing time slices (ndim > 1) or not # approach is the same as used for the mask only with the signal # as bin-weights if signal.ndim > 1: t_dim = signal.shape[1] square_img = np.histogramdd([np.repeat(rot_y, t_dim), np.repeat(rot_x, t_dim), [a for a in range(t_dim)] * len(rot_x)], bins=(y_edges, x_edges, range(t_dim + 1)), weights=signal.ravel())[0] else: square_img = np.histogramdd([rot_y, rot_x], bins=(y_edges, x_edges), weights=signal)[0] return new_geom, square_img
def convert_geometry_hex1d_to_rect2d(geom, signal, key=None, add_rot=0): """converts the geometry object of a camera with a hexagonal grid into a square grid by slanting and stretching the 1D arrays of pixel x and y positions and signal intensities are converted to 2D arrays. If the signal array contains a time-dimension it is conserved. Parameters ---------- geom : CameraGeometry object geometry object of hexagonal cameras signal : ndarray 1D (no timing) or 2D (with timing) array of the pmt signals key : (default: None) arbitrary key (float, string) to store the transformed geometry in a buffer The geometries (hex and rect) will be stored in a buffer. The key is necessary to make the conversion back from rect to hex. add_rot : int/float (default: 0) parameter to apply an additional rotation of `add_rot` times 60° Returns ------- new_geom : CameraGeometry object geometry object of the slanted picture now with a rectangular grid and a 2D grid for the pixel positions. contains now a 2D masking array signifying which of the pixels came from the original geometry and which are simply fillers from the rectangular grid rot_img : ndarray 2D (no timing) or 3D (with timing) the rectangular signal image Examples -------- camera = event.inst.subarray.tel[tel_id].camera image = event.r0.tel[tel_id].image[0] key = camera.camera_name square_geom, square_image = convert_geometry_hex1d_to_rect2d(camera, image, key=key) """ if key in rot_buffer: # if the conversion with this key was done before and stored, # just read it in (geom, new_geom, hex_to_rect_map) = rot_buffer[key] else: # otherwise, we have to do the conversion first now, # skew all the coordinates of the original geometry # extra_rot is the angle to get back to aligned hexagons with flat # tops. Note that the pixel rotation angle brings the camera so that # hexagons have a point at the top, so need to go 30deg back to # make them flat extra_rot = geom.pix_rotation - 30 * u.deg # total rotation angle: rot_angle = (add_rot * 60 * u.deg) - extra_rot logger.debug(f"geom={geom}") logger.debug(f"rot={rot_angle}, extra={extra_rot}") rot_x, rot_y = unskew_hex_pixel_grid(geom.pix_x, geom.pix_y, cam_angle=rot_angle) # with all the coordinate points, we can define the bin edges # of a 2D histogram x_edges, y_edges, x_scale = get_orthogonal_grid_edges( rot_x.to_value(u.m), rot_y.to_value(u.m)) # this histogram will introduce bins that do not correspond to # any pixel from the original geometry. so we create a mask to # remember the true camera pixels by simply throwing all pixel # positions into numpy.histogramdd: proper pixels contain the # value 1, false pixels the value 0. square_mask = np.histogramdd( [rot_y.to_value(u.m), rot_x.to_value(u.m)], bins=(y_edges, x_edges))[0].astype(bool) # to be consistent with the pixel intensity, instead of saving # only the rotated positions of the true pixels (rot_x and # rot_y), create 2D arrays of all x and y positions (also the # false ones). grid_x, grid_y = np.meshgrid((x_edges[:-1] + x_edges[1:]) / 2.0, (y_edges[:-1] + y_edges[1:]) / 2.0) ids = [] # instead of blindly enumerating all pixels, let's instead # store a list of all valid -- i.e. picked by the mask -- 2D # indices for i, row in enumerate(square_mask): for j, val in enumerate(row): if val is True: ids.append((i, j)) # the area of the pixels (note that this is still a deformed # image) pix_area = (np.ones_like(grid_x) * (x_edges[1] - x_edges[0]) * (y_edges[1] - y_edges[0])) # creating a new geometry object with the attributes we just determined new_geom = CameraGeometry( camera_name=geom.camera_name + "_rect", pix_id=ids, # this is a list of all the valid coordinate pairs now pix_x=u.Quantity(grid_x.ravel(), u.meter), pix_y=u.Quantity(grid_y.ravel(), u.meter), pix_area=pix_area * u.meter**2, neighbors=geom.neighbors, pix_type="rectangular", apply_derotation=False, ) # storing the pixel mask for later use new_geom.mask = square_mask # create a transfer map by enumerating all pixel positions in a 2D histogram hex_to_rect_map = np.histogramdd( [rot_y.to_value(u.m), rot_x.to_value(u.m)], bins=(y_edges, x_edges), weights=np.arange(len(signal)), )[0].astype(int) # bins that do not correspond to the original image get an entry of `-1` hex_to_rect_map[~square_mask] = -1 if signal.ndim > 1: long_map = [] for i in range(signal.shape[-1]): tmp_map = hex_to_rect_map + i * (np.max(hex_to_rect_map) + 1) tmp_map[~square_mask] = -1 long_map.append(tmp_map) hex_to_rect_map = np.array(long_map) if key is not None: # if a key is given, store the essential objects in a buffer rot_buffer[key] = (geom, new_geom, hex_to_rect_map) # done `if key in rot_buffer` # create the rotated rectangular image by applying `hex_to_rect_map` to the flat, # extended input image # `input_img_ext` is the flattened input image extended by one entry that contains NaN # since `hex_to_rect_map` contains `-1` for "fake" pixels, it maps this extra NaN # value at the last array position to any bin that does not correspond to a pixel of # the original image input_img_ext = np.full(np.prod(signal.shape) + 1, np.nan) # the way the map is produced, it has the time dimension as axis=0; # but `signal` has it as axis=-1, so we need to roll the axes back and forth a bit. # if there is no time dimension, `signal` is a 1d array and `rollaxis` has no effect. input_img_ext[:-1] = np.rollaxis(signal, axis=-1, start=0).ravel() # now apply the transfer map rot_img = input_img_ext[hex_to_rect_map] # if there is a time dimension, roll the time axis back to the last position try: rot_img = np.rollaxis(rot_img, 0, 3) except ValueError: pass return new_geom, rot_img