28 #include "gsl/gsl_interp.h" 29 #include "gsl/gsl_interp2d.h" 30 #include "gsl/gsl_errno.h" 36 #include "lsst/afw/table/io/Persistable.cc" 40 #define LSST_CHECK_GSL(type, status) \ 41 if (status) throw LSST_EXCEPT(type, "GSL error: "s + ::gsl_strerror(status)) 58 class IdentityTransmissionCurve :
public TransmissionCurve {
60 static constexpr
char const* NAME =
"IdentityTransmissionCurve";
74 void sampleAt(
lsst::geom::Point2D const&, ndarray::Array<double const, 1, 1>
const& wavelengths,
75 ndarray::Array<double, 1, 1>
const& out)
const override {
79 bool isPersistable()
const noexcept
override {
return true; }
85 return shared_from_this();
94 std::string getPersistenceName()
const override {
return NAME; }
96 void write(OutputArchiveHandle& handle)
const override { handle.saveEmpty(); }
98 class Factory :
public table::io::PersistableFactory {
101 CatalogVector
const& catalogs)
const override {
106 Factory() : table::io::PersistableFactory(NAME) {}
112 IdentityTransmissionCurve() =
default;
138 template <
typename T>
141 template <
typename T>
142 GslPtr<T> makeGslPtr(T* p,
void (*
free)(T*)) {
146 return GslPtr<T>(p,
free);
157 static constexpr
bool isSpatiallyConstant =
true;
158 static constexpr
char const* NAME =
"SpatiallyConstantTransmissionCurve";
163 Impl1d(ndarray::Array<double, 1, 1>
const& throughput, ndarray::Array<double, 1, 1>
const& wavelengths)
164 : _throughput(throughput),
165 _wavelengths(wavelengths),
166 _interp(makeGslPtr(::gsl_interp_alloc(::gsl_interp_linear, _wavelengths.size()),
167 &::gsl_interp_free)) {
168 int status = ::gsl_interp_init(_interp.get(), _wavelengths.getData(), _throughput.getData(),
169 _wavelengths.size());
177 static void setupPersistence(table::Schema&
schema, ArrayKeyVector&
keys) {
178 keys.push_back(schema.addField<table::Array<double>>(
"throughput",
"array of known throughput values",
180 keys.push_back(schema.addField<table::Array<double>>(
181 "wavelengths",
"array of known wavelength values",
"angstrom", 0));
184 void persist(table::BaseRecord& record, ArrayKeyVector
const& keys)
const {
185 record.set(keys[0], _throughput);
186 record.set(keys[1], _wavelengths);
189 static Impl1d unpersist(table::BaseRecord& record, ArrayKeyVector
const& keys) {
190 return Impl1d(record[keys[0]], record[keys[1]]);
197 : _accel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)) {}
199 double operator()(Impl1d
const& parent,
double wavelength) {
201 int status = ::gsl_interp_eval_e(parent._interp.get(), parent._wavelengths.getData(),
202 parent._throughput.getData(), wavelength, _accel.get(), &
result);
208 GslPtr<::gsl_interp_accel> _accel;
212 ndarray::Array<double, 1, 1> _throughput;
213 ndarray::Array<double, 1, 1> _wavelengths;
214 GslPtr<::gsl_interp> _interp;
223 static constexpr
bool isSpatiallyConstant =
false;
224 static constexpr
char const* NAME =
"RadialTransmissionCurve";
226 Impl2d(ndarray::Array<double, 1, 1>
const& throughput, ndarray::Array<double, 1, 1>
const& wavelengths,
227 ndarray::Array<double, 1, 1>
const&
radii)
228 : _throughput(throughput),
229 _wavelengths(wavelengths),
232 ::gsl_interp2d_alloc(::gsl_interp2d_bilinear, _wavelengths.size(), _radii.size()),
233 &::gsl_interp2d_free)) {
234 int status = ::gsl_interp2d_init(_interp.get(), _wavelengths.getData(), _radii.getData(),
235 _throughput.getData(), _wavelengths.size(), _radii.size());
243 static void setupPersistence(table::Schema&
schema, ArrayKeyVector&
keys) {
244 keys.push_back(schema.addField<table::Array<double>>(
246 "flattenned 2-d array of known throughput values, with radius dimension fastest",
"", 0));
247 keys.push_back(schema.addField<table::Array<double>>(
248 "wavelengths",
"array of known wavelength values",
"angstrom", 0));
249 keys.push_back(schema.addField<table::Array<double>>(
"radii",
"array of known radius values",
"", 0));
252 void persist(table::BaseRecord& record, ArrayKeyVector
const& keys)
const {
253 record.set(keys[0], _throughput);
254 record.set(keys[1], _wavelengths);
255 record.set(keys[2], _radii);
258 static Impl2d unpersist(table::BaseRecord& record, ArrayKeyVector
const& keys) {
259 return Impl2d(record[keys[0]], record[keys[1]], record[keys[2]]);
266 : _radius(point.
asEigen().norm()),
267 _radiusAccel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)),
268 _wavelengthAccel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)) {
269 _radius =
std::max(_radius, parent._radii.front());
270 _radius =
std::min(_radius, parent._radii.back());
273 double operator()(Impl2d
const& parent,
double wavelength) {
276 ::gsl_interp2d_eval_e(parent._interp.get(), parent._wavelengths.getData(),
277 parent._radii.getData(), parent._throughput.getData(), wavelength,
278 _radius, _radiusAccel.get(), _wavelengthAccel.get(), &
result);
285 GslPtr<::gsl_interp_accel> _radiusAccel;
286 GslPtr<::gsl_interp_accel> _wavelengthAccel;
290 ndarray::Array<double, 1, 1> _throughput;
291 ndarray::Array<double, 1, 1> _wavelengths;
292 ndarray::Array<double, 1, 1> _radii;
293 GslPtr<::gsl_interp2d> _interp;
296 template <
typename Impl>
297 class InterpolatedTransmissionCurve :
public TransmissionCurve {
300 : _atBounds(throughputAtBounds), _impl(
std::move(impl)) {
302 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
303 "Throughput values at bounds must be finite");
311 void sampleAt(
lsst::geom::Point2D const& point, ndarray::Array<double const, 1, 1>
const& wavelengths,
312 ndarray::Array<double, 1, 1>
const& out)
const override {
313 LSST_THROW_IF_NE(wavelengths.getSize<0>(), out.getSize<0>(), pex::exceptions::LengthError,
314 "Length of wavelength array (%d) does not match size of output array (%d)");
315 typename Impl::Functor functor(_impl, point);
316 auto bounds = _impl.getWavelengthBounds();
317 auto wlIter = wavelengths.begin();
318 for (
auto outIter = out.begin(); outIter != out.end(); ++outIter, ++wlIter) {
319 double&
y = *outIter;
320 if (*wlIter < bounds.first) {
322 }
else if (*wlIter > bounds.second) {
323 y = _atBounds.second;
325 y = functor(_impl, *wlIter);
330 bool isPersistable()
const noexcept
override {
return true; }
335 if (_impl.isSpatiallyConstant) {
336 return shared_from_this();
338 return TransmissionCurve::_transformedByImpl(transform);
342 std::string getPersistenceName()
const override {
return Impl::NAME; }
344 struct PersistenceHelper {
350 static PersistenceHelper
const&
get() {
351 static PersistenceHelper
const instance;
359 schema.addField<
double>(
"throughputAtMin",
"throughput below minimum wavelength")),
361 schema.addField<
double>(
"throughputAtMax",
"throughput above minimum wavelength")) {
362 Impl::setupPersistence(schema, arrays);
363 schema.getCitizen().markPersistent();
367 void write(OutputArchiveHandle& handle)
const override {
368 auto const&
keys = PersistenceHelper::get();
369 auto catalog = handle.makeCatalog(
keys.schema);
370 auto record = catalog.addNew();
371 record->set(
keys.throughputAtMin, _atBounds.first);
372 record->set(
keys.throughputAtMax, _atBounds.second);
373 _impl.persist(*record,
keys.arrays);
374 handle.saveCatalog(catalog);
377 class Factory :
public table::io::PersistableFactory {
380 CatalogVector
const& catalogs)
const override {
381 auto const&
keys = PersistenceHelper::get();
384 auto& record = catalogs.front().front();
385 return std::make_shared<InterpolatedTransmissionCurve>(
386 Impl::unpersist(record,
keys.arrays),
390 Factory() : table::io::PersistableFactory(Impl::NAME) {}
400 template <
typename Impl>
403 template class InterpolatedTransmissionCurve<Impl1d>;
404 template class InterpolatedTransmissionCurve<Impl2d>;
412 class ProductTransmissionCurve :
public TransmissionCurve {
414 static constexpr
char const* NAME =
"ProductTransmissionCurve";
421 auto aWavelengthBounds = _a->getWavelengthBounds();
422 auto bWavelengthBounds = _b->getWavelengthBounds();
423 auto aThroughputAtBounds = _a->getThroughputAtBounds();
424 auto bThroughputAtBounds = _b->getThroughputAtBounds();
426 auto determineWavelengthBound = [](
double aWavelength,
double bWavelength,
double aThroughput,
427 double bThroughput,
auto isFirstOuter) ->
double {
431 if (isFirstOuter(aWavelength, bWavelength)) {
432 return (bThroughput == 0.0) ? bWavelength : aWavelength;
434 return (aThroughput == 0.0) ? aWavelength : bWavelength;
438 return std::make_pair(determineWavelengthBound(aWavelengthBounds.first, bWavelengthBounds.first,
439 aThroughputAtBounds.first, bThroughputAtBounds.first,
441 determineWavelengthBound(aWavelengthBounds.second, bWavelengthBounds.second,
442 aThroughputAtBounds.second, bThroughputAtBounds.second,
447 auto aAtBounds = _a->getThroughputAtBounds();
448 auto bAtBounds = _b->getThroughputAtBounds();
449 return std::make_pair(aAtBounds.first * bAtBounds.first, aAtBounds.second * bAtBounds.second);
452 void sampleAt(
lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths,
453 ndarray::Array<double, 1, 1>
const& out)
const override {
454 _a->sampleAt(position, wavelengths, out);
455 ndarray::Array<double, 1, 1> tmp = ndarray::allocate(wavelengths.getSize<0>());
456 _b->sampleAt(position, wavelengths, tmp);
460 bool isPersistable()
const noexcept
override {
return _a->isPersistable() && _b->isPersistable(); }
463 std::string getPersistenceName()
const override {
return NAME; }
465 struct PersistenceHelper {
470 static PersistenceHelper
const&
get() {
471 static PersistenceHelper
const instance;
478 a(schema.addField<
int>(
"a",
"archive ID of first operand")),
479 b(schema.addField<
int>(
"b",
"archive ID of second operand")) {
480 schema.getCitizen().markPersistent();
484 class Factory :
public table::io::PersistableFactory {
487 CatalogVector
const& catalogs)
const override {
488 auto const&
keys = PersistenceHelper::get();
491 auto const& record = catalogs.front().front();
492 return std::make_shared<ProductTransmissionCurve>(
493 archive.get<TransmissionCurve>(record.get(
keys.a)),
494 archive.get<TransmissionCurve>(record.get(
keys.b)));
497 Factory() : table::io::PersistableFactory(NAME) {}
500 void write(OutputArchiveHandle& handle)
const override {
501 auto const&
keys = PersistenceHelper::get();
502 auto catalog = handle.makeCatalog(
keys.schema);
503 auto record = catalog.addNew();
504 record->set(
keys.a, handle.put(_a));
505 record->set(
keys.b, handle.put(_b));
506 handle.saveCatalog(catalog);
524 class TransformedTransmissionCurve :
public TransmissionCurve {
526 static constexpr
char const* NAME =
"TransformedTransmissionCurve";
535 return _nested->getThroughputAtBounds();
538 void sampleAt(
lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths,
539 ndarray::Array<double, 1, 1>
const& out)
const override {
540 return _nested->sampleAt(_transform->applyInverse(position), wavelengths, out);
543 bool isPersistable()
const noexcept
override {
544 return _nested->isPersistable() && _transform->isPersistable();
551 return std::make_shared<TransformedTransmissionCurve>(_nested, transform->then(*_transform));
554 std::string getPersistenceName()
const override {
return NAME; }
556 struct PersistenceHelper {
561 static PersistenceHelper
const&
get() {
562 static PersistenceHelper
const instance;
569 nested(schema.addField<
int>(
"nested",
"archive ID of the nested TransmissionCurve")),
570 transform(schema.addField<
int>(
"transform",
"archive ID of the coordinate transform")) {
571 schema.getCitizen().markPersistent();
575 void write(OutputArchiveHandle& handle)
const override {
576 auto const&
keys = PersistenceHelper::get();
577 auto catalog = handle.makeCatalog(
keys.schema);
578 auto record = catalog.addNew();
579 record->set(
keys.nested, handle.put(_nested));
580 record->set(
keys.transform, handle.put(_transform));
581 handle.saveCatalog(catalog);
584 class Factory :
public table::io::PersistableFactory {
587 CatalogVector
const& catalogs)
const override {
588 auto const&
keys = PersistenceHelper::get();
591 auto const& record = catalogs.front().front();
592 return std::make_shared<TransformedTransmissionCurve>(
593 archive.get<TransmissionCurve>(record.get(
keys.nested)),
597 Factory() : table::io::PersistableFactory(NAME) {}
616 return IdentityTransmissionCurve::get();
620 ndarray::Array<double const, 1>
const& throughput, ndarray::Array<double const, 1>
const& wavelengths,
622 ::gsl_set_error_handler_off();
624 "Length of wavelength array (%d) does not match size of throughput array (%d)");
625 return std::make_shared<InterpolatedTransmissionCurve<Impl1d>>(
626 Impl1d(ndarray::copy(throughput), ndarray::copy(wavelengths)),
631 ndarray::Array<double const, 2>
const& throughput, ndarray::Array<double const, 1>
const& wavelengths,
633 ::gsl_set_error_handler_off();
636 "Length of wavelength array (%d) does not match first dimension of of throughput array (%d)");
639 "Length of radii array (%d) does not match second dimension of of throughput array (%d)");
643 ndarray::Array<double, 2, 2> throughputTransposed = ndarray::allocate(throughput.getShape().reverse());
644 throughputTransposed.transpose() = throughput;
645 ndarray::Array<double, 1, 1> throughputFlat = ndarray::flatten<1>(throughputTransposed);
646 return std::make_shared<InterpolatedTransmissionCurve<Impl2d>>(
647 Impl2d(throughputFlat, ndarray::copy(wavelengths), ndarray::copy(radii)),
653 auto a = shared_from_this();
655 auto result =
a->_multipliedByImpl(
b);
667 return _transformedByImpl(
std::move(transform));
670 ndarray::Array<double, 1, 1> TransmissionCurve::sampleAt(
671 lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths)
const {
672 ndarray::Array<double, 1, 1> out = ndarray::allocate(wavelengths.getSize<0>());
673 sampleAt(position, wavelengths, out);
679 return std::make_shared<TransformedTransmissionCurve>(shared_from_this(),
std::move(transform));
687 std::string TransmissionCurve::getPythonModule()
const {
return "lsst.afw.image"; }
#define LSST_CHECK_GSL(type, status)
A spatially-varying transmission curve as a function of wavelength.
T shared_from_this(T... args)
#define LSST_THROW_IF_NE(N1, N2, EXC_CLASS, MSG)
Check whether the given values are equal, and throw an LSST Exception if they are not...
Reports attempts to exceed implementation-defined length limits for some classes. ...
Transform< Point2Endpoint, Point2Endpoint > TransformPoint2ToPoint2
A base class for image defects.
table::Key< double > throughputAtMin
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
EigenVector const & asEigen() const noexcept(IS_ELEMENT_NOTHROW_COPYABLE)
Return a fixed-size Eigen representation of the coordinate object.
ItemVariant const * other
table::Key< double > throughputAtMax