From 262ff3488a5c0b753616728bc0c1327ff0ab994e Mon Sep 17 00:00:00 2001 From: Connor Stone Date: Thu, 15 Aug 2024 12:29:39 -0400 Subject: [PATCH 1/5] Setup pytest --- .github/workflows/testing.yaml | 51 +++++++ autoprof/autoprofutils/Diagnostic_Plots.py | 20 +-- ...test_ESO479-G1_r.fits => ESO479-G1_r.fits} | Bin test/{test_config.py => basic_config.py} | 2 +- .../{test_batch_config.py => batch_config.py} | 8 +- ...test_custom_config.py => custom_config.py} | 6 +- ...test_forced_config.py => forced_config.py} | 2 +- test/test_run_tests.py | 125 ++++++++++++++++++ test/{test_tree_config.py => tree_config.py} | 4 +- 9 files changed, 189 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/testing.yaml rename test/{test_ESO479-G1_r.fits => ESO479-G1_r.fits} (100%) rename test/{test_config.py => basic_config.py} (79%) rename test/{test_batch_config.py => batch_config.py} (85%) rename test/{test_custom_config.py => custom_config.py} (94%) rename test/{test_forced_config.py => forced_config.py} (79%) create mode 100644 test/test_run_tests.py rename test/{test_tree_config.py => tree_config.py} (98%) diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml new file mode 100644 index 00000000..64220fc4 --- /dev/null +++ b/.github/workflows/testing.yaml @@ -0,0 +1,51 @@ +name: Unit Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + - dev + workflow_dispatch: + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11"] + os: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - uses: actions/checkout@master + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Record State + run: | + pwd + echo github.ref is: ${{ github.ref }} + echo GITHUB_SHA is: $GITHUB_SHA + echo github.event_name is: ${{ github.event_name }} + pip --version + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + pip install wheel + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + shell: bash + - name: Install AutoProf + run: | + cd $GITHUB_WORKSPACE/ + pip install . + pip show AutoProf + shell: bash + - name: Test with pytest + run: | + cd $GITHUB_WORKSPACE/tests/ + pwd + pytest -s + shell: bash diff --git a/autoprof/autoprofutils/Diagnostic_Plots.py b/autoprof/autoprofutils/Diagnostic_Plots.py index 2583ef31..f4da95f7 100644 --- a/autoprof/autoprofutils/Diagnostic_Plots.py +++ b/autoprof/autoprofutils/Diagnostic_Plots.py @@ -1,31 +1,18 @@ import numpy as np -from astropy.visualization import SqrtStretch, LogStretch +from astropy.visualization import LogStretch from astropy.visualization.mpl_normalize import ImageNormalize import matplotlib.pyplot as plt from matplotlib.patches import Ellipse, Wedge -import matplotlib.cm as cm import matplotlib from itertools import compress -import sys import os from .SharedFunctions import ( - _x_to_pa, - _x_to_eps, - _inv_x_to_eps, - _inv_x_to_pa, LSBImage, AddScale, AddLogo, - _average, - _scatter, - flux_to_sb, - flux_to_mag, - PA_shift_convention, autocolours, autocmap, - fluxdens_to_fluxsum_errorprop, - mag_to_flux, parametric_SuperEllipse, Rotate_Cartesian, ) @@ -647,7 +634,7 @@ def Plot_Radial_Profiles(dat, R, sb, sbE, pa, nwedges, wedgeangles, wedgewidth, ], ] - cmap = cm.get_cmap("hsv") + cmap = matplotlib.colormaps["hsv"] colorind = (np.linspace(0, 1 - 1 / nwedges, nwedges) + 0.1) % 1.0 for sa_i in range(len(wedgeangles)): CHOOSE = np.logical_and(np.array(sb[sa_i]) < 99, np.array(sbE[sa_i]) < 1) @@ -767,7 +754,6 @@ def Plot_Axial_Profiles(dat, R, sb, sbE, pa, results, options): for rd in [1, -1]: for ang in [1, -1]: key = (rd, ang) - # cmap = matplotlib.cm.get_cmap('viridis_r') norm = matplotlib.colors.Normalize(vmin=0, vmax=R[-1] * options["ap_pixscale"]) for pi, pR in enumerate(R): if pi % 3 != 0: @@ -858,7 +844,7 @@ def Plot_Axial_Profiles(dat, R, sb, sbE, pa, results, options): ) AddScale(plt.gca(), (ranges[0][1] - ranges[0][0]) * options["ap_pixscale"]) count = 0 - cmap = matplotlib.cm.get_cmap("hsv") + cmap = matplotlib.colormaps["hsv"] colorind = (np.linspace(0, 1 - 1 / 4, 4) + 0.1) % 1 colours = list(cmap(c) for c in colorind) for rd in [1, -1]: diff --git a/test/test_ESO479-G1_r.fits b/test/ESO479-G1_r.fits similarity index 100% rename from test/test_ESO479-G1_r.fits rename to test/ESO479-G1_r.fits diff --git a/test/test_config.py b/test/basic_config.py similarity index 79% rename from test/test_config.py rename to test/basic_config.py index 14fd5e2e..8b82ed71 100644 --- a/test/test_config.py +++ b/test/basic_config.py @@ -1,6 +1,6 @@ ap_process_mode = "image" -ap_image_file = "test_ESO479-G1_r.fits" +ap_image_file = "ESO479-G1_r.fits" ap_name = "testimage" ap_pixscale = 0.262 ap_zeropoint = 22.5 diff --git a/test/test_batch_config.py b/test/batch_config.py similarity index 85% rename from test/test_batch_config.py rename to test/batch_config.py index 9a0b5049..af1c0617 100644 --- a/test/test_batch_config.py +++ b/test/batch_config.py @@ -2,10 +2,10 @@ ap_n_procs = 4 ap_image_file = [ - "test_ESO479-G1_r.fits", - "test_ESO479-G1_r.fits", - "test_ESO479-G1_r.fits", - "test_ESO479-G1_r.fits", + "ESO479-G1_r.fits", + "ESO479-G1_r.fits", + "ESO479-G1_r.fits", + "ESO479-G1_r.fits", ] ap_pixscale = 0.262 ap_name = ["testbatchimage1", "testbatchimage2", "testbatchimage3", "testbatchimage4"] diff --git a/test/test_custom_config.py b/test/custom_config.py similarity index 94% rename from test/test_custom_config.py rename to test/custom_config.py index 67b2b88d..8ec709b3 100644 --- a/test/test_custom_config.py +++ b/test/custom_config.py @@ -18,7 +18,7 @@ ap_process_mode = "image" ap_doplot = True -ap_image_file = "test_ESO479-G1_r.fits" +ap_image_file = "ESO479-G1_r.fits" ap_name = "testcustomprocessing" ap_pixscale = 0.262 ap_zeropoint = 22.5 @@ -57,9 +57,7 @@ def mywriteoutput(IMG, results, options): def count_pixel_range(IMG, results, options): count = np.sum( - np.logical_and( - IMG > options["ap_mycountrange_low"], IMG < options["ap_mycountrange_high"] - ) + np.logical_and(IMG > options["ap_mycountrange_low"], IMG < options["ap_mycountrange_high"]) ) logging.info("%s: counted %i pixels in custom range" % (options["ap_name"], count)) diff --git a/test/test_forced_config.py b/test/forced_config.py similarity index 79% rename from test/test_forced_config.py rename to test/forced_config.py index d32c44c1..8d6a7ac4 100644 --- a/test/test_forced_config.py +++ b/test/forced_config.py @@ -1,6 +1,6 @@ ap_process_mode = "forced image" -ap_image_file = "test_ESO479-G1_r.fits" +ap_image_file = "ESO479-G1_r.fits" ap_name = "testforcedimage" ap_pixscale = 0.262 ap_doplot = True diff --git a/test/test_run_tests.py b/test/test_run_tests.py new file mode 100644 index 00000000..bab387fd --- /dev/null +++ b/test/test_run_tests.py @@ -0,0 +1,125 @@ +import sys +from os.path import isfile +from os import remove + +from autoprof import run_from_terminal + + +def test_basic_config(): + sys.argv = ["", "basic_config.py"] + run_from_terminal() + for checkfile in [ + "Background_hist_testimage.jpg", + "fit_ellipse_testimage.jpg", + "initialize_ellipse_optimize_testimage.jpg", + "initialize_ellipse_testimage.jpg", + "phase_profile_testimage.jpg", + "photometry_ellipse_testimage.jpg", + "photometry_testimage.jpg", + "testimage.aux", + "testimage.prof", + ]: + assert isfile(checkfile) + remove(checkfile) + + +def test_batch_config(): + import multiprocessing as mp + + mp.set_start_method("spawn", force=True) + + sys.argv = ["", "batch_config.py"] + run_from_terminal() + for imgname in ["testbatchimage1", "testbatchimage2", "testbatchimage3", "testbatchimage4"]: + for checkfile in [ + f"Background_hist_{imgname}.jpg", + f"fit_ellipse_{imgname}.jpg", + f"initialize_ellipse_optimize_{imgname}.jpg", + f"initialize_ellipse_{imgname}.jpg", + f"phase_profile_{imgname}.jpg", + f"photometry_ellipse_{imgname}.jpg", + f"photometry_{imgname}.jpg", + f"{imgname}.aux", + f"{imgname}.prof", + ]: + assert isfile(checkfile) + remove(checkfile) + + +def test_custom_config(): + sys.argv = ["", "custom_config.py"] + run_from_terminal() + for checkfile in [ + "Background_hist_testcustomprocessing.jpg", + "testcustomprocessing_mask.fits.gz", + "testcustomprocessing.aux", + ]: + print(checkfile) + assert isfile(checkfile) + remove(checkfile) + + +def test_forced_config(): + sys.argv = ["", "basic_config.py"] + run_from_terminal() + sys.argv = ["", "forced_config.py"] + run_from_terminal() + for checkfile in [ + "Background_hist_testforcedimage.jpg", + "phase_profile_testforcedimage.jpg", + "photometry_ellipse_testforcedimage.jpg", + "photometry_testforcedimage.jpg", + "testforcedimage.aux", + "testforcedimage.prof", + ]: + print(checkfile) + assert isfile(checkfile) + remove(checkfile) + + for checkfile in [ + "Background_hist_testimage.jpg", + "fit_ellipse_testimage.jpg", + "initialize_ellipse_optimize_testimage.jpg", + "initialize_ellipse_testimage.jpg", + "phase_profile_testimage.jpg", + "photometry_ellipse_testimage.jpg", + "photometry_testimage.jpg", + "testimage.aux", + "testimage.prof", + ]: + remove(checkfile) + + +def test_tree_config(): + sys.argv = ["", "tree_config.py"] + run_from_terminal() + for checkfile in [ + "axial_profile_0_testtreeimage.jpg", + "axial_profile_1_testtreeimage.jpg", + "axial_profile_2_testtreeimage.jpg", + "axial_profile_3_testtreeimage.jpg", + "axial_profile_lines_testtreeimage.jpg", + "Background_hist_testtreeimage.jpg", + "clean_image_testtreeimage.jpg", + "ellipsemodel_gen_testtreeimage.jpg", + "ellipseresidual_gen_testtreeimage.jpg", + "fit_ellipse_testtreeimage.jpg", + "initialize_ellipse_optimize_testtreeimage.jpg", + "initialize_ellipse_testtreeimage.jpg", + "mask_testtreeimage.jpg", + "phase_profile_testtreeimage.jpg", + "photometry_ellipse_testtreeimage.jpg", + "photometry_testtreeimage.jpg", + "radial_profiles_testtreeimage.jpg", + "radial_profiles_wedges_testtreeimage.jpg", + "slice_profile_testtreeimage.jpg", + "slice_profile_window_testtreeimage.jpg", + "testtreeimage.aux", + "testtreeimage.prof", + "testtreeimage_axial_profile.prof", + "testtreeimage_genmodel.fits", + "testtreeimage_slice_profile.prof", + ]: + print(checkfile) + assert isfile(checkfile) + remove(checkfile) diff --git a/test/test_tree_config.py b/test/tree_config.py similarity index 98% rename from test/test_tree_config.py rename to test/tree_config.py index d97785f3..251a18cb 100644 --- a/test/test_tree_config.py +++ b/test/tree_config.py @@ -2,7 +2,7 @@ ap_process_mode = "image" -ap_image_file = "test_ESO479-G1_r.fits" +ap_image_file = "ESO479-G1_r.fits" ap_pixscale = 0.262 ap_name = "testtreeimage" ap_doplot = True @@ -68,7 +68,7 @@ def whenrerun(IMG, results, options): "center", "isophoteinit", "branch edgeon", - ], + ], "standard": [ "isophotefit", "starmask", From b901569580110fe5603e4feab0490a7440b00efc Mon Sep 17 00:00:00 2001 From: Connor Stone Date: Thu, 15 Aug 2024 12:33:12 -0400 Subject: [PATCH 2/5] Move test to tests folder --- {test => tests}/ESO479-G1_r.fits | Bin {test => tests}/basic_config.py | 0 {test => tests}/batch_config.py | 0 {test => tests}/clearresults.sh | 0 {test => tests}/custom_config.py | 0 {test => tests}/forced_config.py | 0 {test => tests}/runtests.sh | 0 {test => tests}/test_run_tests.py | 0 {test => tests}/tree_config.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {test => tests}/ESO479-G1_r.fits (100%) rename {test => tests}/basic_config.py (100%) rename {test => tests}/batch_config.py (100%) rename {test => tests}/clearresults.sh (100%) rename {test => tests}/custom_config.py (100%) rename {test => tests}/forced_config.py (100%) rename {test => tests}/runtests.sh (100%) rename {test => tests}/test_run_tests.py (100%) rename {test => tests}/tree_config.py (100%) diff --git a/test/ESO479-G1_r.fits b/tests/ESO479-G1_r.fits similarity index 100% rename from test/ESO479-G1_r.fits rename to tests/ESO479-G1_r.fits diff --git a/test/basic_config.py b/tests/basic_config.py similarity index 100% rename from test/basic_config.py rename to tests/basic_config.py diff --git a/test/batch_config.py b/tests/batch_config.py similarity index 100% rename from test/batch_config.py rename to tests/batch_config.py diff --git a/test/clearresults.sh b/tests/clearresults.sh similarity index 100% rename from test/clearresults.sh rename to tests/clearresults.sh diff --git a/test/custom_config.py b/tests/custom_config.py similarity index 100% rename from test/custom_config.py rename to tests/custom_config.py diff --git a/test/forced_config.py b/tests/forced_config.py similarity index 100% rename from test/forced_config.py rename to tests/forced_config.py diff --git a/test/runtests.sh b/tests/runtests.sh similarity index 100% rename from test/runtests.sh rename to tests/runtests.sh diff --git a/test/test_run_tests.py b/tests/test_run_tests.py similarity index 100% rename from test/test_run_tests.py rename to tests/test_run_tests.py diff --git a/test/tree_config.py b/tests/tree_config.py similarity index 100% rename from test/tree_config.py rename to tests/tree_config.py From 6b7326ec169579ae8f705638a5e57cc1ce89d2df Mon Sep 17 00:00:00 2001 From: Connor Stone Date: Thu, 15 Aug 2024 12:47:55 -0400 Subject: [PATCH 3/5] update version control to automatic pyproject toml --- .github/workflows/cd.yaml | 100 ++++++++++++++++++++++++++++++++++++++ autoprof/__init__.py | 14 +++++- pyproject.toml | 52 ++++++++++++++++++++ setup.py | 35 ------------- 4 files changed, 165 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/cd.yaml create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 00000000..c368bdea --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,100 @@ +name: CD + +on: + workflow_dispatch: + push: + branches: + - main + - dev + release: + types: + - published + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: 3 + +jobs: + dist: + name: Distribution build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build sdist and wheel + run: pipx run build + + - uses: actions/upload-artifact@v4 + with: + path: dist + + - name: Check products + run: pipx run twine check dist/* + + test-built-dist: + needs: [dist] + name: Test built distribution + runs-on: ubuntu-latest + environment: deploy-testpypi + permissions: + id-token: write + steps: + - uses: actions/setup-python@v5 + name: Install Python + with: + python-version: "3.10" + - uses: actions/download-artifact@v4 + with: + name: artifact + path: dist + - name: List contents of built dist + run: | + ls -ltrh + ls -ltrh dist + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@v1.9.0 + with: + repository-url: https://test.pypi.org/legacy/ + verbose: true + skip-existing: true + - name: Check test pypi packages + run: | + sleep 3 + python -m pip install --upgrade pip + + echo "=== Testing wheel file ===" + # Install wheel to get dependencies and check import + python -m pip install --extra-index-url https://test.pypi.org/simple --upgrade --pre autoprof + python -c "import autoprof; print(autoprof.__version__)" + echo "=== Done testing wheel file ===" + + echo "=== Testing source tar file ===" + # Install tar gz and check import + python -m pip uninstall --yes autoprof + python -m pip install --extra-index-url https://test.pypi.org/simple --upgrade --pre --no-binary=:all: autoprof + python -c "import autoprof; print(autoprof.__version__)" + echo "=== Done testing source tar file ===" + + publish: + needs: [dist, test-built-dist] + name: Publish to PyPI + environment: deploy-pypi + permissions: + id-token: write + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + + steps: + - uses: actions/download-artifact@v4 + with: + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@v1.9.0 + if: startsWith(github.ref, 'refs/tags') diff --git a/autoprof/__init__.py b/autoprof/__init__.py index 061ea848..eb0dbdb4 100644 --- a/autoprof/__init__.py +++ b/autoprof/__init__.py @@ -3,7 +3,16 @@ from . import autoprofutils, Pipeline, pipeline_steps -__version__ = "1.2.1" +try: + from ._version import version as __version__ # noqa +except ModuleNotFoundError: + __version__ = "0.0.0" + import warnings + + warnings.warn( + "WARNING: AstroPhot version number not found. This is likely because you are running AstroPhot from a source directory." + ) + __author__ = "Connor Stone" __email__ = "connorstone628@gmail.com" @@ -16,6 +25,9 @@ def run_from_terminal(): config_file = sys.argv[1] + if config_file.strip().lower() == "--version": + print(__version__) + return try: if ".log" == sys.argv[2][-4:]: logfile = sys.argv[2] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..86108798 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["hatchling", "hatch-requirements-txt", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "autoprof" +dynamic = [ + "dependencies", + "version" +] +authors = [ + { name="Connor Stone", email="connorstone628@gmail.com" }, +] +description = "Fast, robust, deep isophotal solutions for galaxy images." +readme = "README.md" +requires-python = ">=3.9" +license = {file = "LICENSE"} +keywords = [ + "autoprof", + "photometry", + "astronomy", + "scientific computing", + "astrophysics", +] +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3" +] + +[project.urls] +Homepage = "https://autostronomy.github.io/AutoProf/" +Documentation = "https://autoprof.readthedocs.io" +Repository = "https://github.com/Autostronomy/AutoProf" +Issues = "https://github.com/Autostronomy/AutoProf/issues" + +[project.scripts] +astrophot = "autoprof:run_from_terminal" + +[tool.hatch.metadata.hooks.requirements_txt] +files = ["requirements.txt"] + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.hooks.vcs] +version-file = "autoprof/_version.py" + +[tool.hatch.version.raw-options] +local_scheme = "no-local-version" diff --git a/setup.py b/setup.py deleted file mode 100644 index 5356703f..00000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -from setuptools import setup, find_packages -import autoprof.__init__ as ap -import os - - -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() - - -setup( - name="autoprof", - version=ap.__version__, - description="Fast, robust, deep isophotal solutions for galaxy images", - long_description=read("README.md"), - long_description_content_type="text/markdown", - url="https://github.com/Autostronomy/AutoProf", - author=ap.__author__, - author_email=ap.__email__, - license="GPL-3.0 license", - packages=find_packages(), - package_data={"": ["*.png"]}, - include_package_data=True, - install_requires=list(read("requirements.txt").split("\n")), - entry_points={ - "console_scripts": [ - "autoprof = autoprof:run_from_terminal", - ], - }, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Programming Language :: Python :: 3", - ], -) From 5a128a237b2b24bda8c623e8be8b682704243241 Mon Sep 17 00:00:00 2001 From: Connor Stone Date: Thu, 15 Aug 2024 13:05:11 -0400 Subject: [PATCH 4/5] update docs --- README.md | 16 ++- autoprof/__init__.py | 2 +- docs/getting_started.rst | 7 +- docs/pipelinemanipulation.rst | 194 ++++++++++++++++++++++++++++++++++ tests/clearresults.sh | 9 -- tests/runtests.sh | 13 --- 6 files changed, 212 insertions(+), 29 deletions(-) delete mode 100644 tests/clearresults.sh delete mode 100644 tests/runtests.sh diff --git a/README.md b/README.md index 724a0502..5c661c23 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,25 @@ and [Jean-Charles Cuillandre](https://www.cfht.hawaii.edu/~jcc/). pip install autoprof ``` +Please use python version 3.9 or greater. + # Documentation -See our [documentation](https://autoprof.readthedocs.io/en/latest/) for a full description of AutoProf's capabilities +See our [documentation](https://autoprof.readthedocs.io) for a full description +of AutoProf's capabilities # Citation -Please see the [ADS Bibliographic Record](https://ui.adsabs.harvard.edu/abs/2021MNRAS.508.1870S/abstract) of the AutoProf paper for proper citation. +Please see the [ADS Bibliographic +Record](https://ui.adsabs.harvard.edu/abs/2021MNRAS.508.1870S/abstract) of the +AutoProf paper for proper citation. # Notice -This is the AutoProf isophotal code, it works great in its domain which is wherever one would use isophotal fitting. Thus it is suitable for mostly isolated, mostly resolved, objects. If you are limited by the PSF, crowding, or want to model multi-band/epoch data you may want to consider "AstroPhot" a full forward modelling code. Just [follow this link](https://github.com/Autostronomy/AstroPhot) to check it out! +This is the AutoProf isophotal code, it works great in its domain which is +wherever one would use isophotal fitting. Thus it is suitable for mostly +isolated, mostly resolved, objects. If you are limited by the PSF, crowding, or +want to model multi-band/epoch data you may want to consider "AstroPhot" a full +forward modelling code. Just [follow this +link](https://github.com/Autostronomy/AstroPhot) to check it out! diff --git a/autoprof/__init__.py b/autoprof/__init__.py index eb0dbdb4..6ba18492 100644 --- a/autoprof/__init__.py +++ b/autoprof/__init__.py @@ -10,7 +10,7 @@ import warnings warnings.warn( - "WARNING: AstroPhot version number not found. This is likely because you are running AstroPhot from a source directory." + "WARNING: AutoProf version number not found. This is likely because you are running Autoprof from a source directory." ) __author__ = "Connor Stone" diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 0495a045..db433357 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -136,14 +136,14 @@ Here is the forced photometry file just make sure to save it as a ``.py`` file:: ap_process_mode = "forced image" - ap_image_file = "test_ESO479-G1_r.fits" + ap_image_file = "ESO479-G1_r.fits" ap_name = "testforcedimage" ap_pixscale = 0.262 ap_doplot = True ap_isoclip = True ap_forcing_profile = "testimage.prof" - +To try out this pipeline, download the `test file `_. Batch Forced Photometry ----------------------- @@ -162,7 +162,7 @@ It's possible to incorporate descision trees into the AutoProf pipeline. This is ap_process_mode = "image" - ap_image_file = "test_ESO479-G1_r.fits" + ap_image_file = "ESO479-G1_r.fits" ap_pixscale = 0.262 ap_name = "testtreeimage" ap_doplot = True @@ -251,3 +251,4 @@ It's possible to incorporate descision trees into the AutoProf pipeline. This is ], } +To try out this pipeline, download the `test file `_. \ No newline at end of file diff --git a/docs/pipelinemanipulation.rst b/docs/pipelinemanipulation.rst index 744e0ead..a8747a95 100644 --- a/docs/pipelinemanipulation.rst +++ b/docs/pipelinemanipulation.rst @@ -80,3 +80,197 @@ For example, if you had your own method to run after the centering function you in your config file. Note that for *ap_new_pipeline_methods* you need only include the new function, while for *ap_new_pipeline_steps* you must write out the full pipeline steps. If you wish to skip a step, it is sometimes better to write your own "null" version of the function (and change *ap_new_pipeline_methods*) that just returns do-nothing values for it's dictionary as the other functions may still look for the output and could crash. + +Example Custom Pipeline +----------------------- + +This config file demonstrates the flexability of AutoProf pipelines to perform +custom tasks. In this case a mask is produced which removes any pixel with a +negative flux value, then computes the background level of the image. This +background is written to a custom aux file and the pixel mask is saved. No +isophote fitting is performed, only measurement of background level and a count +of pixels within a flux range. + +.. code-block:: python + + import os + from datetime import datetime + from astropy.io import fits + from time import sleep + import logging + import numpy as np + + ap_process_mode = "image" + ap_doplot = True + ap_image_file = "ESO479-G1_r.fits" + ap_name = "testcustomprocessing" + ap_pixscale = 0.262 + ap_zeropoint = 22.5 + ap_badpixel_low = 0 + + + def mywriteoutput(IMG, results, options): + saveto = options["ap_saveto"] if "ap_saveto" in options else "./" + with open(os.path.join(saveto, options["ap_name"] + ".aux"), "w") as f: + # write profile info + f.write("written on: %s\n" % str(datetime.now())) + f.write("name: %s\n" % str(options["ap_name"])) + for r in sorted(results.keys()): + if "auxfile" in r: + f.write(results[r] + "\n") + for k in sorted(options.keys()): + if k == "ap_name": + continue + f.write("option %s: %s\n" % (k, str(options[k]))) + # Write the mask data, if provided + if "mask" in results and (not results["mask"] is None): + header = fits.Header() + header["IMAGE 1"] = "mask" + hdul = fits.HDUList( + [fits.PrimaryHDU(header=header), fits.ImageHDU(results["mask"].astype(int))] + ) + hdul.writeto(saveto + options["ap_name"] + "_mask.fits", overwrite=True) + sleep(1) + # Zip the mask file because it can be large and take a lot of memory, but in principle + # is very easy to compress + os.system("gzip -fq " + saveto + options["ap_name"] + "_mask.fits") + + return IMG, {} + + + def count_pixel_range(IMG, results, options): + + count = np.sum( + np.logical_and(IMG > options["ap_mycountrange_low"], IMG < options["ap_mycountrange_high"]) + ) + + logging.info("%s: counted %i pixels in custom range" % (options["ap_name"], count)) + + return IMG, { + "auxfile count pixels in range": "In range from %.2f to %.2f there were %i pixels" + % (options["ap_mycountrange_low"], options["ap_mycountrange_low"], count) + } + + + ap_new_pipeline_steps = { + "head": [ + "mask badpixels", + "background", + "count pixel range", + "custom writebackground", + ] + } + ap_new_pipeline_methods = { + "custom writebackground": mywriteoutput, + "count pixel range": count_pixel_range, + } + + # note these parameters are not standard for AutoProf, they are only used in the custom function. + # Users can create any such parameters that they like so long as the variable begins with 'ap_' + ap_mycountrange_low = 0.2 + ap_mycountrange_high = 0.3 + +To try out this pipeline, download the `test file `_. + +Example Tree Pipeline +--------------------- + +This example shows how to create a tree pipeline, which dynamically changes the +pipeline based on the results of previous steps. + +.. code-block:: python + + import numpy as np + + ap_process_mode = "image" + + ap_image_file = "ESO479-G1_r.fits" + ap_pixscale = 0.262 + ap_name = "testtreeimage" + ap_doplot = True + ap_isoband_width = 0.05 + ap_samplegeometricscale = 0.05 + ap_truncate_evaluation = True + ap_ellipsemodel_resolution = 2.0 + + ap_fouriermodes = 4 + ap_slice_anchor = {"x": 1700.0, "y": 1350.0} + ap_slice_length = 300.0 + ap_isoclip = True + + + def My_Edgon_Fit_Method(IMG, results, options): + N = 100 + return IMG, { + "fit ellip": np.array([results["init ellip"]] * N), + "fit pa": np.array([results["init pa"]] * N), + "fit ellip_err": np.array([0.05] * N), + "fit pa_err": np.array([5 * np.pi / 180] * N), + "fit R": np.logspace(0, np.log10(results["init R"] * 2), N), + } + + + def whenrerun(IMG, results, options): + count_checks = 0 + for k in results["checkfit"].keys(): + if not results["checkfit"][k]: + count_checks += 1 + + if count_checks <= 0: # if checks all passed, carry on + return None, {"onloop": options["onloop"] if "onloop" in options else 0} + elif ( + not "onloop" in options + ): # start by simply re-running the analysis to see if AutoProf got stuck + return "head", {"onloop": 1} + elif options["onloop"] == 1 and ( + not results["checkfit"]["FFT coefficients"] + or not results["checkfit"]["isophote variability"] + ): # Try smoothing the fit the result was chaotic + return "head", {"onloop": 2, "ap_regularize_scale": 3, "ap_fit_limit": 5} + elif ( + options["onloop"] == 1 and not results["checkfit"]["Light symmetry"] + ): # Try testing larger area to find center if fit found high asymmetry (possibly stuck on a star) + return "head", {"onloop": 2, "ap_centeringring": 20} + else: # Don't try a third time, just give up + return None, {"onloop": options["onloop"] if "onloop" in options else 0} + + + ap_new_pipeline_methods = { + "branch edgeon": lambda IMG, results, options: ( + "edgeon" if results["init ellip"] > 0.8 else "standard", + {}, + ), + "branch rerun": whenrerun, + "edgeonfit": My_Edgon_Fit_Method, + } + ap_new_pipeline_steps = { + "head": [ + "background", + "psf", + "center", + "isophoteinit", + "branch edgeon", + ], + "standard": [ + "isophotefit", + "starmask", + "isophoteextract", + "checkfit", + "branch rerun", + "writeprof", + "plot image", + "ellipsemodel", + "axialprofiles", + "radialprofiles", + "sliceprofile", + ], + "edgeon": [ + "edgeonfit", + "isophoteextract", + "radsample", + "axialprofiles", + "writeprof", + ], + } + +To try out this pipeline, download the `test file `_. \ No newline at end of file diff --git a/tests/clearresults.sh b/tests/clearresults.sh deleted file mode 100644 index 6c7ffaf3..00000000 --- a/tests/clearresults.sh +++ /dev/null @@ -1,9 +0,0 @@ -rm *.jpg -rm *.log -rm *.prof -rm *.aux -rm output_*.txt -rm *model.fits -rm *_psf.fits -rm *.fits.gz -rm -r __pycache__ diff --git a/tests/runtests.sh b/tests/runtests.sh deleted file mode 100644 index fd90824f..00000000 --- a/tests/runtests.sh +++ /dev/null @@ -1,13 +0,0 @@ -echo "autoprof test" -autoprof test_config.py &> output_autoprof.txt -echo "forced test" -autoprof test_forced_config.py Forced.log &> output_forced.txt -echo "batch test" -autoprof test_batch_config.py Batch.log &> output_batch.txt -echo "decision tree test" -autoprof test_tree_config.py Tree.log &> output_tree.txt -echo "custom pipeline test" -autoprof test_custom_config.py Custom.log &> output_custom.txt -echo "checking for errors (will be written below if any):" -grep ERROR *.log -echo "all done!" From 66580fce1f3b0b4a9c742fe0cac7e8788857a307 Mon Sep 17 00:00:00 2001 From: Connor Stone Date: Thu, 15 Aug 2024 13:06:41 -0400 Subject: [PATCH 5/5] fix url --- docs/getting_started.rst | 4 ++-- docs/pipelinemanipulation.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/getting_started.rst b/docs/getting_started.rst index db433357..11c475be 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -143,7 +143,7 @@ Here is the forced photometry file just make sure to save it as a ``.py`` file:: ap_isoclip = True ap_forcing_profile = "testimage.prof" -To try out this pipeline, download the `test file `_. +To try out this pipeline, download the `test file `_. Batch Forced Photometry ----------------------- @@ -251,4 +251,4 @@ It's possible to incorporate descision trees into the AutoProf pipeline. This is ], } -To try out this pipeline, download the `test file `_. \ No newline at end of file +To try out this pipeline, download the `test file `_. \ No newline at end of file diff --git a/docs/pipelinemanipulation.rst b/docs/pipelinemanipulation.rst index a8747a95..9f63fffd 100644 --- a/docs/pipelinemanipulation.rst +++ b/docs/pipelinemanipulation.rst @@ -170,7 +170,7 @@ of pixels within a flux range. ap_mycountrange_low = 0.2 ap_mycountrange_high = 0.3 -To try out this pipeline, download the `test file `_. +To try out this pipeline, download the `test file `_. Example Tree Pipeline --------------------- @@ -273,4 +273,4 @@ pipeline based on the results of previous steps. ], } -To try out this pipeline, download the `test file `_. \ No newline at end of file +To try out this pipeline, download the `test file `_. \ No newline at end of file