示例#1
0
def generate_cameras(normal_vector, distance=100.0, fov=50.0):
    '''
    Set camera positions and orientations
    '''
    from yt.utilities.orientation import Orientation

    print "\nGenerating cameras"

    north = np.array([0., 1., 0.])
    orient = Orientation(normal_vector=normal_vector, north_vector=north)
    R = np.linalg.inv(orient.inv_mat)

    camera_set = OrderedDict([
        ['face', ([0., 0., 1.], [0., -1., 0], True)],  #up is north=+y
        ['edge', ([0., 1., 0.], [0., 0., -1.], True)],  #up is along z
        ['45', ([0., 0.7071, 0.7071], [0., 0., -1.], True)],
        ['Z-axis', ([0., 0., 1.], [0., -1., 0], False)],  #up is north=+y
        ['Y-axis', ([0., 1., 0.], [0., 0., -1.], False)],  #up is along z
    ])
    segments = 10
    np.random.seed(0)
    ts = np.random.random(segments) * np.pi * 2
    ps = np.random.random(segments) * np.pi - np.pi / 2.0
    for i, (theta, phi) in enumerate(zip(ts, ps)):
        pos = [np.cos(theta), 0., np.sin(phi)]
        vc = [np.cos(np.pi / 2. - theta), 0., np.sin(np.pi / 2. - phi)]
        camera_set['Random_%03i' % (i)] = (pos, vc, False)

    i = 0
    cameras = OrderedDict()
    for name, (normal, north, do_rot) in camera_set.iteritems():
        orient = Orientation(normal_vector=normal, north_vector=north)
        if do_rot:
            drot = R.copy()
        else:
            drot = np.identity(3)
        sunrise_pos = np.dot(orient.normal_vector, drot)
        sunrise_up = L.copy()
        if np.all(np.abs(sunrise_up - sunrise_pos) < 1e-3):
            sunrise_up[0] *= 0.5
        sunrise_direction = -1.0 * sunrise_pos
        sunrise_afov = 2.0 * np.arctan((fov / 2.0) / distance)
        norm = lambda x: x / np.sqrt(np.sum(x * x))
        if np.all(np.abs(norm(sunrise_up) - norm(sunrise_pos)) < 1e-3):
            sunrise_up[0] *= 0.5
            sunrise_up = norm(sunrise_up)
        line = (distance * sunrise_pos, distance * sunrise_direction,
                sunrise_up, sunrise_afov, fov, distance)
        cameras[name] = line
        i += 1

    print "Successfully generated cameras\n"
    return cameras
示例#2
0
 def __init__(self,
              normal,
              center,
              north_vector=None,
              ds=None,
              field_parameters=None,
              data_source=None):
     validate_3d_array(normal)
     validate_center(center)
     if north_vector is not None:
         validate_3d_array(north_vector)
     validate_object(ds, Dataset)
     validate_object(field_parameters, dict)
     validate_object(data_source, YTSelectionContainer)
     YTSelectionContainer2D.__init__(self, 4, ds, field_parameters,
                                     data_source)
     self._set_center(center)
     self.set_field_parameter('center', center)
     # Let's set up our plane equation
     # ax + by + cz + d = 0
     self.orienter = Orientation(normal, north_vector=north_vector)
     self._norm_vec = self.orienter.normal_vector
     self._d = -1.0 * np.dot(self._norm_vec, self.center)
     self._x_vec = self.orienter.unit_vectors[0]
     self._y_vec = self.orienter.unit_vectors[1]
     # First we try all three, see which has the best result:
     self._rot_mat = np.array([self._x_vec, self._y_vec, self._norm_vec])
     self._inv_mat = np.linalg.pinv(self._rot_mat)
     self.set_field_parameter('cp_x_vec', self._x_vec)
     self.set_field_parameter('cp_y_vec', self._y_vec)
     self.set_field_parameter('cp_z_vec', self._norm_vec)
示例#3
0
def create_vlos(normal, no_shifting):
    if no_shifting:
        def _v_los(field, data):
            return data.ds.arr(data["index", "zeros"], "cm/s")
    elif isinstance(normal, string_types):
        def _v_los(field, data):
            return -data["gas", "velocity_%s" % normal]
    else:
        orient = Orientation(normal)
        los_vec = orient.unit_vectors[2]
        def _v_los(field, data):
            vz = data["gas", "velocity_x"]*los_vec[0] + \
                data["gas", "velocity_y"]*los_vec[1] + \
                data["gas", "velocity_z"]*los_vec[2]
            return -vz
    return _v_los
 def __init__(self,
              normal,
              center,
              north_vector=None,
              ds=None,
              field_parameters=None):
     YTSelectionContainer2D.__init__(self, 4, ds, field_parameters)
     self._set_center(center)
     self.set_field_parameter('center', center)
     # Let's set up our plane equation
     # ax + by + cz + d = 0
     self.orienter = Orientation(normal, north_vector=north_vector)
     self._norm_vec = self.orienter.normal_vector
     self._d = -1.0 * np.dot(self._norm_vec, self.center)
     self._x_vec = self.orienter.unit_vectors[0]
     self._y_vec = self.orienter.unit_vectors[1]
     # First we try all three, see which has the best result:
     vecs = np.identity(3)
     self._rot_mat = np.array([self._x_vec, self._y_vec, self._norm_vec])
     self._inv_mat = np.linalg.pinv(self._rot_mat)
     self.set_field_parameter('cp_x_vec', self._x_vec)
     self.set_field_parameter('cp_y_vec', self._y_vec)
     self.set_field_parameter('cp_z_vec', self._norm_vec)
示例#5
0
    def __init__(self,
                 ds,
                 normal,
                 field,
                 width=(1.0, "unitary"),
                 dims=(100, 100, 100),
                 velocity_bounds=None):
        r""" Initialize a PPVCube object.

        Parameters
        ----------
        ds : dataset
            The dataset.
        normal : array_like
            The normal vector along with to make the projections.
        field : string
            The field to project.
        width : float or tuple, optional
            The width of the projection in length units. Specify a float
            for code_length units or a tuple (value, units).
        dims : tuple, optional
            A 3-tuple of dimensions (nx,ny,nv) for the cube.
        velocity_bounds : tuple, optional
            A 3-tuple of (vmin, vmax, units) for the velocity bounds to
            integrate over. If None, the largest velocity of the
            dataset will be used, e.g. velocity_bounds = (-v.max(), v.max())

        Examples
        --------
        >>> i = 60*np.pi/180.
        >>> L = [0.0,np.sin(i),np.cos(i)]
        >>> cube = PPVCube(ds, L, "density", width=(10.,"kpc"),
        ...                velocity_bounds=(-5.,4.,"km/s"))
        """
        self.ds = ds
        self.field = field
        self.width = width

        self.nx = dims[0]
        self.ny = dims[1]
        self.nv = dims[2]

        normal = np.array(normal)
        normal /= np.sqrt(np.dot(normal, normal))
        vecs = np.identity(3)
        t = np.cross(normal, vecs).sum(axis=1)
        ax = t.argmax()
        north = np.cross(normal, vecs[ax, :]).ravel()
        orient = Orientation(normal, north_vector=north)

        dd = ds.all_data()

        fd = dd._determine_fields(field)[0]

        self.field_units = ds._get_field_info(fd).units

        if velocity_bounds is None:
            vmin, vmax = dd.quantities.extrema("velocity_magnitude")
            self.v_bnd = -vmax, vmax
        else:
            self.v_bnd = (ds.quan(velocity_bounds[0], velocity_bounds[2]),
                          ds.quan(velocity_bounds[1], velocity_bounds[2]))

        self.vbins = np.linspace(self.v_bnd[0], self.v_bnd[1], num=self.nv + 1)
        self.vmid = 0.5 * (self.vbins[1:] + self.vbins[:-1])
        self.dv = (self.v_bnd[1] - self.v_bnd[0]) / self.nv

        _vlos = create_vlos(orient.unit_vectors[2])
        ds.field_info.add_field(("gas", "v_los"), function=_vlos, units="cm/s")

        self.data = ds.arr(np.zeros((self.nx, self.ny, self.nv)),
                           self.field_units)
        pbar = get_pbar("Generating cube.", self.nv)
        for i in xrange(self.nv):
            _intensity = self._create_intensity(i)
            ds.add_field(("gas", "intensity"),
                         function=_intensity,
                         units=self.field_units)
            prj = off_axis_projection(ds, ds.domain_center, normal, width,
                                      (self.nx, self.ny), "intensity")
            self.data[:, :, i] = prj[:, :]
            ds.field_info.pop(("gas", "intensity"))
            pbar.update(i)

        pbar.finish()
示例#6
0
    def project_photons(self,
                        normal,
                        sky_center,
                        absorb_model=None,
                        nH=None,
                        no_shifting=False,
                        north_vector=None,
                        sigma_pos=None,
                        kernel="top_hat",
                        prng=None,
                        **kwargs):
        r"""
        Projects photons onto an image plane given a line of sight.
        Returns a new :class:`~pyxsim.event_list.EventList`.

        Parameters
        ----------
        normal : character or array-like
            Normal vector to the plane of projection. If "x", "y", or "z", will
            assume to be along that axis (and will probably be faster). Otherwise,
            should be an off-axis normal vector, e.g [1.0, 2.0, -3.0]
        sky_center : array-like
            Center RA, Dec of the events in degrees.
        absorb_model : string or :class:`~pyxsim.spectral_models.AbsorptionModel`
            A model for foreground galactic absorption, to simulate the
            absorption of events before being detected. This cannot be applied
            here if you already did this step previously in the creation of the
            :class:`~pyxsim.photon_list.PhotonList` instance. Known options for
            strings are "wabs" and "tbabs".
        nH : float, optional
            The foreground column density in units of 10^22 cm^{-2}. Only used
            if absorption is applied.
        no_shifting : boolean, optional
            If set, the photon energies will not be Doppler shifted.
        north_vector : a sequence of floats
            A vector defining the "up" direction. This option sets the
            orientation of the plane of projection. If not set, an arbitrary
            grid-aligned north_vector is chosen. Ignored in the case where a
            particular axis (e.g., "x", "y", or "z") is explicitly specified.
        sigma_pos : float, optional
            Apply a gaussian smoothing operation to the sky positions of the
            events. This may be useful when the binned events appear blocky due
            to their uniform distribution within simulation cells. However, this
            will move the events away from their originating position on the
            sky, and so may distort surface brightness profiles and/or spectra.
            Should probably only be used for visualization purposes. Supply a
            float here to smooth with a standard deviation with this fraction
            of the cell size. Default: None
        kernel : string, optional
            The kernel used when smoothing positions of X-rays originating from
            SPH particles, "gaussian" or "top_hat". Default: "top_hat".
        prng : integer or :class:`~numpy.random.RandomState` object 
            A pseudo-random number generator. Typically will only be specified
            if you have a reason to generate the same set of random numbers,
            such as for a test. Default is to use the :mod:`numpy.random`
            module.

        Examples
        --------
        >>> L = np.array([0.1,-0.2,0.3])
        >>> events = my_photons.project_photons(L, [30., 45.])
        """
        prng = parse_prng(prng)

        scale_shift = -1.0 / clight.to("km/s")

        if "smooth_positions" in kwargs:
            issue_deprecation_warning(
                "'smooth_positions' has been renamed to "
                "'sigma_pos' and the former is deprecated!")
            sigma_pos = kwargs["smooth_positions"]

        if "redshift_new" in kwargs or "area_new" in kwargs or \
            "exp_time_new" in kwargs or "dist_new" in kwargs:
            issue_deprecation_warning(
                "Changing the redshift, distance, area, or "
                "exposure time has been deprecated in "
                "project_photons!")

        if sigma_pos is not None and self.parameters[
                "data_type"] == "particles":
            raise RuntimeError(
                "The 'smooth_positions' argument should not be used with "
                "particle-based datasets!")

        if isinstance(absorb_model, string_types):
            if absorb_model not in absorb_models:
                raise KeyError("%s is not a known absorption model!" %
                               absorb_model)
            absorb_model = absorb_models[absorb_model]
        if absorb_model is not None:
            if nH is None:
                raise RuntimeError(
                    "You specified an absorption model, but didn't "
                    "specify a value for nH!")
            absorb_model = absorb_model(nH)

        sky_center = YTArray(sky_center, "degree")

        n_ph = self.photons["num_photons"]

        if not isinstance(normal, string_types):
            L = np.array(normal)
            orient = Orientation(L, north_vector=north_vector)
            x_hat = orient.unit_vectors[0]
            y_hat = orient.unit_vectors[1]
            z_hat = orient.unit_vectors[2]
        else:
            x_hat = np.zeros(3)
            y_hat = np.zeros(3)
            z_hat = np.zeros(3)

        parameters = {}

        D_A = self.parameters["fid_d_a"]

        events = {}

        eobs = self.photons["energy"].v

        if not no_shifting:
            if comm.rank == 0:
                mylog.info("Doppler-shifting photon energies.")
            if isinstance(normal, string_types):
                shift = self.photons["vel"][:,
                                            "xyz".index(normal)] * scale_shift
            else:
                shift = np.dot(self.photons["vel"], z_hat) * scale_shift
            doppler_shift(shift, n_ph, eobs)

        if absorb_model is None:
            det = np.ones(eobs.size, dtype='bool')
            num_det = eobs.size
        else:
            if comm.rank == 0:
                mylog.info("Foreground galactic absorption: using "
                           "the %s model and nH = %g." %
                           (absorb_model._name, nH))
            det = absorb_model.absorb_photons(eobs, prng=prng)
            num_det = det.sum()

        events["eobs"] = YTArray(eobs[det], "keV")

        num_events = comm.mpi_allreduce(num_det)

        if comm.rank == 0:
            mylog.info("%d events have been detected." % num_events)

        if num_det > 0:

            if comm.rank == 0:
                mylog.info("Assigning positions to events.")

            if isinstance(normal, string_types):
                norm = "xyz".index(normal)
            else:
                norm = normal

            xsky, ysky = scatter_events(norm, prng, kernel,
                                        self.parameters["data_type"], num_det,
                                        det, self.photons["num_photons"],
                                        self.photons["pos"].d,
                                        self.photons["dx"].d, x_hat, y_hat)

            if self.parameters[
                    "data_type"] == "cells" and sigma_pos is not None:
                if comm.rank == 0:
                    mylog.info("Optionally smoothing sky positions.")
                sigma = sigma_pos * np.repeat(self.photons["dx"].d, n_ph)[det]
                xsky += sigma * prng.normal(loc=0.0, scale=1.0, size=num_det)
                ysky += sigma * prng.normal(loc=0.0, scale=1.0, size=num_det)

            d_a = D_A.to("kpc").v
            xsky /= d_a
            ysky /= d_a

            if comm.rank == 0:
                mylog.info("Converting pixel to sky coordinates.")

            pixel_to_cel(xsky, ysky, sky_center)

        else:

            xsky = []
            ysky = []

        events["xsky"] = YTArray(xsky, "degree")
        events["ysky"] = YTArray(ysky, "degree")

        parameters["exp_time"] = self.parameters["fid_exp_time"]
        parameters["area"] = self.parameters["fid_area"]
        parameters["sky_center"] = sky_center

        return EventList(events, parameters)
示例#7
0
    def project_photons(self,
                        normal,
                        area_new=None,
                        exp_time_new=None,
                        redshift_new=None,
                        dist_new=None,
                        absorb_model=None,
                        sky_center=None,
                        no_shifting=False,
                        north_vector=None,
                        prng=None):
        r"""
        Projects photons onto an image plane given a line of sight.
        Returns a new :class:`~pyxsim.event_list.EventList`.

        Parameters
        ----------
        normal : character or array-like
            Normal vector to the plane of projection. If "x", "y", or "z", will
            assume to be along that axis (and will probably be faster). Otherwise,
            should be an off-axis normal vector, e.g [1.0, 2.0, -3.0]
        area_new : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            New value for the (constant) collecting area of the detector. If
            units are not specified, is assumed to be in cm**2.
        exp_time_new : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            The new value for the exposure time. If units are not specified
            it is assumed to be in seconds.
        redshift_new : float, optional
            The new value for the cosmological redshift.
        dist_new : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional
            The new value for the angular diameter distance, used for nearby sources.
            This may be optionally supplied instead of it being determined from the
            cosmology. If units are not specified, it is assumed to be in Mpc. To use this, the
            redshift must be zero.
        absorb_model : :class:`~pyxsim.spectral_models.AbsorptionModel`
            A model for foreground galactic absorption.
        sky_center : array-like, optional
            Center RA, Dec of the events in degrees.
        no_shifting : boolean, optional
            If set, the photon energies will not be Doppler shifted.
        north_vector : a sequence of floats
            A vector defining the "up" direction. This option sets the orientation of
            the plane of projection. If not set, an arbitrary grid-aligned north_vector
            is chosen. Ignored in the case where a particular axis (e.g., "x", "y", or
            "z") is explicitly specified.
        prng : :class:`~numpy.random.RandomState` object or :mod:`~numpy.random`, optional
            A pseudo-random number generator. Typically will only be specified
            if you have a reason to generate the same set of random numbers, such as for a
            test. Default is the :mod:`numpy.random` module.

        Examples
        --------
        >>> L = np.array([0.1,-0.2,0.3])
        >>> events = my_photons.project_photons(L, area_new=10000.,
        ...                                     redshift_new=0.05)
        """

        if prng is None:
            prng = np.random

        if redshift_new is not None and dist_new is not None:
            mylog.error("You may specify a new redshift or distance, " +
                        "but not both!")

        if sky_center is None:
            sky_center = YTArray([30., 45.], "degree")
        else:
            sky_center = YTArray(sky_center, "degree")

        dx = self.photons["dx"].d
        if isinstance(normal, string_types):
            # if on-axis, just use the maximum width of the plane perpendicular
            # to that axis
            w = self.parameters["Width"].copy()
            w["xyz".index(normal)] = 0.0
            ax_idx = np.argmax(w)
        else:
            # if off-axis, just use the largest width to make sure we get everything
            ax_idx = np.argmax(self.parameters["Width"])
        nx = self.parameters["Dimension"][ax_idx]
        dx_min = (self.parameters["Width"] /
                  self.parameters["Dimension"])[ax_idx]

        if not isinstance(normal, string_types):
            L = np.array(normal)
            orient = Orientation(L, north_vector=north_vector)
            x_hat = orient.unit_vectors[0]
            y_hat = orient.unit_vectors[1]
            z_hat = orient.unit_vectors[2]

        n_ph = self.photons["NumberOfPhotons"]
        n_ph_tot = n_ph.sum()

        parameters = {}

        zobs0 = self.parameters["FiducialRedshift"]
        D_A0 = self.parameters["FiducialAngularDiameterDistance"]
        scale_factor = 1.0

        if (exp_time_new is None and area_new is None and redshift_new is None
                and dist_new is None):
            my_n_obs = n_ph_tot
            zobs = zobs0
            D_A = D_A0
        else:
            if exp_time_new is None:
                Tratio = 1.
            else:
                exp_time_new = parse_value(exp_time_new, "s")
                Tratio = exp_time_new / self.parameters["FiducialExposureTime"]
            if area_new is None:
                Aratio = 1.
            else:
                area_new = parse_value(area_new, "cm**2")
                Aratio = area_new / self.parameters["FiducialArea"]
            if redshift_new is None and dist_new is None:
                Dratio = 1.
                zobs = zobs0
                D_A = D_A0
            else:
                if dist_new is not None:
                    if redshift_new is not None and redshift_new > 0.0:
                        mylog.warning(
                            "Redshift must be zero for nearby sources. Resetting redshift to 0.0."
                        )
                        zobs = 0.0
                    D_A = parse_value(dist_new, "Mpc")
                else:
                    zobs = redshift_new
                    D_A = self.cosmo.angular_diameter_distance(
                        0.0, zobs).in_units("Mpc")
                    scale_factor = (1. + zobs0) / (1. + zobs)
                Dratio = D_A0*D_A0*(1.+zobs0)**3 / \
                         (D_A*D_A*(1.+zobs)**3)
            fak = Aratio * Tratio * Dratio
            if fak > 1:
                raise ValueError(
                    "This combination of requested parameters results in "
                    "%g%% more photons collected than are " % (100. *
                                                               (fak - 1.)) +
                    "available in the sample. Please reduce the collecting "
                    "area, exposure time, or increase the distance/redshift "
                    "of the object. Alternatively, generate a larger sample "
                    "of photons.")
            my_n_obs = np.int64(n_ph_tot * fak)

        Nn = 4294967294
        if my_n_obs == n_ph_tot:
            if my_n_obs <= Nn:
                idxs = np.arange(my_n_obs, dtype='uint32')
            else:
                idxs = np.arange(my_n_obs, dtype='uint64')
        else:
            if n_ph_tot <= Nn:
                idxs = np.arange(n_ph_tot, dtype='uint32')
                prng.shuffle(idxs)
                idxs = idxs[:my_n_obs]
            else:
                Nc = np.int32(n_ph_tot / Nn)
                idxs = np.zeros(my_n_obs, dtype=np.uint64)
                Nup = np.uint32(my_n_obs / Nc)
                for i in range(Nc + 1):
                    if (i + 1) * Nc < n_ph_tot:
                        idtm = np.arange(i * Nc, (i + 1) * Nc, dtype='uint64')
                        Nupt = Nup
                    else:
                        idtm = np.arange(i * Nc, n_ph_tot, dtype='uint64')
                        Nupt = my_n_obs - i * Nup
                    prng.shuffle(idtm)
                    idxs[i * Nup, i * Nup + Nupt] = idtm[:Nupt]
                    del (idtm)
            # idxs = prng.permutation(n_ph_tot)[:my_n_obs].astype("int64")
        obs_cells = np.searchsorted(self.p_bins, idxs, side='right') - 1
        delta = dx[obs_cells]

        if isinstance(normal, string_types):

            if self.parameters["DataType"] == "cells":
                xsky = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
                ysky = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
            elif self.parameters["DataType"] == "particles":
                xsky = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
                ysky = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
            xsky *= delta
            ysky *= delta
            xsky += self.photons[axes_lookup[normal][0]].d[obs_cells]
            ysky += self.photons[axes_lookup[normal][1]].d[obs_cells]

            if not no_shifting:
                vz = self.photons["v%s" % normal]

        else:

            if self.parameters["DataType"] == "cells":
                x = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
                y = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
                z = prng.uniform(low=-0.5, high=0.5, size=my_n_obs)
            elif self.parameters["DataType"] == "particles":
                x = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
                y = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)
                z = prng.normal(loc=0.0, scale=1.0, size=my_n_obs)

            if not no_shifting:
                vz = self.photons["vx"]*z_hat[0] + \
                     self.photons["vy"]*z_hat[1] + \
                     self.photons["vz"]*z_hat[2]

            x *= delta
            y *= delta
            z *= delta
            x += self.photons["x"].d[obs_cells]
            y += self.photons["y"].d[obs_cells]
            z += self.photons["z"].d[obs_cells]

            xsky = x * x_hat[0] + y * x_hat[1] + z * x_hat[2]
            ysky = x * y_hat[0] + y * y_hat[1] + z * y_hat[2]

        del (delta)
        if no_shifting:
            eobs = self.photons["Energy"][idxs]
        else:
            # shift = -vz.in_cgs()/clight
            # shift = np.sqrt((1.-shift)/(1.+shift))
            # eobs = self.photons["Energy"][idxs]*shift[obs_cells]
            shift = -vz[obs_cells].in_cgs() / clight
            shift = np.sqrt((1. - shift) / (1. + shift))
            eobs = self.photons["Energy"][idxs]
            eobs *= shift
            del (shift)
        eobs *= scale_factor

        if absorb_model is None:
            detected = np.ones(eobs.shape, dtype='bool')
        else:
            detected = absorb_model.absorb_photons(eobs, prng=prng)

        events = {}

        dtheta = YTQuantity(np.rad2deg(dx_min / D_A), "degree")

        events["xpix"] = xsky[detected] / dx_min.v + 0.5 * (nx + 1)
        events["ypix"] = ysky[detected] / dx_min.v + 0.5 * (nx + 1)
        events["eobs"] = eobs[detected]

        events = comm.par_combine_object(events, datatype="dict", op="cat")

        num_events = len(events["xpix"])

        if comm.rank == 0:
            mylog.info("Total number of observed photons: %d" % num_events)

        if exp_time_new is None:
            parameters["ExposureTime"] = self.parameters[
                "FiducialExposureTime"]
        else:
            parameters["ExposureTime"] = exp_time_new
        if area_new is None:
            parameters["Area"] = self.parameters["FiducialArea"]
        else:
            parameters["Area"] = area_new
        parameters["Redshift"] = zobs
        parameters["AngularDiameterDistance"] = D_A.in_units("Mpc")
        parameters["sky_center"] = sky_center
        parameters["pix_center"] = np.array([0.5 * (nx + 1)] * 2)
        parameters["dtheta"] = dtheta

        return EventList(events, parameters)