roman-snpit-snappl 0.19.0__tar.gz → 0.21.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of roman-snpit-snappl might be problematic. Click here for more details.

Files changed (147) hide show
  1. {roman_snpit_snappl-0.19.0/roman_snpit_snappl.egg-info → roman_snpit_snappl-0.21.0}/PKG-INFO +1 -1
  2. roman_snpit_snappl-0.21.0/changes/86.snappl.rst +1 -0
  3. roman_snpit_snappl-0.21.0/changes/98.docs.rst +1 -0
  4. roman_snpit_snappl-0.21.0/changes/99.feature.rst +1 -0
  5. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/usage.rst +53 -4
  6. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0/roman_snpit_snappl.egg-info}/PKG-INFO +1 -1
  7. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/roman_snpit_snappl.egg-info/SOURCES.txt +7 -0
  8. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/_version.py +3 -3
  9. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/admin/load_ou2024_l2images.py +7 -7
  10. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/config.py +2 -2
  11. roman_snpit_snappl-0.21.0/snappl/db/baseview.py +266 -0
  12. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/db.py +11 -1
  13. roman_snpit_snappl-0.21.0/snappl/db/migrations/20251025_lightcurve.sql +5 -0
  14. roman_snpit_snappl-0.21.0/snappl/db/migrations/20251028_segmap.sql +45 -0
  15. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/webserver.py +233 -61
  16. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/diaobject.py +14 -0
  17. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/image.py +1 -1
  18. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/imagecollection.py +27 -9
  19. roman_snpit_snappl-0.21.0/snappl/lightcurve.py +553 -0
  20. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/provenance.py +8 -0
  21. roman_snpit_snappl-0.21.0/snappl/segmap.py +184 -0
  22. roman_snpit_snappl-0.19.0/snappl/db/baseview.py +0 -88
  23. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.cruft.json +0 -0
  24. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/CODEOWNERS +0 -0
  25. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +0 -0
  26. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md +0 -0
  27. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/ISSUE_TEMPLATE/PR_TEMPLATE.md +0 -0
  28. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/dependabot.yml +0 -0
  29. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/labeler.yml +0 -0
  30. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/workflows/changelog.yml +0 -0
  31. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/workflows/run_labeler.yml +0 -0
  32. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/workflows/run_snappl_tests.yml +0 -0
  33. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/workflows/sphinx-deploy.yml +0 -0
  34. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.github/workflows/sub_package_update.yml +0 -0
  35. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.gitignore +0 -0
  36. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/.pre-commit-config.yaml +0 -0
  37. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/CHANGES.rst +0 -0
  38. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/CITATION.cff +0 -0
  39. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/CODE_OF_CONDUCT.md +0 -0
  40. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/CONTRIBUTING.md +0 -0
  41. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/LICENSE +0 -0
  42. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/MANIFEST.in +0 -0
  43. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/README.rst +0 -0
  44. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/.gitkeep +0 -0
  45. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/10.snappl.rst +0 -0
  46. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/13.bugfix.rst +0 -0
  47. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/14.snappl.rst +0 -0
  48. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/15.feature.rst +0 -0
  49. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/16.feature.rst +0 -0
  50. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/18.feature.rst +0 -0
  51. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/20.bugfix.rst +0 -0
  52. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/23.snappl.rst +0 -0
  53. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/26.feature.rst +0 -0
  54. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/29.feature.rst +0 -0
  55. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/3.snappl.rst +0 -0
  56. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/31.feature.rst +0 -0
  57. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/35.snappl.rst +0 -0
  58. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/36.snappl.rst +0 -0
  59. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/37.snappl.rst +0 -0
  60. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/40.snappl.rst +0 -0
  61. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/41.snappl.rst +0 -0
  62. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/43.snappl.rst +0 -0
  63. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/47.feature.rst +0 -0
  64. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/49.docs.rst +0 -0
  65. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/5.snappl.rst +0 -0
  66. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/54.snappl.rst +0 -0
  67. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/57.snappl.rst +0 -0
  68. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/58.snappl.rst +0 -0
  69. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/61.bugfix.rst +0 -0
  70. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/62.snappl.rst +0 -0
  71. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/63.snappl.rst +0 -0
  72. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/65.bugfix.rst +0 -0
  73. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/68.feature.rst +0 -0
  74. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/72.snappl.rst +0 -0
  75. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/73.feature.rst +0 -0
  76. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/74.bugfix.rst +0 -0
  77. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/79.snappl.rst +0 -0
  78. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/8.snappl.rst +0 -0
  79. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/81.snappl.rst +0 -0
  80. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/82.snappl.rst +0 -0
  81. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/83.snappl.rst +0 -0
  82. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/84.feature.rst +0 -0
  83. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/85.feature.rst +0 -0
  84. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/87.docs.rst +0 -0
  85. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/89.feature.rst +0 -0
  86. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/9.snappl.rst +0 -0
  87. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/91.feature.rst +0 -0
  88. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/92.docs.rst +0 -0
  89. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/changes/94.snappl.rst +0 -0
  90. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/codespell-ignore.txt +0 -0
  91. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/postgres/Dockerfile +0 -0
  92. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/postgres/postgresql.conf +0 -0
  93. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/postgres/run_postgres.sh +0 -0
  94. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/webserver/Dockerfile +0 -0
  95. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/webserver/cert.pem +0 -0
  96. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/webserver/config-test.yaml +0 -0
  97. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/webserver/key.pem +0 -0
  98. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docker/webserver/roman-snpit-server.py +0 -0
  99. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/Makefile +0 -0
  100. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/_static/logo_black_filled.png +0 -0
  101. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/api.rst +0 -0
  102. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/changes.rst +0 -0
  103. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/conf.py +0 -0
  104. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/database_schema.rst +0 -0
  105. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/index.rst +0 -0
  106. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/installation.rst +0 -0
  107. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/docs/make.bat +0 -0
  108. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/experimentation/README.md +0 -0
  109. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/experimentation/ap_phot_simulated_images.py +0 -0
  110. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/experimentation/play_with_photutils.py +0 -0
  111. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/licenses/.DS_Store +0 -0
  112. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/licenses/LICENSE.rst +0 -0
  113. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/licenses/README.rst +0 -0
  114. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/licenses/TEMPLATE_LICENSE.rst +0 -0
  115. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/pyproject.toml +0 -0
  116. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/roman_snpit_snappl.egg-info/dependency_links.txt +0 -0
  117. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/roman_snpit_snappl.egg-info/not-zip-safe +0 -0
  118. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/roman_snpit_snappl.egg-info/requires.txt +0 -0
  119. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/roman_snpit_snappl.egg-info/top_level.txt +0 -0
  120. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/setup.cfg +0 -0
  121. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/setup.py +0 -0
  122. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/__init__.py +0 -0
  123. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/_dev/__init__.py +0 -0
  124. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/_dev/scm_version.py +0 -0
  125. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/admin/load_snana_ou2024_diaobject.py +0 -0
  126. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/data/README.rst +0 -0
  127. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/20251008_init.sql +0 -0
  128. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/20251017_objdetcount.sql +0 -0
  129. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/20251024_l2image_mjd.sql +0 -0
  130. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/apply_migrations.py +0 -0
  131. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/schema_to_rst.py +0 -0
  132. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/scorched_earth.py +0 -0
  133. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/migrations/wipe_all_data.py +0 -0
  134. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/static/romansnpit.css +0 -0
  135. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/static/romansnpit.js +0 -0
  136. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/static/romansnpit_start.js +0 -0
  137. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/templates/base.html +0 -0
  138. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/db/templates/romansnpitdb.html +0 -0
  139. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/dbclient.py +0 -0
  140. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/http.py +0 -0
  141. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/image_simulator.py +0 -0
  142. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/logger.py +0 -0
  143. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/psf.py +0 -0
  144. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/sed.py +0 -0
  145. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/utils.py +0 -0
  146. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/snappl/wcs.py +0 -0
  147. {roman_snpit_snappl-0.19.0 → roman_snpit_snappl-0.21.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roman_snpit_snappl
3
- Version: 0.19.0
3
+ Version: 0.21.0
4
4
  Summary: General, database, and photometry utilities for the Roman SNPIT
5
5
  Author: Roman Supernova Project Infrastructure Team
6
6
  Maintainer-email: Roman SN PIT <raknop@lbl.gov>
@@ -0,0 +1 @@
1
+ Added lightcurve class.
@@ -0,0 +1 @@
1
+ Add lightcurve reading and writing to the nov 2025 primer in the usage docs
@@ -0,0 +1 @@
1
+ Add storage and database tracking of segmentation maps
@@ -145,6 +145,14 @@ However, we recommend against that. While 4500 is perhaps not an overwhelming n
145
145
  Finding Objects
146
146
  ===============
147
147
 
148
+ You may just be given an diaobject id. In that case, all you have to do is::
149
+
150
+ from snappl.diaobject import DiaObject
151
+
152
+ obj = DiaObject.get_object( diaobject_id=<id> )
153
+
154
+ where ``<id>`` is the diaobject id you were given. ``obj`` will be a ``DiaObject`` object.
155
+
148
156
  There is also an interface that lets you find objects. For instance, if you want to find all objects within 100 arcseconds of a given location, you could run::
149
157
 
150
158
  from snappl.diaobject import DiaObject
@@ -158,7 +166,7 @@ Here, you can use ``ou2024`` for ``TAG`` and ``load_ou2024_diaobject`` for ``PRO
158
166
  Getting Better Object Positions
159
167
  ===============================
160
168
 
161
- ``DiaObject.find_objects`` will return a list of ``DiaObject`` objects, and these include properties ``ra`` and ``dec``. **However, the positions in the DiaObject object should be viewed as approximate.** They will be the position it had when the object was first discovered. For objects loaded from truth tables, they will be perfect, but of course we won't have truth tables for the real survey. Often, the first discovery will be a relatively low S/N point, and much better positions can be determined; doing so will be one of the jobs of ``phrosty``.
169
+ The ``DiaObject`` you got from ``DiaObject.get_object`` or ``DiaObject.find_object`` include properties ``ra`` and ``dec``. **However, the positions in the DiaObject object should be viewed as approximate.** They will be the position it had when the object was first discovered. For objects loaded from truth tables, they will be perfect, but of course we won't have truth tables for the real survey. Often, the first discovery will be a relatively low S/N point, and much better positions can be determined; doing so will be one of the jobs of ``phrosty``.
162
170
 
163
171
  To get an improved position for an object, assume you have the object in the variable ``diaobj``. You can then call::
164
172
 
@@ -181,10 +189,36 @@ See :ref:`nov2025-provtags` below to figure out what ``TAG`` and ``PROCESS`` sho
181
189
  **Important**: if you use an updated DiaObject position, then the provenance of that position should be one of your upstream provenances; see :ref:`nov2025-making-prov`.
182
190
 
183
191
 
184
- Finding Lightcurves
192
+ Reading Lightcurves
185
193
  ===================
186
194
 
187
- TODO
195
+ To read a lightcurve, you need three or four things:
196
+
197
+ * The ``diaobject_id`` of the object whose lightcurve you want
198
+ * The ``provenance_tag`` of the provenance you want to get lightcurves from
199
+ * The ``process`` to go with the provenance tag
200
+ * (Optional) The ``band`` of the lightcurve
201
+
202
+ The ``provenance_tag`` and ``process`` will be given to you through our fake orchestration process, and may possibly be found in :ref:`nov2025-provtags`. The ``diaobject_id`` will be given to you through the fake orchestration process.
203
+
204
+ To get the lightcurves::
205
+
206
+ from snappl.lightcurve import Lightcurve
207
+
208
+ ltcvs = Lightcurve.find_lightcurves( <diaobject_id>, provenance_tag=<tag>, process=<proc>, dbclient=dbclient )
209
+
210
+ That will return a list of lightcurves. If you also specify ``band=<band>``, that will be a 1-element list with the lightcurve just for that band. (Or a 0-element list if it's not found.)
211
+
212
+ Each element of the list will be a ``Lightcurve`` object. You can find the actual lightcurve data in the ``.lightcurve`` property as an astropy QTable. You can find the metadata dictionary either in the ``.lightcurve.meta`` or in the ``.meta`` property (though the latter will intially be ``None`` until you access the ``.lightcurve`` property). Guaranteed metadata can be found in the `lightcurve schema specification on the PIT wiki <https://github.com/Roman-Supernova-PIT/Roman-Supernova-PIT/wiki/lightcurve>`_. You should probably ignore the ``filepath`` in the metadata, because ``snappl`` has already read the file for you (and put it in the ``.lightcurve`` property).
213
+
214
+
215
+ Finding Segmentation Maps
216
+ =========================
217
+
218
+ You will need to ``from snappl.segmap import SegmentationMap`` and then call ``SegmentationMap.find_segmaps``. You need to pass a provenance tag and a process to ``find_segmaps``; you will be given these (see :ref:`nov2025-provtags`). Beyond that, look at the docstring for that function to see what you can search on.
219
+
220
+ You will get back a list of ``SegmentationMap`` objects. A ``SegmentationMap`` object has several attributes, including ``band``, and eight attributes ``ra_corner_00``, ``dec_corner_00``, etc., where ``00`` is the lower-left pixel, ``01`` upper-left pixel, ``10`` is the lower-right pixel, and ``11`` is the upper-right pixel. The most important field is ``.image``. This is a ``snappl.image.Image`` object. You can get the 2d numpy array of the image data of the segmentaton map from the ``.data`` property of the ``Image`` object.
221
+
188
222
 
189
223
  .. _nov2025-making-prov:
190
224
 
@@ -250,9 +284,24 @@ This will (I believe) return a dictionary that's the same as what you'd get back
250
284
  Saving Lightcurves
251
285
  ==================
252
286
 
253
- TODO
287
+ Lightcurves saved to the database are for only a single band. If you have a multiband lightcurve, from the point of view of the database that's several different lightcurves.
288
+
289
+ To write a Lightcurve, first create a ``Lightcurve`` object::
290
+
291
+ from snappl.lightcurve import Lightcurve
292
+
293
+ ltcv = Lightcurve( data=<data>, meta=<meta> )
294
+
295
+ where ``<meta>`` is a dictionary with metadata, and ``<data>`` is one of an astropy Table, a pandas DataFrame, or a dictionary of lists. On the `lightcurve schema specification on the PIT wiki <https://github.com/Roman-Supernova-PIT/Roman-Supernova-PIT/wiki/lightcurve>`_ you can find the mandatory fields in the metadata dictionary; it's allowed to have additional ones as well. Likewise, there you can find the mandatory columns (and the order of those columns) in the data array. You may also have additional columns in that data array.
296
+
297
+ In order to create a lightcurve, you will need to make a provenance for it; see :ref:`nov2025-making-prov`. Make sure to include the ``diaobject position`` provenance as an upstream provenance if you used a position other than the cheesy approximate one that comes with the ``DiaObject``.
298
+
299
+ Once you have your lightcurve object, do two things::
254
300
 
301
+ ltcv.write()
302
+ ltcv.save_to_db( dbclient=dbclient )
255
303
 
304
+ The first one writes the actual file; it will write it in the standard location, and will populate the ``.filepath`` property with the location of the file *relative to the configured base directory for lightcurves* (which is in config option ``system.paths.lightcurves``). The second call saves a record to the database with information about your lightcurve.
256
305
 
257
306
  .. _nov2025-provtags:
258
307
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roman_snpit_snappl
3
- Version: 0.19.0
3
+ Version: 0.21.0
4
4
  Summary: General, database, and photometry utilities for the Roman SNPIT
5
5
  Author: Roman Supernova Project Infrastructure Team
6
6
  Maintainer-email: Roman SN PIT <raknop@lbl.gov>
@@ -63,12 +63,15 @@ changes/82.snappl.rst
63
63
  changes/83.snappl.rst
64
64
  changes/84.feature.rst
65
65
  changes/85.feature.rst
66
+ changes/86.snappl.rst
66
67
  changes/87.docs.rst
67
68
  changes/89.feature.rst
68
69
  changes/9.snappl.rst
69
70
  changes/91.feature.rst
70
71
  changes/92.docs.rst
71
72
  changes/94.snappl.rst
73
+ changes/98.docs.rst
74
+ changes/99.feature.rst
72
75
  docker/postgres/Dockerfile
73
76
  docker/postgres/postgresql.conf
74
77
  docker/postgres/run_postgres.sh
@@ -109,10 +112,12 @@ snappl/http.py
109
112
  snappl/image.py
110
113
  snappl/image_simulator.py
111
114
  snappl/imagecollection.py
115
+ snappl/lightcurve.py
112
116
  snappl/logger.py
113
117
  snappl/provenance.py
114
118
  snappl/psf.py
115
119
  snappl/sed.py
120
+ snappl/segmap.py
116
121
  snappl/utils.py
117
122
  snappl/wcs.py
118
123
  snappl/_dev/__init__.py
@@ -126,6 +131,8 @@ snappl/db/webserver.py
126
131
  snappl/db/migrations/20251008_init.sql
127
132
  snappl/db/migrations/20251017_objdetcount.sql
128
133
  snappl/db/migrations/20251024_l2image_mjd.sql
134
+ snappl/db/migrations/20251025_lightcurve.sql
135
+ snappl/db/migrations/20251028_segmap.sql
129
136
  snappl/db/migrations/apply_migrations.py
130
137
  snappl/db/migrations/schema_to_rst.py
131
138
  snappl/db/migrations/scorched_earth.py
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.19.0'
32
- __version_tuple__ = version_tuple = (0, 19, 0)
31
+ __version__ = version = '0.21.0'
32
+ __version_tuple__ = version_tuple = (0, 21, 0)
33
33
 
34
- __commit_id__ = commit_id = 'g463af9082'
34
+ __commit_id__ = commit_id = 'g7a9941597'
@@ -38,7 +38,7 @@ def _parse_fits_file( relpath, base_path=None, provid=None ):
38
38
  'provenance_id': provid,
39
39
  'pointing': image.pointing,
40
40
  'sca': image.sca,
41
- 'filter': image.band,
41
+ 'band': image.band,
42
42
  'ra': ra,
43
43
  'dec': dec,
44
44
  'ra_corner_00': ra_corner_00,
@@ -96,17 +96,17 @@ class OU2024_L2image_loader:
96
96
  words = line.split(',')
97
97
  if len(words) != 3:
98
98
  raise ValueError( f'Failed to parse line: "{line}"' )
99
- if ( ( words[0].strip() == 'filter' ) and
99
+ if ( any( words[0].strip() == i for i in ('filter', 'band') ) and
100
100
  ( words[1].strip() == 'pointing' ) and
101
101
  ( words[2].strip() == 'sca' )
102
102
  ):
103
103
  header = True
104
104
  elif not header:
105
- raise ValueError( f'First line was "{line}", not "filter,pointing,sca"' )
105
+ raise ValueError( f'First line was "{line}", not "band,pointing,sca"' )
106
106
  else:
107
- filter, pointing, sca = words
108
- fpath = pathlib.Path( f'{filter}/{pointing}/'
109
- f'Roman_TDS_simple_model_{filter}_{pointing}_{sca}.fits.gz' )
107
+ band, pointing, sca = words
108
+ fpath = pathlib.Path( f'{band}/{pointing}/'
109
+ f'Roman_TDS_simple_model_{band}_{pointing}_{sca}.fits.gz' )
110
110
  if ( self.base_path / fpath ).is_file():
111
111
  imagefiles.append( fpath )
112
112
  else:
@@ -190,7 +190,7 @@ def main():
190
190
  parser.add_argument( '-b', '--basedir', default='/ou2024/RomanTDS/images/simple_model',
191
191
  help='Base directory.' )
192
192
  parser.add_argument( '-f', '--filelist', default=None,
193
- help="File with list of filter,pointing,sca" )
193
+ help="File with list of band,pointing,sca" )
194
194
  parser.add_argument( '-j', '--just-get-filenames', default=False, action='store_true',
195
195
  help="Don't actually load files to the database, just generate a file list." )
196
196
  parser.add_argument( '-s', '--save-file-list', default=None,
@@ -112,7 +112,7 @@ class Config:
112
112
  images:
113
113
  format: fits
114
114
  single_file: false
115
- name_convention: "{inst_name}_{date}_{time}_{section_id}_{filter}_{im_type}_{prov_hash:.6s}"
115
+ name_convention: "{inst_name}_{date}_{time}_{section_id}_{band}_{im_type}_{prov_hash:.6s}"
116
116
 
117
117
  then confobj.value("storage.images.format") will return
118
118
  "fits". You can also ask configobj.value for higher levels. For
@@ -120,7 +120,7 @@ class Config:
120
120
 
121
121
  { "format": "fits",
122
122
  "single_file": False,
123
- "name_convention": "{inst_name}_{date}_{time}_{section_id}_{filter}_{im_type}_{prov_hash:.6s}"
123
+ "name_convention": "{inst_name}_{date}_{time}_{section_id}_{band}_{im_type}_{prov_hash:.6s}"
124
124
  }
125
125
 
126
126
  4. Change a config value with::
@@ -0,0 +1,266 @@
1
+ from types import SimpleNamespace
2
+ import simplejson
3
+
4
+ import flask
5
+ import flask.views
6
+ from psycopg import sql
7
+
8
+ from snappl.db import db
9
+ from snappl.logger import SNLogger
10
+ from snappl.utils import SNPITJsonEncoder
11
+
12
+
13
+ # ======================================================================
14
+
15
+ class BaseView( flask.views.View ):
16
+ """A BaseView that all other views can be based on.
17
+
18
+ If the view doesn't override dispatch_request, then it must define a
19
+ function do_the_things. That should return a dict, list, string,
20
+ tuple, or ...something else.
21
+
22
+ If it returns a dict or a list, the web server will send to the
23
+ client application/json with status 200. If the result is a string,
24
+ it the web server will send to the client text/plain with status
25
+ 200. If it's a tuple, just let Flask deal with that tuple to figure
26
+ out what the web server should send to the client. Otherwise, the
27
+ web server will send to the client application/octet-stream with
28
+ status 200.
29
+
30
+ Subclasses that do not override dispatch_request do not need to call
31
+ check_auth. However, if they do override it, they should call that
32
+ if the results shouldn't be sent back to an unauthenticated user.
33
+
34
+ """
35
+
36
+ _admin_required = False
37
+
38
+ def __init__( self, *args, **kwargs ):
39
+ super().__init__( *args, **kwargs )
40
+
41
+
42
+ def check_json_keys( self, needed_keys, allowed_keys, minmax_keys=set() ):
43
+ if not flask.request.is_json:
44
+ raise RuntimeError( "Expected json POST data, didn't get any." )
45
+
46
+ all_allowed_keys = allowed_keys.copy()
47
+ for kw in minmax_keys:
48
+ for edge in [ 'min', 'max' ]:
49
+ all_allowed_keys.add( f"{kw}_{edge}" )
50
+
51
+ data = flask.request.json
52
+ passed_keys = set( data.keys() )
53
+ if not needed_keys.issubset( passed_keys ):
54
+ raise RuntimeError( f"Missing required keys: {needed_keys - passed_keys}" )
55
+ if not passed_keys.issubset( all_allowed_keys ):
56
+ raise RuntimeError( f"Unknown keys: {passed_keys - all_allowed_keys}" )
57
+ return data
58
+
59
+ def get_provenance_id( self, data, dbcon=None ):
60
+ with db.DBCon( dictcursor=True ) as dbcon:
61
+ provid = None
62
+ if 'provenance' in data:
63
+ provid = data['provenance']
64
+ del data['provenance']
65
+ elif ( 'provenance_tag' in data ) and ( 'process' in data ):
66
+ rows = dbcon.execute( "SELECT provenance_id FROM provenance_tag "
67
+ "WHERE tag=%(tag)s AND process=%(proc)s",
68
+ {'tag': data['provenance_tag'], 'proc': data['process'] } )
69
+ if len(rows) == 0:
70
+ raise RuntimeError( f"Unknown provenance with tag {data['provenance_tag']} "
71
+ f"and process {data['process']}" )
72
+ if len(rows) > 1:
73
+ raise RuntimeError ( f"Database corruption: multiple provenances with tag {data['provenance_tag']}"
74
+ f"and process {data['process']}; this should never happen." )
75
+ provid = db.Provenance.get( rows[0]['provenance_id'] ).id
76
+ del data['provenance_tag']
77
+ del data['process']
78
+ else:
79
+ raise RuntimeError( "Must give either provenance_id, or both of provenance_tag and process" )
80
+
81
+ return data, provid
82
+
83
+
84
+ # Warning : only call this next one if you've made sure that there is no SQL injection in keys!
85
+ def build_sql_insert( self, keys ):
86
+ keysql = None
87
+ subs = None
88
+ for key in keys:
89
+ if keysql is None:
90
+ keysql = sql.SQL( "{key}" ).format( key=sql.Identifier( key ) )
91
+ else:
92
+ keysql += sql.SQL( ", {key}" ).format( key=sql.Identifier( key ) )
93
+ if subs is None:
94
+ subs = f"%({key})s"
95
+ else:
96
+ subs += f", %({key})s"
97
+ return keysql, subs
98
+
99
+
100
+ # WARNING. Do not use this unless you're sure that everything in
101
+ # the passed arrays have no SQL injection attacks.
102
+ def make_sql_conditions( self, data, equalses=[], minmaxes=[],
103
+ q3ctriplets=[], cornerpolypairs=[],
104
+ conditions=[], subdict={} ):
105
+ finalclause = None
106
+
107
+ if any( i in data for i in [ 'order_by', 'limit', 'offset' ] ):
108
+ orderbyclause = None
109
+ limitclause = None
110
+ offsetclause = None
111
+
112
+ if 'order_by' in data:
113
+ orderby = data['order_by']
114
+ if not isinstance( orderby, list ):
115
+ orderby = [ orderby ]
116
+ orderbyclause = sql.SQL( " ORDER BY " )
117
+ comma = ""
118
+ for o in orderby:
119
+ orderbyclause += sql.SQL( f"{comma}{{orderby}}" ).format( orderby=sql.Identifier(o) )
120
+ comma = ","
121
+ del data['order_by']
122
+ finalclause = orderbyclause
123
+
124
+ if 'limit' in data:
125
+ limitclause = sql.SQL( " LIMIT %(finalclause_limit)s" )
126
+ subdict['finalclause_limit'] = data['limit']
127
+ del data['limit']
128
+ if finalclause is None:
129
+ finalclause = limitclause
130
+ else:
131
+ finalclause += limitclause
132
+
133
+ if 'offset' in data:
134
+ offsetclause = sql.SQL( " OFFSET %(finalclause_offset)s" )
135
+ subdict['finalclause_offset'] = data['offset']
136
+ del data['offset']
137
+ if finalclause is None:
138
+ finalclause = offsetclause
139
+ else:
140
+ finalclause += offsetclause
141
+
142
+ finalclause = sql.SQL("") if finalclause is None else finalclause
143
+
144
+ for i, triplet in enumerate(q3ctriplets):
145
+ presencecheck = [ ( i in data and data[i] is not None ) for i in triplet ]
146
+ if any( presencecheck ):
147
+ if not all( presencecheck ):
148
+ raise RuntimeError( f"Must include all or none of {triplet}" )
149
+ barf = f"q3c_triplet_{i}"
150
+ formatsubs = { f"{barf}_0": sql.Identifier( triplet[0] ),
151
+ f"{barf}_1": sql.Identifier( triplet[1] ) }
152
+ conditions.append( sql.SQL( f"q3c_radial_query({{{barf}_0}},{{{barf}_1}},"
153
+ f"%({barf}_0)s,%({barf}_1)s,%({barf}_2)s)"
154
+ ).format( **formatsubs ) )
155
+ subdict.update( { f"{barf}_0": data[triplet[0]],
156
+ f"{barf}_1": data[triplet[1]],
157
+ f"{barf}_2": data[triplet[2]] / 3600. } )
158
+ del data[triplet[0]]
159
+ del data[triplet[1]]
160
+ del data[triplet[2]]
161
+
162
+ for i, pair in enumerate(cornerpolypairs):
163
+ presencecheck = [ ( i in data and data[i] is not None ) for i in pair ]
164
+ if any( presencecheck ):
165
+ if not all( presencecheck ):
166
+ raise RuntimeError( f"Must include both or neither of {pair}" )
167
+ barf = f"corner_pair_{i}"
168
+ # THINKING AHEAD : this poly query doesn't use the q3c index
169
+ # As the number of database rows get large, we should look
170
+ # at performance. We many need to do this in two steps,
171
+ # which would mean using a temp table. First step would use
172
+ # regular indexes on the eight corner variables and use
173
+ # LEAST and GREATEST with ra and dec. Then, a second query
174
+ # would use the poly query on the temp table resulting from
175
+ # that first query. (Or maybe you can do it all with clever
176
+ # nested queries.) This becomes very complicated.
177
+ conditions.append( sql.SQL( f"q3c_poly_query(%({barf}_0)s, %({barf}_1)s, "
178
+ f"ARRAY[ ra_corner_00, dec_corner_00, ra_corner_01, dec_corner_01, "
179
+ f" ra_corner_11, dec_corner_11, ra_corner_10, dec_corner_10 ] )"
180
+ ) )
181
+ subdict.update( { f"{barf}_0": data[pair[0]],
182
+ f"{barf}_1": data[pair[1]] } )
183
+ del data[pair[0]]
184
+ del data[pair[1]]
185
+
186
+ for kw in equalses:
187
+ if ( kw in data ) and ( data[kw] is not None ):
188
+ conditions.append( sql.SQL( f"{kw}=%({kw})s" ) )
189
+ # I hate that we're doing str here, but we have a legacy of diaobject names
190
+ # being either strings or integers
191
+ subdict[kw] = str( data[kw] )
192
+ del data[kw]
193
+
194
+ for kw in minmaxes:
195
+ if kw in data:
196
+ if data['kw'] is not None:
197
+ conditions.append( sql.SQL( f"{kw}=%({kw})s" ) )
198
+ subdict[kw] = data[kw]
199
+ del data[kw]
200
+ for edge, op in zip( [ 'min', 'max' ], [ '>=', '<=' ] ):
201
+ if f'{kw}_{edge}' in data:
202
+ if data[f'{kw}_{edge}'] is not None:
203
+ conditions.append( sql.SQL( f"{kw} {op} %({kw}_{edge})s" ) )
204
+ subdict[f'{kw}_{edge}'] = data[f'{kw}_{edge}']
205
+ del data[f'{kw}_{edge}']
206
+
207
+ _and = None
208
+ for condition in conditions:
209
+ if _and is None:
210
+ condstr = condition
211
+ else:
212
+ _and = " AND "
213
+ condstr += sql.SQL(_and) + condition
214
+
215
+ return data, condstr, subdict, finalclause
216
+
217
+
218
+ def check_auth( self ):
219
+ self.username = flask.session['username'] if 'username' in flask.session else '(None)'
220
+ self.displayname = flask.session['userdisplayname'] if 'userdisplayname' in flask.session else '(None)'
221
+ self.authenticated = ( 'authenticated' in flask.session ) and flask.session['authenticated']
222
+ self.user = None
223
+ if self.authenticated:
224
+ with db.DB() as conn:
225
+ cursor = conn.cursor()
226
+ cursor.execute( "SELECT id,username,displayname,email FROM authuser WHERE username=%(username)s",
227
+ {'username': self.username } )
228
+ rows = cursor.fetchall()
229
+ if len(rows) > 1:
230
+ self.authenticated = False
231
+ raise RuntimeError( f"Error, more than one {self.username} in database, "
232
+ f"this should never happen." )
233
+ if len(rows) == 0:
234
+ self.authenticated = False
235
+ raise ValueError( f"Error, failed to find user {self.username} in database" )
236
+ row = rows[0]
237
+ self.user = SimpleNamespace( id=row[0], username=row[1], displayname=row[2], email=row[3] )
238
+ # Verify that session displayname and database displayname match? Eh. Whatevs.
239
+ return self.authenticated
240
+
241
+ def dispatch_request( self, *args, **kwargs ):
242
+ if not self.check_auth():
243
+ return "Not logged in", 500
244
+ if ( self._admin_required ) and ( not self.user.isadmin ):
245
+ return "Action requires admin", 500
246
+ try:
247
+ retval = self.do_the_things( *args, **kwargs )
248
+ # Can't just use the default JSON handling, because it
249
+ # writes out NaN which is not standard JSON and which
250
+ # the javascript JSON parser chokes on. Sigh.
251
+ if isinstance( retval, dict ) or isinstance( retval, list ):
252
+ # SNLogger.warning( f"Dumping to json: {retval}" )
253
+ return ( simplejson.dumps( retval, ignore_nan=True, cls=SNPITJsonEncoder ),
254
+ 200, { 'Content-Type': 'application/json' } )
255
+ elif isinstance( retval, str ):
256
+ return retval, 200, { 'Content-Type': 'text/plain; charset=utf-8' }
257
+ elif isinstance( retval, tuple ):
258
+ return retval
259
+ else:
260
+ return retval, 200, { 'Content-Type': 'application/octet-stream' }
261
+ except Exception as ex:
262
+ # sio = io.StringIO()
263
+ # traceback.print_exc( file=sio )
264
+ # SNLogger.debug( sio.getvalue() )
265
+ SNLogger.exception( str(ex) )
266
+ return str(ex), 500
@@ -13,6 +13,7 @@ from contextlib import contextmanager
13
13
 
14
14
  import numpy as np
15
15
  import psycopg
16
+ from psycopg import sql
16
17
 
17
18
  from snappl.config import Config
18
19
  from snappl.logger import SNLogger
@@ -266,7 +267,8 @@ class DBCon:
266
267
 
267
268
  """
268
269
  if self.echoqueries and not silent:
269
- SNLogger.debug( f"Sending query\n{q}\nwith substitutions: {subdict}" )
270
+ qprint = q.as_string() if isinstance( q, sql.Composable ) else q
271
+ SNLogger.debug( f"Sending query\n{qprint}\nwith substitutions: {subdict}" )
270
272
 
271
273
  if self.alwaysexplain and not silent:
272
274
  self.cursor.execute( f"EXPLAIN {q}", subdict )
@@ -1074,6 +1076,14 @@ class SummedImage( DBBase ):
1074
1076
  _pk = [ 'id' ]
1075
1077
 
1076
1078
 
1079
+ # ======================================================================
1080
+
1081
+ class SegMap( DBBase ):
1082
+ __tablename__ = "segmap"
1083
+ _tablemeta = None
1084
+ _pk = [ 'id' ]
1085
+
1086
+
1077
1087
  # ======================================================================
1078
1088
 
1079
1089
  class Lightcurve( DBBase ):
@@ -0,0 +1,5 @@
1
+ ALTER TABLE l2image RENAME COLUMN filter TO band;
2
+ ALTER TABLE summed_image RENAME COLUMN filter TO band;
3
+ ALTER TABLE lightcurve RENAME COLUMN filter TO band;
4
+
5
+ ALTER TABLE lightcurve ADD COLUMN diaobject_position_id UUID DEFAULT NULL;
@@ -0,0 +1,45 @@
1
+ CREATE TABLE segmap(
2
+ id UUID PRIMARY KEY,
3
+ provenance_id UUID NOT NULL,
4
+ band text NOT NULL,
5
+ ra double precision NOT NULL,
6
+ dec double precision NOT NULL,
7
+ ra_corner_00 real NOT NULL,
8
+ ra_corner_01 real NOT NULL,
9
+ ra_corner_10 real NOT NULL,
10
+ ra_corner_11 real NOT NULL,
11
+ dec_corner_00 real NOT NULL,
12
+ dec_corner_01 real NOT NULL,
13
+ dec_corner_10 real NOT NULL,
14
+ dec_corner_11 real NOT NULL,
15
+ filepath text NOT NULL,
16
+ width smallint,
17
+ height smallint,
18
+ format smallint NOT NULL DEFAULT 0,
19
+ l2image_id UUID DEFAULT NULL
20
+ );
21
+ CREATE INDEX ix_segmap_band ON segmap (band);
22
+ CREATE INDEX ix_segmap_q3c ON segmap (q3c_ang2ipix(ra,dec));
23
+ CREATE INDEX ix_segmap_ra00 ON segmap (ra_corner_00);
24
+ CREATE INDEX ix_segmap_ra01 ON segmap (ra_corner_01);
25
+ CREATE INDEX ix_segmap_ra10 ON segmap (ra_corner_10);
26
+ CREATE INDEX ix_segmap_ra11 ON segmap (ra_corner_11);
27
+ CREATE INDEX ix_segmap_dec00 ON segmap (dec_corner_00);
28
+ CREATE INDEX ix_segmap_dec01 ON segmap (dec_corner_01);
29
+ CREATE INDEX ix_segmap_dec10 ON segmap (dec_corner_10);
30
+ CREATE INDEX ix_segmap_dec11 ON segmap (dec_corner_11);
31
+ CREATE UNIQUE INDEX ix_segmap_filepath ON segmap (filepath);
32
+ CREATE INDEX iX_segmap_l2image ON segmap (l2image_id);
33
+ ALTER TABLE segmap ADD CONSTRAINT fk_segmap_l2image
34
+ FOREIGN KEY(l2image_id) REFERENCES l2image(id) ON DELETE RESTRICT;
35
+ COMMENT ON TABLE segmap IS 'Segmentation maps';
36
+ COMMENT ON COLUMN segmap.ra_corner_00 IS 'RA of pixel (0,0)';
37
+ COMMENT ON COLUMN segmap.ra_corner_01 IS 'RA of pixel (0,height-1)';
38
+ COMMENT ON COLUMN segmap.ra_corner_10 IS 'RA of pixel (width-1,0)';
39
+ COMMENT ON COLUMN segmap.ra_corner_11 IS 'RA of pixel (width-1,height-1)';
40
+ COMMENT ON COLUMN segmap.dec_corner_00 IS 'Dec of pixel (0,0)';
41
+ COMMENT ON COLUMN segmap.dec_corner_01 IS 'Dec of pixel (0,height-1)';
42
+ COMMENT ON COLUMN segmap.dec_corner_10 IS 'Dec of pixel (width-1,0)';
43
+ COMMENT ON COLUMN segmap.dec_corner_11 IS 'Dec of pixel (width-1,height-1)';
44
+ COMMENT ON COLUMN segmap.l2image_id IS 'This segmentation map is for this image';
45
+ COMMENT ON COLUMN segmap.format IS '0=Unknown, 1=OpenUniverse2024FITSImage';