roman-snpit-snappl 0.6.0__tar.gz → 0.8.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 (77) hide show
  1. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/workflows/run_snappl_tests.yml +12 -1
  2. {roman_snpit_snappl-0.6.0/roman_snpit_snappl.egg-info → roman_snpit_snappl-0.8.0}/PKG-INFO +1 -1
  3. roman_snpit_snappl-0.8.0/changes/35.snappl.rst +1 -0
  4. roman_snpit_snappl-0.8.0/changes/36.snappl.rst +1 -0
  5. roman_snpit_snappl-0.8.0/changes/37.snappl.rst +1 -0
  6. roman_snpit_snappl-0.8.0/changes/40.snappl.rst +1 -0
  7. roman_snpit_snappl-0.8.0/changes/41.snappl.rst +1 -0
  8. roman_snpit_snappl-0.8.0/changes/43.snappl.rst +1 -0
  9. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0/roman_snpit_snappl.egg-info}/PKG-INFO +1 -1
  10. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/roman_snpit_snappl.egg-info/SOURCES.txt +7 -0
  11. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/_version.py +16 -3
  12. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/image.py +60 -1
  13. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/psf.py +167 -10
  14. roman_snpit_snappl-0.8.0/snappl/sed.py +169 -0
  15. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.cruft.json +0 -0
  16. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/CODEOWNERS +0 -0
  17. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md +0 -0
  18. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md +0 -0
  19. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/ISSUE_TEMPLATE/PR_TEMPLATE.md +0 -0
  20. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/dependabot.yml +0 -0
  21. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/labeler.yml +0 -0
  22. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/workflows/changelog.yml +0 -0
  23. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/workflows/run_labeler.yml +0 -0
  24. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/workflows/sphinx-deploy.yml +0 -0
  25. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.github/workflows/sub_package_update.yml +0 -0
  26. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.gitignore +0 -0
  27. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/.pre-commit-config.yaml +0 -0
  28. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/CHANGES.rst +0 -0
  29. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/CITATION.cff +0 -0
  30. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/CODE_OF_CONDUCT.md +0 -0
  31. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/CONTRIBUTING.md +0 -0
  32. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/LICENSE +0 -0
  33. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/MANIFEST.in +0 -0
  34. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/README.rst +0 -0
  35. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/.gitkeep +0 -0
  36. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/10.snappl.rst +0 -0
  37. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/13.bugfix.rst +0 -0
  38. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/14.snappl.rst +0 -0
  39. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/15.feature.rst +0 -0
  40. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/16.feature.rst +0 -0
  41. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/18.feature.rst +0 -0
  42. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/20.bugfix.rst +0 -0
  43. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/23.snappl.rst +0 -0
  44. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/26.feature.rst +0 -0
  45. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/3.snappl.rst +0 -0
  46. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/31.feature.rst +0 -0
  47. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/5.snappl.rst +0 -0
  48. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/8.snappl.rst +0 -0
  49. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/changes/9.snappl.rst +0 -0
  50. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/codespell-ignore.txt +0 -0
  51. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/Makefile +0 -0
  52. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/_static/logo_black_filled.png +0 -0
  53. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/changes.rst +0 -0
  54. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/conf.py +0 -0
  55. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/index.rst +0 -0
  56. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/installation.rst +0 -0
  57. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/make.bat +0 -0
  58. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/docs/usage.rst +0 -0
  59. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/experimentation/README.md +0 -0
  60. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/experimentation/play_with_photutils.py +0 -0
  61. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/licenses/.DS_Store +0 -0
  62. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/licenses/LICENSE.rst +0 -0
  63. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/licenses/README.rst +0 -0
  64. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/licenses/TEMPLATE_LICENSE.rst +0 -0
  65. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/pyproject.toml +0 -0
  66. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/roman_snpit_snappl.egg-info/dependency_links.txt +0 -0
  67. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/roman_snpit_snappl.egg-info/not-zip-safe +0 -0
  68. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/roman_snpit_snappl.egg-info/requires.txt +0 -0
  69. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/roman_snpit_snappl.egg-info/top_level.txt +0 -0
  70. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/setup.cfg +0 -0
  71. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/setup.py +0 -0
  72. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/__init__.py +0 -0
  73. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/_dev/__init__.py +0 -0
  74. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/_dev/scm_version.py +0 -0
  75. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/data/README.rst +0 -0
  76. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/snappl/wcs.py +0 -0
  77. {roman_snpit_snappl-0.6.0 → roman_snpit_snappl-0.8.0}/tox.ini +0 -0
@@ -20,10 +20,20 @@ jobs:
20
20
  uses: jwalton/gh-docker-logs@v2
21
21
 
22
22
  - name: checkout code
23
- uses: actions/checkout@v4
23
+ uses: actions/checkout@v5
24
24
  with:
25
+ path: main
25
26
  submodules: recursive
26
27
 
28
+ - name: run ruff
29
+ uses: astral-sh/ruff-action@v3
30
+
31
+ - name: checkout photometry test data
32
+ uses: actions/checkout@v5
33
+ with:
34
+ repository: Roman-Supernova-PIT/photometry_test_data
35
+ path: photometry_test_data
36
+
27
37
  - name: log into github container registry
28
38
  uses: docker/login-action@v3
29
39
  with:
@@ -46,4 +56,5 @@ jobs:
46
56
 
47
57
  - name: run test
48
58
  run: |
59
+ cd main
49
60
  docker compose run runtests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roman_snpit_snappl
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Photometry utilities for the Roman SNPIT
5
5
  Author: Roman Supernove Project Infrastructure Team
6
6
  Maintainer-email: Roman SN PIT <raknop@lbl.gov>
@@ -0,0 +1 @@
1
+ photon ops now actually turns off
@@ -0,0 +1 @@
1
+ created OU24 SED Collection object
@@ -0,0 +1 @@
1
+ Added the non-slow ou24PSF class
@@ -0,0 +1 @@
1
+ Added manual fits image and test
@@ -0,0 +1 @@
1
+ Made OU24 PSFs have passable WCSs
@@ -0,0 +1 @@
1
+ Update tests to use photometry_test_data, small edit to config file layout.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: roman_snpit_snappl
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Photometry utilities for the Roman SNPIT
5
5
  Author: Roman Supernove Project Infrastructure Team
6
6
  Maintainer-email: Roman SN PIT <raknop@lbl.gov>
@@ -35,6 +35,12 @@ changes/23.snappl.rst
35
35
  changes/26.feature.rst
36
36
  changes/3.snappl.rst
37
37
  changes/31.feature.rst
38
+ changes/35.snappl.rst
39
+ changes/36.snappl.rst
40
+ changes/37.snappl.rst
41
+ changes/40.snappl.rst
42
+ changes/41.snappl.rst
43
+ changes/43.snappl.rst
38
44
  changes/5.snappl.rst
39
45
  changes/8.snappl.rst
40
46
  changes/9.snappl.rst
@@ -62,6 +68,7 @@ snappl/__init__.py
62
68
  snappl/_version.py
63
69
  snappl/image.py
64
70
  snappl/psf.py
71
+ snappl/sed.py
65
72
  snappl/wcs.py
66
73
  snappl/_dev/__init__.py
67
74
  snappl/_dev/scm_version.py
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '0.6.0'
21
- __version_tuple__ = version_tuple = (0, 6, 0)
31
+ __version__ = version = '0.8.0'
32
+ __version_tuple__ = version_tuple = (0, 8, 0)
33
+
34
+ __commit_id__ = commit_id = 'gfdb162af4'
@@ -386,6 +386,42 @@ class FITSImage( Numpy2DImage ):
386
386
  self._wcs = GalsimWCS.from_header( hdr )
387
387
  return self._wcs
388
388
 
389
+ def get_data(self, which="all"):
390
+ if self._is_cutout:
391
+ raise RuntimeError(
392
+ "get_data called on a cutout image, this will return the ORIGINAL UNCUT image. Currently not supported."
393
+ )
394
+ if which not in Image.data_array_list:
395
+ raise ValueError(f"Unknown which {which}, must be all, data, noise, or flags")
396
+
397
+ if (which == "all"):
398
+ if (self._data is not None) and (self._noise is not None) and (self._flags is not None):
399
+ return [self._data, self._noise, self._flags]
400
+ else:
401
+ raise RuntimeError(
402
+ f"get_data called with which='all', but not all data arrays are set. "
403
+ f"Data: {self._data is not None}, Noise: {self._noise is not None},"
404
+ f" Flags: {self._flags is not None}"
405
+ )
406
+
407
+ if (which == "data"):
408
+ if (self._data is not None):
409
+ return [self._data]
410
+ else:
411
+ raise RuntimeError("get_data called with which='data', but data is not set.")
412
+
413
+ if (which == "noise"):
414
+ if (self._noise is not None):
415
+ return [self._noise]
416
+ else:
417
+ raise RuntimeError("get_data called with which='noise', but noise is not set.")
418
+
419
+ if (which == "flags"):
420
+ if (self._flags is not None):
421
+ return [self._flags]
422
+ else:
423
+ raise RuntimeError("get_data called with which='flags', but flags are not set.")
424
+
389
425
  def get_cutout(self, x, y, xsize, ysize=None):
390
426
  """Creates a new snappl image object that is a cutout of the original image, at a location in pixel-space.
391
427
 
@@ -422,7 +458,7 @@ class FITSImage( Numpy2DImage ):
422
458
  f"pixel, you tried to pass a size of {xsize, ysize}.")
423
459
 
424
460
  SNLogger.debug(f'Cutting out at {x , y}')
425
- data, noise, flags = self.get_data( 'all', always_reload=False )
461
+ data, noise, flags = self.get_data( 'all' )
426
462
 
427
463
  wcs = self.get_wcs()
428
464
  if ( wcs is not None ) and ( not isinstance( wcs, AstropyWCS ) ):
@@ -554,3 +590,26 @@ class OpenUniverse2024FITSImage( FITSImage ):
554
590
  def _get_zeropoint( self ):
555
591
  header = self._get_header()
556
592
  return galsim.roman.getBandpasses()[self.band].zeropoint + header['ZPTMAG']
593
+
594
+
595
+ class ManualFITSImage(FITSImage):
596
+ def __init__(self, header, data, noise=None, flags=None, path = None, exposure = None, sca = None, *args, **kwargs):
597
+
598
+ self._data = data
599
+ self._noise = noise
600
+ self._flags = flags
601
+ self._header = header
602
+ self._wcs = None
603
+ self._is_cutout = False
604
+ self._image_shape = None
605
+
606
+ self.inputs = types.SimpleNamespace()
607
+ self.inputs.path = None
608
+ self.inputs.exposure = None
609
+ self.inputs.sca = None
610
+
611
+ def _get_header(self):
612
+ """Get the header of the image."""
613
+ if self._header is None:
614
+ raise RuntimeError("Header is not set for ManualFITSImage.")
615
+ return self._header
@@ -13,6 +13,7 @@ import galsim
13
13
  from roman_imsim.utils import roman_utils
14
14
 
15
15
  # roman snpit library imports
16
+ from snappl.wcs import BaseWCS
16
17
  from snpit_utils.config import Config
17
18
  from snpit_utils.logger import SNLogger
18
19
 
@@ -1051,7 +1052,7 @@ class ou24PSF_slow( PSF ):
1051
1052
  self.sed = sed
1052
1053
 
1053
1054
  if config_file is None:
1054
- config_file = Config.get().value( 'ou24psf.config_file' )
1055
+ config_file = Config.get().value( 'ou24.config_file' )
1055
1056
  self.config_file = config_file
1056
1057
  self.pointing = pointing
1057
1058
  self.sca = sca
@@ -1067,13 +1068,17 @@ class ou24PSF_slow( PSF ):
1067
1068
  return self.size
1068
1069
 
1069
1070
 
1070
- def get_stamp( self, x=None, y=None, x0=None, y0=None, flux=1., seed=None ):
1071
+ def get_stamp( self, x=None, y=None, x0=None, y0=None, flux=1., seed=None, input_wcs=None):
1071
1072
  """Return a 2d numpy image of the PSF at the image resolution.
1072
1073
 
1073
1074
  Parameters are as in PSF.get_stamp, plus:
1074
1075
 
1075
1076
  Parameters
1076
1077
  ----------
1078
+ input_wcs : BaseWCS or galsim.BaseWCS
1079
+ WARNING: DO NOT USE. Not part of a standard interface, for testing purposes only.
1080
+ An alternative WCS to use for the stamp.
1081
+
1077
1082
  seed : int
1078
1083
  A random seed to pass to galsim.BaseDeviate for photonOps.
1079
1084
  NOTE: this is not part of the base PSF interface (at least,
@@ -1103,6 +1108,7 @@ class ou24PSF_slow( PSF ):
1103
1108
  raise ValueError( f"PSF would be rendered at ({stampx},{stampy}), which is too far off of the "
1104
1109
  f"edge of a {self.stamp_size}-pixel stamp." )
1105
1110
 
1111
+
1106
1112
  if (x, y, stampx, stampy) not in self._stamps:
1107
1113
  rmutils = roman_utils( self.config_file, self.pointing, self.sca )
1108
1114
  if seed is not None:
@@ -1111,9 +1117,20 @@ class ou24PSF_slow( PSF ):
1111
1117
  # It seems that galsim.ChromaticObject.drawImage won't function without stamp having
1112
1118
  # a wcs. Without a WCS, the stamp was coming out all zeros.
1113
1119
  # TODO : does rmutils.getLocalWCS want 1-indexed or 0-indexed coordinates???
1114
- wcs = rmutils.getLocalWCS( x+1, y+1 )
1115
- stamp = galsim.Image( self.stamp_size, self.stamp_size, wcs=wcs )
1116
-
1120
+ # wcs = rmutils.getLocalWCS( x+1, y+1 )self._
1121
+
1122
+ if input_wcs is None:
1123
+ self._wcs = rmutils.getLocalWCS( x+1, y+1 )
1124
+ elif isinstance(input_wcs, BaseWCS):
1125
+ SNLogger.debug( "Using user-supplied wcs for ou24PSF." )
1126
+ self._wcs = input_wcs.get_galsim_wcs().local( image_pos = galsim.PositionD(x+1, y+1 ))
1127
+ elif isinstance( input_wcs, galsim.BaseWCS ):
1128
+ SNLogger.debug( "Using user-supplied wcs for ou24PSF." )
1129
+ self._wcs = input_wcs.local( image_pos = galsim.PositionD(x+1, y+1 ))
1130
+ else:
1131
+ raise TypeError( f"wcs must be a galsim.BaseWCS, not a {type(input_wcs)}" )
1132
+
1133
+ stamp = galsim.Image( self.stamp_size, self.stamp_size, wcs=self._wcs )
1117
1134
  point = ( galsim.DeltaFunction() * self.sed ).withFlux( flux, rmutils.bpass )
1118
1135
  # TODO : make sure that rmutils.getPSF wants 1-indexed positions (which we assume here).
1119
1136
  # (This is not that big a deal, because the PSF is not going to vary significantly
@@ -1124,18 +1141,158 @@ class ou24PSF_slow( PSF ):
1124
1141
 
1125
1142
  # Note the +1s in galsim.PositionD below; galsim uses 1-indexed pixel positions,
1126
1143
  # whereas snappl uses 0-indexed pixel positions
1127
- point.drawImage( rmutils.bpass, method='phot', rng=rmutils.rng, photon_ops=photon_ops,
1128
- n_photons=self.n_photons, maxN=self.n_photons, poisson_flux=False,
1129
- center=galsim.PositionD(stampx+1, stampy+1), use_true_center=True, image=stamp )
1130
- self._stamps[(x, y, stampx, stampy)] = stamp.array
1144
+ center = galsim.PositionD(stampx+1, stampy+1)
1145
+ # Note: self.include_photonOps is a bool that states whether we are
1146
+ # shooting photons or not, photon_ops is the actual map (not sure
1147
+ # if that's the correct word) that describes where the photons
1148
+ # should be shot, with some randomness.
1149
+ if self.include_photonOps:
1150
+ point.drawImage(rmutils.bpass, method='phot', rng=rmutils.rng, photon_ops=photon_ops,
1151
+ n_photons=self.n_photons, maxN=self.n_photons, poisson_flux=False,
1152
+ center=center, use_true_center=True, image=stamp)
1131
1153
 
1154
+ else:
1155
+ psf = galsim.Convolve(point, photon_ops[0])
1156
+ psf.drawImage(rmutils.bpass, method="no_pixel", center=center,
1157
+ use_true_center=True, image=stamp, wcs=self._wcs)
1158
+
1159
+ self._stamps[(x, y, stampx, stampy)] = stamp.array
1132
1160
 
1133
1161
  return self._stamps[(x, y, stampx, stampy)]
1134
1162
 
1135
1163
 
1136
1164
  # TODO : make a ou24PSF that makes an image and caches... when things are working better
1137
1165
  class ou24PSF( ou24PSF_slow ):
1138
- pass
1166
+ def __init__( self, *args, **kwargs ):
1167
+ super().__init__(*args, **kwargs)
1168
+ self._psf = None
1169
+
1170
+ def _init_psf_object( self, x0=None, y0=None, flux=1., input_wcs = None):
1171
+ """Create the galsim PSF object, WCS, and galsim.chromatic.SimpleChromaticTransformation
1172
+ that can be reused for multiple calls to get_stamp.
1173
+
1174
+ WARNING: Do not use input_wcs. Not part of a standard interface, for testing & simulation purposes only.
1175
+
1176
+
1177
+ Parameters are as in PSF.get_stamp, plus:
1178
+
1179
+ Parameters
1180
+ ----------
1181
+ input_wcs : BaseWCS or galsim.BaseWCS
1182
+ An alternative WCS to use for the stamp.
1183
+
1184
+ seed : int
1185
+ A random seed to pass to galsim.BaseDeviate for photonOps.
1186
+ NOTE: this is not part of the base PSF interface (at least,
1187
+ as of yet), so don't use it in production pipeline code.
1188
+ However, it will be useful in tests for purposes of testing
1189
+ reproducibility.
1190
+
1191
+
1192
+ """
1193
+ self._rmutils = roman_utils(self.config_file, self.pointing, self.sca)
1194
+ self._psf = self._rmutils.getPSF(x0+1, y0+1, pupil_bin=8)
1195
+ # TODO : does rmutils.getLocalWCS want 1-indexed or 0-indexed coordinates???
1196
+ if input_wcs is None:
1197
+ self._wcs = self._rmutils.getLocalWCS( x0+1, y0+1 )
1198
+ elif isinstance(input_wcs, BaseWCS):
1199
+ SNLogger.debug( "Using user-supplied wcs for ou24PSF." )
1200
+ self._wcs = input_wcs.get_galsim_wcs().local( image_pos = galsim.PositionD(x0+1, y0+1))
1201
+ elif isinstance( input_wcs, galsim.BaseWCS):
1202
+ SNLogger.debug( "Using user-supplied wcs for ou24PSF." )
1203
+ self._wcs = input_wcs.local( image_pos=galsim.PositionD(x0+1, y0+1))
1204
+ else:
1205
+ raise TypeError( f"wcs must be a galsim.BaseWCS, not a {type(input_wcs)}" )
1206
+ SNLogger.debug( f"ou24PSF wcs fetched at: {x0, y0}" )
1207
+ SNLogger.debug( f"ou24PSF wcs: {self._wcs}" )
1208
+ self._stamp = galsim.Image( self.stamp_size, self.stamp_size, wcs=self._wcs )
1209
+ self._point = ( galsim.DeltaFunction() * self.sed ).withFlux( flux, self._rmutils.bpass )
1210
+ self._convolved_psf = galsim.Convolve(self._point, self._psf)
1211
+ # This is only used to ensure the user isn't trying to move the PSF around
1212
+ self._stored_x0 = x0
1213
+ self._stored_y0 = y0
1214
+
1215
+ def get_stamp(self, x=None, y=None, x0=None, y0=None, flux=1.0, seed=None, input_wcs=None):
1216
+ """Return a 2d numpy image of the PSF at the image resolution.
1217
+ Parameters are as in PSF.get_stamp, plus:
1218
+
1219
+ Parameters
1220
+ ----------
1221
+ wcs : BaseWCS or galsim.BaseWCS
1222
+ WARNING: DO NOT USE. Not part of a standard interface, for testing purposes only.
1223
+ An alternative WCS to use for the stamp.
1224
+
1225
+ seed : int
1226
+ A random seed to pass to galsim.BaseDeviate for photonOps.
1227
+ NOTE: this is not part of the base PSF interface (at least,
1228
+ as of yet), so don't use it in production pipeline code.
1229
+ However, it will be useful in tests for purposes of testing
1230
+ reproducibility.
1231
+
1232
+ """
1233
+
1234
+ # If a position is not given, assume the middle of the SCA
1235
+ # (within 1/2 pixel; by default, we want to make x and y
1236
+ # centered on a pixel).
1237
+
1238
+ x = x if x is not None else float( self.sca_size // 2 )
1239
+ y = y if y is not None else float( self.sca_size // 2 )
1240
+
1241
+ xc = int( np.floor( x + 0.5 ) )
1242
+ yc = int( np.floor( y + 0.5 ) )
1243
+ x0 = xc if x0 is None else x0
1244
+ y0 = yc if y0 is None else y0
1245
+
1246
+ if ( not isinstance( x0, numbers.Integral ) ) or ( not isinstance( y0, numbers.Integral ) ):
1247
+ raise TypeError( f"x0 and y0 must be integers; got x0 as a {type(x0)} and y0 as a {type(y0)}" )
1248
+
1249
+ if self._psf is None:
1250
+ SNLogger.debug( "Initializing ou24PSF galsim PSF object." )
1251
+ # If we don't have a psf object, then we need to initialize it, we then re use it for multiple calls to
1252
+ # get_stamp.
1253
+ self._init_psf_object( x0=x0, y0=y0, flux=flux, input_wcs = input_wcs)
1254
+ else:
1255
+ if x0 != self._stored_x0 or y0 != self._stored_y0:
1256
+ raise ValueError("ou24PSF.get_stamp called with x0 or y0 that does not match the x0 or y0 used"
1257
+ "to initialize the PSF object. If you want to recreate the PSF object, use "
1258
+ "ou24PSF_slow instead.")
1259
+
1260
+ stampx = self.stamp_size // 2 + ( x - x0 )
1261
+ stampy = self.stamp_size // 2 + ( y - y0 )
1262
+
1263
+ if ( ( stampx < -self.stamp_size ) or ( stampx > 2.*self.stamp_size ) or
1264
+ ( stampy < -self.stamp_size ) or ( stampy > 2.*self.stamp_size ) ):
1265
+ raise ValueError( f"PSF would be rendered at ({stampx},{stampy}), which is too far off of the "
1266
+ f"edge of a {self.stamp_size}-pixel stamp." )
1267
+
1268
+ if (x, y, stampx, stampy) not in self._stamps:
1269
+
1270
+ if seed is not None:
1271
+ self._rmutils.rng = galsim.BaseDeviate( seed )
1272
+
1273
+ photon_ops = [ self._psf ]
1274
+ if self.include_photonOps:
1275
+ photon_ops += self._rmutils.photon_ops
1276
+
1277
+ # Note the +1s in galsim.PositionD below; galsim uses 1-indexed pixel positions,
1278
+ # whereas snappl uses 0-indexed pixel positions
1279
+ center = galsim.PositionD(stampx+1, stampy+1)
1280
+ # Note: self.include_photonOps is a bool that states whether we are
1281
+ # shooting photons or not, photon_ops is the actual map (not sure
1282
+ # if that's the correct word) that describes where the photons
1283
+ # should be shot, with some randomness.
1284
+ if self.include_photonOps:
1285
+ self._point.drawImage(self._rmutils.bpass, method='phot', rng=self._rmutils.rng, photon_ops=photon_ops,
1286
+ n_photons=self.n_photons, maxN=self.n_photons, poisson_flux=False,
1287
+ center=center, use_true_center=True, image=self._stamp)
1288
+
1289
+ else:
1290
+ self._convolved_psf.drawImage(self._rmutils.bpass, method="no_pixel", center=center,
1291
+ use_true_center=True, image=self._stamp, wcs=self._wcs)
1292
+
1293
+ self._stamps[(x, y, stampx, stampy)] = self._stamp.array
1294
+
1295
+ return self._stamps[(x, y, stampx, stampy)]
1139
1296
 
1140
1297
  # class ou24PSF( OversampledImagePSF ):
1141
1298
  # """An OversampledImagePSF that renders its internally stored image from a galsim roman_imsim PSF.
@@ -0,0 +1,169 @@
1
+ # Put in necessary imports
2
+ import numpy as np
3
+ import os
4
+ import h5py
5
+ import galsim
6
+ import pathlib
7
+ import pandas as pd
8
+
9
+
10
+ from snpit_utils.config import Config
11
+ from snpit_utils.logger import SNLogger
12
+
13
+
14
+ class SED_collection:
15
+ def __init__(self, *args, **kwargs):
16
+ pass
17
+
18
+ def get_sed(self, filename=None, snid=None, mjd=None):
19
+ """Return a galsim SED."""
20
+ raise NotImplementedError( f"{self.__class__.__name__} needs to implement get_sed" )
21
+
22
+
23
+ class Flat_SED( SED_collection ):
24
+ def __init__( self ):
25
+ self.sed = galsim.SED( galsim.LookupTable( [1000, 26000], [1, 1], interpolant='linear' ),
26
+ wave_type='Angstrom', flux_type='fphotons' )
27
+
28
+ def get_sed( self, **kwargs):
29
+ return self.sed
30
+
31
+
32
+ class Single_CSV_SED( SED_collection ):
33
+ def __init__( self, csv_file ):
34
+ # READ THE CSV FILE, make a galsim SED in self.sed
35
+ raise NotImplementedError( "Single_CSV_SED is not implemented yet.")
36
+
37
+ def get_sed( self, **kwargs ):
38
+ return self.sed
39
+
40
+
41
+ class OU2024_Truth_SED(SED_collection):
42
+ def __init__(self, snid=None, isstar=False):
43
+ # if (snid is None) or (sn_path is None):
44
+ # raise ValueError("Must specify all of snid, sn_path")
45
+
46
+ self.snid = snid
47
+ cfg = Config.get()
48
+ self.sn_path = cfg.value("ou24.sn_truth_dir")
49
+ self.isstar = isstar
50
+
51
+ if isstar:
52
+ self.lam_array, self.flambda_array = \
53
+ self._ou24_get_star_SED()
54
+ else:
55
+ self.lam_array, self.flambda_array, self.mjd_array = \
56
+ self._ou24_get_SN_SED()
57
+
58
+ def get_sed(self, snid=None, mjd=None):
59
+ """Return an SED for the given snid and mjd.
60
+ Inputs:
61
+ snid: the ID of the object
62
+ mjd: the MJD of the observation (only used if this is a SN)
63
+
64
+ Returns:
65
+ galsim.SED: the SED for the given snid and mjd, if a supernova, for the ID if a star.
66
+ """
67
+ assert snid == self.snid, "ID does not match the SED collection ID."
68
+
69
+ if not self.isstar:
70
+ # If this is a SN, we need to find the closest SED to the given MJD.
71
+ bestindex = np.argmin(np.abs(np.array(self.mjd_array) - mjd))
72
+ max_days_cutoff = 10
73
+ closest_days_away = np.min(np.abs(np.array(self.mjd_array) - mjd))
74
+
75
+ if closest_days_away > max_days_cutoff:
76
+ SNLogger.warning(f"WARNING: No SED data within {max_days_cutoff} days of "
77
+ + f"date. \n The closest SED is {closest_days_away} days away.")
78
+
79
+ return galsim.SED(
80
+ galsim.LookupTable(np.array(self.lam_array), (self.flambda_array[bestindex]), interpolant="linear"),
81
+ wave_type="Angstrom",
82
+ flux_type="fphotons",
83
+ )
84
+
85
+ else:
86
+ # If this is a star, we just return the SED
87
+ return galsim.SED(
88
+ galsim.LookupTable(np.array(self.lam_array), (self.flambda_array), interpolant="linear"),
89
+ wave_type="Angstrom",
90
+ flux_type="fphotons",
91
+ )
92
+
93
+ def _ou24_open_parquet(self, parq, obj_type="SN", engine="fastparquet"):
94
+ """Convenience function to open a parquet file given its number."""
95
+ path = self.sn_path
96
+ file_prefix = {"SN": "snana", "star": "pointsource"}
97
+ base_name = "{:s}_{}.parquet".format(file_prefix[obj_type], parq)
98
+ file_path = os.path.join(path, base_name)
99
+ df = pd.read_parquet(file_path, engine=engine)
100
+ return df
101
+
102
+ def _ou24_find_parquet(self, obj_type="SN"):
103
+ """Find the parquet file that contains a given supernova ID."""
104
+ path = self.sn_path
105
+ ID = self.snid
106
+ files = os.listdir(path)
107
+ SNLogger.debug(f"Looking for parquet files in {path} for {obj_type} with ID {ID}")
108
+ file_prefix = {"SN": "snana", "star": "pointsource"}
109
+ files = [f for f in files if file_prefix[obj_type] in f]
110
+ files = [f for f in files if ".parquet" in f]
111
+ files = [f for f in files if "flux" not in f]
112
+
113
+ for f in files:
114
+ pqfile = int(f.split("_")[1].split(".")[0])
115
+ df = self._ou24_open_parquet(pqfile, obj_type=obj_type)
116
+ if ID in df.id.values or str(ID) in df.id.values:
117
+ return pqfile
118
+
119
+ def _ou24_get_star_SED(self):
120
+ """Return the appropriate SED for the star.
121
+ Inputs:
122
+ SNID: the ID of the object
123
+ sn_path: the path to the supernova data
124
+
125
+ Returns:
126
+ lam: the wavelength of the SED in Angstrom (numpy array of floats)
127
+ flambda: the flux of the SED units in erg/s/cm^2/Angstrom
128
+ (numpy array of floats)
129
+ """
130
+
131
+ filenum = self._ou24_find_parquet(obj_type="star")
132
+ pqfile = self._ou24_open_parquet(filenum, obj_type="star")
133
+ file_name = pqfile[pqfile["id"] == str(self.snid)]["sed_filepath"].values[0]
134
+ fullpath = pathlib.Path(Config.get().value("ou24.sims_sed_library")) / file_name
135
+ sed_table = pd.read_csv(fullpath, compression="gzip", sep=r"\s+", comment="#")
136
+ lam = sed_table.iloc[:, 0]
137
+ flambda = sed_table.iloc[:, 1]
138
+ return np.array(lam), np.array(flambda)
139
+
140
+ def _ou24_get_SN_SED(self):
141
+ """Return the appropriate SED for the supernova on the given day.
142
+
143
+ Inputs:
144
+ SNID: the ID of the object
145
+ sn_path: the path to the supernova data
146
+
147
+ Returns:
148
+ lam: the wavelength of the SED in Angstrom
149
+ flambda: the flux of the SED units in erg/s/cm^2/Angstrom
150
+ """
151
+ filenum = self._ou24_find_parquet(obj_type="SN")
152
+ file_name = "snana" + "_" + str(filenum) + ".hdf5"
153
+ fullpath = os.path.join(self.sn_path, file_name)
154
+ # Setting locking=False on the next line becasue it seems that you can't
155
+ # open an h5py file unless you have write access to... something.
156
+ # Not sure what. The directory where it exists? We won't
157
+ # always have that. It's scary to set locking to false, because it
158
+ # subverts all kinds of safety stuff that hdf5 does. However,
159
+ # because these files were created once in this case, it's not actually
160
+ # scary, and we expect them to be static. Locking only matters if you
161
+ # think somebody else might change the file
162
+ # while you're in the middle of reading bits of it.
163
+ sed_table = h5py.File(fullpath, "r", locking=False)
164
+ sed_table = sed_table[str(self.snid)]
165
+ flambda = sed_table["flambda"]
166
+ lam = sed_table["lambda"]
167
+ mjd = sed_table["mjd"]
168
+
169
+ return np.array(lam), np.array(flambda), np.array(mjd)