def get_prediction(self, tel_id, shower_reco, energy_reco): horizon_seed = HorizonFrame(az=shower_reco.az, alt=shower_reco.alt) nominal_seed = horizon_seed.transform_to( NominalFrame(array_direction=horizon_seed)) source_x = nominal_seed.x.to(u.rad).value source_y = nominal_seed.y.to(u.rad).value ground = GroundFrame(x=shower_reco.core_x, y=shower_reco.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=self.array_direction)) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value zenith = 90 * u.deg - self.array_direction.alt x_max = shower_reco.h_max / np.cos(zenith) # Calculate expected Xmax given this energy x_max_exp = guess_shower_depth(energy_reco.energy) # Convert to binning of Xmax, addition of 100 can probably be removed x_max_bin = x_max - x_max_exp # Check for range if x_max_bin > 250 * (u.g * u.cm**-2): x_max_bin = 250 * (u.g * u.cm**-2) if x_max_bin < -250 * (u.g * u.cm**-2): x_max_bin = -250 * (u.g * u.cm**-2) x_max_bin = x_max_bin.value impact = np.sqrt( pow(self.tel_pos_x[tel_id] - tilt_x, 2) + pow(self.tel_pos_y[tel_id] - tilt_y, 2)) phi = np.arctan2((self.tel_pos_y[tel_id] - tilt_y), (self.tel_pos_x[tel_id] - tilt_x)) pix_x_rot, pix_y_rot = self.rotate_translate(self.pixel_x[tel_id] * -1, self.pixel_y[tel_id], source_x, source_y, phi) prediction = self.image_prediction(self.type[tel_id], (90 * u.deg) - shower_reco.alt, shower_reco.az, energy_reco.energy.value, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi)) prediction *= self.scale[self.type[tel_id]] # prediction *= self.pixel_area[tel_id] prediction[prediction < 0] = 0 prediction[np.isnan(prediction)] = 0 return prediction
def sky_to_camera(alt, az, focal, pointing_alt, pointing_az): """ Coordinate transform from aky position (alt, az) (in angles) to camera coordinates (x, y) in distance Parameters ---------- alt: astropy Quantity az: astropy Quantity focal: astropy Quantity pointing_alt: pointing altitude in angle unit pointing_az: pointing altitude in angle unit Returns ------- """ pointing_direction = HorizonFrame(alt=pointing_alt, az=pointing_az) event_direction = HorizonFrame(alt=alt, az=az) nom_frame = NominalFrame(array_direction=pointing_direction, pointing_direction=pointing_direction) event_dir_nom = event_direction.transform_to(nom_frame) camera_pos = NominalFrame( pointing_direction=pointing_direction, x=event_dir_nom.x.to(u.rad).value * focal, y=event_dir_nom.y.to(u.rad).value * focal, ) # return focal * (event_dir_nom.x.to(u.rad).value, event_dir_nom.y.to(u.rad).value) return camera_pos
def get_position_in_cam(dir_alt, dir_az, event, tel_id): """ transform position in HorizonFrame to CameraFrame Parameters ---------- dir_alt : direction altitude dir_az : direction azimuth event : event data container tel_id : telescope ID Returns ------- cam_coord : position in camera of telescope tel_id """ # pointing direction of telescope pointing_az = event.mc.tel[tel_id].azimuth_raw * u.rad pointing_alt = event.mc.tel[tel_id].altitude_raw * u.rad pointing = SkyCoord(alt=pointing_alt, az=pointing_az, frame='altaz') focal_length = event.inst.subarray.tel[tel_id].\ optics.equivalent_focal_length cf = CameraFrame(focal_length=focal_length, array_direction=pointing, pointing_direction=pointing) # direction of event direction = HorizonFrame(alt=dir_alt, az=dir_az) cam_coord = direction.transform_to(cf) return cam_coord
def get_prediction(self, tel_id, shower_reco, energy_reco): horizon_seed = HorizonFrame(az=shower_reco.az, alt=shower_reco.alt) nominal_seed = horizon_seed.transform_to( NominalFrame(array_direction=horizon_seed)) source_x = nominal_seed.x.to(u.rad).value source_y = nominal_seed.y.to(u.rad).value ground = GroundFrame(x=shower_reco.core_x, y=shower_reco.core_y, z=0*u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=self.array_direction)) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value zenith = 90*u.deg - self.array_direction.alt azimuth = self.array_direction.az x_max = shower_reco.h_max / np.cos(zenith) # Calculate expected Xmax given this energy x_max_exp = guess_shower_depth(energy_reco.energy) # Convert to binning of Xmax, addition of 100 can probably be removed x_max_bin = x_max - x_max_exp # Check for range if x_max_bin > 250 * (u.g*u.cm**-2): x_max_bin = 250 * (u.g*u.cm**-2) if x_max_bin < -250 * (u.g*u.cm**-2): x_max_bin = -250 * (u.g*u.cm**-2) x_max_bin = x_max_bin.value impact = np.sqrt(pow(self.tel_pos_x[tel_id] - tilt_x, 2) + pow(self.tel_pos_y[tel_id] - tilt_y, 2)) phi = np.arctan2( (self.tel_pos_y[tel_id] - tilt_y), (self.tel_pos_x[tel_id] - tilt_x)) pix_x_rot, pix_y_rot = self.rotate_translate(self.pixel_x[tel_id] * -1, self.pixel_y[tel_id], source_x, source_y, phi) prediction = self.image_prediction(self.type[tel_id], (90 * u.deg) - shower_reco.alt, shower_reco.az, energy_reco.energy.value, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi)) prediction *= self.scale[self.type[tel_id]] #prediction *= self.pixel_area[tel_id] prediction[prediction < 0] = 0 prediction[np.isnan(prediction)] = 0 return prediction
def test_ground_to_tilt(): from ctapipe.coordinates import GroundFrame, TiltedGroundFrame, HorizonFrame # define ground coordinate grd_coord = GroundFrame(x=1 * u.m, y=2 * u.m, z=0 * u.m) pointing_direction = SkyCoord(alt=90 * u.deg, az=0 * u.deg, frame=HorizonFrame()) # Convert to tilted frame at zenith (should be the same) tilt_coord = grd_coord.transform_to( TiltedGroundFrame(pointing_direction=pointing_direction)) assert tilt_coord.separation_3d(grd_coord) == 0 * u.m # Check 180 degree rotation reverses y coordinate pointing_direction = SkyCoord(alt=90 * u.deg, az=180 * u.deg, frame=HorizonFrame()) tilt_coord = grd_coord.transform_to( TiltedGroundFrame(pointing_direction=pointing_direction)) assert np.abs(tilt_coord.y + 2. * u.m) < 1e-5 * u.m # Check that if we look at horizon the x coordinate is 0 pointing_direction = SkyCoord(alt=0 * u.deg, az=0 * u.deg, frame=HorizonFrame()) tilt_coord = grd_coord.transform_to( TiltedGroundFrame(pointing_direction=pointing_direction)) assert np.abs(tilt_coord.x) < 1e-5 * u.m
def draw_tilted_surface(self, shower_seed, energy_seed, bins=50, core_range=100 * u.m): """ Simple reconstruction for evaluating the likelihood in a grid across the nominal system, fixing all values but the core position of the gamma rays. Useful for checking the reconstruction performance of the algorithm Parameters ---------- shower_seed: ReconstructedShowerContainer Best fit ImPACT shower geometry energy_seed: ReconstructedEnergyContainer Best fit ImPACT energy bins: int Number of bins in surface evaluation nominal_range: Quantity Range over which to create likelihood surface Returns ------- ndarray, ndarray, ndarray: Bin centres in X and Y coordinates and the values of the likelihood at each position """ horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt) nominal_seed = horizon_seed.transform_to( NominalFrame(array_direction=self.array_direction)) source_x = nominal_seed.x[0].to(u.rad).value source_y = nominal_seed.y[0].to(u.rad).value ground = GroundFrame(x=shower_seed.core_x, y=shower_seed.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=self.array_direction) ) tilt_x = tilted.x.to(u.m) tilt_y = tilted.y.to(u.m) x_ground_list = np.linspace(tilt_x - core_range, tilt_x + core_range, num=bins) y_ground_list = np.linspace(tilt_y - core_range, tilt_y + core_range, num=bins) w = np.zeros([bins, bins]) zenith = 90*u.deg - self.array_direction.alt for xb in range(bins): for yb in range(bins): x_max_scale = shower_seed.h_max / \ self.get_shower_max(source_x, source_y, x_ground_list[xb].value, y_ground_list[yb].value, zenith.to(u.rad).value) w[xb][yb] = self.get_likelihood(source_x, source_y, x_ground_list[xb].value, y_ground_list[yb].value, energy_seed.energy.value, x_max_scale) return x_ground_list, y_ground_list, w
def nominal_to_altaz(): nom = SkyCoord(x=0 * u.deg, y=0 * u.deg, frame=NominalFrame( origin=HorizonFrame(alt=75 * u.deg, az=180 * u.deg))) alt_az = nom.transform_to(HorizonFrame()) print("HorizonCoordinate", alt_az)
def get_prediction(self, tel_id, shower_reco, energy_reco): horizon_seed = HorizonFrame(az=shower_reco.az, alt=shower_reco.alt) nominal_seed = horizon_seed.transform_to( NominalFrame(array_direction=horizon_seed)) source_x = nominal_seed.x.to(u.rad).value source_y = nominal_seed.y.to(u.rad).value print(self.array_direction[0]) ground = GroundFrame(x=shower_reco.core_x, y=shower_reco.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame( pointing_direction=HorizonFrame(alt=self.array_direction[0], az=self.array_direction[1]) ) ) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value zenith = 90 * u.deg - self.array_direction[0] azimuth = self.array_direction[1] x_max_exp = 300 + 93 * np.log10(energy_reco.energy.value) x_max = shower_reco.h_max / np.cos(zenith) # Convert to binning of Xmax, addition of 100 can probably be removed x_max_bin = x_max.value - x_max_exp if x_max_bin > 100: x_max_bin = 100 if x_max_bin < -100: x_max_bin = -100 impact = np.sqrt(pow(self.tel_pos_x[tel_id] - tilt_x, 2) + pow(self.tel_pos_y[tel_id] - tilt_y, 2)) phi = np.arctan2( (self.tel_pos_y[tel_id] - tilt_y), (self.tel_pos_x[tel_id] - tilt_x)) pix_x_rot, pix_y_rot = self.rotate_translate(self.pixel_x[tel_id] * -1, self.pixel_y[tel_id], source_x, source_y, phi) prediction = self.image_prediction(self.type[tel_id], 20 * u.deg, 0 * u.deg, energy_reco.energy.value, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi)) prediction *= self.scale[self.type[tel_id]] prediction[prediction < 0] = 0 prediction[np.isnan(prediction)] = 0 return prediction
def get_prediction(self, tel_id, shower_reco, energy_reco): horizon_seed = HorizonFrame(az=shower_reco.az, alt=shower_reco.alt) nominal_seed = horizon_seed.transform_to( NominalFrame(array_direction=horizon_seed)) source_x = nominal_seed.x.to(u.rad).value source_y = nominal_seed.y.to(u.rad).value print(self.array_direction[0]) ground = GroundFrame(x=shower_reco.core_x, y=shower_reco.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=HorizonFrame( alt=self.array_direction[0], az=self.array_direction[1]))) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value zenith = 90 * u.deg - self.array_direction[0] azimuth = self.array_direction[1] x_max_exp = 300 + 93 * np.log10(energy_reco.energy.value) x_max = shower_reco.h_max / np.cos(zenith) # Convert to binning of Xmax, addition of 100 can probably be removed x_max_bin = x_max.value - x_max_exp if x_max_bin > 100: x_max_bin = 100 if x_max_bin < -100: x_max_bin = -100 impact = np.sqrt( pow(self.tel_pos_x[tel_id] - tilt_x, 2) + pow(self.tel_pos_y[tel_id] - tilt_y, 2)) phi = np.arctan2((self.tel_pos_y[tel_id] - tilt_y), (self.tel_pos_x[tel_id] - tilt_x)) pix_x_rot, pix_y_rot = self.rotate_translate(self.pixel_x[tel_id] * -1, self.pixel_y[tel_id], source_x, source_y, phi) prediction = self.image_prediction(self.type[tel_id], 20 * u.deg, 0 * u.deg, energy_reco.energy.value, impact, x_max_bin, pix_x_rot * (180 / math.pi), pix_y_rot * (180 / math.pi)) prediction *= self.scale[self.type[tel_id]] prediction[prediction < 0] = 0 prediction[np.isnan(prediction)] = 0 return prediction
def test_estimator_results(): """ creating some planes pointing in different directions (two north-south, two east-west) and that have a slight position errors (+- 0.1 m in one of the four cardinal directions """ horizon_frame = HorizonFrame() p1 = SkyCoord(alt=43 * u.deg, az=45 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=47 * u.deg, az=45 * u.deg, frame=horizon_frame) circle1 = HillasPlane(p1=p1, p2=p2, telescope_position=[0, 1, 0] * u.m) p1 = SkyCoord(alt=44 * u.deg, az=90 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=46 * u.deg, az=90 * u.deg, frame=horizon_frame) circle2 = HillasPlane(p1=p1, p2=p2, telescope_position=[1, 0, 0] * u.m) p1 = SkyCoord(alt=44.5 * u.deg, az=45 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=46.5 * u.deg, az=45 * u.deg, frame=horizon_frame) circle3 = HillasPlane(p1=p1, p2=p2, telescope_position=[0, -1, 0] * u.m) p1 = SkyCoord(alt=43.5 * u.deg, az=90 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=45.5 * u.deg, az=90 * u.deg, frame=horizon_frame) circle4 = HillasPlane(p1=p1, p2=p2, telescope_position=[-1, 0, 0] * u.m) # creating the fit class and setting the the great circle member fit = HillasReconstructor() fit.hillas_planes = {1: circle1, 2: circle2, 3: circle3, 4: circle4} # performing the direction fit with the minimisation algorithm # and a seed that is perpendicular to the up direction dir_fit_minimise, _ = fit.estimate_direction() print("direction fit test minimise:", dir_fit_minimise) print()
def camera_to_sky(pos_x, pos_y, focal, pointing_alt, pointing_az): """ Parameters ---------- pos_x: X coordinate in camera (distance) pos_y: Y coordinate in camera (distance) focal: telescope focal (distance) pointing_alt: pointing altitude in angle unit pointing_az: pointing altitude in angle unit Returns ------- (alt, az) Example: -------- import astropy.units as u import numpy as np x = np.array([1,0]) * u.m y = np.array([1,1]) * u.m """ pointing_direction = HorizonFrame(alt=pointing_alt, az=pointing_az) source_pos_in_camera = NominalFrame( array_direction=pointing_direction, pointing_direction=pointing_direction, x=pos_x / focal * u.rad, y=pos_y / focal * u.rad, ) return source_pos_in_camera.transform_to(pointing_direction)
def test_h_max_results(): """ creating some planes pointing in different directions (two north-south, two east-west) and that have a slight position errors (+- 0.1 m in one of the four cardinal directions """ horizon_frame = HorizonFrame() p1 = SkyCoord(alt=0 * u.deg, az=45 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=0 * u.deg, az=45 * u.deg, frame=horizon_frame) circle1 = HillasPlane(p1=p1, p2=p2, telescope_position=[0, 1, 0] * u.m) p1 = SkyCoord(alt=0 * u.deg, az=90 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=0 * u.deg, az=90 * u.deg, frame=horizon_frame) circle2 = HillasPlane(p1=p1, p2=p2, telescope_position=[1, 0, 0] * u.m) p1 = SkyCoord(alt=0 * u.deg, az=45 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=0 * u.deg, az=45 * u.deg, frame=horizon_frame) circle3 = HillasPlane(p1=p1, p2=p2, telescope_position=[0, -1, 0] * u.m) p1 = SkyCoord(alt=0 * u.deg, az=90 * u.deg, frame=horizon_frame) p2 = SkyCoord(alt=0 * u.deg, az=90 * u.deg, frame=horizon_frame) circle4 = HillasPlane(p1=p1, p2=p2, telescope_position=[-1, 0, 0] * u.m) # creating the fit class and setting the the great circle member fit = HillasReconstructor() fit.hillas_planes = {1: circle1, 2: circle2, 3: circle3, 4: circle4} # performing the direction fit with the minimisation algorithm # and a seed that is perpendicular to the up direction h_max_reco = fit.estimate_h_max() print("h max fit test minimise:", h_max_reco) # the results should be close to the direction straight up np.testing.assert_allclose(h_max_reco.value, 0, atol=1e-8)
def test_cam_to_nominal(): from ctapipe.coordinates import CameraFrame, HorizonFrame, NominalFrame telescope_pointing = SkyCoord(alt=70 * u.deg, az=0 * u.deg, frame=HorizonFrame()) array_pointing = SkyCoord(alt=72 * u.deg, az=0 * u.deg, frame=HorizonFrame()) cam_frame = CameraFrame(focal_length=28 * u.m, telescope_pointing=telescope_pointing) cam = SkyCoord(x=0.5 * u.m, y=0.1 * u.m, frame=cam_frame) nom_frame = NominalFrame(origin=array_pointing) cam.transform_to(nom_frame)
def grd_to_tilt(): grd_coord = GroundFrame(x=1 * u.m, y=2 * u.m, z=0 * u.m) tilt_coord = grd_coord.transform_to( TiltedGroundFrame( pointing_direction=HorizonFrame(alt=90 * u.deg, az=180 * u.deg))) print(project_to_ground(tilt_coord)) print("Tilted Coordinate", tilt_coord)
def test_icrs_to_camera(): from ctapipe.coordinates import CameraFrame, HorizonFrame obstime = Time('2013-11-01T03:00') location = EarthLocation.of_site('Roque de los Muchachos') horizon_frame = HorizonFrame(location=location, obstime=obstime) # simulate crab "on" observations crab = SkyCoord(ra='05h34m31.94s', dec='22d00m52.2s') telescope_pointing = crab.transform_to(horizon_frame) camera_frame = CameraFrame( focal_length=28 * u.m, telescope_pointing=telescope_pointing, location=location, obstime=obstime, ) ceta_tauri = SkyCoord(ra='5h37m38.6854231s', dec='21d08m33.158804s') ceta_tauri_camera = ceta_tauri.transform_to(camera_frame) camera_center = SkyCoord(0 * u.m, 0 * u.m, frame=camera_frame) crab_camera = crab.transform_to(camera_frame) assert crab_camera.x.to_value(u.m) == approx(0.0, abs=1e-10) assert crab_camera.y.to_value(u.m) == approx(0.0, abs=1e-10) # assert ceta tauri is in FoV assert camera_center.separation_3d(ceta_tauri_camera) < u.Quantity( 0.6, u.m)
def inititialize_hillas_planes( self, hillas_dict, subarray, pointing_alt, pointing_az ): """ creates a dictionary of :class:`.HillasPlane` from a dictionary of hillas parameters Parameters ---------- hillas_dict : dictionary dictionary of hillas moments subarray : ctapipe.instrument.SubarrayDescription subarray information tel_phi, tel_theta : dictionaries dictionaries of the orientation angles of the telescopes needs to contain at least the same keys as in `hillas_dict` """ self.hillas_planes = {} for tel_id, moments in hillas_dict.items(): p2_x = moments.cen_x + 0.1 * u.m * np.cos(moments.psi) p2_y = moments.cen_y + 0.1 * u.m * np.sin(moments.psi) focal_length = subarray.tel[tel_id].optics.equivalent_focal_length pointing = SkyCoord( alt=pointing_alt[tel_id], az=pointing_az[tel_id], frame='altaz' ) hf = HorizonFrame( array_direction=pointing, pointing_direction=pointing ) cf = CameraFrame( focal_length=focal_length, array_direction=pointing, pointing_direction=pointing ) cog_coord = SkyCoord(x=moments.cen_x, y=moments.cen_y, frame=cf) cog_coord = cog_coord.transform_to(hf) p2_coord = SkyCoord(x=p2_x, y=p2_y, frame=cf) p2_coord = p2_coord.transform_to(hf) circle = HillasPlane( p1=cog_coord, p2=p2_coord, telescope_position=subarray.positions[tel_id], weight=moments.size * (moments.length / moments.width), ) self.hillas_planes[tel_id] = circle
def initialize_hillas_planes( self, hillas_dict, subarray, pointing_alt, pointing_az ): """ creates a dictionary of :class:`.HillasPlane` from a dictionary of hillas parameters Parameters ---------- hillas_dict : dictionary dictionary of hillas moments subarray : ctapipe.instrument.SubarrayDescription subarray information tel_phi, tel_theta : dictionaries dictionaries of the orientation angles of the telescopes needs to contain at least the same keys as in `hillas_dict` """ self.hillas_planes = {} horizon_frame = HorizonFrame() for tel_id, moments in hillas_dict.items(): # we just need any point on the main shower axis a bit away from the cog p2_x = moments.x + 0.1 * u.m * np.cos(moments.psi) p2_y = moments.y + 0.1 * u.m * np.sin(moments.psi) focal_length = subarray.tel[tel_id].optics.equivalent_focal_length pointing = SkyCoord( alt=pointing_alt[tel_id], az=pointing_az[tel_id], frame=horizon_frame, ) camera_frame = CameraFrame( focal_length=focal_length, telescope_pointing=pointing ) cog_coord = SkyCoord( x=moments.x, y=moments.y, frame=camera_frame, ) cog_coord = cog_coord.transform_to(horizon_frame) p2_coord = SkyCoord(x=p2_x, y=p2_y, frame=camera_frame) p2_coord = p2_coord.transform_to(horizon_frame) circle = HillasPlane( p1=cog_coord, p2=p2_coord, telescope_position=subarray.positions[tel_id], weight=moments.intensity * (moments.length / moments.width), ) self.hillas_planes[tel_id] = circle
def nominal_to_altaz(): t = np.zeros(10) t[5] = 1 nom = NominalFrame( x=t * u.deg, y=t * u.deg, array_direction=HorizonFrame(alt=75 * u.deg, az=180 * u.deg) ) alt_az = nom.transform_to(HorizonFrame) print("AltAz Coordinate", alt_az)
def cam_to_nom(): pix = [np.ones(2048), np.ones(2048), np.zeros(2048)] * u.m camera_coord = CameraFrame(pix, focal_length=15 * u.m) # In this case we bypass the telescope system nom_coord = camera_coord.transform_to( NominalFrame( pointing_direction=HorizonFrame(alt=70 * u.deg, az=180 * u.deg), array_direction=HorizonFrame(alt=75 * u.deg, az=180 * u.deg) ) ) alt_az = camera_coord.transform_to( HorizonFrame( pointing_direction=HorizonFrame(alt=70 * u.deg, az=180 * u.deg), array_direction=HorizonFrame(alt=75 * u.deg, az=180 * u.deg) ) ) print("Nominal Coordinate", nom_coord) print("AltAz coordinate", alt_az)
def transform_azel_to_xy(stars_az, stars_alt, az_obs, el_obs): """ function to transform azimuth and elevation coodinates to XY coordinates in the fild of view. :param stars_az: 2D array of azimuth coordinates to be transformed. 1st dimension is for different objects and 2nd dimension is for different observation time. :param stars_alt: 2D array of elevation coordinates to be transformed. 1st dimension is for different objects and 2nd dimension is for different observation time. :param az_obs: 1D array of telescope azimuth pointing direction (1 element per observation time). :param el_obs: 1D array of telescope elevation pointing direction (1 element per observation time). :return: stars_x, stars_y : 2D array giving for each object (1st dimension) and each observation time (2nd dimension) the XY coordinates in the field of view. """ n_stars = stars_az.shape[0] n_event = az_obs.shape[0] assert stars_az.shape == (n_stars, n_event) assert stars_alt.shape == (n_stars, n_event) assert el_obs.shape[0] == n_event stars_x = np.zeros([n_stars, n_event]) * u.mm stars_y = np.zeros([n_stars, n_event]) * u.mm for event in range(n_event): pd = SkyCoord(alt=el_obs[event], az=az_obs[event], frame=HorizonFrame()) cam_frame = CameraFrame( focal_length=5.6 * u.m, rotation=90 * u.deg, pointing_direction=pd, array_direction=pd, ) stars_sky = SkyCoord(alt=stars_alt[:, event], az=stars_az[:, event], frame=HorizonFrame()) stars_cam = stars_sky.transform_to(cam_frame) stars_x[:, event] = -stars_cam.x stars_y[:, event] = stars_cam.y return stars_x, stars_y
def cam_to_nom(): pix_x = np.ones(2048) * u.m pix_y = np.ones(2048) * u.m pointing_direction = SkyCoord(alt=70 * u.deg, az=180 * u.deg, frame=HorizonFrame()) camera_frame = CameraFrame(focal_length=15 * u.m, telescope_pointing=pointing_direction) camera_coord = SkyCoord(pix_x, pix_y, frame=camera_frame) # In this case we bypass the telescope system nominal_frame = NominalFrame( origin=HorizonFrame(alt=75 * u.deg, az=180 * u.deg)) nom_coord = camera_coord.transform_to(nominal_frame) horizon = camera_coord.transform_to(HorizonFrame()) print("Nominal Coordinate", nom_coord) print("Horizon coordinate", horizon)
def __init__(self, config, tool, event, **kwargs): super().__init__(config=config, tool=tool, **kwargs) self.camera_geom_dict = {} self.nominal_geom_dict = {} self.inst = event.inst array_pointing = HorizonFrame( alt=event.mcheader.run_array_direction[1] * u.rad, az=event.mcheader.run_array_direction[0] * u.rad) self.nom_system = NominalFrame(array_direction=array_pointing, pointing_direction=array_pointing)
def get_event_pos_in_sky(hillas, disp, tel, pointing_direction): side = 1 # TODO: method to guess side focal = tel.optics.equivalent_focal_length source_pos_in_camera = NominalFrame(array_direction=pointing_direction, pointing_direction=pointing_direction, x=(hillas.x + side * disp * np.cos(hillas.phi)) / focal * u.rad, y=(hillas.y + side * disp * np.sin(hillas.phi)) / focal * u.rad ) horizon_frame = HorizonFrame(alt=pointing_direction.alt, az=pointing_direction.az) return source_pos_in_camera.transform_to(horizon_frame)
def test_roundtrip_camera_horizon(): from ctapipe.coordinates import CameraFrame, TelescopeFrame, HorizonFrame telescope_pointing = SkyCoord(alt=70 * u.deg, az=0 * u.deg, frame=HorizonFrame()) camera_frame = CameraFrame(focal_length=28 * u.m, telescope_pointing=telescope_pointing) cam_coord = SkyCoord(x=0.5 * u.m, y=0.1 * u.m, frame=camera_frame) telescope_coord = cam_coord.transform_to(TelescopeFrame()) horizon_coord = telescope_coord.transform_to(HorizonFrame()) back_telescope_coord = horizon_coord.transform_to(TelescopeFrame()) back_cam_coord = back_telescope_coord.transform_to(camera_frame) delta_az = back_telescope_coord.delta_az.to_value(u.deg) delta_alt = back_telescope_coord.delta_alt.to_value(u.deg) assert delta_az == approx(telescope_coord.delta_az.to_value(u.deg)) assert delta_alt == approx(telescope_coord.delta_alt.to_value(u.deg)) assert back_cam_coord.x.to_value(u.m) == approx(cam_coord.x.to_value(u.m)) assert back_cam_coord.y.to_value(u.m) == approx(cam_coord.y.to_value(u.m))
def ground_grid(event, tel_pos=False): """ Return the telescopes positions in the GroundFrame and plot on ground :param event: input event selected from simtel :param tel_pos: (bool) if True, plot the telescopes as spheres :return: """ alt = event.mcheader.run_array_direction[1] az = event.mcheader.run_array_direction[0] array_pointing = HorizonFrame(alt=alt, az=az) ground_coordinates = GroundFrame(x=event.inst.subarray.tel_coords.x, y=event.inst.subarray.tel_coords.y, z=event.inst.subarray.tel_coords.z, pointing_direction=array_pointing) grid_unit = 20000 # in centimeters ground_system = union() if tel_pos: # for i in range(ground_coordinates.x.size): for i in range(50): coords = [ 100 * ground_coordinates.x[i].value, 100 * ground_coordinates.y[i].value, 100 * ground_coordinates.z[i].value ] position = translate(coords)(color([0, 0, 1])(sphere(r=800))) ground_system.add(position) grid = grid_plane( grid_unit=grid_unit, count=2 * int(100 * np.max(np.abs(ground_coordinates.x.value)) / grid_unit), line_weight=200, plane='xy') grid = color([0, 0, 1, 0.5])(grid) ground_system.add(grid) # SYSTEM + ARROW ref_arr = ref_arrow_3d(8000, origin=(1000, 1000, 0), label={ 'x': "x_gnd = NORTH", 'y': "y_gnd = WEST", 'z': "z_gnd" }) ground_system = ground_system + ref_arr return ground_system
def get_event_pos_in_camera(event, tel): """ Return the position of the source in the camera frame Parameters ---------- event: `ctapipe.io.containers.DataContainer` tel: `ctapipe.instruement.telescope.TelescopeDescription` Returns ------- (x, y) (float, float): position in the camera """ array_pointing = HorizonFrame(alt=event.mcheader.run_array_direction[1], az=event.mcheader.run_array_direction[0]) event_direction = HorizonFrame(alt=event.mc.alt.to(u.rad), az=event.mc.az.to(u.rad)) nom_frame = NominalFrame(array_direction=array_pointing, pointing_direction=array_pointing) event_dir_nom = event_direction.transform_to(nom_frame) focal = tel.optics.equivalent_focal_length return focal * (event_dir_nom.x.to(u.rad).value, event_dir_nom.y.to(u.rad).value)
def test_telescope_separation(): from ctapipe.coordinates import TelescopeFrame, HorizonFrame telescope_pointing = SkyCoord(alt=70 * u.deg, az=0 * u.deg, frame=HorizonFrame()) telescope_frame = TelescopeFrame(telescope_pointing=telescope_pointing) tel1 = SkyCoord(delta_az=0 * u.deg, delta_alt=0 * u.deg, frame=telescope_frame) tel2 = SkyCoord(delta_az=0 * u.deg, delta_alt=1 * u.deg, frame=telescope_frame) assert tel1.separation(tel2) == u.Quantity(1, u.deg)
def estimate_h_max(self, hillas_dict, subarray, pointing_alt, pointing_az): weights = [] tels = [] dirs = [] for tel_id, moments in hillas_dict.items(): focal_length = subarray.tel[tel_id].optics.equivalent_focal_length pointing = SkyCoord( alt=pointing_alt[tel_id], az=pointing_az[tel_id], frame='altaz' ) hf = HorizonFrame( array_direction=pointing, pointing_direction=pointing ) cf = CameraFrame( focal_length=focal_length, array_direction=pointing, pointing_direction=pointing ) cog_coord = SkyCoord(x=moments.cen_x, y=moments.cen_y, frame=cf) cog_coord = cog_coord.transform_to(hf) cog_direction = spherical_to_cartesian(1, cog_coord.alt, cog_coord.az) cog_direction = np.array(cog_direction).ravel() weights.append(self.hillas_planes[tel_id].weight) tels.append(self.hillas_planes[tel_id].pos) dirs.append(cog_direction) # minimising the test function pos_max = minimize(dist_to_line3d, np.array([0, 0, 10000]), args=(np.array(tels), np.array(dirs), np.array(weights)), method='BFGS', options={'disp': False} ).x return pos_max[2] * u.m
def configure(self, config): config[self.cout_pix_pos] = self.pix_pos config[self.cout_fov] = 0.8 # around a pixel # precomputing transformations # Will need to do this smarter (in chunks) when the number of frames reaches 10k-100k self.obstimes = config["time_steps"] self.precomp_hf = HorizonFrame(location=self.location, obstime=self.obstimes) target = SkyCoord(ra=self.par_pointingra.val, dec=self.par_pointingdec.val, unit="deg") self.precomp_point = target.transform_to(self.precomp_hf) config["start_pointing"] = self.precomp_point[0] config[self.cout_altazframes] = self.precomp_hf config["cam_frame0"] = CameraFrame( telescope_pointing=config["start_pointing"], focal_length=u.Quantity(self.focal_length, u.m), # 28 for LST obstime=self.obstimes[0], location=self.location, )
def test_separation_is_the_same(): from ctapipe.coordinates import TelescopeFrame, HorizonFrame obstime = Time('2013-11-01T03:00') location = EarthLocation.of_site('Roque de los Muchachos') horizon_frame = HorizonFrame(location=location, obstime=obstime) crab = SkyCoord(ra='05h34m31.94s', dec='22d00m52.2s') ceta_tauri = SkyCoord(ra='5h37m38.6854231s', dec='21d08m33.158804s') # simulate crab "on" observations telescope_pointing = crab.transform_to(horizon_frame) telescope_frame = TelescopeFrame( telescope_pointing=telescope_pointing, location=location, obstime=obstime, ) ceta_tauri_telescope = ceta_tauri.transform_to(telescope_frame) crab_telescope = crab.transform_to(telescope_frame) sep = ceta_tauri_telescope.separation(crab_telescope).to_value(u.deg) assert ceta_tauri.separation(crab).to_value(u.deg) == approx(sep, rel=1e-4)
def predict(self, shower_seed, energy_seed): """ Parameters ---------- shower_seed: ReconstructedShowerContainer Seed shower geometry to be used in the fit energy_seed: ReconstructedEnergyContainer Seed energy to be used in fit Returns ------- ReconstructedShowerContainer, ReconstructedEnergyContainer: Reconstructed ImPACT shower geometry and energy """ horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt) nominal_seed = horizon_seed.transform_to(NominalFrame( array_direction=self.array_direction)) source_x = nominal_seed.x.to(u.rad).value source_y = nominal_seed.y.to(u.rad).value ground = GroundFrame(x=shower_seed.core_x, y=shower_seed.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=self.array_direction) ) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value zenith = 90 * u.deg - self.array_direction.alt if len(self.hillas_parameters) > 3: shift = [1] else: shift = [1.5, 1, 0.5, 0, -0.5, -1, -1.5] seed_list = spread_line_seed(self.hillas_parameters, self.tel_pos_x, self.tel_pos_y, source_x[0], source_y[0], tilt_x, tilt_y, energy_seed.energy.value, shift_frac = shift) chosen_seed = self.choose_seed(seed_list) # Perform maximum likelihood fit fit_params, errors, like = self.minimise(params=chosen_seed[0], step=chosen_seed[1], limits=chosen_seed[2], minimiser_name=self.minimiser_name) # Create a container class for reconstructed shower shower_result = ReconstructedShowerContainer() # Convert the best fits direction and core to Horizon and ground systems and # copy to the shower container nominal = NominalFrame(x=fit_params[0] * u.rad, y=fit_params[1] * u.rad, array_direction=self.array_direction) horizon = nominal.transform_to(HorizonFrame()) shower_result.alt, shower_result.az = horizon.alt, horizon.az tilted = TiltedGroundFrame(x=fit_params[2] * u.m, y=fit_params[3] * u.m, pointing_direction=self.array_direction) ground = project_to_ground(tilted) shower_result.core_x = ground.x shower_result.core_y = ground.y shower_result.is_valid = True # Currently no errors not availible to copy NaN shower_result.alt_uncert = np.nan shower_result.az_uncert = np.nan shower_result.core_uncert = np.nan # Copy reconstructed Xmax shower_result.h_max = fit_params[5] * self.get_shower_max(fit_params[0], fit_params[1], fit_params[2], fit_params[3], zenith.to(u.rad).value) shower_result.h_max *= np.cos(zenith) shower_result.h_max_uncert = errors[5] * shower_result.h_max shower_result.goodness_of_fit = like # Create a container class for reconstructed energy energy_result = ReconstructedEnergyContainer() # Fill with results energy_result.energy = fit_params[4] * u.TeV energy_result.energy_uncert = errors[4] * u.TeV energy_result.is_valid = True return shower_result, energy_result
def predict(self, shower_seed, energy_seed): """ Parameters ---------- source_x: float Initial guess of source position in the nominal frame source_y: float Initial guess of source position in the nominal frame core_x: float Initial guess of the core position in the tilted system core_y: float Initial guess of the core position in the tilted system energy: float Initial guess of energy Returns ------- Shower object with fit results """ horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt) nominal_seed = horizon_seed.transform_to( NominalFrame(array_direction=self.array_direction) ) source_x = nominal_seed.x.to(u.rad).value source_y = nominal_seed.y.to(u.rad).value ground = GroundFrame(x=shower_seed.core_x, y=shower_seed.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=self.array_direction) ) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value lower_en_limit = energy_seed.energy * 0.1 if lower_en_limit < 0.04 * u.TeV: lower_en_limit = 0.04 * u.TeV # Create Minuit object with first guesses at parameters, strip away the # units as Minuit doesnt like them min = Minuit(self.get_likelihood, print_level=1, source_x=source_x, error_source_x=0.01 / 57.3, fix_source_x=False, limit_source_x=(source_x - 0.5 / 57.3, source_x + 0.5 / 57.3), source_y=source_y, error_source_y=0.01 / 57.3, fix_source_y=False, limit_source_y=(source_y - 0.5 / 57.3, source_y + 0.5 / 57.3), core_x=tilt_x, error_core_x=10, limit_core_x=(tilt_x - 200, tilt_x + 200), core_y=tilt_y, error_core_y=10, limit_core_y=(tilt_y - 200, tilt_y + 200), energy=energy_seed.energy.value, error_energy=energy_seed.energy.value * 0.05, limit_energy=(lower_en_limit.value, energy_seed.energy.value * 10.), x_max_scale=1, error_x_max_scale=0.1, limit_x_max_scale=(0.5, 2), fix_x_max_scale=False, errordef=1) min.tol *= 1000 min.strategy = 0 # Perform minimisation migrad = min.migrad() fit_params = min.values errors = min.errors # print(migrad) # print(min.minos()) # container class for reconstructed showers ''' shower_result = ReconstructedShowerContainer() nominal = NominalFrame(x=fit_params["source_x"] * u.rad, y=fit_params["source_y"] * u.rad, array_direction=self.array_direction) horizon = nominal.transform_to(HorizonFrame()) shower_result.alt, shower_result.az = horizon.alt, horizon.az tilted = TiltedGroundFrame(x=fit_params["core_x"] * u.m, y=fit_params["core_y"] * u.m, pointing_direction=self.array_direction) ground = project_to_ground(tilted) shower_result.core_x = ground.x shower_result.core_y = ground.y shower_result.is_valid = True shower_result.alt_uncert = np.nan shower_result.az_uncert = np.nan shower_result.core_uncert = np.nan zenith = 90 * u.deg - self.array_direction[0] shower_result.h_max = fit_params["x_max_scale"] * \ self.get_shower_max(fit_params["source_x"], fit_params["source_y"], fit_params["core_x"], fit_params["core_y"], zenith.to(u.rad).value) shower_result.h_max_uncert = errors["x_max_scale"] * shower_result.h_max shower_result.goodness_of_fit = np.nan shower_result.tel_ids = list(self.image.keys()) energy_result = ReconstructedEnergyContainer() energy_result.energy = fit_params["energy"] * u.TeV energy_result.energy_uncert = errors["energy"] * u.TeV energy_result.is_valid = True energy_result.tel_ids = list(self.image.keys()) # Return interesting stuff return shower_result, energy_result
def plot_muon_event(event, muonparams, args=None): if muonparams['MuonRingParams'] is not None: # Plot the muon event and overlay muon parameters fig = plt.figure(figsize=(16, 7)) colorbar = None colorbar2 = None #for tel_id in event.dl0.tels_with_data: for tel_id in muonparams['TelIds']: idx = muonparams['TelIds'].index(tel_id) if not muonparams['MuonRingParams'][idx]: continue #otherwise... npads = 2 # Only create two pads if there is timing information extracted # from the calibration ax1 = fig.add_subplot(1, npads, 1) plotter = CameraPlotter(event) image = event.dl1.tel[tel_id].image[0] geom = event.inst.subarray.tel[tel_id].camera tailcuts = (5., 7.) # Try a higher threshold for if geom.cam_id == 'FlashCam': tailcuts = (10., 12.) clean_mask = tailcuts_clean(geom, image, picture_thresh=tailcuts[0], boundary_thresh=tailcuts[1]) signals = image * clean_mask #print("Ring Centre in Nominal Coords:",muonparams[0].ring_center_x,muonparams[0].ring_center_y) muon_incl = np.sqrt( muonparams['MuonRingParams'][idx].ring_center_x**2. + muonparams['MuonRingParams'][idx].ring_center_y**2.) muon_phi = np.arctan( muonparams['MuonRingParams'][idx].ring_center_y / muonparams['MuonRingParams'][idx].ring_center_x) rotr_angle = geom.pix_rotation # if event.inst.optical_foclen[tel_id] > 10.*u.m and # event.dl0.tel[tel_id].num_pixels != 1764: if geom.cam_id == 'LSTCam' or geom.cam_id == 'NectarCam': #print("Resetting the rotation angle") rotr_angle = 0. * u.deg # Convert to camera frame (centre & radius) altaz = HorizonFrame(alt=event.mc.alt, az=event.mc.az) ring_nominal = NominalFrame( x=muonparams['MuonRingParams'][idx].ring_center_x, y=muonparams['MuonRingParams'][idx].ring_center_y, array_direction=altaz, pointing_direction=altaz) # embed() ring_camcoord = ring_nominal.transform_to( CameraFrame(pointing_direction=altaz, focal_length=event.inst.optical_foclen[tel_id], rotation=rotr_angle)) centroid_rad = np.sqrt(ring_camcoord.y**2 + ring_camcoord.x**2) centroid = (ring_camcoord.x.value, ring_camcoord.y.value) ringrad_camcoord = muonparams['MuonRingParams'][idx].ring_radius.to(u.rad) \ * event.inst.optical_foclen[tel_id] * 2. # But not FC? px, py = event.inst.pixel_pos[tel_id] flen = event.inst.optical_foclen[tel_id] camera_coord = CameraFrame(x=px, y=py, focal_length=flen, rotation=geom.pix_rotation) nom_coord = camera_coord.transform_to( NominalFrame(array_direction=altaz, pointing_direction=altaz)) px = nom_coord.x.to(u.deg) py = nom_coord.y.to(u.deg) dist = np.sqrt( np.power(px - muonparams['MuonRingParams'][idx].ring_center_x, 2) + np.power(py - muonparams['MuonRingParams'][idx].ring_center_y, 2)) ring_dist = np.abs(dist - muonparams['MuonRingParams'][idx].ring_radius) pixRmask = ring_dist < muonparams['MuonRingParams'][ idx].ring_radius * 0.4 #if muonparams[1] is not None: if muonparams['MuonIntensityParams'][idx] is not None: signals *= muonparams['MuonIntensityParams'][idx].mask camera1 = plotter.draw_camera(tel_id, signals, ax1) cmaxmin = (max(signals) - min(signals)) cmin = min(signals) if not cmin: cmin = 1. if not cmaxmin: cmaxmin = 1. cmap_charge = colors.LinearSegmentedColormap.from_list( 'cmap_c', [(0 / cmaxmin, 'darkblue'), (np.abs(cmin) / cmaxmin, 'black'), (2.0 * np.abs(cmin) / cmaxmin, 'blue'), (2.5 * np.abs(cmin) / cmaxmin, 'green'), (1, 'yellow')]) camera1.pixels.set_cmap(cmap_charge) if not colorbar: camera1.add_colorbar(ax=ax1, label=" [photo-electrons]") colorbar = camera1.colorbar else: camera1.colorbar = colorbar camera1.update(True) camera1.add_ellipse(centroid, ringrad_camcoord.value, ringrad_camcoord.value, 0., 0., color="red") if muonparams['MuonIntensityParams'][idx] is not None: # continue #Comment this...(should ringwidthfrac also be *0.5?) ringwidthfrac = muonparams['MuonIntensityParams'][ idx].ring_width / muonparams['MuonRingParams'][ idx].ring_radius ringrad_inner = ringrad_camcoord * (1. - ringwidthfrac) ringrad_outer = ringrad_camcoord * (1. + ringwidthfrac) camera1.add_ellipse(centroid, ringrad_inner.value, ringrad_inner.value, 0., 0., color="magenta") camera1.add_ellipse(centroid, ringrad_outer.value, ringrad_outer.value, 0., 0., color="magenta") npads = 2 ax2 = fig.add_subplot(1, npads, npads) pred = muonparams['MuonIntensityParams'][idx].prediction if len(pred) != np.sum( muonparams['MuonIntensityParams'][idx].mask): print("Warning! Lengths do not match...len(pred)=", len(pred), "len(mask)=", np.sum(muonparams['MuonIntensityParams'][idx].mask)) # Numpy broadcasting - fill in the shape plotpred = np.zeros(image.shape) plotpred[muonparams['MuonIntensityParams'][idx].mask == True] = pred camera2 = plotter.draw_camera(tel_id, plotpred, ax2) if np.isnan(max(plotpred)) or np.isnan(min(plotpred)): print("nan prediction, skipping...") continue c2maxmin = (max(plotpred) - min(plotpred)) if not c2maxmin: c2maxmin = 1. c2map_charge = colors.LinearSegmentedColormap.from_list( 'c2map_c', [(0 / c2maxmin, 'darkblue'), (np.abs(min(plotpred)) / c2maxmin, 'black'), (2.0 * np.abs(min(plotpred)) / c2maxmin, 'blue'), (2.5 * np.abs(min(plotpred)) / c2maxmin, 'green'), (1, 'yellow')]) camera2.pixels.set_cmap(c2map_charge) if not colorbar2: camera2.add_colorbar(ax=ax2, label=" [photo-electrons]") colorbar2 = camera2.colorbar else: camera2.colorbar = colorbar2 camera2.update(True) plt.pause(1.) # make shorter # plt.pause(0.1) # if pp is not None: # pp.savefig(fig) #fig.savefig(str(args.output_path) + "_" + # str(event.dl0.event_id) + '.png') plt.close()
def predict(self, shower_seed, energy_seed): """ Parameters ---------- shower_seed: ReconstructedShowerContainer Seed shower geometry to be used in the fit energy_seed: ReconstructedEnergyContainer Seed energy to be used in fit Returns ------- ReconstructedShowerContainer, ReconstructedEnergyContainer: Reconstructed ImPACT shower geometry and energy """ horizon_seed = HorizonFrame(az=shower_seed.az, alt=shower_seed.alt) nominal_seed = horizon_seed.transform_to(NominalFrame(array_direction=self.array_direction)) print(nominal_seed) print(horizon_seed) print(self.array_direction) source_x = nominal_seed.x[0].to(u.rad).value source_y = nominal_seed.y[0].to(u.rad).value ground = GroundFrame(x=shower_seed.core_x, y=shower_seed.core_y, z=0 * u.m) tilted = ground.transform_to( TiltedGroundFrame(pointing_direction=self.array_direction) ) tilt_x = tilted.x.to(u.m).value tilt_y = tilted.y.to(u.m).value lower_en_limit = energy_seed.energy * 0.5 en_seed = energy_seed.energy if lower_en_limit < 0.04 * u.TeV: lower_en_limit = 0.04 * u.TeV en_seed = 0.041 * u.TeV seed = (source_x, source_y, tilt_x, tilt_y, en_seed.value, 0.8) step = (0.001, 0.001, 10, 10, en_seed.value*0.1, 0.1) limits = ((source_x-0.01, source_x+0.01), (source_y-0.01, source_y+0.01), (tilt_x-100, tilt_x+100), (tilt_y-100, tilt_y+100), (lower_en_limit.value, en_seed.value*2), (0.5,2)) fit_params, errors = self.minimise(params=seed, step=step, limits=limits, minimiser_name=self.minimiser_name) # container class for reconstructed showers ''' shower_result = ReconstructedShowerContainer() nominal = NominalFrame(x=fit_params[0] * u.rad, y=fit_params[1] * u.rad, array_direction=self.array_direction) horizon = nominal.transform_to(HorizonFrame()) shower_result.alt, shower_result.az = horizon.alt, horizon.az tilted = TiltedGroundFrame(x=fit_params[2] * u.m, y=fit_params[3] * u.m, pointing_direction=self.array_direction) ground = project_to_ground(tilted) shower_result.core_x = ground.x shower_result.core_y = ground.y shower_result.is_valid = True shower_result.alt_uncert = np.nan shower_result.az_uncert = np.nan shower_result.core_uncert = np.nan zenith = 90*u.deg - self.array_direction.alt shower_result.h_max = fit_params[5] * \ self.get_shower_max(fit_params[0], fit_params[1], fit_params[2], fit_params[3], zenith.to(u.rad).value) shower_result.h_max_uncert = errors[5] * shower_result.h_max shower_result.goodness_of_fit = np.nan shower_result.tel_ids = list(self.image.keys()) energy_result = ReconstructedEnergyContainer() energy_result.energy = fit_params[4] * u.TeV energy_result.energy_uncert = errors[4] * u.TeV energy_result.is_valid = True energy_result.tel_ids = list(self.image.keys()) # Return interesting stuff return shower_result, energy_result