lsst.meas.algorithms  14.0-18-gf7dca964+11
CoaddTransmissionCurve.cc
Go to the documentation of this file.
1 /*
2  * LSST Data Management System
3  * Copyright 2018 LSST/AURA.
4  *
5  * This product includes software developed by the
6  * LSST Project (http://www.lsst.org/).
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the LSST License Statement and
19  * the GNU General Public License along with this program. If not,
20  * see <http://www.lsstcorp.org/LegalNotices/>.
21  */
22 
24 #include "lsst/afw/geom/SkyWcs.h"
28 
29 
30 namespace lsst {
31 namespace meas {
32 namespace algorithms {
33 
34 namespace {
35 
36 struct CoaddInputData {
40  afw::geom::Box2D bbox;
41  double weight;
42 };
43 
44 class CoaddTransmissionCurve : public afw::image::TransmissionCurve {
45 public:
46 
47  CoaddTransmissionCurve(
49  afw::table::ExposureCatalog const & inputSensors
50  ) : _coaddWcs(coaddWcs),
51  _wavelengthBounds(std::numeric_limits<double>::infinity(), // min = +inf, max = -inf *for now*
53  _throughputAtBounds(0.0, 0.0)
54  {
55  _inputs.reserve(inputSensors.size());
56  afw::table::Key<double> weightKey = inputSensors.getSchema()["weight"];
57  double weightSum = 0.0;
58  for (auto const & record : inputSensors) {
59  double const weight = record.get(weightKey);
60  weightSum += weight;
61  auto transmission = record.getTransmissionCurve();
62  if (transmission == nullptr) {
63  throw LSST_EXCEPT(
64  pex::exceptions::InvalidParameterError,
65  (boost::format("No TransmissionCurve for input with ID %d") % record.getId()).str()
66  );
67  }
68  if (record.getWcs() == nullptr) {
69  throw LSST_EXCEPT(
70  pex::exceptions::InvalidParameterError,
71  (boost::format("No Wcs for input with ID %d") % record.getId()).str()
72  );
73  }
74  // Set wavelength bounds from the overall minimum and maximum wavelengths from all inputs.
75  auto const inputWavelengthBounds = transmission->getWavelengthBounds();
76  _wavelengthBounds.first = std::min(_wavelengthBounds.first, inputWavelengthBounds.first);
77  _wavelengthBounds.second = std::max(_wavelengthBounds.second, inputWavelengthBounds.second);
78  // Set throughput at and outside bounds from weighted average of throughput at and outside bounds.
79  auto const inputThroughputAtBounds = transmission->getThroughputAtBounds();
80  _throughputAtBounds.first += inputThroughputAtBounds.first*weight;
81  _throughputAtBounds.second += inputThroughputAtBounds.second*weight;
82  // Add an element to the vector with all the stuff we need to store for each epoch.
83  CoaddInputData input = {
85  record.getWcs(),
86  record.getValidPolygon(),
87  afw::geom::Box2D(record.getBBox()),
88  weight
89  };
90  _inputs.push_back(std::move(input));
91  }
92  _throughputAtBounds.first /= weightSum;
93  _throughputAtBounds.second /= weightSum;
94  }
95 
96  // Private constructor used only for persistence
97  CoaddTransmissionCurve(
99  std::pair<double, double> wavelengthBounds,
100  std::pair<double, double> throughputAtBounds,
102  ) :
103  _coaddWcs(std::move(coaddWcs)),
104  _wavelengthBounds(std::move(wavelengthBounds)),
105  _throughputAtBounds(std::move(throughputAtBounds)),
106  _inputs(std::move(inputs))
107  {}
108 
109  // All TransmissionCurves are immutable and noncopyable.
110  CoaddTransmissionCurve(CoaddTransmissionCurve const &) = delete;
111  CoaddTransmissionCurve(CoaddTransmissionCurve &&) = delete;
112  CoaddTransmissionCurve & operator=(CoaddTransmissionCurve const &) = delete;
113  CoaddTransmissionCurve & operator=(CoaddTransmissionCurve &&) = delete;
114 
115  std::pair<double, double> getWavelengthBounds() const override {
116  return _wavelengthBounds;
117  }
118 
119  std::pair<double, double> getThroughputAtBounds() const override {
120  return _throughputAtBounds;
121  }
122 
123  void sampleAt(
124  afw::geom::Point2D const & position,
125  ndarray::Array<double const,1,1> const & wavelengths,
126  ndarray::Array<double,1,1> const & out
127  ) const override {
128  auto const coord = _coaddWcs->pixelToSky(position);
129  ndarray::Array<double,1,1> tmp = ndarray::allocate(out.getShape());
130  tmp.deep() = 0.0;
131  out.deep() = 0.0;
132  double weightSum = 0.0;
133  for (auto const & input : _inputs) {
134  afw::geom::Point2D const inputPosition = input.sensorWcs->skyToPixel(coord);
135  if (!input.bbox.contains(inputPosition)) {
136  continue;
137  }
138  if (input.validPolygon && !input.validPolygon->contains(inputPosition)) {
139  continue;
140  }
141  // note that `tmp` is an output argument here
142  input.transmission->sampleAt(inputPosition, wavelengths, tmp);
143  tmp.deep() *= input.weight;
144  out.deep() += tmp;
145  weightSum += input.weight;
146  }
147  if (weightSum == 0.0) {
148  throw LSST_EXCEPT(
149  pex::exceptions::InvalidParameterError,
150  (boost::format("No input TransmissionCurves at point (%s, %s)")
151  % position.getX() % position.getY()).str()
152  );
153  }
154  out.deep() /= weightSum;
155  }
156 
157  bool isPersistable() const override {
158  for (auto const & input : _inputs) {
159  if (!input.transmission->isPersistable()) {
160  return false;
161  }
162  }
163  return true;
164  }
165 
166 private:
167 
168  std::string getPersistenceName() const override { return "CoaddTransmissionCurve"; }
169 
170  std::string getPythonModule() const override { return "lsst.meas.algorithms"; }
171 
172  struct PersistenceHelper;
173 
174  class Factory;
175 
176  void write(OutputArchiveHandle& handle) const override;
177 
178  static Factory registration;
179 
181  std::pair<double, double> _wavelengthBounds;
182  std::pair<double, double> _throughputAtBounds;
184 };
185 
186 struct CoaddTransmissionCurve::PersistenceHelper {
187  afw::table::Schema mainSchema;
188  afw::table::Key<int> coaddWcs;
189  afw::table::Key<double> wavelengthMin;
190  afw::table::Key<double> wavelengthMax;
191  afw::table::Key<double> throughputAtMin;
192  afw::table::Key<double> throughputAtMax;
193  afw::table::Schema inputDataSchema;
194  afw::table::Key<int> transmission;
195  afw::table::Key<int> sensorWcs;
196  afw::table::Key<int> validPolygon;
198  afw::table::Key<double> weight;
199 
200  static PersistenceHelper const & get() {
201  static PersistenceHelper const instance;
202  return instance;
203  }
204 
205 private:
206 
207  PersistenceHelper() :
208  mainSchema(),
209  coaddWcs(mainSchema.addField<int>("coaddWcs", "archive ID for the coadd's WCS")),
210  wavelengthMin(mainSchema.addField<double>("wavelengthMin", "getWavelengthBounds().min")),
211  wavelengthMax(mainSchema.addField<double>("wavelengthMax", "getWavelengthBounds().max")),
212  throughputAtMin(mainSchema.addField<double>("throughputAtMin", "throughputAtBounds().first")),
213  throughputAtMax(mainSchema.addField<double>("throughputAtMax", "throughputAtBounds().second")),
214  inputDataSchema(),
215  transmission(inputDataSchema.addField<int>("transmission", "archive ID for this sensor's TransmissionCurve")),
216  sensorWcs(inputDataSchema.addField<int>("sensorWcs", "archive ID for this sensor's WCS")),
217  validPolygon(inputDataSchema.addField<int>("validPolygon", "archive ID for this sensor's ValidPolygon")),
218  bbox(afw::table::Box2DKey::addFields(inputDataSchema, "bbox", "bounding box of the sensor", "pixel")),
219  weight(inputDataSchema.addField<double>("weight", "relative weight for this sensor in the average"))
220  {
221  mainSchema.getCitizen().markPersistent();
222  inputDataSchema.getCitizen().markPersistent();
223  }
224 
225 };
226 
227 void CoaddTransmissionCurve::write(OutputArchiveHandle & handle) const {
228  auto const & keys = PersistenceHelper::get();
229  auto mainCat = handle.makeCatalog(keys.mainSchema);
230  auto mainRecord = mainCat.addNew();
231  mainRecord->set(keys.coaddWcs, handle.put(_coaddWcs));
232  mainRecord->set(keys.wavelengthMin, _wavelengthBounds.first);
233  mainRecord->set(keys.wavelengthMax, _wavelengthBounds.second);
234  mainRecord->set(keys.throughputAtMin, _throughputAtBounds.first);
235  mainRecord->set(keys.throughputAtMax, _throughputAtBounds.second);
236  handle.saveCatalog(mainCat);
237  auto inputDataCat = handle.makeCatalog(keys.inputDataSchema);
238  for (auto const & input : _inputs) {
239  auto inputDataRecord = inputDataCat.addNew();
240  inputDataRecord->set(keys.transmission, handle.put(input.transmission));
241  inputDataRecord->set(keys.sensorWcs, handle.put(input.sensorWcs));
242  inputDataRecord->set(keys.validPolygon, handle.put(input.validPolygon));
243  inputDataRecord->set(keys.bbox, input.bbox);
244  inputDataRecord->set(keys.weight, input.weight);
245  }
246  handle.saveCatalog(inputDataCat);
247 }
248 
249 class CoaddTransmissionCurve::Factory : public afw::table::io::PersistableFactory {
250 public:
251 
253  InputArchive const& archive,
254  CatalogVector const& catalogs
255  ) const override {
256  auto const& keys = PersistenceHelper::get();
257  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
258  LSST_ARCHIVE_ASSERT(catalogs.front().getSchema() == keys.mainSchema);
259  LSST_ARCHIVE_ASSERT(catalogs.back().getSchema() == keys.inputDataSchema);
260  auto const & mainRecord = catalogs.front().front();
262  inputs.reserve(catalogs.back().size());
263  for (auto const & inputDataRecord : catalogs.back()) {
264  CoaddInputData input = {
265  archive.get<afw::image::TransmissionCurve>(inputDataRecord.get(keys.transmission)),
266  archive.get<afw::geom::SkyWcs>(inputDataRecord.get(keys.sensorWcs)),
267  archive.get<afw::geom::polygon::Polygon>(inputDataRecord.get(keys.validPolygon)),
268  inputDataRecord.get(keys.bbox),
269  inputDataRecord.get(keys.weight)
270  };
271  inputs.push_back(std::move(input));
272  }
273  return std::make_shared<CoaddTransmissionCurve>(
274  archive.get<afw::geom::SkyWcs>(mainRecord.get(keys.coaddWcs)),
275  std::make_pair(mainRecord.get(keys.wavelengthMin), mainRecord.get(keys.wavelengthMax)),
276  std::make_pair(mainRecord.get(keys.throughputAtMin), mainRecord.get(keys.throughputAtMax)),
277  std::move(inputs)
278  );
279  }
280 
281  Factory(std::string const& name) : afw::table::io::PersistableFactory(name) {}
282 };
283 
284 CoaddTransmissionCurve::Factory CoaddTransmissionCurve::registration("CoaddTransmissionCurve");
285 
286 
287 } // anonymous
288 
289 
292  afw::table::ExposureCatalog const & inputSensors
293 ) {
294  return std::make_shared<CoaddTransmissionCurve>(coaddWcs, inputSensors);
295 }
296 
297 
298 }}} // lsst::meas::algorithms
afw::table::Schema mainSchema
std::shared_ptr< afw::image::TransmissionCurve const > makeCoaddTransmissionCurve(std::shared_ptr< afw::geom::SkyWcs const > coaddWcs, afw::table::ExposureCatalog const &inputSensors)
Create a TransmissionCurve that represents the effective throughput on a coadd.
afw::geom::Box2D bbox
#define LSST_ARCHIVE_ASSERT(EXPR)
Factory
BoxKey< geom::Box2D > Box2DKey
afw::table::Key< double > throughputAtMax
afw::table::Key< double > wavelengthMax
string name
std::shared_ptr< afw::geom::polygon::Polygon const > validPolygon
STL class.
T min(T... args)
afw::table::Schema inputDataSchema
T push_back(T... args)
Point< double, 2 > Point2D
double weight
afw::table::Key< double > throughputAtMin
std::shared_ptr< afw::image::TransmissionCurve const > transmission
afw::table::Key< int > coaddWcs
T make_pair(T... args)
afw::table::Key< double > wavelengthMin
T max(T... args)
T move(T... args)
#define LSST_EXCEPT(type,...)
STL class.
std::shared_ptr< afw::geom::SkyWcs const > sensorWcs
ExposureCatalogT< ExposureRecord > ExposureCatalog
T reserve(T... args)
static BoxKey addFields(Schema &schema, std::string const &name, std::string const &doc, std::string const &unit)