Пример #1
0
    def from_regions(cls, regions, **kwargs):
        """Create region geom from list of regions

        The regions are combined with union to a compound region.

        Parameters
        ----------
        regions : list of `~regions.SkyRegion` or str
            Regions
        **kwargs: dict
            Keyword arguments forwarded to `RegionGeom`

        Returns
        -------
        geom : `RegionGeom`
            Region map geometry
        """
        if isinstance(regions, str):
            regions = Regions.parse(data=regions, format="ds9")
        elif isinstance(regions, SkyRegion):
            regions = [regions]
        elif isinstance(regions, SkyCoord):
            regions = [PointSkyRegion(center=regions)]

        if regions:
            regions = regions_to_compound_region(regions)

        return cls(region=regions, **kwargs)
Пример #2
0
def test_compound_region_center():
    regions_ds9 = ("galactic;"
                   "circle(1,1,0.1);"
                   "circle(-1,1,0.1);"
                   "circle(1,-1,0.1);"
                   "circle(-1,-1,0.1);")

    regions = Regions.parse(regions_ds9, format="ds9")

    region = regions_to_compound_region(regions)

    center = compound_region_center(region)

    assert_allclose(center.galactic.l.wrap_at("180d"), 0 * u.deg, atol=1e-6)
    assert_allclose(center.galactic.b, 0 * u.deg, atol=1e-6)
Пример #3
0
    def from_hdulist(cls, hdulist, format="ogip", hdu=None):
        """Read region table and convert it to region list.

        Parameters
        ----------
        hdulist : `~astropy.io.fits.HDUList`
            HDU list
        format : {"ogip", "ogip-arf", "gadf"}
            HDU format

        Returns
        -------
        geom : `RegionGeom`
            Region map geometry

        """
        region_hdu = "REGION"

        if format == "gadf" and hdu:
            region_hdu = hdu + "_" + region_hdu

        if region_hdu in hdulist:
            region_table = Table.read(hdulist[region_hdu])
            wcs = WcsGeom.from_header(region_table.meta).wcs

            regions = []

            for reg in Regions.parse(data=region_table, format="fits"):
                # TODO: remove workaround once regions issue with fits serialization is sorted out
                # see https://github.com/astropy/regions/issues/400
                reg.meta["include"] = True
                regions.append(reg.to_sky(wcs))
            region = regions_to_compound_region(regions)
        else:
            region, wcs = None, None

        if format == "ogip":
            hdu_bands = "EBOUNDS"
        elif format == "ogip-arf":
            hdu_bands = "SPECRESP"
        elif format == "gadf":
            hdu_bands = hdu + "_BANDS"
        else:
            raise ValueError(f"Unknown format {format}")

        axes = MapAxes.from_table_hdu(hdulist[hdu_bands], format=format)
        return cls(region=region, wcs=wcs, axes=axes)
Пример #4
0
def generate_plan(observation_plan_id, request_id, user_id):
    """Use gwemopt to construct observing plan."""

    from ..models import DBSession
    from skyportal.handlers.api.instrument import add_tiles

    Session = scoped_session(
        sessionmaker(bind=DBSession.session_factory.kw["bind"]))

    import gwemopt
    import gwemopt.utils
    import gwemopt.segments
    import gwemopt.skyportal

    from ..models import (
        EventObservationPlan,
        Galaxy,
        InstrumentField,
        ObservationPlanRequest,
        PlannedObservation,
        User,
    )

    session = Session()
    try:
        plan = session.query(EventObservationPlan).get(observation_plan_id)
        request = session.query(ObservationPlanRequest).get(request_id)
        user = session.query(User).get(user_id)

        event_time = Time(request.gcnevent.dateobs,
                          format='datetime',
                          scale='utc')
        start_time = Time(request.payload["start_date"],
                          format='iso',
                          scale='utc')
        end_time = Time(request.payload["end_date"], format='iso', scale='utc')

        params = {
            'config': {
                request.instrument.name: {
                    # field list from skyportal
                    'tesselation': request.instrument.fields,
                    # telescope longitude [deg]
                    'longitude': request.instrument.telescope.lon,
                    # telescope latitude [deg]
                    'latitude': request.instrument.telescope.lat,
                    # telescope elevation [m]
                    'elevation': request.instrument.telescope.elevation,
                    # telescope name
                    'telescope': request.instrument.name,
                    # telescope horizon
                    'horizon': -12.0,
                    # time in seconds to change the filter
                    'filt_change_time': 0.0,
                    # extra overhead in seconds
                    'overhead_per_exposure': 0.0,
                    # slew rate for the telescope [deg/s]
                    'slew_rate': 2.6,
                    # camera readout time
                    'readout': 0.0,
                    # telescope field of view
                    'FOV': 0.0,
                    # exposure time for the given limiting magnitude
                    'exposuretime': 1.0,
                    # limiting magnitude given telescope time
                    'magnitude': 0.0,
                },
            },
            # gwemopt filter strategy
            # options: block (blocks of single filters), integrated (series of alternating filters)
            'doAlternativeFilters':
            request.payload["filter_strategy"] == "block",
            # flag to indicate fields come from DB
            'doDatabase':
            True,
            # only keep tiles within powerlaw_cl
            'doMinimalTiling':
            True,
            # single set of scheduled observations
            'doSingleExposure':
            True,
            # gwemopt scheduling algorithms
            # options: greedy, greedy_slew, sear, airmass_weighted
            'scheduleType':
            request.payload["schedule_type"],
            # list of filters to use for observations
            'filters':
            request.payload["filters"].split(","),
            # GPS time for event
            'gpstime':
            event_time.gps,
            # Healpix nside for the skymap
            'nside':
            512,
            # maximum integrated probability of the skymap to consider
            'powerlaw_cl':
            request.payload["integrated_probability"],
            'telescopes': [request.instrument.name],
            # minimum difference between observations of the same field
            'mindiff':
            request.payload["minimum_time_difference"],
            # maximum airmass with which to observae
            'airmass':
            request.payload["maximum_airmass"],
            # array of exposure times (same length as filter array)
            'exposuretimes':
            np.array([int(request.payload["exposure_time"])] *
                     len(request.payload["filters"].split(","))),
        }

        if request.payload["schedule_strategy"] == "galaxy":
            params = {
                **params,
                'tilesType': 'galaxy',
                'galaxy_catalog': request.payload["galaxy_catalog"],
                'galaxy_grade': 'S',
                'writeCatalog': False,
                'catalog_n': 1.0,
                'powerlaw_dist_exp': 1.0,
            }
        elif request.payload["schedule_strategy"] == "tiling":
            params = {**params, 'tilesType': 'moc'}
        else:
            raise AttributeError(
                'scheduling_strategy should be tiling or galaxy')

        params = gwemopt.utils.params_checker(params)
        params = gwemopt.segments.get_telescope_segments(params)

        params["Tobs"] = [
            start_time.mjd - event_time.mjd,
            end_time.mjd - event_time.mjd,
        ]

        params['map_struct'] = dict(
            zip(['prob', 'distmu', 'distsigma', 'distnorm'],
                request.localization.flat))

        params['is3D'] = request.localization.is_3d

        # Function to read maps
        map_struct = gwemopt.utils.read_skymap(params,
                                               is3D=params["do3D"],
                                               map_struct=params['map_struct'])

        if params["tilesType"] == "galaxy":
            query = Galaxy.query_records_accessible_by(user, mode="read")
            query = query.filter(
                Galaxy.catalog_name == params["galaxy_catalog"])
            galaxies = query.all()
            catalog_struct = {}
            catalog_struct["ra"] = np.array([g.ra for g in galaxies])
            catalog_struct["dec"] = np.array([g.dec for g in galaxies])
            catalog_struct["S"] = np.array([1.0 for g in galaxies])
            catalog_struct["Sloc"] = np.array([1.0 for g in galaxies])
            catalog_struct["Smass"] = np.array([1.0 for g in galaxies])

        if params["tilesType"] == "moc":
            moc_structs = gwemopt.skyportal.create_moc_from_skyportal(
                params, map_struct=map_struct)
            tile_structs = gwemopt.tiles.moc(params, map_struct, moc_structs)
        elif params["tilesType"] == "galaxy":
            if request.instrument.region is None:
                raise ValueError(
                    'Must define the instrument region in the case of galaxy requests'
                )
            regions = Regions.parse(request.instrument.region, format='ds9')
            tile_structs = gwemopt.skyportal.create_galaxy_from_skyportal(
                params, map_struct, catalog_struct, regions=regions)

        tile_structs, coverage_struct = gwemopt.coverage.timeallocation(
            params, map_struct, tile_structs)

        # if the fields do not yet exist, we need to add them
        if params["tilesType"] == "galaxy":
            regions = Regions.parse(request.instrument.region, format='ds9')
            data = {
                'RA': coverage_struct["data"][:, 0],
                'Dec': coverage_struct["data"][:, 1],
            }
            field_data = pd.DataFrame.from_dict(data)
            field_ids = add_tiles(
                request.instrument.id,
                request.instrument.name,
                regions,
                field_data,
                session=session,
            )

        planned_observations = []
        for ii in range(len(coverage_struct["ipix"])):
            data = coverage_struct["data"][ii, :]
            filt = coverage_struct["filters"][ii]
            mjd = data[2]
            tt = Time(mjd, format='mjd')

            overhead_per_exposure = params["config"][
                request.instrument.name]["overhead_per_exposure"]

            exposure_time, prob = data[4], data[6]
            if params["tilesType"] == "galaxy":
                field_id = field_ids[ii]
            else:
                field_id = data[5]

            field = InstrumentField.query.filter(
                InstrumentField.instrument_id == request.instrument.id,
                InstrumentField.field_id == field_id,
            ).first()
            if field is None:
                return log(f"Missing field {field_id} from list")

            planned_observation = PlannedObservation(
                obstime=tt.datetime,
                dateobs=request.gcnevent.dateobs,
                field_id=field.id,
                exposure_time=exposure_time,
                weight=prob,
                filt=filt,
                instrument_id=request.instrument.id,
                planned_observation_id=ii,
                observation_plan_id=plan.id,
                overhead_per_exposure=overhead_per_exposure,
            )
            planned_observations.append(planned_observation)

        session.add_all(planned_observations)
        plan.status = 'complete'
        session.merge(plan)
        session.commit()

        request.status = 'complete'
        session.merge(request)
        session.commit()

        flow = Flow()
        flow.push(
            '*',
            "skyportal/REFRESH_GCNEVENT",
            payload={"gcnEvent_dateobs": request.gcnevent.dateobs},
        )

        return log(
            f"Generated plan for observation plan {observation_plan_id}")

    except Exception as e:
        return log(
            f"Unable to generate plan for observation plan {observation_plan_id}: {e}"
        )
    finally:
        Session.remove()
Пример #5
0
def test_compound_region_center_single():
    region = Regions.parse("galactic;circle(1,1,0.1)", format="ds9")[0]
    center = compound_region_center(region)

    assert_allclose(center.galactic.l.wrap_at("180d"), 1 * u.deg, atol=1e-6)
    assert_allclose(center.galactic.b, 1 * u.deg, atol=1e-6)
Пример #6
0
    def put(self, instrument_id):
        """
        ---
        description: Update instrument
        tags:
          - instruments
        parameters:
          - in: path
            name: instrument_id
            required: true
            schema:
              type: integer
        requestBody:
          content:
            application/json:
              schema: InstrumentNoID
        responses:
          200:
            content:
              application/json:
                schema: Success
          400:
            content:
              application/json:
                schema: Error
        """
        data = self.get_json()
        data['id'] = int(instrument_id)

        # permission check
        instrument = Instrument.get_if_accessible_by(
            int(instrument_id), self.current_user, mode='update'
        )
        if instrument is None:
            return self.error(f'Missing instrument with ID {instrument_id}')

        filters = instrument.filters
        sensitivity_data = data.get('sensitivity_data', None)
        if sensitivity_data:
            if not set(sensitivity_data.keys()).issubset(filters):
                return self.error(
                    'Filter names must be present in both sensitivity_data property and filters property'
                )

        field_data = data.pop("field_data", None)
        field_region = data.pop("field_region", None)

        field_fov_type = data.pop("field_fov_type", None)
        field_fov_attributes = data.pop("field_fov_attributes", None)

        if (field_region is not None) and (field_fov_type is not None):
            return self.error('must supply only one of field_region or field_fov_type')

        if field_region is not None:
            regions = Regions.parse(field_region, format='ds9')
            data['region'] = regions.serialize(format='ds9')

        if field_fov_type is not None:
            if field_fov_attributes is None:
                return self.error(
                    'field_fov_attributes required if field_fov_type supplied'
                )
            if not field_fov_type.lower() in ["circle", "rectangle"]:
                return self.error('field_fov_type must be circle or rectangle')
            if isinstance(field_fov_attributes, list):
                field_fov_attributes = [float(x) for x in field_fov_attributes]
            else:
                field_fov_attributes = [float(field_fov_attributes)]

            center = SkyCoord(0.0, 0.0, unit='deg', frame='icrs')
            if field_fov_type.lower() == "circle":
                if not len(field_fov_attributes) == 1:
                    return self.error(
                        'If field_fov_type is circle, then should supply only radius for field_fov_attributes'
                    )
                radius = field_fov_attributes[0]
                regions = CircleSkyRegion(center=center, radius=radius * u.deg)
            elif field_fov_type.lower() == "rectangle":
                if not len(field_fov_attributes) == 2:
                    return self.error(
                        'If field_fov_type is rectangle, then should supply width and height for field_fov_attributes'
                    )
                width, height = field_fov_attributes
                regions = RectangleSkyRegion(
                    center=center, width=width * u.deg, height=height * u.deg
                )
            data['region'] = regions.serialize(format='ds9')

        schema = Instrument.__schema__()
        try:
            schema.load(data, partial=True)
        except ValidationError as exc:
            return self.error(
                'Invalid/missing parameters: ' f'{exc.normalized_messages()}'
            )
        self.verify_and_commit()

        if field_data is not None:
            if (field_region is None) and (field_fov_type is None):
                return self.error(
                    'field_region or field_fov_type is required with field_data'
                )

            if type(field_data) is str:
                field_data = pd.read_table(StringIO(field_data), sep=",").to_dict(
                    orient='list'
                )

            if not {'ID', 'RA', 'Dec'}.issubset(field_data):
                return self.error("ID, RA, and Dec required in field_data.")

            log(f"Started generating fields for instrument {instrument.id}")
            # run async
            IOLoop.current().run_in_executor(
                None,
                lambda: add_tiles(instrument.id, instrument.name, regions, field_data),
            )

        self.push_all(action="skyportal/REFRESH_INSTRUMENTS")
        return self.success()
Пример #7
0
    def post(self):
        # See bottom of this file for redoc docstring -- moved it there so that
        # it could be made an f-string.

        data = self.get_json()
        telescope_id = data.get('telescope_id')
        telescope = Telescope.get_if_accessible_by(
            telescope_id, self.current_user, raise_if_none=True, mode="read"
        )

        sensitivity_data = data.get("sensitivity_data", None)
        if sensitivity_data:
            filters = data.get("filters", [])
            if not set(sensitivity_data.keys()).issubset(filters):
                return self.error(
                    'Sensitivity_data filters must be a subset of the instrument filters'
                )

        field_data = data.pop("field_data", None)
        field_region = data.pop("field_region", None)

        field_fov_type = data.pop("field_fov_type", None)
        field_fov_attributes = data.pop("field_fov_attributes", None)

        if (field_region is not None) and (field_fov_type is not None):
            return self.error('must supply only one of field_region or field_fov_type')

        if field_region is not None:
            regions = Regions.parse(field_region, format='ds9')
            data['region'] = regions.serialize(format='ds9')

        if field_fov_type is not None:
            if field_fov_attributes is None:
                return self.error(
                    'field_fov_attributes required if field_fov_type supplied'
                )
            if not field_fov_type.lower() in ["circle", "rectangle"]:
                return self.error('field_fov_type must be circle or rectangle')
            if isinstance(field_fov_attributes, list):
                field_fov_attributes = [float(x) for x in field_fov_attributes]
            else:
                field_fov_attributes = [float(field_fov_attributes)]

            center = SkyCoord(0.0, 0.0, unit='deg', frame='icrs')
            if field_fov_type.lower() == "circle":
                if not len(field_fov_attributes) == 1:
                    return self.error(
                        'If field_fov_type is circle, then should supply only radius for field_fov_attributes'
                    )
                radius = field_fov_attributes[0]
                regions = CircleSkyRegion(center=center, radius=radius * u.deg)
            elif field_fov_type.lower() == "rectangle":
                if not len(field_fov_attributes) == 2:
                    return self.error(
                        'If field_fov_type is rectangle, then should supply width and height for field_fov_attributes'
                    )
                width, height = field_fov_attributes
                regions = RectangleSkyRegion(
                    center=center, width=width * u.deg, height=height * u.deg
                )
            data['region'] = regions.serialize(format='ds9')

        schema = Instrument.__schema__()
        try:
            instrument = schema.load(data)
        except ValidationError as exc:
            return self.error(
                'Invalid/missing parameters: ' f'{exc.normalized_messages()}'
            )

        existing_instrument = (
            Instrument.query_records_accessible_by(
                self.current_user,
            )
            .filter(
                Instrument.name == data.get('name'),
                Instrument.telescope_id == telescope_id,
            )
            .first()
        )
        if existing_instrument is None:
            instrument.telescope = telescope
            DBSession().add(instrument)
            DBSession().commit()
        else:
            instrument = existing_instrument

        if field_data is not None:
            if (field_region is None) and (field_fov_type is None):
                return self.error(
                    'field_region or field_fov_type is required with field_data'
                )

            if type(field_data) is str:
                field_data = pd.read_table(StringIO(field_data), sep=",").to_dict(
                    orient='list'
                )

            if not {'ID', 'RA', 'Dec'}.issubset(field_data):
                return self.error("ID, RA, and Dec required in field_data.")

            log(f"Started generating fields for instrument {instrument.id}")
            # run async
            IOLoop.current().run_in_executor(
                None,
                lambda: add_tiles(instrument.id, instrument.name, regions, field_data),
            )

        self.push_all(action="skyportal/REFRESH_INSTRUMENTS")
        return self.success(data={"id": instrument.id})
Пример #8
0
def add_observations(instrument_id, obstable):
    """Post executed observations for a given instrument.
    obstable is a pandas DataFrame of the form:

   observation_id  field_id       obstime   seeing    limmag  exposure_time  \
0        84434604         1  2.458599e+06  1.57415  20.40705             30
1        84434651         1  2.458599e+06  1.58120  20.49405             30
2        84434696         1  2.458599e+06  1.64995  20.56030             30
3        84434741         1  2.458599e+06  1.54945  20.57400             30
4        84434788         1  2.458599e+06  1.62870  20.60385             30

  filter  processed_fraction airmass
0   ztfr                 1.0    None
1   ztfr                 1.0    None
2   ztfr                 1.0    None
3   ztfr                 1.0    None
4   ztfr                 1.0    None
     """

    session = Session()
    # if the fields do not yet exist, we need to add them
    if ('RA' in obstable) and ('Dec'
                               in obstable) and not ('field_id' in obstable):
        instrument = session.query(Instrument).get(instrument_id)
        regions = Regions.parse(instrument.region, format='ds9')
        field_data = obstable[['RA', 'Dec']]
        field_ids = add_tiles(instrument.id,
                              instrument.name,
                              regions,
                              field_data,
                              session=session)
        obstable['field_id'] = field_ids

    try:
        observations = []
        for index, row in obstable.iterrows():
            field_id = int(row["field_id"])
            field = (session.query(InstrumentField).filter(
                InstrumentField.instrument_id == instrument_id,
                InstrumentField.field_id == field_id,
            ).first())
            if field is None:
                return log(
                    f"Unable to add observations for instrument {instrument_id}: Missing field {field_id}"
                )

            observation = (session.query(ExecutedObservation).filter_by(
                instrument_id=instrument_id,
                observation_id=row["observation_id"]).first())
            if observation is not None:
                log(f"Observation {row['observation_id']} for instrument {instrument_id} already exists... continuing."
                    )
                continue

            # enable multiple obstime formats
            try:
                # can catch iso and isot this way
                obstime = Time(row["obstime"])
            except ValueError:
                # otherwise catch jd as the numerical example
                obstime = Time(row["obstime"], format='jd')

            observations.append(
                ExecutedObservation(
                    instrument_id=instrument_id,
                    observation_id=row["observation_id"],
                    instrument_field_id=field.id,
                    obstime=obstime.datetime,
                    seeing=row["seeing"],
                    limmag=row["limmag"],
                    exposure_time=row["exposure_time"],
                    filt=row["filter"],
                    processed_fraction=row["processed_fraction"],
                ))
        session.add_all(observations)
        session.commit()

        flow = Flow()
        flow.push('*', "skyportal/REFRESH_OBSERVATIONS")

        return log(
            f"Successfully added observations for instrument {instrument_id}")
    except Exception as e:
        return log(
            f"Unable to add observations for instrument {instrument_id}: {e}")
    finally:
        Session.remove()