Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-41209: Make standalone, pip-installable lsst package #24

Merged
merged 10 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
build/
dist/
multiprofit.egg-info/
*/__pycache__/
__pycache__/
.coverage
.sconsign.dblite
config.log
version.py

# Generated by pip install -e .
python/lsst_multiprofit.egg-info/

# IDE folders and files
.cache/
Expand All @@ -11,6 +18,9 @@ multiprofit.egg-info/
.vscode/
compile_commands.json

# test outputs
tests/.tests

# pytest plugins
.hypothesis/
prof/
Expand Down
4 changes: 4 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- python -*-
from lsst.sconsUtils import scripts
# Python-only package
scripts.BasicSConstruct("multiprofit", disableCc=True, noCfgFile=True)
6 changes: 3 additions & 3 deletions examples/fithsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
import gauss2d.fit as g2f
import matplotlib as mpl
import matplotlib.pyplot as plt
from multiprofit.componentconfig import SersicConfig, SersicIndexConfig
from multiprofit.fit_psf import CatalogExposurePsfABC, CatalogPsfFitterConfig, CatalogPsfFitter
from multiprofit.fit_source import CatalogExposureSourcesABC, CatalogSourceFitterABC, CatalogSourceFitterConfig
from lsst.multiprofit.componentconfig import SersicConfig, SersicIndexConfig
from lsst.multiprofit.fit_psf import CatalogExposurePsfABC, CatalogPsfFitterConfig, CatalogPsfFitter
from lsst.multiprofit.fit_source import CatalogExposureSourcesABC, CatalogSourceFitterABC, CatalogSourceFitterConfig
import numpy as np
from typing import Any, Iterable, Mapping

Expand Down
2 changes: 1 addition & 1 deletion examples/test_gaussians.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
nbenchmark=100, do_like=True, do_residual=True, do_grad=True, do_jac=True,
do_meas_modelfit=False, nsub=4,
)
for x in test:
for x in test:
print(f"re={x['reff']:.3f} q={x['axrat']:.2f}"
f" ang={x['ang']:2.1f} { x['string']}")
print(f'Test complete in {timer() - start:.2f}s')
9 changes: 5 additions & 4 deletions examples/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
from importlib.util import find_spec
import numpy as np
import matplotlib.pyplot as plt
from multiprofit.fitutils import get_model
# TODO: Replace with e.g. fit_source.get_model?
from lsst.multiprofit.fitutils import get_model
import gauss2d as g2
import timeit

Expand Down Expand Up @@ -181,7 +182,7 @@ def get_setup(xdim=15, ydim=15, r_major=1, axrat=1, angle=0, nsub=1,
f'xdim={xdim}',
f'ydim={ydim}',
f'centroid = g2.Centroid({xdim}/2, {ydim}/2)',
f'kernel = g2.Gaussian(centroid=g2.Centroid(0, 0),'
'kernel = g2.Gaussian(centroid=g2.Centroid(0, 0),'
'ellipse=g2.Ellipse(sigma_x=0., sigma_y=0))',
'ellipse = g2.Ellipse(g2.EllipseMajor('
f'r_major={r_major}*g2.M_SIGMA_HWHM, axrat={axrat}, angle={angle}, degrees=True))',
Expand Down Expand Up @@ -318,7 +319,7 @@ def gaussian_test(xdim=49, ydim=51, reffs=None, angs=None, axrats=None, nbenchma
ang_rad = np.deg2rad(ang)
for key in ("dev", "exp"):
times[f"mmf-{key}"] = np.min(timeit.repeat(
f"msf.evaluate().addToImage(img)",
"msf.evaluate().addToImage(img)",
setup=(
f"import numpy as np;"
f"from lsst.shapelet import RadialProfile;"
Expand Down Expand Up @@ -360,7 +361,7 @@ def gradient_test(dimx=5, dimy=4, flux=1e4, reff=2, axrat=0.5, ang=0, bg=1e3,
reff_psf=0, axrat_psf=0.95, ang_psf=0, n_psfs=1, printout=False, plot=False):

if n_psfs > 1:
raise ValueError(f"n_psfs>1 not yet supported")
raise ValueError("n_psfs>1 not yet supported")

cen_x, cen_y = dimx/2., dimy/2.
# Keep this in units of sigma, not re==FWHM/2
Expand Down
Empty file removed multiprofit/__init__.py
Empty file.
1,823 changes: 0 additions & 1,823 deletions multiprofit/multigaussianapproxprofile.py

This file was deleted.

106 changes: 106 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
[build-system]
requires = ["setuptools", "lsst-versions >= 1.3.0"]
build-backend = "setuptools.build_meta"

[project]
name = "lsst-multiprofit"
description = "Astronomical image and source model fitting code."
license = {file = "LICENSE"}
readme = "README.rst"
authors = [
{name="Rubin Observatory Data Management", email="dm-admin@lists.lsst.org"},
]
classifiers = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add GPL license to the classifier list and maybe there is a Science/Research intended audience and topic Scientific/Engineering :: Astronomy (see astropy)?

"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Scientific/Engineering :: Astronomy",
]
keywords = [
"astronomy",
"astrophysics",
"fitting",
"lsst",
"models",
"modeling",
]
requires-python = ">=3.10.0"
dependencies = [
"astropy",
"gauss2d",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this on pypi?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I suppose it could be as it's a single repo and meson is pip-installable, although I'm not sure if the build configuration meets PyPI's requirements. For now, I configured the non-eups build to pip-install the bindings, so that they show up in pip list and are recognized as dependencies.

"gauss2dfit",
"lsst-pex-config",
"lsst-utils",
"importlib_resources",
"matplotlib",
"numpy",
"pydantic",
"scipy",
]
dynamic = ["version"]

[project.urls]
"Homepage" = "https://github.com/lsst-dm/multiprofit"

[project.optional-dependencies]
galsim = ["galsim"]
test = [
"pytest",
]

[tool.setuptools.packages.find]
where = ["python"]

[tool.setuptools.dynamic]
version = { attr = "lsst_versions.get_lsst_version" }

[tool.black]
line-length = 110
target-version = ["py311"]

[tool.isort]
profile = "black"
line_length = 110

[tool.ruff]
exclude = [
"__init__.py",
"examples/fithsc.py",
"examples/test_utils.py",
"tests/*.py",
]
ignore = [
"N802",
"N803",
"N806",
"N812",
"N815",
"N816",
"N999",
"D107",
"D105",
"D102",
"D104",
"D100",
"D200",
"D205",
"D400",
]
line-length = 110
select = [
"E", # pycodestyle
"F", # pycodestyle
"N", # pep8-naming
"W", # pycodestyle
"D", # pydocstyle
]
target-version = "py311"

[tool.ruff.pycodestyle]
max-doc-length = 79

[tool.ruff.pydocstyle]
convention = "numpy"
4 changes: 4 additions & 0 deletions python/lsst/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import pkgutil

__path__ = pkgutil.extend_path(__path__, __name__)

4 changes: 4 additions & 0 deletions python/lsst/multiprofit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import pkgutil

__path__ = pkgutil.extend_path(__path__, __name__)

Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ def _prepare(values, clip=True, out=None):
Prepare the data by optionally clipping and copying, and return the
array that should be subsequently used for in-place calculations.
"""

if clip:
return np.clip(values, 0., 1., out=out)
return np.clip(values, 0.0, 1.0, out=out)
else:
if out is None:
return np.array(values, copy=True)
Expand All @@ -59,7 +58,7 @@ class AsinhStretchSigned(apvis.BaseStretch):
to logarithmic behavior, expressed as a fraction of the
normalized image. Must be in the range between 0 and 1.
Default is 0.1
"""
""" # noqa: W505
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because of the math? Can you put a backslash in after the / to fix that? Does it render okay in sphinx?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The math line is 10 characters too long, yes. Is a backslash supposed to split it? Anyway, most of this is copy-pasted from astropy and lightly modified. I haven't actually configured docs for the package yet so I'll do that on another ticket.


def __init__(self, a=0.1):
super().__init__()
Expand All @@ -74,14 +73,14 @@ def __call__(self, values, clip=True, out=None):
np.abs(values, out=values)
np.true_divide(values, self.a, out=values)
np.arcsinh(values, out=values)
np.true_divide(values, np.arcsinh(1. / self.a), out=values)
np.true_divide(1. + signs*values, 2., out=values)
np.true_divide(values, np.arcsinh(1.0 / self.a), out=values)
np.true_divide(1.0 + signs * values, 2.0, out=values)
return values

@property
def inverse(self):
"""A stretch object that performs the inverse operation."""
return SinhStretchSigned(a=1. / np.arcsinh(1. / self.a))
return SinhStretchSigned(a=1.0 / np.arcsinh(1.0 / self.a))


class SinhStretchSigned(apvis.BaseStretch):
Expand All @@ -99,24 +98,24 @@ class SinhStretchSigned(apvis.BaseStretch):
The ``a`` parameter used in the above formula. Default is 1/3.
"""

def __init__(self, a=1. / 3.):
def __init__(self, a=1.0 / 3.0):
super().__init__()
self.a = a

# [docs]
# [docs]

def __call__(self, values, clip=True, out=None):
values = _prepare(values, clip=clip, out=out)
values *= 2.
values -= 1.
values *= 2.0
values -= 1.0
np.true_divide(values, self.a, out=values)
np.sinh(values, out=values)
np.true_divide(values, np.sinh(1. / self.a), out=values)
values += 1.
values /= 2.
np.true_divide(values, np.sinh(1.0 / self.a), out=values)
values += 1.0
values /= 2.0
return values

@property
def inverse(self):
"""A stretch object that performs the inverse operation."""
return AsinhStretchSigned(a=1. / np.sinh(1. / self.a))
return AsinhStretchSigned(a=1.0 / np.sinh(1.0 / self.a))
Loading