From c6ccae1008f20308f8679ae0b0493486b10c0cfb Mon Sep 17 00:00:00 2001 From: WGolay Date: Wed, 14 Aug 2024 09:16:50 -0400 Subject: [PATCH 01/30] Prep for next update --- pyscope/__init__.py | 2 +- requirements.txt | 13 ++++--------- setup.cfg | 18 +++++------------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/pyscope/__init__.py b/pyscope/__init__.py index 3bf5f717..6bf642a2 100644 --- a/pyscope/__init__.py +++ b/pyscope/__init__.py @@ -76,7 +76,7 @@ import logging -__version__ = "0.2.0" +__version__ = "0.3.0" from . import utils from . import observatory diff --git a/requirements.txt b/requirements.txt index dfeb3c24..8b48b1c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -alpyca == 2.0.4 -astroplan == 0.10 +# alpyca == 2.0.4 # not in conda astropy == 6.1.2 astroquery == 0.4.7 astroscrappy == 1.2.0 @@ -7,17 +6,13 @@ ccdproc == 2.4.2 click == 8.1.7 cmcrameri == 1.9 markdown == 3.6 -numpy == 2.0.1 matplotlib == 3.9.1 +numpy == 2.0.1 oschmod == 0.3.12 -paramiko == 3.4.0 +paramiko == 3.4.1 photutils == 1.13.0 -prettytable == 3.10.0 pywin32 == 306;platform_system=='Windows' -scikit-image == 0.24.0 scipy == 1.14.0 -smplotlib == 0.0.9 -timezonefinder == 6.5.0 -tksheet == 7.2.12 +timezonefinder == 6.5.2 tqdm == 4.66.4 # twirl == 0.4.2 diff --git a/setup.cfg b/setup.cfg index 1d92b376..4bb029e5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,10 +62,9 @@ console_scripts = [options.extras_require] docs = - sphinx==7.2.6 + sphinx==8.0.2 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 - sphinxcontrib-programoutput==0.17 tests = pytest==8.1.1 @@ -73,21 +72,14 @@ tests = pytest-doctestplus==1.2.1 dev = - black==24.3.0 - build==1.2.1 + black==24.8.0 docutils==0.20.1 esbonio==0.16.4 isort==5.13.2 - pre-commit==3.7.0 + pre-commit==3.8.0 pytest==8.1.1 pytest-cov==5.0.0 - pytest-order==1.2.1 - rstcheck==6.2.1 - sphinx==7.2.6 + pytest-order==1.0.1 + sphinx==8.0.2 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 - sphinxcontrib-programoutput==0.17 - twine==5.0.0 - -[options.package_data] -pyscope = *.txt From e2b85cf0c44a657dc09c6bd22924ac32f8fef0dc Mon Sep 17 00:00:00 2001 From: WGolay Date: Mon, 9 Sep 2024 08:54:41 -0400 Subject: [PATCH 02/30] Cleanup, prep for schedule updates --- _tba/analysis/BV2gr.py | 25 - _tba/analysis/exoplanet_fitter | 319 ------ _tba/analysis/field-photom | 345 ------- _tba/analysis/find-asteroid | 307 ------ _tba/analysis/find-rocks.py | 549 ---------- _tba/analysis/find-transients.py | 375 ------- _tba/analysis/plot-photom.py | 628 ------------ _tba/analysis/plt-phot | 508 ---------- _tba/analysis/rockfinder | 174 ---- _tba/analysis/rocklister.py | 365 ------- _tba/analysis/sexphot.py | 743 -------------- _tba/analysis/sexphot2.py | 626 ------------ _tba/codecov.yml | 28 - _tba/observatory/calibration_config.txt | 13 - _tba/observatory/calibration_images.py | 355 ------- .../measure_filter_focus_offsets.py | 154 --- _tba/observatory/slew_grid.py | 219 ---- _tba/pwi4/README.txt | 15 - _tba/pwi4/close_shutter.bat | 26 - _tba/pwi4/open_shutter.bat | 26 - _tba/pwi4/platesolve.py | 98 -- _tba/pwi4/pwi4_build_model.py | 149 --- _tba/pwi4/pwi4_client.py | 953 ------------------ _tba/pwi4/pwi4_client_demo.py | 45 - _tba/pwi4/pwi4_startup.py | 60 -- _tba/pwi4/shutter_status.bat | 37 - _tba/reduction/align_images.py | 151 --- _tba/reduction/compare_fits_headers.py | 71 -- _tba/reduction/crop-image | 97 -- _tba/reduction/file_renumber.py | 41 - _tba/reduction/mvToDate | 36 - _tba/reduction/rename_fts | 62 -- _tba/reduction/retrieve_images | 118 --- _tba/reduction/sort-files | 56 - _tba/telrun/distemail | 139 --- _tba/telrun/get-asassn | 108 -- _tba/telrun/notification.py | 86 -- _tba/telrun/obs-plan | 186 ---- _tba/telrun/offsetCalc.py | 39 - _tba/telrun/rebootnotify.py | 34 - pyscope/bin/gui/theme/dark.tcl | 484 --------- pyscope/bin/gui/theme/dark/arrow-down.png | Bin 270 -> 0 bytes pyscope/bin/gui/theme/dark/arrow-right.png | Bin 261 -> 0 bytes pyscope/bin/gui/theme/dark/arrow-up.png | Bin 274 -> 0 bytes .../gui/theme/dark/button-accent-disabled.png | Bin 262 -> 0 bytes .../gui/theme/dark/button-accent-hover.png | Bin 373 -> 0 bytes .../gui/theme/dark/button-accent-pressed.png | Bin 363 -> 0 bytes .../bin/gui/theme/dark/button-accent-rest.png | Bin 377 -> 0 bytes .../bin/gui/theme/dark/button-disabled.png | Bin 301 -> 0 bytes pyscope/bin/gui/theme/dark/button-hover.png | Bin 276 -> 0 bytes pyscope/bin/gui/theme/dark/button-pressed.png | Bin 288 -> 0 bytes pyscope/bin/gui/theme/dark/button-rest.png | Bin 301 -> 0 bytes pyscope/bin/gui/theme/dark/card.png | Bin 386 -> 0 bytes pyscope/bin/gui/theme/dark/check-disabled.png | Bin 383 -> 0 bytes pyscope/bin/gui/theme/dark/check-hover.png | Bin 474 -> 0 bytes pyscope/bin/gui/theme/dark/check-pressed.png | Bin 460 -> 0 bytes pyscope/bin/gui/theme/dark/check-rest.png | Bin 475 -> 0 bytes .../bin/gui/theme/dark/check-tri-disabled.png | Bin 294 -> 0 bytes .../bin/gui/theme/dark/check-tri-hover.png | Bin 362 -> 0 bytes .../bin/gui/theme/dark/check-tri-pressed.png | Bin 358 -> 0 bytes pyscope/bin/gui/theme/dark/check-tri-rest.png | Bin 363 -> 0 bytes .../gui/theme/dark/check-unsel-disabled.png | Bin 312 -> 0 bytes .../bin/gui/theme/dark/check-unsel-hover.png | Bin 353 -> 0 bytes .../gui/theme/dark/check-unsel-pressed.png | Bin 302 -> 0 bytes .../bin/gui/theme/dark/check-unsel-rest.png | Bin 353 -> 0 bytes pyscope/bin/gui/theme/dark/empty.png | Bin 129 -> 0 bytes pyscope/bin/gui/theme/dark/entry-disabled.png | Bin 273 -> 0 bytes pyscope/bin/gui/theme/dark/entry-focus.png | Bin 335 -> 0 bytes pyscope/bin/gui/theme/dark/entry-hover.png | Bin 269 -> 0 bytes pyscope/bin/gui/theme/dark/entry-invalid.png | Bin 324 -> 0 bytes pyscope/bin/gui/theme/dark/entry-rest.png | Bin 297 -> 0 bytes .../bin/gui/theme/dark/notebook-border.png | Bin 337 -> 0 bytes pyscope/bin/gui/theme/dark/notebook.png | Bin 186 -> 0 bytes .../bin/gui/theme/dark/progress-pbar-hor.png | Bin 193 -> 0 bytes .../bin/gui/theme/dark/progress-pbar-vert.png | Bin 214 -> 0 bytes .../gui/theme/dark/progress-trough-hor.png | Bin 157 -> 0 bytes .../gui/theme/dark/progress-trough-vert.png | Bin 160 -> 0 bytes pyscope/bin/gui/theme/dark/radio-disabled.png | Bin 553 -> 0 bytes pyscope/bin/gui/theme/dark/radio-hover.png | Bin 853 -> 0 bytes pyscope/bin/gui/theme/dark/radio-pressed.png | Bin 786 -> 0 bytes pyscope/bin/gui/theme/dark/radio-rest.png | Bin 830 -> 0 bytes .../gui/theme/dark/radio-unsel-disabled.png | Bin 552 -> 0 bytes .../bin/gui/theme/dark/radio-unsel-hover.png | Bin 602 -> 0 bytes .../gui/theme/dark/radio-unsel-pressed.png | Bin 616 -> 0 bytes .../bin/gui/theme/dark/radio-unsel-rest.png | Bin 621 -> 0 bytes .../gui/theme/dark/scale-thumb-disabled.png | Bin 724 -> 0 bytes .../bin/gui/theme/dark/scale-thumb-hover.png | Bin 808 -> 0 bytes .../gui/theme/dark/scale-thumb-pressed.png | Bin 735 -> 0 bytes .../bin/gui/theme/dark/scale-thumb-rest.png | Bin 771 -> 0 bytes .../bin/gui/theme/dark/scale-trough-hor.png | Bin 216 -> 0 bytes .../bin/gui/theme/dark/scale-trough-vert.png | Bin 215 -> 0 bytes pyscope/bin/gui/theme/dark/scroll-down.png | Bin 226 -> 0 bytes .../bin/gui/theme/dark/scroll-hor-thumb.png | Bin 254 -> 0 bytes .../bin/gui/theme/dark/scroll-hor-trough.png | Bin 338 -> 0 bytes pyscope/bin/gui/theme/dark/scroll-left.png | Bin 233 -> 0 bytes pyscope/bin/gui/theme/dark/scroll-right.png | Bin 227 -> 0 bytes pyscope/bin/gui/theme/dark/scroll-up.png | Bin 236 -> 0 bytes .../bin/gui/theme/dark/scroll-vert-thumb.png | Bin 264 -> 0 bytes .../bin/gui/theme/dark/scroll-vert-trough.png | Bin 343 -> 0 bytes pyscope/bin/gui/theme/dark/separator.png | Bin 128 -> 0 bytes pyscope/bin/gui/theme/dark/sizegrip.png | Bin 276 -> 0 bytes .../gui/theme/dark/switch-off-disabled.png | Bin 733 -> 0 bytes .../bin/gui/theme/dark/switch-off-hover.png | Bin 945 -> 0 bytes .../bin/gui/theme/dark/switch-off-pressed.png | Bin 963 -> 0 bytes .../bin/gui/theme/dark/switch-off-rest.png | Bin 895 -> 0 bytes .../bin/gui/theme/dark/switch-on-disabled.png | Bin 623 -> 0 bytes .../bin/gui/theme/dark/switch-on-hover.png | Bin 927 -> 0 bytes .../bin/gui/theme/dark/switch-on-pressed.png | Bin 936 -> 0 bytes pyscope/bin/gui/theme/dark/switch-on-rest.png | Bin 859 -> 0 bytes pyscope/bin/gui/theme/dark/tab-hover.png | Bin 265 -> 0 bytes pyscope/bin/gui/theme/dark/tab-rest.png | Bin 164 -> 0 bytes pyscope/bin/gui/theme/dark/tab-selected.png | Bin 319 -> 0 bytes .../bin/gui/theme/dark/treeheading-hover.png | Bin 295 -> 0 bytes .../gui/theme/dark/treeheading-pressed.png | Bin 317 -> 0 bytes .../bin/gui/theme/dark/treeheading-rest.png | Bin 321 -> 0 bytes pyscope/bin/gui/theme/light.tcl | 489 --------- pyscope/bin/gui/theme/light/arrow-down.png | Bin 278 -> 0 bytes pyscope/bin/gui/theme/light/arrow-right.png | Bin 273 -> 0 bytes pyscope/bin/gui/theme/light/arrow-up.png | Bin 285 -> 0 bytes .../theme/light/button-accent-disabled.png | Bin 271 -> 0 bytes .../gui/theme/light/button-accent-hover.png | Bin 374 -> 0 bytes .../gui/theme/light/button-accent-pressed.png | Bin 367 -> 0 bytes .../gui/theme/light/button-accent-rest.png | Bin 384 -> 0 bytes .../bin/gui/theme/light/button-disabled.png | Bin 307 -> 0 bytes pyscope/bin/gui/theme/light/button-hover.png | Bin 306 -> 0 bytes .../bin/gui/theme/light/button-pressed.png | Bin 289 -> 0 bytes pyscope/bin/gui/theme/light/button-rest.png | Bin 303 -> 0 bytes pyscope/bin/gui/theme/light/card.png | Bin 394 -> 0 bytes .../bin/gui/theme/light/check-disabled.png | Bin 381 -> 0 bytes pyscope/bin/gui/theme/light/check-hover.png | Bin 476 -> 0 bytes pyscope/bin/gui/theme/light/check-pressed.png | Bin 467 -> 0 bytes pyscope/bin/gui/theme/light/check-rest.png | Bin 473 -> 0 bytes .../gui/theme/light/check-tri-disabled.png | Bin 299 -> 0 bytes .../bin/gui/theme/light/check-tri-hover.png | Bin 365 -> 0 bytes .../bin/gui/theme/light/check-tri-pressed.png | Bin 362 -> 0 bytes .../bin/gui/theme/light/check-tri-rest.png | Bin 367 -> 0 bytes .../gui/theme/light/check-unsel-disabled.png | Bin 324 -> 0 bytes .../bin/gui/theme/light/check-unsel-hover.png | Bin 334 -> 0 bytes .../gui/theme/light/check-unsel-pressed.png | Bin 302 -> 0 bytes .../bin/gui/theme/light/check-unsel-rest.png | Bin 333 -> 0 bytes pyscope/bin/gui/theme/light/empty.png | Bin 129 -> 0 bytes .../bin/gui/theme/light/entry-disabled.png | Bin 289 -> 0 bytes pyscope/bin/gui/theme/light/entry-focus.png | Bin 331 -> 0 bytes pyscope/bin/gui/theme/light/entry-hover.png | Bin 302 -> 0 bytes pyscope/bin/gui/theme/light/entry-invalid.png | Bin 324 -> 0 bytes pyscope/bin/gui/theme/light/entry-rest.png | Bin 308 -> 0 bytes .../bin/gui/theme/light/notebook-border.png | Bin 298 -> 0 bytes pyscope/bin/gui/theme/light/notebook.png | Bin 185 -> 0 bytes .../bin/gui/theme/light/progress-pbar-hor.png | Bin 192 -> 0 bytes .../gui/theme/light/progress-pbar-vert.png | Bin 216 -> 0 bytes .../gui/theme/light/progress-trough-hor.png | Bin 158 -> 0 bytes .../gui/theme/light/progress-trough-vert.png | Bin 161 -> 0 bytes .../bin/gui/theme/light/radio-disabled.png | Bin 523 -> 0 bytes pyscope/bin/gui/theme/light/radio-hover.png | Bin 837 -> 0 bytes pyscope/bin/gui/theme/light/radio-pressed.png | Bin 764 -> 0 bytes pyscope/bin/gui/theme/light/radio-rest.png | Bin 773 -> 0 bytes .../gui/theme/light/radio-unsel-disabled.png | Bin 521 -> 0 bytes .../bin/gui/theme/light/radio-unsel-hover.png | Bin 573 -> 0 bytes .../gui/theme/light/radio-unsel-pressed.png | Bin 636 -> 0 bytes .../bin/gui/theme/light/radio-unsel-rest.png | Bin 576 -> 0 bytes .../gui/theme/light/scale-thumb-disabled.png | Bin 658 -> 0 bytes .../bin/gui/theme/light/scale-thumb-hover.png | Bin 749 -> 0 bytes .../gui/theme/light/scale-thumb-pressed.png | Bin 675 -> 0 bytes .../bin/gui/theme/light/scale-thumb-rest.png | Bin 701 -> 0 bytes .../bin/gui/theme/light/scale-trough-hor.png | Bin 208 -> 0 bytes .../bin/gui/theme/light/scale-trough-vert.png | Bin 214 -> 0 bytes pyscope/bin/gui/theme/light/scroll-down.png | Bin 229 -> 0 bytes .../bin/gui/theme/light/scroll-hor-thumb.png | Bin 234 -> 0 bytes .../bin/gui/theme/light/scroll-hor-trough.png | Bin 321 -> 0 bytes pyscope/bin/gui/theme/light/scroll-left.png | Bin 232 -> 0 bytes pyscope/bin/gui/theme/light/scroll-right.png | Bin 223 -> 0 bytes pyscope/bin/gui/theme/light/scroll-up.png | Bin 237 -> 0 bytes .../bin/gui/theme/light/scroll-vert-thumb.png | Bin 262 -> 0 bytes .../gui/theme/light/scroll-vert-trough.png | Bin 324 -> 0 bytes pyscope/bin/gui/theme/light/separator.png | Bin 128 -> 0 bytes pyscope/bin/gui/theme/light/sizegrip.png | Bin 272 -> 0 bytes .../gui/theme/light/switch-off-disabled.png | Bin 726 -> 0 bytes .../bin/gui/theme/light/switch-off-hover.png | Bin 867 -> 0 bytes .../gui/theme/light/switch-off-pressed.png | Bin 880 -> 0 bytes .../bin/gui/theme/light/switch-off-rest.png | Bin 814 -> 0 bytes .../gui/theme/light/switch-on-disabled.png | Bin 590 -> 0 bytes .../bin/gui/theme/light/switch-on-hover.png | Bin 906 -> 0 bytes .../bin/gui/theme/light/switch-on-pressed.png | Bin 916 -> 0 bytes .../bin/gui/theme/light/switch-on-rest.png | Bin 857 -> 0 bytes pyscope/bin/gui/theme/light/tab-hover.png | Bin 295 -> 0 bytes pyscope/bin/gui/theme/light/tab-rest.png | Bin 164 -> 0 bytes pyscope/bin/gui/theme/light/tab-selected.png | Bin 318 -> 0 bytes .../bin/gui/theme/light/treeheading-hover.png | Bin 338 -> 0 bytes .../gui/theme/light/treeheading-pressed.png | Bin 318 -> 0 bytes .../bin/gui/theme/light/treeheading-rest.png | Bin 330 -> 0 bytes pyscope/bin/gui/themeSetup.tcl | 88 -- pyscope/bin/scripts/start_sync_manager | 3 - pyscope/bin/scripts/start_sync_manager.bat | 33 - pyscope/bin/scripts/start_telrun | 3 - pyscope/bin/scripts/start_telrun.bat | 33 - pyscope/gui/theme/dark.tcl | 484 --------- pyscope/gui/theme/dark/arrow-down.png | Bin 270 -> 0 bytes pyscope/gui/theme/dark/arrow-right.png | Bin 261 -> 0 bytes pyscope/gui/theme/dark/arrow-up.png | Bin 274 -> 0 bytes .../gui/theme/dark/button-accent-disabled.png | Bin 262 -> 0 bytes .../gui/theme/dark/button-accent-hover.png | Bin 373 -> 0 bytes .../gui/theme/dark/button-accent-pressed.png | Bin 363 -> 0 bytes pyscope/gui/theme/dark/button-accent-rest.png | Bin 377 -> 0 bytes pyscope/gui/theme/dark/button-disabled.png | Bin 301 -> 0 bytes pyscope/gui/theme/dark/button-hover.png | Bin 276 -> 0 bytes pyscope/gui/theme/dark/button-pressed.png | Bin 288 -> 0 bytes pyscope/gui/theme/dark/button-rest.png | Bin 301 -> 0 bytes pyscope/gui/theme/dark/card.png | Bin 386 -> 0 bytes pyscope/gui/theme/dark/check-disabled.png | Bin 383 -> 0 bytes pyscope/gui/theme/dark/check-hover.png | Bin 474 -> 0 bytes pyscope/gui/theme/dark/check-pressed.png | Bin 460 -> 0 bytes pyscope/gui/theme/dark/check-rest.png | Bin 475 -> 0 bytes pyscope/gui/theme/dark/check-tri-disabled.png | Bin 294 -> 0 bytes pyscope/gui/theme/dark/check-tri-hover.png | Bin 362 -> 0 bytes pyscope/gui/theme/dark/check-tri-pressed.png | Bin 358 -> 0 bytes pyscope/gui/theme/dark/check-tri-rest.png | Bin 363 -> 0 bytes .../gui/theme/dark/check-unsel-disabled.png | Bin 312 -> 0 bytes pyscope/gui/theme/dark/check-unsel-hover.png | Bin 353 -> 0 bytes .../gui/theme/dark/check-unsel-pressed.png | Bin 302 -> 0 bytes pyscope/gui/theme/dark/check-unsel-rest.png | Bin 353 -> 0 bytes pyscope/gui/theme/dark/empty.png | Bin 129 -> 0 bytes pyscope/gui/theme/dark/entry-disabled.png | Bin 273 -> 0 bytes pyscope/gui/theme/dark/entry-focus.png | Bin 335 -> 0 bytes pyscope/gui/theme/dark/entry-hover.png | Bin 269 -> 0 bytes pyscope/gui/theme/dark/entry-invalid.png | Bin 324 -> 0 bytes pyscope/gui/theme/dark/entry-rest.png | Bin 297 -> 0 bytes pyscope/gui/theme/dark/notebook-border.png | Bin 337 -> 0 bytes pyscope/gui/theme/dark/notebook.png | Bin 186 -> 0 bytes pyscope/gui/theme/dark/progress-pbar-hor.png | Bin 193 -> 0 bytes pyscope/gui/theme/dark/progress-pbar-vert.png | Bin 214 -> 0 bytes .../gui/theme/dark/progress-trough-hor.png | Bin 157 -> 0 bytes .../gui/theme/dark/progress-trough-vert.png | Bin 160 -> 0 bytes pyscope/gui/theme/dark/radio-disabled.png | Bin 553 -> 0 bytes pyscope/gui/theme/dark/radio-hover.png | Bin 853 -> 0 bytes pyscope/gui/theme/dark/radio-pressed.png | Bin 786 -> 0 bytes pyscope/gui/theme/dark/radio-rest.png | Bin 830 -> 0 bytes .../gui/theme/dark/radio-unsel-disabled.png | Bin 552 -> 0 bytes pyscope/gui/theme/dark/radio-unsel-hover.png | Bin 602 -> 0 bytes .../gui/theme/dark/radio-unsel-pressed.png | Bin 616 -> 0 bytes pyscope/gui/theme/dark/radio-unsel-rest.png | Bin 621 -> 0 bytes .../gui/theme/dark/scale-thumb-disabled.png | Bin 724 -> 0 bytes pyscope/gui/theme/dark/scale-thumb-hover.png | Bin 808 -> 0 bytes .../gui/theme/dark/scale-thumb-pressed.png | Bin 735 -> 0 bytes pyscope/gui/theme/dark/scale-thumb-rest.png | Bin 771 -> 0 bytes pyscope/gui/theme/dark/scale-trough-hor.png | Bin 216 -> 0 bytes pyscope/gui/theme/dark/scale-trough-vert.png | Bin 215 -> 0 bytes pyscope/gui/theme/dark/scroll-down.png | Bin 226 -> 0 bytes pyscope/gui/theme/dark/scroll-hor-thumb.png | Bin 254 -> 0 bytes pyscope/gui/theme/dark/scroll-hor-trough.png | Bin 338 -> 0 bytes pyscope/gui/theme/dark/scroll-left.png | Bin 233 -> 0 bytes pyscope/gui/theme/dark/scroll-right.png | Bin 227 -> 0 bytes pyscope/gui/theme/dark/scroll-up.png | Bin 236 -> 0 bytes pyscope/gui/theme/dark/scroll-vert-thumb.png | Bin 264 -> 0 bytes pyscope/gui/theme/dark/scroll-vert-trough.png | Bin 343 -> 0 bytes pyscope/gui/theme/dark/separator.png | Bin 128 -> 0 bytes pyscope/gui/theme/dark/sizegrip.png | Bin 276 -> 0 bytes .../gui/theme/dark/switch-off-disabled.png | Bin 733 -> 0 bytes pyscope/gui/theme/dark/switch-off-hover.png | Bin 945 -> 0 bytes pyscope/gui/theme/dark/switch-off-pressed.png | Bin 963 -> 0 bytes pyscope/gui/theme/dark/switch-off-rest.png | Bin 895 -> 0 bytes pyscope/gui/theme/dark/switch-on-disabled.png | Bin 623 -> 0 bytes pyscope/gui/theme/dark/switch-on-hover.png | Bin 927 -> 0 bytes pyscope/gui/theme/dark/switch-on-pressed.png | Bin 936 -> 0 bytes pyscope/gui/theme/dark/switch-on-rest.png | Bin 859 -> 0 bytes pyscope/gui/theme/dark/tab-hover.png | Bin 265 -> 0 bytes pyscope/gui/theme/dark/tab-rest.png | Bin 164 -> 0 bytes pyscope/gui/theme/dark/tab-selected.png | Bin 319 -> 0 bytes pyscope/gui/theme/dark/treeheading-hover.png | Bin 295 -> 0 bytes .../gui/theme/dark/treeheading-pressed.png | Bin 317 -> 0 bytes pyscope/gui/theme/dark/treeheading-rest.png | Bin 321 -> 0 bytes pyscope/gui/theme/light.tcl | 489 --------- pyscope/gui/theme/light/arrow-down.png | Bin 278 -> 0 bytes pyscope/gui/theme/light/arrow-right.png | Bin 273 -> 0 bytes pyscope/gui/theme/light/arrow-up.png | Bin 285 -> 0 bytes .../theme/light/button-accent-disabled.png | Bin 271 -> 0 bytes .../gui/theme/light/button-accent-hover.png | Bin 374 -> 0 bytes .../gui/theme/light/button-accent-pressed.png | Bin 367 -> 0 bytes .../gui/theme/light/button-accent-rest.png | Bin 384 -> 0 bytes pyscope/gui/theme/light/button-disabled.png | Bin 307 -> 0 bytes pyscope/gui/theme/light/button-hover.png | Bin 306 -> 0 bytes pyscope/gui/theme/light/button-pressed.png | Bin 289 -> 0 bytes pyscope/gui/theme/light/button-rest.png | Bin 303 -> 0 bytes pyscope/gui/theme/light/card.png | Bin 394 -> 0 bytes pyscope/gui/theme/light/check-disabled.png | Bin 381 -> 0 bytes pyscope/gui/theme/light/check-hover.png | Bin 476 -> 0 bytes pyscope/gui/theme/light/check-pressed.png | Bin 467 -> 0 bytes pyscope/gui/theme/light/check-rest.png | Bin 473 -> 0 bytes .../gui/theme/light/check-tri-disabled.png | Bin 299 -> 0 bytes pyscope/gui/theme/light/check-tri-hover.png | Bin 365 -> 0 bytes pyscope/gui/theme/light/check-tri-pressed.png | Bin 362 -> 0 bytes pyscope/gui/theme/light/check-tri-rest.png | Bin 367 -> 0 bytes .../gui/theme/light/check-unsel-disabled.png | Bin 324 -> 0 bytes pyscope/gui/theme/light/check-unsel-hover.png | Bin 334 -> 0 bytes .../gui/theme/light/check-unsel-pressed.png | Bin 302 -> 0 bytes pyscope/gui/theme/light/check-unsel-rest.png | Bin 333 -> 0 bytes pyscope/gui/theme/light/empty.png | Bin 129 -> 0 bytes pyscope/gui/theme/light/entry-disabled.png | Bin 289 -> 0 bytes pyscope/gui/theme/light/entry-focus.png | Bin 331 -> 0 bytes pyscope/gui/theme/light/entry-hover.png | Bin 302 -> 0 bytes pyscope/gui/theme/light/entry-invalid.png | Bin 324 -> 0 bytes pyscope/gui/theme/light/entry-rest.png | Bin 308 -> 0 bytes pyscope/gui/theme/light/notebook-border.png | Bin 298 -> 0 bytes pyscope/gui/theme/light/notebook.png | Bin 185 -> 0 bytes pyscope/gui/theme/light/progress-pbar-hor.png | Bin 192 -> 0 bytes .../gui/theme/light/progress-pbar-vert.png | Bin 216 -> 0 bytes .../gui/theme/light/progress-trough-hor.png | Bin 158 -> 0 bytes .../gui/theme/light/progress-trough-vert.png | Bin 161 -> 0 bytes pyscope/gui/theme/light/radio-disabled.png | Bin 523 -> 0 bytes pyscope/gui/theme/light/radio-hover.png | Bin 837 -> 0 bytes pyscope/gui/theme/light/radio-pressed.png | Bin 764 -> 0 bytes pyscope/gui/theme/light/radio-rest.png | Bin 773 -> 0 bytes .../gui/theme/light/radio-unsel-disabled.png | Bin 521 -> 0 bytes pyscope/gui/theme/light/radio-unsel-hover.png | Bin 573 -> 0 bytes .../gui/theme/light/radio-unsel-pressed.png | Bin 636 -> 0 bytes pyscope/gui/theme/light/radio-unsel-rest.png | Bin 576 -> 0 bytes .../gui/theme/light/scale-thumb-disabled.png | Bin 658 -> 0 bytes pyscope/gui/theme/light/scale-thumb-hover.png | Bin 749 -> 0 bytes .../gui/theme/light/scale-thumb-pressed.png | Bin 675 -> 0 bytes pyscope/gui/theme/light/scale-thumb-rest.png | Bin 701 -> 0 bytes pyscope/gui/theme/light/scale-trough-hor.png | Bin 208 -> 0 bytes pyscope/gui/theme/light/scale-trough-vert.png | Bin 214 -> 0 bytes pyscope/gui/theme/light/scroll-down.png | Bin 229 -> 0 bytes pyscope/gui/theme/light/scroll-hor-thumb.png | Bin 234 -> 0 bytes pyscope/gui/theme/light/scroll-hor-trough.png | Bin 321 -> 0 bytes pyscope/gui/theme/light/scroll-left.png | Bin 232 -> 0 bytes pyscope/gui/theme/light/scroll-right.png | Bin 223 -> 0 bytes pyscope/gui/theme/light/scroll-up.png | Bin 237 -> 0 bytes pyscope/gui/theme/light/scroll-vert-thumb.png | Bin 262 -> 0 bytes .../gui/theme/light/scroll-vert-trough.png | Bin 324 -> 0 bytes pyscope/gui/theme/light/separator.png | Bin 128 -> 0 bytes pyscope/gui/theme/light/sizegrip.png | Bin 272 -> 0 bytes .../gui/theme/light/switch-off-disabled.png | Bin 726 -> 0 bytes pyscope/gui/theme/light/switch-off-hover.png | Bin 867 -> 0 bytes .../gui/theme/light/switch-off-pressed.png | Bin 880 -> 0 bytes pyscope/gui/theme/light/switch-off-rest.png | Bin 814 -> 0 bytes .../gui/theme/light/switch-on-disabled.png | Bin 590 -> 0 bytes pyscope/gui/theme/light/switch-on-hover.png | Bin 906 -> 0 bytes pyscope/gui/theme/light/switch-on-pressed.png | Bin 916 -> 0 bytes pyscope/gui/theme/light/switch-on-rest.png | Bin 857 -> 0 bytes pyscope/gui/theme/light/tab-hover.png | Bin 295 -> 0 bytes pyscope/gui/theme/light/tab-rest.png | Bin 164 -> 0 bytes pyscope/gui/theme/light/tab-selected.png | Bin 318 -> 0 bytes pyscope/gui/theme/light/treeheading-hover.png | Bin 338 -> 0 bytes .../gui/theme/light/treeheading-pressed.png | Bin 318 -> 0 bytes pyscope/gui/theme/light/treeheading-rest.png | Bin 330 -> 0 bytes pyscope/gui/themeSetup.tcl | 88 -- pyscope/telrun/__init__.py | 4 - pyscope/telrun/block.py | 0 pyscope/telrun/boundary_condition.py | 2 + pyscope/telrun/calibration_block.py | 5 + pyscope/telrun/dark_field.py | 5 + pyscope/telrun/field.py | 2 + pyscope/telrun/flat_field.py | 5 + pyscope/telrun/init_queue.py | 13 - pyscope/telrun/init_telrun_dir.py | 2 - pyscope/telrun/light_field.py | 5 + pyscope/telrun/optimizer.py | 2 + pyscope/telrun/prioritizer.py | 2 + pyscope/telrun/queue.py | 2 + pyscope/telrun/schedule.py | 2 + pyscope/telrun/schedule_block.py | 18 + pyscope/telrun/scheduler.py | 2 + pyscope/telrun/telrun_block.py | 7 - pyscope/telrun/telrun_operator.py | 1 - pyscope/telrun/transition_field.py | 0 pyscope/telrun/unallocated_block.py | 5 + requirements.txt | 7 +- 367 files changed, 61 insertions(+), 10590 deletions(-) delete mode 100755 _tba/analysis/BV2gr.py delete mode 100755 _tba/analysis/exoplanet_fitter delete mode 100755 _tba/analysis/field-photom delete mode 100755 _tba/analysis/find-asteroid delete mode 100755 _tba/analysis/find-rocks.py delete mode 100755 _tba/analysis/find-transients.py delete mode 100755 _tba/analysis/plot-photom.py delete mode 100755 _tba/analysis/plt-phot delete mode 100755 _tba/analysis/rockfinder delete mode 100755 _tba/analysis/rocklister.py delete mode 100755 _tba/analysis/sexphot.py delete mode 100755 _tba/analysis/sexphot2.py delete mode 100644 _tba/codecov.yml delete mode 100755 _tba/observatory/calibration_config.txt delete mode 100755 _tba/observatory/calibration_images.py delete mode 100755 _tba/observatory/measure_filter_focus_offsets.py delete mode 100755 _tba/observatory/slew_grid.py delete mode 100644 _tba/pwi4/README.txt delete mode 100644 _tba/pwi4/close_shutter.bat delete mode 100644 _tba/pwi4/open_shutter.bat delete mode 100644 _tba/pwi4/platesolve.py delete mode 100644 _tba/pwi4/pwi4_build_model.py delete mode 100644 _tba/pwi4/pwi4_client.py delete mode 100644 _tba/pwi4/pwi4_client_demo.py delete mode 100644 _tba/pwi4/pwi4_startup.py delete mode 100644 _tba/pwi4/shutter_status.bat delete mode 100755 _tba/reduction/align_images.py delete mode 100755 _tba/reduction/compare_fits_headers.py delete mode 100644 _tba/reduction/crop-image delete mode 100755 _tba/reduction/file_renumber.py delete mode 100755 _tba/reduction/mvToDate delete mode 100755 _tba/reduction/rename_fts delete mode 100755 _tba/reduction/retrieve_images delete mode 100644 _tba/reduction/sort-files delete mode 100755 _tba/telrun/distemail delete mode 100644 _tba/telrun/get-asassn delete mode 100755 _tba/telrun/notification.py delete mode 100755 _tba/telrun/obs-plan delete mode 100755 _tba/telrun/offsetCalc.py delete mode 100755 _tba/telrun/rebootnotify.py delete mode 100755 pyscope/bin/gui/theme/dark.tcl delete mode 100755 pyscope/bin/gui/theme/dark/arrow-down.png delete mode 100755 pyscope/bin/gui/theme/dark/arrow-right.png delete mode 100755 pyscope/bin/gui/theme/dark/arrow-up.png delete mode 100755 pyscope/bin/gui/theme/dark/button-accent-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/button-accent-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/button-accent-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/button-accent-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/button-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/button-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/button-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/button-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/card.png delete mode 100755 pyscope/bin/gui/theme/dark/check-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/check-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/check-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/check-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/check-tri-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/check-tri-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/check-tri-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/check-tri-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/check-unsel-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/check-unsel-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/check-unsel-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/check-unsel-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/empty.png delete mode 100755 pyscope/bin/gui/theme/dark/entry-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/entry-focus.png delete mode 100755 pyscope/bin/gui/theme/dark/entry-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/entry-invalid.png delete mode 100755 pyscope/bin/gui/theme/dark/entry-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/notebook-border.png delete mode 100755 pyscope/bin/gui/theme/dark/notebook.png delete mode 100755 pyscope/bin/gui/theme/dark/progress-pbar-hor.png delete mode 100755 pyscope/bin/gui/theme/dark/progress-pbar-vert.png delete mode 100755 pyscope/bin/gui/theme/dark/progress-trough-hor.png delete mode 100755 pyscope/bin/gui/theme/dark/progress-trough-vert.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-unsel-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-unsel-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-unsel-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/radio-unsel-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/scale-thumb-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/scale-thumb-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/scale-thumb-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/scale-thumb-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/scale-trough-hor.png delete mode 100755 pyscope/bin/gui/theme/dark/scale-trough-vert.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-down.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-hor-thumb.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-hor-trough.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-left.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-right.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-up.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-vert-thumb.png delete mode 100755 pyscope/bin/gui/theme/dark/scroll-vert-trough.png delete mode 100755 pyscope/bin/gui/theme/dark/separator.png delete mode 100755 pyscope/bin/gui/theme/dark/sizegrip.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-off-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-off-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-off-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-off-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-on-disabled.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-on-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-on-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/switch-on-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/tab-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/tab-rest.png delete mode 100755 pyscope/bin/gui/theme/dark/tab-selected.png delete mode 100755 pyscope/bin/gui/theme/dark/treeheading-hover.png delete mode 100755 pyscope/bin/gui/theme/dark/treeheading-pressed.png delete mode 100755 pyscope/bin/gui/theme/dark/treeheading-rest.png delete mode 100755 pyscope/bin/gui/theme/light.tcl delete mode 100755 pyscope/bin/gui/theme/light/arrow-down.png delete mode 100755 pyscope/bin/gui/theme/light/arrow-right.png delete mode 100755 pyscope/bin/gui/theme/light/arrow-up.png delete mode 100755 pyscope/bin/gui/theme/light/button-accent-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/button-accent-hover.png delete mode 100755 pyscope/bin/gui/theme/light/button-accent-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/button-accent-rest.png delete mode 100755 pyscope/bin/gui/theme/light/button-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/button-hover.png delete mode 100755 pyscope/bin/gui/theme/light/button-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/button-rest.png delete mode 100755 pyscope/bin/gui/theme/light/card.png delete mode 100755 pyscope/bin/gui/theme/light/check-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/check-hover.png delete mode 100755 pyscope/bin/gui/theme/light/check-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/check-rest.png delete mode 100755 pyscope/bin/gui/theme/light/check-tri-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/check-tri-hover.png delete mode 100755 pyscope/bin/gui/theme/light/check-tri-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/check-tri-rest.png delete mode 100755 pyscope/bin/gui/theme/light/check-unsel-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/check-unsel-hover.png delete mode 100755 pyscope/bin/gui/theme/light/check-unsel-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/check-unsel-rest.png delete mode 100755 pyscope/bin/gui/theme/light/empty.png delete mode 100755 pyscope/bin/gui/theme/light/entry-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/entry-focus.png delete mode 100755 pyscope/bin/gui/theme/light/entry-hover.png delete mode 100755 pyscope/bin/gui/theme/light/entry-invalid.png delete mode 100755 pyscope/bin/gui/theme/light/entry-rest.png delete mode 100755 pyscope/bin/gui/theme/light/notebook-border.png delete mode 100755 pyscope/bin/gui/theme/light/notebook.png delete mode 100755 pyscope/bin/gui/theme/light/progress-pbar-hor.png delete mode 100755 pyscope/bin/gui/theme/light/progress-pbar-vert.png delete mode 100755 pyscope/bin/gui/theme/light/progress-trough-hor.png delete mode 100755 pyscope/bin/gui/theme/light/progress-trough-vert.png delete mode 100755 pyscope/bin/gui/theme/light/radio-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/radio-hover.png delete mode 100755 pyscope/bin/gui/theme/light/radio-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/radio-rest.png delete mode 100755 pyscope/bin/gui/theme/light/radio-unsel-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/radio-unsel-hover.png delete mode 100755 pyscope/bin/gui/theme/light/radio-unsel-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/radio-unsel-rest.png delete mode 100755 pyscope/bin/gui/theme/light/scale-thumb-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/scale-thumb-hover.png delete mode 100755 pyscope/bin/gui/theme/light/scale-thumb-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/scale-thumb-rest.png delete mode 100755 pyscope/bin/gui/theme/light/scale-trough-hor.png delete mode 100755 pyscope/bin/gui/theme/light/scale-trough-vert.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-down.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-hor-thumb.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-hor-trough.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-left.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-right.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-up.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-vert-thumb.png delete mode 100755 pyscope/bin/gui/theme/light/scroll-vert-trough.png delete mode 100755 pyscope/bin/gui/theme/light/separator.png delete mode 100755 pyscope/bin/gui/theme/light/sizegrip.png delete mode 100755 pyscope/bin/gui/theme/light/switch-off-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/switch-off-hover.png delete mode 100755 pyscope/bin/gui/theme/light/switch-off-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/switch-off-rest.png delete mode 100755 pyscope/bin/gui/theme/light/switch-on-disabled.png delete mode 100755 pyscope/bin/gui/theme/light/switch-on-hover.png delete mode 100755 pyscope/bin/gui/theme/light/switch-on-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/switch-on-rest.png delete mode 100755 pyscope/bin/gui/theme/light/tab-hover.png delete mode 100755 pyscope/bin/gui/theme/light/tab-rest.png delete mode 100755 pyscope/bin/gui/theme/light/tab-selected.png delete mode 100755 pyscope/bin/gui/theme/light/treeheading-hover.png delete mode 100755 pyscope/bin/gui/theme/light/treeheading-pressed.png delete mode 100755 pyscope/bin/gui/theme/light/treeheading-rest.png delete mode 100755 pyscope/bin/gui/themeSetup.tcl delete mode 100644 pyscope/bin/scripts/start_sync_manager delete mode 100644 pyscope/bin/scripts/start_sync_manager.bat delete mode 100644 pyscope/bin/scripts/start_telrun delete mode 100644 pyscope/bin/scripts/start_telrun.bat delete mode 100644 pyscope/gui/theme/dark.tcl delete mode 100644 pyscope/gui/theme/dark/arrow-down.png delete mode 100644 pyscope/gui/theme/dark/arrow-right.png delete mode 100644 pyscope/gui/theme/dark/arrow-up.png delete mode 100644 pyscope/gui/theme/dark/button-accent-disabled.png delete mode 100644 pyscope/gui/theme/dark/button-accent-hover.png delete mode 100644 pyscope/gui/theme/dark/button-accent-pressed.png delete mode 100644 pyscope/gui/theme/dark/button-accent-rest.png delete mode 100644 pyscope/gui/theme/dark/button-disabled.png delete mode 100644 pyscope/gui/theme/dark/button-hover.png delete mode 100644 pyscope/gui/theme/dark/button-pressed.png delete mode 100644 pyscope/gui/theme/dark/button-rest.png delete mode 100644 pyscope/gui/theme/dark/card.png delete mode 100644 pyscope/gui/theme/dark/check-disabled.png delete mode 100644 pyscope/gui/theme/dark/check-hover.png delete mode 100644 pyscope/gui/theme/dark/check-pressed.png delete mode 100644 pyscope/gui/theme/dark/check-rest.png delete mode 100644 pyscope/gui/theme/dark/check-tri-disabled.png delete mode 100644 pyscope/gui/theme/dark/check-tri-hover.png delete mode 100644 pyscope/gui/theme/dark/check-tri-pressed.png delete mode 100644 pyscope/gui/theme/dark/check-tri-rest.png delete mode 100644 pyscope/gui/theme/dark/check-unsel-disabled.png delete mode 100644 pyscope/gui/theme/dark/check-unsel-hover.png delete mode 100644 pyscope/gui/theme/dark/check-unsel-pressed.png delete mode 100644 pyscope/gui/theme/dark/check-unsel-rest.png delete mode 100644 pyscope/gui/theme/dark/empty.png delete mode 100644 pyscope/gui/theme/dark/entry-disabled.png delete mode 100644 pyscope/gui/theme/dark/entry-focus.png delete mode 100644 pyscope/gui/theme/dark/entry-hover.png delete mode 100644 pyscope/gui/theme/dark/entry-invalid.png delete mode 100644 pyscope/gui/theme/dark/entry-rest.png delete mode 100644 pyscope/gui/theme/dark/notebook-border.png delete mode 100644 pyscope/gui/theme/dark/notebook.png delete mode 100644 pyscope/gui/theme/dark/progress-pbar-hor.png delete mode 100644 pyscope/gui/theme/dark/progress-pbar-vert.png delete mode 100644 pyscope/gui/theme/dark/progress-trough-hor.png delete mode 100644 pyscope/gui/theme/dark/progress-trough-vert.png delete mode 100644 pyscope/gui/theme/dark/radio-disabled.png delete mode 100644 pyscope/gui/theme/dark/radio-hover.png delete mode 100644 pyscope/gui/theme/dark/radio-pressed.png delete mode 100644 pyscope/gui/theme/dark/radio-rest.png delete mode 100644 pyscope/gui/theme/dark/radio-unsel-disabled.png delete mode 100644 pyscope/gui/theme/dark/radio-unsel-hover.png delete mode 100644 pyscope/gui/theme/dark/radio-unsel-pressed.png delete mode 100644 pyscope/gui/theme/dark/radio-unsel-rest.png delete mode 100644 pyscope/gui/theme/dark/scale-thumb-disabled.png delete mode 100644 pyscope/gui/theme/dark/scale-thumb-hover.png delete mode 100644 pyscope/gui/theme/dark/scale-thumb-pressed.png delete mode 100644 pyscope/gui/theme/dark/scale-thumb-rest.png delete mode 100644 pyscope/gui/theme/dark/scale-trough-hor.png delete mode 100644 pyscope/gui/theme/dark/scale-trough-vert.png delete mode 100644 pyscope/gui/theme/dark/scroll-down.png delete mode 100644 pyscope/gui/theme/dark/scroll-hor-thumb.png delete mode 100644 pyscope/gui/theme/dark/scroll-hor-trough.png delete mode 100644 pyscope/gui/theme/dark/scroll-left.png delete mode 100644 pyscope/gui/theme/dark/scroll-right.png delete mode 100644 pyscope/gui/theme/dark/scroll-up.png delete mode 100644 pyscope/gui/theme/dark/scroll-vert-thumb.png delete mode 100644 pyscope/gui/theme/dark/scroll-vert-trough.png delete mode 100644 pyscope/gui/theme/dark/separator.png delete mode 100644 pyscope/gui/theme/dark/sizegrip.png delete mode 100644 pyscope/gui/theme/dark/switch-off-disabled.png delete mode 100644 pyscope/gui/theme/dark/switch-off-hover.png delete mode 100644 pyscope/gui/theme/dark/switch-off-pressed.png delete mode 100644 pyscope/gui/theme/dark/switch-off-rest.png delete mode 100644 pyscope/gui/theme/dark/switch-on-disabled.png delete mode 100644 pyscope/gui/theme/dark/switch-on-hover.png delete mode 100644 pyscope/gui/theme/dark/switch-on-pressed.png delete mode 100644 pyscope/gui/theme/dark/switch-on-rest.png delete mode 100644 pyscope/gui/theme/dark/tab-hover.png delete mode 100644 pyscope/gui/theme/dark/tab-rest.png delete mode 100644 pyscope/gui/theme/dark/tab-selected.png delete mode 100644 pyscope/gui/theme/dark/treeheading-hover.png delete mode 100644 pyscope/gui/theme/dark/treeheading-pressed.png delete mode 100644 pyscope/gui/theme/dark/treeheading-rest.png delete mode 100644 pyscope/gui/theme/light.tcl delete mode 100644 pyscope/gui/theme/light/arrow-down.png delete mode 100644 pyscope/gui/theme/light/arrow-right.png delete mode 100644 pyscope/gui/theme/light/arrow-up.png delete mode 100644 pyscope/gui/theme/light/button-accent-disabled.png delete mode 100644 pyscope/gui/theme/light/button-accent-hover.png delete mode 100644 pyscope/gui/theme/light/button-accent-pressed.png delete mode 100644 pyscope/gui/theme/light/button-accent-rest.png delete mode 100644 pyscope/gui/theme/light/button-disabled.png delete mode 100644 pyscope/gui/theme/light/button-hover.png delete mode 100644 pyscope/gui/theme/light/button-pressed.png delete mode 100644 pyscope/gui/theme/light/button-rest.png delete mode 100644 pyscope/gui/theme/light/card.png delete mode 100644 pyscope/gui/theme/light/check-disabled.png delete mode 100644 pyscope/gui/theme/light/check-hover.png delete mode 100644 pyscope/gui/theme/light/check-pressed.png delete mode 100644 pyscope/gui/theme/light/check-rest.png delete mode 100644 pyscope/gui/theme/light/check-tri-disabled.png delete mode 100644 pyscope/gui/theme/light/check-tri-hover.png delete mode 100644 pyscope/gui/theme/light/check-tri-pressed.png delete mode 100644 pyscope/gui/theme/light/check-tri-rest.png delete mode 100644 pyscope/gui/theme/light/check-unsel-disabled.png delete mode 100644 pyscope/gui/theme/light/check-unsel-hover.png delete mode 100644 pyscope/gui/theme/light/check-unsel-pressed.png delete mode 100644 pyscope/gui/theme/light/check-unsel-rest.png delete mode 100644 pyscope/gui/theme/light/empty.png delete mode 100644 pyscope/gui/theme/light/entry-disabled.png delete mode 100644 pyscope/gui/theme/light/entry-focus.png delete mode 100644 pyscope/gui/theme/light/entry-hover.png delete mode 100644 pyscope/gui/theme/light/entry-invalid.png delete mode 100644 pyscope/gui/theme/light/entry-rest.png delete mode 100644 pyscope/gui/theme/light/notebook-border.png delete mode 100644 pyscope/gui/theme/light/notebook.png delete mode 100644 pyscope/gui/theme/light/progress-pbar-hor.png delete mode 100644 pyscope/gui/theme/light/progress-pbar-vert.png delete mode 100644 pyscope/gui/theme/light/progress-trough-hor.png delete mode 100644 pyscope/gui/theme/light/progress-trough-vert.png delete mode 100644 pyscope/gui/theme/light/radio-disabled.png delete mode 100644 pyscope/gui/theme/light/radio-hover.png delete mode 100644 pyscope/gui/theme/light/radio-pressed.png delete mode 100644 pyscope/gui/theme/light/radio-rest.png delete mode 100644 pyscope/gui/theme/light/radio-unsel-disabled.png delete mode 100644 pyscope/gui/theme/light/radio-unsel-hover.png delete mode 100644 pyscope/gui/theme/light/radio-unsel-pressed.png delete mode 100644 pyscope/gui/theme/light/radio-unsel-rest.png delete mode 100644 pyscope/gui/theme/light/scale-thumb-disabled.png delete mode 100644 pyscope/gui/theme/light/scale-thumb-hover.png delete mode 100644 pyscope/gui/theme/light/scale-thumb-pressed.png delete mode 100644 pyscope/gui/theme/light/scale-thumb-rest.png delete mode 100644 pyscope/gui/theme/light/scale-trough-hor.png delete mode 100644 pyscope/gui/theme/light/scale-trough-vert.png delete mode 100644 pyscope/gui/theme/light/scroll-down.png delete mode 100644 pyscope/gui/theme/light/scroll-hor-thumb.png delete mode 100644 pyscope/gui/theme/light/scroll-hor-trough.png delete mode 100644 pyscope/gui/theme/light/scroll-left.png delete mode 100644 pyscope/gui/theme/light/scroll-right.png delete mode 100644 pyscope/gui/theme/light/scroll-up.png delete mode 100644 pyscope/gui/theme/light/scroll-vert-thumb.png delete mode 100644 pyscope/gui/theme/light/scroll-vert-trough.png delete mode 100644 pyscope/gui/theme/light/separator.png delete mode 100644 pyscope/gui/theme/light/sizegrip.png delete mode 100644 pyscope/gui/theme/light/switch-off-disabled.png delete mode 100644 pyscope/gui/theme/light/switch-off-hover.png delete mode 100644 pyscope/gui/theme/light/switch-off-pressed.png delete mode 100644 pyscope/gui/theme/light/switch-off-rest.png delete mode 100644 pyscope/gui/theme/light/switch-on-disabled.png delete mode 100644 pyscope/gui/theme/light/switch-on-hover.png delete mode 100644 pyscope/gui/theme/light/switch-on-pressed.png delete mode 100644 pyscope/gui/theme/light/switch-on-rest.png delete mode 100644 pyscope/gui/theme/light/tab-hover.png delete mode 100644 pyscope/gui/theme/light/tab-rest.png delete mode 100644 pyscope/gui/theme/light/tab-selected.png delete mode 100644 pyscope/gui/theme/light/treeheading-hover.png delete mode 100644 pyscope/gui/theme/light/treeheading-pressed.png delete mode 100644 pyscope/gui/theme/light/treeheading-rest.png delete mode 100644 pyscope/gui/themeSetup.tcl create mode 100644 pyscope/telrun/block.py create mode 100644 pyscope/telrun/boundary_condition.py create mode 100644 pyscope/telrun/calibration_block.py create mode 100644 pyscope/telrun/dark_field.py create mode 100644 pyscope/telrun/field.py create mode 100644 pyscope/telrun/flat_field.py delete mode 100644 pyscope/telrun/init_queue.py create mode 100644 pyscope/telrun/light_field.py create mode 100644 pyscope/telrun/optimizer.py create mode 100644 pyscope/telrun/prioritizer.py create mode 100644 pyscope/telrun/queue.py create mode 100644 pyscope/telrun/schedule.py create mode 100644 pyscope/telrun/schedule_block.py create mode 100644 pyscope/telrun/scheduler.py delete mode 100644 pyscope/telrun/telrun_block.py create mode 100644 pyscope/telrun/transition_field.py create mode 100644 pyscope/telrun/unallocated_block.py diff --git a/_tba/analysis/BV2gr.py b/_tba/analysis/BV2gr.py deleted file mode 100755 index d04bc5ed..00000000 --- a/_tba/analysis/BV2gr.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -# Convert B,V to Sloan g,r -# Reference: Bilir et al. 2005 -# 22 Feb 2017 RLM - -import numpy as np - -ans = input("Enter B,V (separated by comma): ") -B, V = [float(x) for x in ans.split(",")] - -g = V + 0.634 * (B - V) - 0.108 -g_r = 1.124 * (B - V) - 0.252 -r = g - g_r -print("B = %.2f, V = %.2f, B-V = %.2f" % (B, V, B - V)) -print("g = %.2f, r = %.2f, g-r = %.2f" % (g, r, g - r)) - -""" -# Jordi 2005 -g_V = 0.630*(B-V) - 0.124 -g_B = -0.370*(B-V) - 0.124 - -print 'g-V = %.2f, %.2f' % (g_V, g-V) -print 'g-B = %.2f, %.2f' % (g_B, g-B) -""" diff --git a/_tba/analysis/exoplanet_fitter b/_tba/analysis/exoplanet_fitter deleted file mode 100755 index 2ab8a139..00000000 --- a/_tba/analysis/exoplanet_fitter +++ /dev/null @@ -1,319 +0,0 @@ -#!/usr/bin/env python - -# Exoplanet transit plotter -# v1.0 15 Jan 2016 Jacob Isbell - -import math -from sys import argv - -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -from astropy.time import Time -from scipy import optimize - - -def dflux(p, z): - """Takes radius ratio and distance z as arguments. - Returns the stellar intensity when planet is at distance z from center. Relative to 1. - """ - lam = 0 - if p + 1 < z: - lam = 0 - elif z > abs(1 - p) and z <= 1 + p: - k1 = math.acos((1 - p**2 + z**2) / (2 * z)) - k0 = math.acos((p**2 + z**2 - 1) / (2 * p * z)) - lam = (1 / math.pi) * ( - k0 * (p**2) - + k1 - - math.sqrt((4 * z**2 - math.pow(1 + (-(p**2)) + z**2, 2)) / 4) - ) - elif z <= 1 - p: - lam = p**2 - elif z <= p - 1: - lam = 1 - fraction = 1 - lam - return limb_darkening(p, z) * fraction - - -def limb_darkening(p, z): - """Calculates and returns the intensity of the star at distance z from center - which is affected by linear limb darkening""" - i = 1 - if not (z > 1 + p): - q = math.atan(z / D_to_star) - i = 1 - w * (1 - math.cos(q)) - return i - - -def dmag(t, params): - """Function to call from other scripts, houses the process of computing - intensity over time""" - p, b, v = params[:] - z = np.sqrt((v * t) ** 2 + b**2) # distance from center of star to center of planet - # print (p, z) - f = dflux(p, z) - mag = 2.5 * math.log(f) # converts fractional intensity to magnitude change - - return mag - - -def transit_center_index(smooth): - """Returns duration of transit (time from "limb" to "limb" of lightcurve) - as well as the time it takes to do from "limb" to bottom (called tau)""" - obs_data = np.array(smooth) - k = 5 - beg_limb = np.median(obs_data[:k]) - blimb_end = 0 - elimb_beg = 0 - for i in range(0, len(smooth) - (k - 1), k): - rng_med = np.median(obs_data[i : i + k]) - if ( - abs(rng_med - beg_limb) >= 0.013 - ): # mmag. Assumed minimum of Gemini's visibility - blimb_end = i - break - l = len(smooth) - end_limb = np.median( - [ - obs_data[l - 1], - obs_data[l - 2], - obs_data[l - 3], - obs_data[l - 4], - obs_data[l - 5], - ] - ) - for i in range(l, 0, -k): - rng_med = np.median(obs_data[i : (i - k - 1) : -1]) - if ( - abs(rng_med - end_limb) >= 0.013 - ): # mmag. Assumed minimum of Gemini's visibility - elimb_beg = i - break - center = int((blimb_end + elimb_beg) / 2) - limb_avg = 0.5 * ( - np.median(obs_data[:blimb_end]) + np.median(obs_data[l - 1 : elimb_beg : -1]) - ) - return center, limb_avg - - -def prepare_curve_data(filename, reduce=False): - """Reads data from file and processes it to use in other functions - Takes a filename as argument. Reduce is optional, and takes the median of every 5 - data points to smooth out the data. It is usually unnecessary.""" - file = open(filename, "r") - line1 = file.readline() # skip first line - all_data = file.readlines() - file.close() - time, obs_data, obs_err = [], [], [] - for line in all_data: - d = line.split() - time.append(float(d[6])) - obs_data.append(-1 * float(d[9])) - obs_err.append(float(d[10])) - smooth = [] - smooth_time = [] - sigma = [] - """Takes a median of every five data points to smooth the data. Optional""" - if reduce: - for i in range(0, len(v0) - 4, 5): - med = np.median([v0[i], v0[i + 1], v0[i + 2], v0[i + 3], v0[i + 4]]) - medTime = np.median( - [time[i], time[i + 1], time[i + 2], time[i + 3], time[i + 4]] - ) - medErr = np.median( - [err0[i], err0[i + 1], err0[i + 2], err0[i + 3], err0[i + 4]] - ) - smooth.append(med) - smooth_time.append(medTime) - sigma.append(medErr) - else: - smooth = obs_data - smooth_time = time - sigma = obs_err - """Subtracts any overall slope the data might have by comparing - the median of the first few smooth points to the last few""" - med_beg = np.median([smooth[0], smooth[1], smooth[2]]) - mx = len(smooth) - med_end = np.median([smooth[mx - 1], smooth[mx - 2], smooth[mx - 3]]) - slope = (med_end - med_beg) / mx - no_slope_smooth = [smooth[x] - slope * x for x in range(len(smooth))] - """Estimate transit center using minimum value of smooth - AND KEEP MHJD FOR LATER CONVERSION BACK TO UT""" - MHJD_list = [x for x in smooth_time] - c, limb_avg = transit_center_index(smooth) - """Move the light curve to y=0 because absolute height doesn't matter, only relative change.""" - processed_obs_data = [ - no_slope_smooth[i] - limb_avg for i in range(len(no_slope_smooth)) - ] - """Center of transit time""" - midTime = (smooth_time[c] - smooth_time[0]) * 24 * 3600 - """Convert smooth_time to a difference from center""" - dTime = [ - ((smooth_time[x] - smooth_time[0]) * 24 * 3600) - midTime - for x in range(len(smooth_time)) - ] - - return processed_obs_data, dTime, sigma, MHJD_list - - -def chi_squared(params, *args): - """Calculates the distance from the actual data using weighted least squares. - This is the function that is minimized.""" - time, data, sigma = args - error = 0 - for n in range(len(time)): - error += ((dmag(time[n], params) - data[n]) / sigma[n]) ** 2 - error = error / (len(time) - len(params)) - return error - - -def uncertainty(residuals): - variance = 0 - for x in residuals: - variance = variance + x**2 - unc = math.sqrt(variance / (len(residuals) - 3)) - return unc - - -def residuals(model_data, data): - """Returns difference from each model data point to the corresponding observation data - as an array""" - a_md = np.asarray(model_data) - a_d = np.asarray(data) - resid = np.subtract(a_md, a_d) - return resid - - -def MHJD_to_UT_list(time): - """Takes a list of MHJD time points and converts them to UT time for - representation on the plot. Returns list of UT times.""" - ut = [x for x in time] - for i in range(len(ut)): - ut[i] = ut[i] + 0.5 - ut[i] = ut[i] - int(ut[i]) - ut[i] = ut[i] * 24.0 - return ut - - -def calculate_date(JD): - """Takes Julian Date (JD) of observation and converts it to calendar date. - Returns calendar date and start time of observation""" - t = Time(JD, format="jd") - date = t.iso - return date - - -def plot_values( - title, - subtitle, - time, - transit_values, - transit_uncertainties, - model_values, - residual_values, -): - """Function to plot observation data, model data, and residuals on the same plot with titles and legend. - Takes main title, subtitle as strings. transit_values and transit_uncertainties are lists of the data points - given by the photout file. model_values is a list of simulated data points using the calculated parameters. - residual values is the list of differences between model and observational data.""" - # create plot - fig = plt.figure(figsize=(15, 10)) - # Obs data has known uncertainties, so plotting with errorbars. Alpha tweaked so model is visible. - plt.errorbar( - time, - transit_values, - yerr=transit_uncertainties, - fmt=".", - label="Observed", - alpha=0.7, - ) - # Model and residuals plotted as distinct points that correspond to obs data - plt.plot(time, model_values, ".", label="Model") - plt.plot(time, residual_values, ".", label="Residuals") - # Labeling axes - plt.xlabel("UT Time (hours)") - plt.ylabel("Differential Magnitude (mag)") - # Adding titles, main title first - plt.suptitle(title) - plt.title(subtitle) - # Add grid for clarity - plt.grid() - # Add plot legend to bottom right corner of graph - plt.legend(bbox_to_anchor=(1, 0.2)) - name = title.split()[0] + "model.png" - plt.savefig(name, bbinches="tight") - print(("Completed. Graph is called %s" % name)) - - -# ============= MAIN ============== - -# Define constants -R_SUN = 695500.0 -D_to_star = 293 * (9.46e15) / R_SUN # in units of solar radius -R_JUPITER = 71492.0 -R_WASP10 = 0.7 * R_SUN -w = 0.5 # limb darkening coefficient - -# Get file to be processed, either from initial call or from user input -fname = "" -if len(argv) == 1: - fname = input("Please enter a photometric file (.photout) to analyze: ") -else: - fname = argv[1] - -# Read photout file, and return observation data for use in minimization -transit_values, time, transit_uncertainties, MHJD_list = prepare_curve_data(fname) - -# Initial Parameter Values -- GUESSES -p = 0.15 -b = 0.5 -v = 0.001 -x0 = (p, b, v) - -# Minimizes parameter values and returns them along with least squares error. Uses a scipy minimzation function. -method = "Nelder-Mead" -res = optimize.minimize( - chi_squared, x0, args=(time, transit_values, transit_uncertainties), method=method -) -params = [res.x[0], res.x[1], res.x[2]] -error = res.fun -print(("Weighted Least Squares Value: %f" % error)) -print( - ( - "Radius ratio: %f of parent star \t Impact Parameter: %f of parent star\t Fractional Velocity: %f of parent star/second" - % (params[0], params[1], params[2]) - ) -) - -# Uses calculated parameter values to calculate model values at each data point. -model_values = [dmag(x, params) for x in time] -# Subtracts model values from transit values to calculate the model's residuals -model_residuals = residuals(model_values, transit_values) -model_uncertainty = uncertainty(model_residuals) -model_residuals = ( - model_residuals + 0.04 -) # for clarity in plotting -- shifts residuals away from 0 so they're not in the way - -# Converts MHJD to a list of UT times for plotting -ut_time_list = MHJD_to_UT_list(MHJD_list) -# From MHJD given in data file, calculate first the JD of the observation then the calendar date -obs_JD = MHJD_list[0] + 2449000 # adding 2449000 to change MJD to JD -obs_date = calculate_date(obs_JD) - -# Plotting the model, observational data, and residuals. -TARGET = fname.split(".")[0] -title = TARGET + " (Observation Start: " + obs_date + ")" -subtitle = ( - r"$R_p/R_*$ = %.2f, Impact Parameter = %.2f, P = %.2f days, $\sigma$ = %.2f mmag" - % (params[0], abs(params[1]), params[2], model_uncertainty * 1.0e3) -) -plot_values( - title, - subtitle, - ut_time_list, - transit_values, - transit_uncertainties, - model_values, - model_residuals, -) diff --git a/_tba/analysis/field-photom b/_tba/analysis/field-photom deleted file mode 100755 index 613b5c8c..00000000 --- a/_tba/analysis/field-photom +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env python - -# field-photom -# Finds all stars in FITS image using sextractor, does absolute photometry based on the zero-point magnitude, eith in the FITS header or user-specified -# v. 1.0 26 March 2019 RLM - -vers = "%prog 1.0, 26 Mar 2019" - -import glob -import os -import sys -import warnings - -import matplotlib as mpl -import numpy as np -from astropy import units as u -from astropy.coordinates import SkyCoord -from astropy.io import fits -from astropy.io.fits import getheader, setval, update -from scipy.optimize import minimize - -mpl.use("Agg") -from optparse import OptionParser - -import matplotlib.pyplot as plt - -# Avoid annoying warning about matplotlib building the font cache -warnings.filterwarnings("ignore") - - -def get_args(): - usage = "Usage: %prog [options] FITS files[s]" - parser = OptionParser(description="Program %prog", usage=usage, version=vers) - parser.add_option( - "-s", - dest="sigma", - metavar="sigma", - action="store", - type=float, - default=3, - help="Sextractor detection threshold [default 3 sigma]", - ) - parser.add_option( - "-t", - dest="outlier", - metavar="outlier", - action="store", - type=float, - default=3, - help="Outlier trim threshold [default 3 sigma]", - ) - parser.add_option( - "-d", - dest="delta", - metavar="delta", - action="store", - type=float, - default=5, - help="Sextractor position tolerance, arcsec [default 5]", - ) - parser.add_option( - "-z", - dest="zmag", - metavar="Write", - action="store", - type=float, - default=0, - help="Zero point magnitude [overrides FITS header value]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - return parser.parse_args() - - -def get_hdrdata(ftsfile): - hdr = getheader(ftsfile, 0) - jd = hdr["JD"] - date = hdr["DATE-OBS"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"][0] - airmass = hdr["AIRMASS"] - if "ZMAG" in hdr: - zmag = hdr["ZMAG"] - else: - zmag = "" - if "EGAIN" in hdr: - egain = hdr["EGAIN"] - else: - egain = 1.00 - nbin = hdr["XBINNING"] # Assume same for y binning - arcsec_pixel = np.abs(hdr["CDELT1"] * 3600.0) - return jd, date, exptime, filter, arcsec_pixel, nbin, airmass, zmag, egain - - -def get_sexinfo(sexname, exptime, arcsec_pixel): - fn = open(sexname, "r") - lines = fn.readlines()[15:] - Nr = [] - Ra = [] - Dec = [] - Snr = [] - Flux = [] - Fluxerr = [] - Fwhm = [] - V = [] - Verr = [] - for line in lines: - ( - nr, - flux, - fluxerr, - dum, - dum, - x_pix, - y_pix, - ra_deg, - dec_deg, - profile_x, - profile_y, - pa, - fwhm_pixel, - dum, - flag, - ) = [float(x) for x in line.split()] - v = -2.5 * np.log10(flux / exptime) - if np.isnan(v) or flux == 0 or fluxerr == 0: - continue - snr = flux / fluxerr - verr = 2.5 * (fluxerr / flux) # Expanding log10(1+x) ~ 2.5x - Ra.append(ra_deg) - Dec.append(dec_deg) - Flux.append(flux) - Fluxerr.append(fluxerr) - Fwhm.append(fwhm_pixel * np.abs(arcsec_pixel)) - Snr.append(snr) - V.append(v) - Verr.append(verr) - fn.close() - - # Trim list to stars by restricting fwhm values - fwhm_min = 1.4 - fwhm_max = 4.0 - A = list(zip(Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr)) - B = [] - for j in range(len(A)): - if fwhm_min < A[j][5] < fwhm_max: - B.append(A[j]) - Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr = list(zip(*B)) - V = np.array(V) - Verr = np.array(Verr) - return Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr - - -def calc_snr(mag_star, mag_sky): - """SNR of a star, given star magnitude, exp. time, zero point mag, camera parameters, and sky brightness - gain is in e/ADU; fwhm is in arcsec; plate_scale is arcsec/binned pixel""" - global RN, DC, gain, arcsec_pixel, nbin, zp, exptime, fwhm - # Binned pixels in FWHM - npix = (fwhm / arcsec_pixel) ** 2 - star_counts = gain * exptime * (10 ** (0.4 * (zp - mag_star))) - # mag_sky is in magnitudes per square arcsec - sky_counts = gain * exptime * fwhm**2 * (10 ** (0.4 * (zp - mag_sky))) - # Dark current is in e/pixel/sec - dc_counts = DC * exptime * (npix * nbin) ** 2 - # All counts in electrons - snr = star_counts / np.sqrt(star_counts + sky_counts + dc_counts + RN**2) - return snr - - -def solve_mag_sky(Snr_obs): - mag_sky = 20 # Initial guess for sky background (mag per sec^2) - res = minimize(chisq_snr, mag_sky, args=(Snr_obs, Mag_obs), method="Nelder-Mead") - mag_sky = res.x[0] - success = res.success - return mag_sky, success - - -def chisq_snr(mag_sky, *args): - Snr_obs, Mag_obs = args - Snr_mod = calc_snr(Mag_obs, mag_sky) - diff_wt = (Snr_obs - Snr_mod) / Snr_mod - chisq = np.sum(np.abs(diff_wt)) - return chisq - - -def fchisq(zp, *args): - Mag_ref, Mag_obs, Mag_obs_err = args - if len(Mag_ref) > 0: - chisq = np.sum(((Mag_ref - (Mag_obs + zp)) / Mag_obs_err) ** 2) / len(Mag_ref) - else: - chisq = 1.0e99 - return chisq - - -def trim(indices, A): - # Trims arrays packed in A, dropping elements with given indices - B = [] - for a in A: - B.append(np.delete(a, indices)) - return B - - -def solve_k_color(Mag_obs, Mag_obs_err, Mag_ref, Color): - k_color = 0.1 # initial guess - res = minimize( - kcolor_chisq, - k_color, - args=(Mag_obs, Mag_obs_err, Mag_ref, Color), - method="Nelder-Mead", - ) - k_color = res.x[0] - success = res.success - return k_color, success - - -def kcolor_chisq(k_color, *args): - Mag_obs, Mag_obs_err, Mag_ref, Color = args - diff_wt = (Mag_obs + k_color * Color - Mag_ref) / Mag_obs_err - chisq = np.sum(diff_wt**2) - return chisq - - -def sigma_mark(sigma): - if sigma < 2.0: - mark = " " - elif 2.0 <= sigma <= 2.5: - mark = "* " - elif 2.5 < sigma <= 3.0: - mark = "** " - elif sigma > 3.0: - mark = "***" - return mark - - -# ======== MAIN ================ - -# Sextractor config file path -sex_path = "/usr/local/sextractor/default.sex" - -# Camera parameters IKON L936 -gain_unbinned = 1.0 # e/ADU -RN = 6.5 # e, From manufacturers spec sheet -DC = 0.001 # e/unbinned pixel/sec at -30 C - -# Extinction coefficients -k_g = 0.28 -k_r = 0.11 - -k_color_g = 0.10 -k_color_r = 0.10 - -# Get command line arguments, assign parameter values -(opts, args) = get_args() - -ftsfile = args[0] -detect_threshold = opts.sigma # Sextractor detection threshold [sigma] -outlier_threshold = ( - opts.outlier -) # Threshold level to reject a star if obs-calc > threshold (sigma) -delta = opts.delta / 3600.0 # Sextractor Tolerance for matching star positions [deg] -verbose = opts.verbose -zmag = opts.zmag - -# Run sextractor, generate output file, get header info, fill arrays, solve for zero-point magnitude -Zp = [] -Zperr = [] -Diff = [] -Mag_obs_all = [] - -# Make sure FTS file is valid and has a WCS solution, quit if not -try: - hdr = getheader(ftsfile, 0) -except: - sys.exit("Cannot retrieve header information from %s, exiting" % ftsfile) -if "CRVAL1" not in hdr: - sys.exit("No WCS solution in %s, exiting" % ftsfile) - -# Get useful header info [NB not currently using nbin]. Note: EGAIN parameter doesn't seem to be correct, so use gain at top of program -( - jd, - date, - exptime, - filter, - arcsec_pixel, - nbin, - airmass, - zmag_hdr, - do_not_use_this_gain, -) = get_hdrdata(ftsfile) -gain = gain_unbinned * np.sqrt(nbin) - -# Decide which zpmag to use -if zmag != 0: - zpmag = zmag - if verbose: - print("%s: Using specified zero-point magnitude = %.2f" % (ftsfile, zmag)) -elif zmag_hdr != "": - zpmag = zmag_hdr - if verbose: - print("%s: Using zero-point magnitude = %.2f in FITS header" % (ftsfile, zmag)) -else: - sys.exit("No ZP magnitude specified (-z) and none in FITS header, exiting") - -# Run sextractor -sexname = os.path.abspath(ftsfile).split(".")[0] + ".sexout" -if verbose: - print( - "Running sextractor on %s with detection threshold = %.1f sigma" - % (ftsfile, detect_threshold) - ) -os.system( - "/usr/local/bin/sex %s -c %s -CATALOG_NAME %s -DETECT_THRESH %.1f -VERBOSE_TYPE QUIET" - % (ftsfile, sex_path, sexname, detect_threshold) -) - -# Get position, magnitude info for each listed star in output file -Ra_obs, Dec_obs, Snr, Flux, Fluxerr, Fwhm_obs, Mag_obs, Mag_obs_err = get_sexinfo( - sexname, exptime, arcsec_pixel -) -nobs = len(Ra_obs) -if verbose: - print("Sextractor found %i stars" % nobs) - -# Add zero-point magnitude -Mag_obs += zpmag - -# Write output to a file -N = len(Ra_obs) -rootname = os.path.splitext(ftsfile)[0] -outfile = rootname + ".field_photom" -fn = open(outfile, "w") -fn.write("RA[deg] Dec[deg] Mag Sigma Snr\n") -for j in range(N): - fn.write( - "%9.5f %9.5f %5.2f %5.2f %6.1f\n" - % (Ra_obs[j], Dec_obs[j], Mag_obs[j], Mag_obs_err[j], Snr[j]) - ) -fn.close() -print("Wrote output file %s" % outfile) diff --git a/_tba/analysis/find-asteroid b/_tba/analysis/find-asteroid deleted file mode 100755 index 83465a38..00000000 --- a/_tba/analysis/find-asteroid +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env python - -# find-asteroid: Finds moving objects by comparing three images, removing fixed stars, and fitting for rectilinear motion in remaining objects - -import os -import sys -from optparse import OptionParser - -import numpy as np -import sewpy -from astropy.coordinates import SkyCoord -from astropy.io import fits -from astropy.table import Table - -vers = "%prog 1.0 5-Dec-2016" - - -def get_args(): - global parser - parser = OptionParser( - description="%prog finds moving object given exactly three input images", - version=vers, - ) - parser.add_option( - "-f", - dest="ftsfiles", - metavar="FITS images", - action="store", - default="", - help="comma-separated list of images [required]", - ) - parser.add_option( - "-m", - dest="min_mag", - metavar="Minimum mag.", - action="store", - type=float, - default=21.0, - help="Minimum magnitude [default 21.0]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - return parser.parse_args() - - -def hdr_info(fitsname): - # Returns header info, substituting default values for zmag if needed - hdr = fits.open(fitsname)[0].header - filter = hdr["FILTER"][0] - z = hdr["AIRMASS"] - exptime = hdr["EXPTIME"] - if filter == "G": - k = 0.28 - elif filter == "R": - k = 0.13 - elif filter == "W": - k = 0.05 - elif filter == "N": - k = 0.3 - elif filter == "B": - k = 0.35 - else: - k = 0.2 - if "ZMAG" in hdr: - zmag = hdr["ZMAG"] - else: - if filter == "B": - zmag = 20.0 - elif filter == "G": - zmag = 21.05 - elif filter == "R": - zmag = 20.20 - elif filter == "W": - zmag = 20.65 - elif filter == "N": - zmag = 21.8 - else: - zmag = 20.0 - - print( - "WARNING: No Zero-point magnitude in header, using assumed zmag = %.2f , k = %.2f based on filter %s" - % (zmag, k, filter) - ) - return zmag, k, exptime, z - - -def get_stars(fitsname, theta_max, min_mag): - """ - Uses sewpy to retrieves star positions, fluxes from Table T, - omitting any with flags != 0, theta > theta_max - """ - zmag, k, exptime, z = hdr_info(fitsname) - out = sew(fitsname) - T = out["table"] # this is an astropy table - adu = T["FLUX_BEST"] - adu = [x / exptime for x in adu] - mag = zmag - 1.091 * np.log(adu) - z * k - N = len(T) - RA_deg = [] - Dec_deg = [] - Coords = [] - Radius = [] - Mag = [] - Flag = T["FLAGS"] - radius = T["FLUX_RADIUS"] - for n in range(N): - if Flag[n] == 0 and radius[n] < theta_max and mag[n] < min_mag: - ra_deg = T["ALPHA_J2000"][n] - dec_deg = T["DELTA_J2000"][n] - RA_deg.append(ra_deg) - Dec_deg.append(dec_deg) - Coords.append(SkyCoord(ra_deg, dec_deg, unit="deg")) - Mag.append(mag[n]) - Radius.append(radius[n]) - RA_deg, Dec_deg, Coords, Mag, Radius = list( - zip(*sorted(zip(RA_deg, Dec_deg, Coords, Mag, Radius))) - ) # Sort on RA - return RA_deg, Dec_deg, Coords, Mag, Radius - - -def chisq(ra, dec, t): - # Calculates linear fit and chi-square to ra vs time, dec vs. time, assuming origin is at first point - ra = np.array(ra) - dec = np.array(dec) - dt = t[1:] - t[0] - ra_dot = (np.sum(ra[1:] * dt) - ra[0] * np.sum(dt)) / np.sum(dt**2) - dec_dot = (np.sum(dec[1:] * dt) - dec[0] * np.sum(dt)) / np.sum(dt**2) - ra_mod = ra[0] + ra_dot * (t - t[0]) - dec_mod = dec[0] + dec_dot * (t - t[0]) - chisq = (1.0 / (len(t) - 2)) * np.sum((ra_mod - ra) ** 2 + (dec_mod - dec) ** 2) - return chisq, ra_dot, dec_dot - - -def compare_2fields(j, k): - # Returns indices of stars in field j that are and are not in field k - global Coords, Nstars, max_sepn - ra0 = np.array([s.ra.deg for s in Coords[j]]) - dec0 = np.array([s.dec.deg for s in Coords[j]]) - ra1 = np.array([s.ra.deg for s in Coords[k]]) - dec1 = np.array([s.dec.deg for s in Coords[k]]) - indices_match = [] - indices_nomatch = [] - for i in range(Nstars[j]): - dra = np.abs(ra1 - ra0[i]) - ddec = np.abs(dec1 - dec0[i]) - s = np.sqrt(dra**2 + ddec**2) - found = np.any(s < max_sepn) - if found: - indices_match.append(i) - else: - indices_nomatch.append(i) - return indices_match, indices_nomatch - - -def find_no_match(): - # Find indices of stars in fields that are not in either of the other 2 fields - dum, no_match01 = compare_2fields(0, 1) - dum, no_match02 = compare_2fields(0, 2) - no_match0 = list(set(no_match01).intersection(no_match02)) - dum, no_match10 = compare_2fields(1, 0) - dum, no_match12 = compare_2fields(1, 2) - no_match1 = list(set(no_match10).intersection(no_match12)) - dum, no_match20 = compare_2fields(2, 0) - dum, no_match21 = compare_2fields(2, 1) - no_match2 = list(set(no_match20).intersection(no_match21)) - return [no_match0, no_match1, no_match2] - - -# ======= MAIN ============= - -max_sepn = 1 / 3600.0 # Max separation for match, deg -theta_max = 1.0 # Set largest allowable star width [pixels - -# Parse command line arguments -(opts, args) = get_args() -ftsfiles = opts.ftsfiles.split(",") -Nfts = len(ftsfiles) -if len(ftsfiles) != 3: - sys.exit( - "Exactly 3 FITS image names (comma separated) need to be given (option -f), try again" - ) -min_mag = opts.min_mag -verbose = opts.verbose - - -# Define Sextractor dictionary items to retrieve -sew = sewpy.SEW( - params=["ALPHA_J2000", "DELTA_J2000", "FLUX_RADIUS(3)", "FLUX_BEST", "FLAGS"], - config={"DETECT_MINAREA": 5, "PHOT_FLUXFRAC": "0.3, 0.5, 0.7"}, -) - -# Loop through FITS files, getting lists of star positions and fluxes -Coords = [] -Coords_hms = [] -ADU = [] -Nstars = [] -Radius = [] -Mag = [] -jd = [] -RA_deg = [] -Dec_deg = [] -j = 0 -for ftsfile in ftsfiles: - if not os.path.isfile(ftsfile): - sys.exit("File %s not found, exiting" % ftsfile) - hdr = fits.open(ftsfile)[0].header - jd.append(hdr["JD"]) - ra_deg, dec_deg, coords, mag, radius = get_stars(ftsfile, theta_max, min_mag) - Radius.append(radius) - Mag.append(mag) - RA_deg.append(ra_deg) - Dec_deg.append(dec_deg) - Coords.append(coords) - c = [ - s.to_string(style="hmsdms", precision=2, sep=":", decimal=False) for s in coords - ] - Coords_hms.append(c) - Nstars.append(len(mag)) - print("%i stars found in FITS image %s" % (Nstars[j], ftsfile)) - j += 1 - -print() -no_match = find_no_match() - -if verbose: - # Print all stars found in each field - for k in range(3): - print("Field %i stars" % k) - for j in range(Nstars[k]): - print("%s %.2f" % (Coords_hms[k][j], Mag[k][j])) - print() - - # Print star in each field that have no matching stars - for k in range(3): - print("Image: %s - stars with no matches" % ftsfiles[k]) - no_match[k].sort() - for j in no_match[k]: - print("%s %.2f" % (Coords_hms[k][j], Mag[k][j])) - -# Build lists of star coordinates [deg] for non-matching stars in each field -ra0 = [] -dec0 = [] -ra1 = [] -dec1 = [] -ra2 = [] -dec2 = [] -for k in no_match[0]: - ra0.append(RA_deg[0][k]) - dec0.append(Dec_deg[0][k]) -for k in no_match[1]: - ra1.append(RA_deg[1][k]) - dec1.append(Dec_deg[1][k]) -for k in no_match[2]: - ra2.append(RA_deg[2][k]) - dec2.append(Dec_deg[2][k]) - -# Print solutions for any no-match stars sets that lie along a linear trajectory -print() -print("Searching for moving objects") -for k0 in range(len(ra0)): - for k1 in range(len(ra1)): - for k2 in range(len(ra2)): - ra = np.array([ra0[k0], ra1[k1], ra2[k2]]) - dec = np.array([dec0[k0], dec1[k1], dec2[k2]]) - t = np.array([jd[0], jd[1], jd[2]]) - chi, ra_dot, dec_dot = chisq(ra, dec, t) - chi *= 1.0e6 - if chi < 0.1: - print("Found possible moving object, unnormalized chisq = %.2e" % (chi)) - print( - "Image %s: %10.4f %s %.2f" - % ( - ftsfiles[0], - jd[0], - Coords_hms[0][no_match[0][k0]], - Mag[0][no_match[0][k0]], - ) - ) - print( - "Image %s: %10.4f %s %.2f" - % ( - ftsfiles[1], - jd[1], - Coords_hms[1][no_match[1][k1]], - Mag[1][no_match[1][k1]], - ) - ) - print( - "Image %s: %10.4f %s %.2f" - % ( - ftsfiles[2], - jd[2], - Coords_hms[2][no_match[2][k2]], - Mag[2][no_match[2][k2]], - ) - ) - print( - 'Motion: RA = %.1f"/hr, Dec = %.1f"/hr' - % (ra_dot * 3600 / 24.0, dec_dot * 3600 / 24.0) - ) - print() diff --git a/_tba/analysis/find-rocks.py b/_tba/analysis/find-rocks.py deleted file mode 100755 index 75aca0a1..00000000 --- a/_tba/analysis/find-rocks.py +++ /dev/null @@ -1,549 +0,0 @@ -#!/usr/bin/env python - -# find-asteroid: Finds moving objects by comparing three images, removing fixed stars, and fitting for rectilinear motion in remaining objects -# v. 2.0 RLM Add MPC lookup - -import os -import sys -from optparse import OptionParser - -import numpy as np -import requests -import sewpy -from astropy import units as u -from astropy.coordinates import SkyCoord -from astropy.io import fits -from astropy.table import Table - -vers = "%prog 2.0 8-Dec-2016" - - -def get_args(): - global parser - parser = OptionParser( - description="%prog finds moving object given exactly three input images", - version=vers, - ) - parser.add_option( - "-f", - dest="ftsfiles", - metavar="FITS images", - action="store", - default="", - help="comma-separated list of images [required]", - ) - parser.add_option( - "-m", - dest="min_mag", - metavar="Minimum mag.", - action="store", - type=float, - default=21.0, - help="Minimum magnitude [default 21.0]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - parser.add_option( - "-r", - dest="radius", - default=15, - metavar="Radius", - action="store", - help="Search radius [arcmin], default 15", - ) - return parser.parse_args() - - -def hdr_info(fitsname): - # Returns header info, substituting default values for zmag if needed - hdr = fits.open(fitsname)[0].header - filter = hdr["FILTER"][0] - z = hdr["AIRMASS"] - exptime = hdr["EXPTIME"] - if filter == "G": - k = 0.28 - elif filter == "R": - k = 0.13 - elif filter == "W": - k = 0.05 - elif filter == "N": - k = 0.3 - elif filter == "B": - k = 0.35 - else: - k = 0.2 - if "ZMAG" in hdr: - zmag = hdr["ZMAG"] - else: - if filter == "B": - zmag = 20.0 - elif filter == "G": - zmag = 21.05 - elif filter == "R": - zmag = 20.20 - elif filter == "W": - zmag = 20.65 - elif filter == "N": - zmag = 21.8 - else: - zmag = 20.0 - - print( - "WARNING: No Zero-point magnitude in header, using assumed zmag = %.2f , k = %.2f based on filter %s" - % (zmag, k, filter) - ) - return zmag, k, exptime, z - - -def get_hdr_info(ftsfile): - im, hdr = fits.getdata(ftsfile, 0, header=True) - object = hdr["OBJECT"] - nbin = hdr["XBINNING"] - D = hdr["DATE-OBS"] - date = D[0:10] - ut = D[11:] - ra = hdr["RA"] - dec = hdr["DEC"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"] - z = hdr["AIRMASS"] - return object, nbin, date, ut, ra, dec, exptime, filter, z - - -def substring(string, i, j): - return string[i:j] - - -def make2dlist(string): - objlist = string.split("\n") - object2d = [] - for index in range(len(objlist) - 1): - object2d.append([]) - object2d[index].append(substring(str(objlist[index]), 0, 24)) - object2d[index].append(substring(str(objlist[index]), 24, 36)) - object2d[index].append(substring(str(objlist[index]), 36, 47)) - object2d[index].append(substring(str(objlist[index]), 47, 53)) - object2d[index].append(substring(str(objlist[index]), 53, 58)) - object2d[index].append(substring(str(objlist[index]), 59, 65)) - object2d[index].append(substring(str(objlist[index]), 69, 73)) - object2d[index].append(substring(str(objlist[index]), 76, 81)) - object2d[index].append(substring(str(objlist[index]), 82, 86)) - object2d[index].append( - substring(str(objlist[index]), 87, len(objlist[index]) - 1) - ) - del object2d[0:4] - return object2d - - -def fix_signs(ra_off, dec_off, ra_dot, dec_dot): - # Crack crazy suffixs N/S and +/- on MPC offsets, rates - ra_off = ra_off.strip() - dec_off = dec_off.strip() - ra_dot = ra_dot.strip() - dec_dot = dec_dot.strip() - if ra_off.endswith("E"): - ra_off = -1 * float(ra_off[:-1]) - else: - ra_off = float(ra_off[:-1]) - if dec_off.endswith("S"): - dec_off = -1 * float(dec_off[:-1]) - else: - dec_off = float(dec_off[:-1]) - if ra_dot.endswith("-"): - ra_dot = -1 * float(ra_dot[:-1]) - else: - ra_dot = float(ra_dot[:-1]) - if dec_dot.endswith("-"): - dec_dot = -1 * float(dec_dot[:-1]) - else: - dec_dot = float(dec_dot[:-1]) - return ra_off, dec_off, ra_dot, dec_dot - - -def get_stars(fitsname, theta_max, min_mag): - """ - Uses sewpy to retrieves star positions, fluxes from Table T, - omitting any with flags != 0, theta > theta_max - """ - zmag, k, exptime, z = hdr_info(fitsname) - out = sew(fitsname) - T = out["table"] # this is an astropy table - adu = T["FLUX_BEST"] - adu = [x / exptime for x in adu] - mag = zmag - 1.091 * np.log(adu) - z * k - N = len(T) - RA_deg = [] - Dec_deg = [] - Coords = [] - Radius = [] - Mag = [] - Flag = T["FLAGS"] - radius = T["FLUX_RADIUS"] - for n in range(N): - if Flag[n] == 0 and radius[n] < theta_max and mag[n] < min_mag: - ra_deg = T["ALPHA_J2000"][n] - dec_deg = T["DELTA_J2000"][n] - RA_deg.append(ra_deg) - Dec_deg.append(dec_deg) - Coords.append(SkyCoord(ra_deg, dec_deg, unit="deg")) - Mag.append(mag[n]) - Radius.append(radius[n]) - RA_deg, Dec_deg, Coords, Mag, Radius = list( - zip(*sorted(zip(RA_deg, Dec_deg, Coords, Mag, Radius))) - ) # Sort on RA - return RA_deg, Dec_deg, Coords, Mag, Radius - - -def chisq(ra, dec, t): - # Calculates linear fit and chi-square to ra vs time, dec vs. time, assuming origin is at first point - ra = np.array(ra) - dec = np.array(dec) - dt = t[1:] - t[0] - ra_dot = (np.sum(ra[1:] * dt) - ra[0] * np.sum(dt)) / np.sum(dt**2) - dec_dot = (np.sum(dec[1:] * dt) - dec[0] * np.sum(dt)) / np.sum(dt**2) - ra_mod = ra[0] + ra_dot * (t - t[0]) - dec_mod = dec[0] + dec_dot * (t - t[0]) - chisq = (1.0 / (len(t) - 2)) * np.sum((ra_mod - ra) ** 2 + (dec_mod - dec) ** 2) - return chisq, ra_dot, dec_dot - - -def compare_2fields(j, k): - # Returns indices of stars in field j that are and are not in field k - global Coords, Nstars, max_sepn - ra0 = np.array([s.ra.deg for s in Coords[j]]) - dec0 = np.array([s.dec.deg for s in Coords[j]]) - ra1 = np.array([s.ra.deg for s in Coords[k]]) - dec1 = np.array([s.dec.deg for s in Coords[k]]) - indices_match = [] - indices_nomatch = [] - for i in range(Nstars[j]): - dra = np.abs(ra1 - ra0[i]) - ddec = np.abs(dec1 - dec0[i]) - s = np.sqrt(dra**2 + ddec**2) - found = np.any(s < max_sepn) - if found: - indices_match.append(i) - else: - indices_nomatch.append(i) - return indices_match, indices_nomatch - - -def find_no_match(): - # Find indices of stars in fields that are not in either of the other 2 fields - dum, no_match01 = compare_2fields(0, 1) - dum, no_match02 = compare_2fields(0, 2) - no_match0 = list(set(no_match01).intersection(no_match02)) - dum, no_match10 = compare_2fields(1, 0) - dum, no_match12 = compare_2fields(1, 2) - no_match1 = list(set(no_match10).intersection(no_match12)) - dum, no_match20 = compare_2fields(2, 0) - dum, no_match21 = compare_2fields(2, 1) - no_match2 = list(set(no_match20).intersection(no_match21)) - return [no_match0, no_match1, no_match2] - - -def find_mpc_objects(ftsfile, radius, limmag): - """Query the MPC database for small bodies within a specified radius [arcmin] and limiting magnitude of the center of a FITS image""" - Objects = [] - Obj_Coords = [] - Magnitudes = [] - Offsets = [] - Rates = [] - Comments = [] - object, nbin, date, ut, ra, dec, exptime, filter, z = get_hdr_info(ftsfile) - year, month, day = [int(s) for s in date.split("-")] - uth, utm, uts = [float(s) for s in ut.split(":")] - day += (uth + utm / 60.0 + uts / 3600.0) / 24.0 - day = "%.2f" % day - coord = "%s %s" % (ra, dec) - Ctr_Coords = SkyCoord(coord, unit=(u.hourangle, u.deg)) - ra = ra.replace(":", " ") - dec = dec.replace(":", " ") - payload = { - "year": year, - "month": month, - "day": day, - "which": "pos", - "ra": ra, - "decl": dec, - "TextArea": " ", - "radius": radius, - "limit": limmag, - "oc": "857", - "sort": "d", - "mot": "h", - "tmot": "s", - "pdes": "u", - "needed": "f", - "ps": "n", - "type": "p", - } - r = requests.get( - "http://www.minorplanetcenter.net/cgi-bin/mpcheck.cgi", params=payload - ) - myString = r.text - # check to see if any objects found - if "No known minor planets" in r.text: - return Objects, Ctr_Coords, Obj_Coords, Magnitudes, Offsets, Rates, Comments - else: - mySubString = r.text[r.text.find("
") + 5 : r.text.find("
")] - mySubString = mySubString.replace("°", "d") - mySubString = mySubString.replace( - 'Further observations?', - "", - ) - # make a 2d list of objects for manipulation by other programs - objectlist = make2dlist(mySubString) - for line in objectlist: - objname, ra, dec, mag, ra_off, dec_off, ra_dot, dec_dot, dum, comment = line - ra_off, dec_off, ra_dot, dec_dot = fix_signs( - ra_off, dec_off, ra_dot, dec_dot - ) - mag = float(mag) - Objects.append(objname) - coord = "%s %s" % (ra, dec) - c = SkyCoord(coord, unit=(u.hourangle, u.deg)) - Obj_Coords.append(c) - Magnitudes.append(mag) - Offsets.append([ra_off, dec_off]) - Rates.append([ra_dot, dec_dot]) - Comments.append(comment) - return Objects, Ctr_Coords, Obj_Coords, Magnitudes, Offsets, Rates, Comments - - -# ======= MAIN ============= - -max_sepn = 1 / 3600.0 # Max separation for match, deg -theta_max = 1.0 # Set largest allowable star width [pixels - -# Parse command line arguments -(opts, args) = get_args() -ftsfiles = opts.ftsfiles.split(",") -Nfts = len(ftsfiles) -if len(ftsfiles) != 3: - sys.exit( - "Exactly 3 FITS image names (comma separated) need to be given (option -f), try again" - ) -min_mag = opts.min_mag -verbose = opts.verbose -search_radius = opts.radius - -# Define Sextractor dictionary items to retrieve -sew = sewpy.SEW( - params=["ALPHA_J2000", "DELTA_J2000", "FLUX_RADIUS(3)", "FLUX_BEST", "FLAGS"], - config={"DETECT_MINAREA": 5, "PHOT_FLUXFRAC": "0.3, 0.5, 0.7"}, -) - -# Loop through FITS files, getting lists of star positions and fluxes -Coords = [] -Coords_hms = [] -ADU = [] -Nstars = [] -Radius = [] -Mag = [] -jd = [] -RA_deg = [] -Dec_deg = [] -j = 0 - -print("Sampling images for objects...") -for ftsfile in ftsfiles: - if not os.path.isfile(ftsfile): - sys.exit("File %s not found, exiting" % ftsfile) - hdr = fits.open(ftsfile)[0].header - jd.append(hdr["JD"]) - ra_deg, dec_deg, coords, mag, radius = get_stars(ftsfile, theta_max, min_mag) - Radius.append(radius) - Mag.append(mag) - RA_deg.append(ra_deg) - Dec_deg.append(dec_deg) - Coords.append(coords) - c = [ - s.to_string(style="hmsdms", precision=2, sep=":", decimal=False) for s in coords - ] - Coords_hms.append(c) - Nstars.append(len(mag)) - print("%i stars found in FITS image %s" % (Nstars[j], ftsfile)) - j += 1 -print() -print("Looking for moving objects...") -no_match = find_no_match() - -if verbose: - # Print all stars found in each field - for k in range(3): - print("Field %i stars" % k) - for j in range(Nstars[k]): - print("%s %.2f" % (Coords_hms[k][j], Mag[k][j])) - print() - - # Print star in each field that have no matching stars - for k in range(3): - print("Image: %s - stars with no matches" % ftsfiles[k]) - no_match[k].sort() - for j in no_match[k]: - print("%s %.2f" % (Coords_hms[k][j], Mag[k][j])) - -# Build lists of star coordinates [deg] for non-matching stars in each field -ra0 = [] -dec0 = [] -ra1 = [] -dec1 = [] -ra2 = [] -dec2 = [] -for k in no_match[0]: - ra0.append(RA_deg[0][k]) - dec0.append(Dec_deg[0][k]) -for k in no_match[1]: - ra1.append(RA_deg[1][k]) - dec1.append(Dec_deg[1][k]) -for k in no_match[2]: - ra2.append(RA_deg[2][k]) - dec2.append(Dec_deg[2][k]) - -# Print solutions for any no-match stars sets that lie along a linear trajectory -print() -k_found = 0 -ra_found = [] -dec_found = [] -for k0 in range(len(ra0)): - for k1 in range(len(ra1)): - for k2 in range(len(ra2)): - ra = np.array([ra0[k0], ra1[k1], ra2[k2]]) - dec = np.array([dec0[k0], dec1[k1], dec2[k2]]) - t = np.array([jd[0], jd[1], jd[2]]) - chi, ra_dot, dec_dot = chisq(ra, dec, t) - chi *= 1.0e6 - if chi < 0.1: - print("Object nr. %i, unnormalized chisq = %.2e" % (k_found + 1, chi)) - print( - "Image %s: %10.4f %s %.2f" - % ( - ftsfiles[0], - jd[0], - Coords_hms[0][no_match[0][k0]], - Mag[0][no_match[0][k0]], - ) - ) - print( - "Image %s: %10.4f %s %.2f" - % ( - ftsfiles[1], - jd[1], - Coords_hms[1][no_match[1][k1]], - Mag[1][no_match[1][k1]], - ) - ) - print( - "Image %s: %10.4f %s %.2f" - % ( - ftsfiles[2], - jd[2], - Coords_hms[2][no_match[2][k2]], - Mag[2][no_match[2][k2]], - ) - ) - print( - 'Motion: RA = %.1f"/hr, Dec = %.1f"/hr' - % (ra_dot * 3600 / 24.0, dec_dot * 3600 / 24.0) - ) - ra_found.append(ra0[k0]) - dec_found.append(dec0[k0]) - k_found += 1 - print() - -# Now perform MPC query of first field, report results -print("Looking for known objects using MPC online query...") -ftsfile = ftsfiles[0] -object, nbin, date, ut, ra, dec, exptime, filter, z = get_hdr_info(ftsfile) -( - Objects, - Ctr_Coords, - Obj_Coords, - Magnitudes, - Offsets, - Rates, - Comments, -) = find_mpc_objects(ftsfile, search_radius, min_mag) -ctr_coords = Ctr_Coords.to_string(style="hmsdms", precision=1, sep=":", decimal=False) -ra_mpc = [] -dec_mpc = [] - -# Print results -N = len(Objects) -if N == 0: - print( - "No known minor bodies with V < %s within %s' radius of %s on %s at %s UT" - % (min_mag, search_radius, ctr_coords, date, ut) - ) -else: - print() - print( - "MPC query found %i minor bodies with V < %s within %s' radius of field center (%s) on %s at %s UT" - % (N, min_mag, search_radius, ctr_coords, date, ut) - ) - print() - print( - " Number Name V RA(J2000) Dec(J2000) Offsets Motion Comment" - ) - print( - "----------------------------------------------------------------------------------------------------------------------" - ) - for j in range(N): - coords_hmsdms = Obj_Coords[j].to_string( - style="hmsdms", precision=1, sep=":", decimal=False - ) - ra_mpc.append(Obj_Coords[j].ra.degree) - dec_mpc.append(Obj_Coords[j].dec.degree) - coords = Obj_Coords[j].to_string( - style="hmsdms", precision=2, sep=":", decimal=False - ) - ra_off, dec_off = Offsets[j] - ra_rate, dec_rate = Rates[j] - print( - "%s %.2f %s [%5.1f',%5.1f'] [%3i\"/hr, %3i\"/hr] %s" - % ( - Objects[j], - Magnitudes[j], - coords, - ra_off, - dec_off, - ra_rate, - dec_rate, - Comments[j], - ) - ) -print() -ra_mpc = np.array(ra_mpc) -dec_mpc = np.array(dec_mpc) -max_sepn = 5 / 3600.0 -print("Object matches") -print( - "----------------------------------------------------------------------------------" -) -# Spin through found objects , print matches to MPC objects -if k_found > 0: - for k in range(k_found): - dra = np.abs(ra_found[k] - ra_mpc) - ddec = np.abs(dec_found[k] - dec_mpc) - s = np.sqrt(dra**2 + ddec**2) - index = np.where(s < max_sepn) - if index[0].size > 0: - j = np.min(index) - coords = Obj_Coords[j].to_string( - style="hmsdms", precision=2, sep=":", decimal=False - ) - print( - "Object %i matches position of %s at %s" - % (k + 1, Objects[j].strip(), coords) - ) - else: - print("Object %i has no MPC match (New discovery?)" % (k + 1)) -else: - print("No matches found") diff --git a/_tba/analysis/find-transients.py b/_tba/analysis/find-transients.py deleted file mode 100755 index 14c12c97..00000000 --- a/_tba/analysis/find-transients.py +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/env python - -""" -find-transients: Find all instances of objects found in taget images, but not in archive image -It will list both newly-found objects, and objects with significantly different magnitudes -""" -vers = "find-transients version 1.0, 13 Feb 2017" - -import glob -import os -import sys -import warnings -from operator import itemgetter -from optparse import OptionParser - -import numpy as np -from astropy import units as u -from astropy.coordinates import SkyCoord -from astropy.io import fits -from astropy.io.fits import getheader, setval, update -from astroquery.sdss import SDSS - - -def get_args(): - parser = OptionParser( - usage="Usage: %prog [options] -a archive.fts target.fts", - description="Program %prog", - version=vers, - ) - parser.add_option( - "-a", - dest="archive", - metavar="archive image", - action="store", - help="Archive image [required]", - ) - parser.add_option( - "-d", - dest="detect", - metavar="detect threshold", - default=3.0, - type=float, - action="store", - help="Sextractor detect threshold, sigma [default 3.0]", - ) - parser.add_option( - "-s", - dest="sigma_diff", - metavar="min. sigma", - default=3.0, - type=float, - action="store", - help="Min. sigma for variability [default 3.0 ", - ) - parser.add_option( - "-m", - dest="mag_diff", - metavar="min. mag diff", - default=0.50, - type=float, - action="store", - help="Min. mag. diff. for variability [default 0.5]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="verbose", - default=False, - action="store_true", - help="Verbose output", - ) - return parser.parse_args() - - -def get_hdrdata(ftsfile): - hdr = getheader(ftsfile, 0) - jd = hdr["JD"] - date = hdr["DATE-OBS"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"][0] - airmass = hdr["AIRMASS"] - if "EGAIN" in hdr: - egain = hdr["EGAIN"] - else: - egain = 1.00 - nbin = hdr["XBINNING"] # Assume same for y binning - if "CRVAL1" not in hdr: - sys.exit("No WCS in %s, cannot continue" % ftsfile) - arcsec_pixel = np.abs(hdr["CDELT1"] * 3600.0) - ra0 = hdr["CRVAL1"] - dec0 = hdr["CRVAL2"] - if "ZMAG" in hdr: - zp = hdr["ZMAG"] - else: - zp = 0 - naxis1 = hdr["NAXIS1"] - naxis2 = hdr["NAXIS2"] - crval1 = hdr["CRVAL1"] - crval2 = hdr["CRVAL2"] - cdelt1 = hdr["CDELT1"] - cdelt2 = hdr["CDELT2"] - trim = 60 - ra_range = [ - crval1 + (naxis1 - trim) * cdelt1 / 2, - crval1 - (naxis1 - trim) * cdelt1 / 2, - ] - dec_range = [ - crval2 + (naxis2 - trim) * cdelt2 / 2, - crval2 - (naxis2 - trim) * cdelt2 / 2, - ] - return ( - jd, - date, - exptime, - filter, - arcsec_pixel, - nbin, - airmass, - ra0, - dec0, - ra_range, - dec_range, - zp, - egain, - ) - - -def get_sexinfo(sexname, exptime, scale): - fn = open(sexname, "r") - lines = fn.readlines()[15:] - Nr = [] - Ra = [] - Dec = [] - Snr = [] - Flux = [] - Fluxerr = [] - Fwhm = [] - V = [] - Verr = [] - for line in lines: - ( - nr, - flux, - fluxerr, - dum, - dum, - x_pix, - y_pix, - ra_deg, - dec_deg, - profile_x, - profile_y, - pa, - fwhm_pixel, - dum, - flag, - ) = [float(x) for x in line.split()] - v = -2.5 * np.log10(flux / exptime) - if fluxerr == 0: - continue - snr = flux / fluxerr - verr = 2.5 * (fluxerr / flux) # Expanding log10(1+x) ~ 2.5x - Ra.append(ra_deg) - Dec.append(dec_deg) - Flux.append(flux) - Fluxerr.append(fluxerr) - Fwhm.append(fwhm_pixel * np.abs(scale)) - Snr.append(snr) - V.append(v) - Verr.append(verr) - fn.close() - - # Trim list to stars by restricting fwhm values - fwhm_min = 1.4 - fwhm_max = 8 - A = list(zip(Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr)) - B = [] - for j in range(len(A)): - if fwhm_min < A[j][5] < fwhm_max: - B.append(A[j]) - Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr = list(zip(*B)) - V = np.array(V) - Verr = np.array(Verr) - return Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr - - -def get_starlist(ftsfile, detect_threshold): - ( - jd, - date, - exptime, - filter, - scale, - nbin, - airmass, - ra0, - dec0, - ra_range, - dec_range, - zp, - do_not_use_this_gain, - ) = get_hdrdata(ftsfile) - # Run sextractor to find stars - sexname = os.path.abspath(ftsfile).split(".")[0] + ".sexout" - if verbose: - print( - "Running sextractor on %s with detection threshold = %.1f sigma" - % (ftsfile, detect_threshold) - ) - os.system( - "/usr/local/bin/sex %s -c %s -CATALOG_NAME %s -DETECT_THRESH %.1f -VERBOSE_TYPE QUIET" - % (ftsfile, sex_path, sexname, detect_threshold) - ) - - # Get position, magnitude info for each listed star in output file, sort by RA - ra, dec, snr, flux, fluxerr, fwhm, mag, mag_err = get_sexinfo( - sexname, exptime, scale - ) - ra, dec, snr, fwhm, mag, mag_err = list( - zip(*sorted(zip(ra, dec, snr, fwhm, mag, mag_err))) - ) - ra = np.array(ra) - dec = np.array(dec) - snr = np.array(snr) - fwhm = np.array(fwhm) - mag = np.array(mag) - mag_err = np.array(mag_err) - # Add ZP magnitude - if zp != 0: - mag += zp - else: - print("Warning: No ZMAG found in %s, using default value" % ftsfile) - if filter == "G": - mag += 22.2 - elif filter == "R": - mag += 21.8 - return ra_range, dec_range, ra, dec, snr, fwhm, mag, mag_err - - -def report_differences(archive_image, target_image): - """given an archive and target FITS image, find both variable objects and 'new' (transient) objects""" - # Generate star lists for archive, target images - ( - ra_range_a, - dec_range_a, - ra_a, - dec_a, - snr_a, - fwhm_a, - mag_a, - mag_err_a, - ) = get_starlist(archive_image, detect_threshold) - ( - jd, - date, - exptime, - filter, - arcsec_pixel, - nbin, - airmass, - ra0, - dec0, - ra_range, - dec_range, - zp, - egain, - ) = get_hdrdata(archive_image) - ra_range, dec_range, ra_t, dec_t, snr_t, fwhm_t, mag_t, mag_err_t = get_starlist( - target_image, detect_threshold - ) - N_a = len(ra_a) - N_t = len(ra_t) - print("Filter = %s" % filter) - print("Found %i stars in archive image %s" % (N_a, archive_image)) - print("Found %i stars in target image %s" % (N_t, target_image)) - print() - if verbose: - print("Archive image stars") - for j in range(N_a): - c = SkyCoord(ra_a[j], dec_a[j], unit=(u.deg, u.deg)) - coords = c.to_string(style="hmsdms", precision=2, sep=":", decimal=False) - print("%s %5.2f +/- %5.2f" % (coords, mag_a[j], mag_err_a[j])) - - print("Target image stars") - for j in range(N_t): - c = SkyCoord(ra_t[j], dec_t[j], unit=(u.deg, u.deg)) - coords = c.to_string(style="hmsdms", precision=2, sep=":", decimal=False) - print("%s %5.2f +/- %5.2f" % (coords, mag_t[j], mag_err_t[j])) - print() - - # print 'Type RA (deg) Dec (deg) FWHM_a FWHM_t %s_a %s_obs Sigma ' %(filter,filter) - # print '--------------------------------------------------------------------' - print() - match = 0 - no_match = 0 - for j in range(N_t): - dra = (ra_a - ra_t[j]) * np.cos(dec_a[0] * deg) - ddec = dec_a - dec_t[j] - sepn = np.sqrt(dra**2 + ddec**2) * 3600.0 - i = np.argmin(sepn) - min_sepn = sepn[i] - c = SkyCoord(ra_t[j], dec_t[j], unit=(u.deg, u.deg)) - coords = c.to_string(style="hmsdms", precision=2, sep=":", decimal=False) - ok = (ra_range[0] <= ra_t[j] <= ra_range[1]) and ( - dec_range[0] <= dec_t[j] <= dec_range[1] - ) - if ok: # Only consider stars whose coords are on archive image - if min_sepn < 2: - diff = np.abs(mag_a[i] - mag_t[j]) - sigma_diff = diff / mag_err_t[j] - comment = "" - if fwhm_a[i] > 3.0: - comment = "Galaxy?" - if diff > min_mag_diff and sigma_diff > min_sigma_diff: - print( - 'Possible variable: %s %s = (%.2f vs. %.2f) FWHM = (%5.2f", %5.2f") sigma = %.1f %s ' - % ( - coords, - filter, - mag_a[i], - mag_t[j], - fwhm_a[i], - fwhm_t[j], - sigma_diff, - comment, - ) - ) - match += 1 - else: - comment = "" - if fwhm_t[j] > 3.0: - comment = "Galaxy?" - print( - "Possible transient: %s %s = %.2f FWHM = %.1f SNR = %4.1f %s" - % (coords, filter, mag_t[j], fwhm_t[j], snr_t[j], comment) - ) - no_match += 1 - - print("-------------------------------------------------------------") - if no_match == 0: - print( - "Image %s: All stars in target image have matches in archive image" - % target_image - ) - else: - print( - "Image %s: No matches for %i of %i stars in target image" - % (target_image, no_match, N_t) - ) - return - - -# ========== MAIN ========== - -deg = np.pi / 180.0 -verbose = True - -# Sextractor config file path -sex_path = "/usr/local/sextractor/default.sex" - -# Get options from command line -(opts, args) = get_args() -target_images = args[0] -archive_image = opts.archive -min_sigma_diff = opts.sigma_diff -min_mag_diff = opts.mag_diff -verbose = opts.verbose -detect_threshold = opts.detect - - -target_images = glob.glob(target_images) - -for target_image in target_images: - if target_image != archive_image: - report_differences(archive_image, target_image) diff --git a/_tba/analysis/plot-photom.py b/_tba/analysis/plot-photom.py deleted file mode 100755 index 00775e3d..00000000 --- a/_tba/analysis/plot-photom.py +++ /dev/null @@ -1,628 +0,0 @@ -#!/usr/bin/env python - -# plot-photom: Plots Talon program photom output both vs. UT and phase -# RLM May 2016 -# 26 May 2016 add JDrange option [-j] -# 27 Oct 2016 sort data by UT date, add period in hr to plot title -# 09 Dec 2016 RLM add error column on ASCII output file for Czech submissions -# 27 Mar 2017 check for reasonable V1, V1 err values, skip otherwise -# 1.41 reverse default y limits to 1,-1 -# 1.50 Add reference line option, change output plot to PDF -# 1.60 Fixed O-C calculation, used numpy.polyfit for fitting, tweaked phase plot, added width option for minimum fit -# 2.0 Switched to Gaussian fitting using scipy.curvefit [much better fits] -# 2.01 30 Oct 2018 add option to exclude output lines with calsource mag. much fainter than reference image magnitude -# 2.10 2 Nov 2018 switch to BJD time -# 2.11 23 Nov 2018 change default plot type to png -# 2.2 11-Dec-2019 make reading FITS files optional -# 3 Python 3 compatible, remove extraneous [?] imp library import - -vers = "v.3.0 (4 March 2020)" - -import math -import sys - -# suppress warning message when object not found -import warnings -from operator import itemgetter -from optparse import OptionParser - -import astropy.io.fits as pyfits -import ephem as ep # pyephem library -import matplotlib.pyplot as plt -import numpy as np -from astropy import coordinates as coord -from astropy import time -from astropy import units as u -from astroquery.simbad import Simbad -from numpy import linalg -from scipy.optimize import curve_fit -from scipy.stats import chi2 - -warnings.filterwarnings("ignore") - - -def get_args(): - global parser - d_txt = "Program plot-photom: plots output from program photom" - parser = OptionParser(description=d_txt, version="%s" % vers) - parser = OptionParser( - description="%prog plots light curves using output file from program photom", - version=vers, - ) - parser.add_option( - "-a", - dest="plottype", - metavar="plottype", - action="store", - default="png", - help="Plot type [default png]", - ) - parser.add_option( - "-b", - dest="barycenter", - metavar="use barycenter time", - action="store_true", - default=False, - help="Use barycenter time (requires FITS images) [def. False]", - ) - parser.add_option( - "-c", - dest="check", - metavar="show checkstar", - action="store_true", - default=False, - help="Show check star False]", - ) - parser.add_option( - "-d", - dest="double", - metavar="show double", - action="store_true", - default=False, - help="Show double phase (0.0-2.0) [default False]", - ) - parser.add_option( - "-P", - dest="period", - metavar="period", - action="store", - type=float, - default=1, - help="Period (days)", - ) - parser.add_option( - "-p", - dest="plot_phase", - metavar="plot_phase", - action="store_true", - default=False, - help="Plot phase [default off]", - ) - parser.add_option( - "-j", - dest="jdrange", - metavar="jdmin, jdmax", - action="store", - default="0,0", - help="JD range e.g. 2456722.73,2456724.56 [default all]", - ) - parser.add_option( - "-l", - dest="line", - metavar="line", - action="store_true", - default=False, - help="Draw median line for check star", - ) - parser.add_option( - "-J", - dest="JD0", - metavar="JD0", - action="store", - type=float, - default=5, - help="Reference Barycentric Julian date, phase =0", - ) - parser.add_option( - "-m", - dest="mag_ref", - metavar="Ref. mag.", - type=float, - default=0.0, - help="Reference star magnitude [default =0]", - ) - parser.add_option( - "-r", - dest="refmag", - metavar="refmag", - action="store", - type=float, - default=1, - help="Exclude images whose ref. star diff. mag exceeds refmag", - ) - parser.add_option( - "-t", - dest="tmin", - metavar="Time_minimum", - action="store_true", - default=False, - help="Solve for time of minimum [default False]", - ) - parser.add_option( - "-w", - dest="width", - metavar="width", - action="store", - type=int, - default=20, - help="Minimum fit width, sample times [default 20]", - ) - parser.add_option( - "-T", - dest="title", - metavar="title", - action="store", - default="", - help="Alternate plot suptitle [default object]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="verbose", - action="store_true", - default=False, - help="Verbose output [default False]", - ) - parser.add_option( - "-y", - dest="yminmax", - metavar="ymin,ymax", - action="store", - default="1,-1", - help="y axis min, max [default -1,1]", - ) - - return parser.parse_args() - - -def plot_lc_jd( - bjd, - ymin, - ymax, - jdmin, - jdmax, - obj, - obj_sigma, - ck, - ck_sigma, - pltname, - plottype, - suptitle, - jd0, - show_check, - solve_tmin, - tmin_args, - box_text, - line, -): - fig, ax = plt.subplots(1) - - if solve_tmin: - bjd_min, sigma, phs_min, jd1, oc, xmod, phs_mod, ymod = tmin_args - bjd_min_frac = bjd_min - jd1 - title = ( - "BJD0: %.5f, P: %.9f, \n BJDmin: %.5f +/- %.5f, O-C: %.5f days [%.1f s+/- %.1f s]" - % (jd0, P, bjd_min, sigma, oc, oc * 86400, sigma * 86400) - ) - if jdmin != 0: - jd_offset = jdmin - xmax = jdmax - jdmin - else: - jd_offset = bjd[0] - xmax = bjd[-1] - bjd[0] - plt.xlim(0, xmax) - plt.errorbar( - bjd - jd_offset, obj, yerr=obj_sigma, ls="none", marker="o", markersize=3 - ) - if show_check: - plt.errorbar(bjd - jd_offset, ck, yerr=ck_sigma, ls="none", marker=".") - if use_barycenter: - plt.xlabel("Barycentric JD - %.3f" % jd_offset) - else: - plt.xlabel("Heliocentric JD - %.3f" % jd_offset) - if np.abs(ref_mag) < 0.01: - plt.ylabel("Differential magnitude") - else: - plt.ylabel("Magnitude (Ref = %.2f)" % ref_mag) - - plt.grid(True) - plt.suptitle(suptitle) - if solve_tmin: - plt.title(title, fontsize=9) - plt.plot(xmod - jd_offset, ymod, "r-") - plt.axvline(bjd_min - jd_offset, color="r", lw=1.5, linestyle="dashed") - plt.ylim(ymin, ymax) - if line: - ymed = np.nanmedian(ck) - plt.axhline(y=ymed, ls="dashed", color="green", label="Check star") - plt.legend() - - # place a text box in lower right in axes coords - if use_barycenter: - props = dict(boxstyle="round", facecolor="wheat", alpha=0.25) - ax.text( - 0.75, - 0.15, - box_text, - transform=ax.transAxes, - fontsize=8, - verticalalignment="top", - bbox=props, - ) - - plotname = pltname + "_lc_jd." + plottype - plt.savefig(plotname) - print("JD-magnitude plot file = %s" % plotname) - return - - -def plot_lc_phs( - phs, - ymin, - ymax, - jdmin, - jdmax, - obj, - obj_sigma, - ck, - ck_sigma, - pltname, - plottype, - suptitle, - jd0, - show_check, - show_double, - solve_tmin, - tmin_args, - box_text, -): - plt.figure() - fig, ax = plt.subplots(1) - if solve_tmin: - bjd_min, sigma, phs_min, jd1, oc, xmod, phs_mod, ymod = tmin_args - bjd_min_frac = bjd_min - jd1 - title = ( - "BJD0: %.5f, P: %.9f,\n BJDmin: %.5f +/- %.5f, O-C: %.5f days [%.1f s +/- %.1f s]" - % (jd0, P, bjd_min, sigma, oc, oc * 86400, sigma * 86400) - ) - else: - title = "BJD0: %.5f, P: %.9f day (%.3f hr)" % (jd0, P, P * 24.0) - xmin = 0 - xmax = 1.0 - if show_double: - xmax = 2.0 - phs = list(phs) + list(phs + 1) - if solve_tmin: - phs_mod = list(phs_mod) + list(phs_mod + 1) - ymod = list(ymod) + list(ymod) - obj = list(obj) + list(obj) - obj_sigma = list(obj_sigma) + list(obj_sigma) - ck = list(ck) + list(ck) - ck_sigma = list(ck_sigma) + list(ck_sigma) - - plt.suptitle(suptitle) - plt.title(title, fontsize=8) - - # place a text box in lower right in axes coords - props = dict(boxstyle="round", facecolor="wheat", alpha=0.25) - ax.text( - 0.75, - 0.15, - box_text, - transform=ax.transAxes, - fontsize=8, - verticalalignment="top", - bbox=props, - ) - - plt.errorbar(phs, obj, yerr=obj_sigma, ls="none", marker="o", markersize=2) - if show_check: - plt.errorbar(phs, ck, yerr=ck_sigma, color="g", ls="none", marker=".") - if solve_tmin: - plt.axvline(phs_min, color="r", lw=1.5, linestyle="dashed") - plt.plot(phs_mod, ymod, "r.") - plt.grid(True) - plt.xlabel("Phase") - plt.ylabel("Differential magnitude") - plt.xlim(xmin, xmax) - plt.ylim(ymin, ymax) - - plotname = pltname + "_lc_phase." + plottype - plt.savefig(plotname) - print("Phase-magnitude plot file = %s" % plotname) - return - - -def phase_diff(jd, jd0, P): - # Returns phase and o-c [day] at heliocentric observed date jd given ephemeris jd0,P - jd_diff = jd - jd0 - phase = (jd_diff % P) / P - if phase >= 0.5: - o_c = (phase - 1) * P - else: - o_c = phase * P - return phase, o_c - - -def fgauss(x, a, b, x0, w): - t = (x - x0) ** 2 / w**2 - return a * np.exp(-t) + b - - -def calc_bjd(jd_utc, ra_str, dec_str): - """ - Calculate barycentric dynamical Julian date from UTC Julian date, source coordinates - Refs: http://docs.astropy.org/en/stable/time - Eastman, et al. 2010 PASP,122,935 - """ - object = coord.SkyCoord(ra_str, dec_str, unit=(u.hourangle, u.deg), frame="icrs") - lowell = coord.EarthLocation.of_site( - "Multiple Mirror Telescope" - ) # close enough to Winer - times = time.Time(jd_utc, format="jd", scale="utc", location=lowell) - ltt_bary = times.light_travel_time(object) - bjd_tdb = times.tdb + ltt_bary - return bjd_tdb.value - - -def get_hdr_info(fts_image): - # returns usefule FITS header information, including Barycentric JD [calculated from RA,Dec,JD) - try: - hdr = pyfits.getheader(fts_image) - except: - sys.exit("Cannot find FITS image %s in current directory, exiting" % fts_image) - ra_str = hdr["RA"] - dec_str = hdr["DEC"] - object = hdr["OBJECT"].replace(" ", "") - filter = hdr["FILTER"] - telescope = hdr["TELESCOP"] - exptime = hdr["EXPTIME"] - date_obs = hdr["DATE-OBS"][0:10].replace("-", "_") - jd_utc = hdr["JD"] + exptime / (2.0 * 86400) - bjd = calc_bjd(jd_utc, ra_str, dec_str) - - return object, ra_str, dec_str, exptime, filter, telescope, date_obs, jd_utc, bjd - - -# MAIN - -deg = np.pi / 180.0 - -# Get command line arguments, assign parameter values -(opts, args) = get_args() - -fname = args[0] -plottype = opts.plottype -use_barycenter = opts.barycenter -show_check = opts.check -show_double = opts.double -refmag = opts.refmag -jd0 = opts.JD0 -jdmin, jdmax = [float(x) for x in opts.jdrange.split(",")] -ref_mag = opts.mag_ref -line = opts.line -P = opts.period -plot_phase = opts.plot_phase -solve_tmin = opts.tmin -width = opts.width -suptitle = opts.title -ymin, ymax = [float(x) for x in opts.yminmax.split(",")] -verbose = opts.verbose - -# Open photom output file -fn = open(fname, "r") -hdr = fn.readline()[1:-1] - -# Read data -lines = fn.readlines() -BJD = [] -ut_hr = [] -obj = [] -obj_sigma = [] -ck = [] -ck_sigma = [] -phs = [] -n = 0 -for line in lines: - mjd, dum, dum, a1, a2, a3, a4, a5, a6 = [float(x) for x in line.split()[6:]] - if use_barycenter: - ftsname = line.split()[1] + ".fts" - if n == 0: - ( - objname, - ra_str, - dec_str, - exptime, - filter, - telescope, - date, - jd_utc, - bjd, - ) = get_hdr_info(ftsname) - else: - dum, dum, dum, dum, dum, dum, dum, jd_utc, bjd = get_hdr_info(ftsname) - else: - jd_utc = mjd + 2449000 # Heliocentric JD at start of exposure) - bjd = jd_utc # Hack! - date = "JD_%7i" % int(jd_utc) # why not - objname = fname.split(".")[0] - telescope = "Gemini" - filter = "" - exptime = 0 - ra_str = "" - dec_str = "" - - if verbose: - print(ftsname, jd_utc, bjd, a1) - jd_ok = (jdmin == 0 and jdmax == 0) or jdmin <= bjd <= jdmax - v1_ok = -8 < a1 < 8 - v1_sigma_ok = a2 < 1.0 - vref_ok = a5 < refmag - if v1_ok and v1_sigma_ok and jd_ok and vref_ok: - hr = math.modf(bjd + 0.5)[0] * 24 - ut_hr.append(hr) - BJD.append(bjd) - a1 += ref_mag - a3 += ref_mag - obj.append(a1) - obj_sigma.append(a2) - ck.append(a3) - ck_sigma.append(a4) - phs.append(((bjd - jd0) % P) / P) - n += 1 - -if verbose: - print("%i points read, computing plot..." % n) - -# Time sort using BJD -vals = [ - [BJD[i], ut_hr[i], obj[i], obj_sigma[i], ck[i], ck_sigma[i], phs[i]] - for i in range(len(BJD)) -] -sorted_vals = sorted(vals, key=itemgetter(0)) -for i in range(n): - BJD[i], ut_hr[i], obj[i], obj_sigma[i], ck[i], ck_sigma[i], phs[i] = sorted_vals[i] - -# Convert to numpy arrays, so means can be subtracted, and offsets applied -ut_hr = np.array(ut_hr) -BJD = np.array(BJD) -obj = np.array(obj) -obj_sigma = np.array(obj_sigma) -ck = np.array(ck) -ck_sigma = np.array(ck_sigma) - -# If plotting differential magnitudes, subtract means -if np.abs(ref_mag < 0.01): - obj -= np.mean(obj) - ck -= np.mean(ck) + 0.0 -phs = np.array(phs) - -# Solve for time of minimum using weighted Gaussian LSQ fit centered on minimum of l.c. -if solve_tmin: - jd_frac, jd_int = np.modf(BJD) - jd1 = jd_int[0] # Integer part of first BJD time - jmin = np.argmax(obj) - BJD_min0 = BJD[jmin] - if verbose: - print( - "Found sample minimum at BJD = %.5f (jmin = %i, mag = %.2f)" - % (BJD_min0, jmin, obj[jmin]) - ) - w = width / 2 # Width of fit in points - x = BJD[jmin - w : jmin + w] - BJD[0] - y = obj[jmin - w : jmin + w] - s = obj_sigma[jmin - w : jmin + w] - - # weighted Gaussian fit - init_vals = (obj[jmin], 0, BJD[jmin] - BJD[0], width * exptime / 86400) - [a_fit, b_fit, x0_fit, w_fit], cov = curve_fit(fgauss, x, y, p0=init_vals) - [a_s, b_s, x0_s, w_s] = np.sqrt(np.diag(cov)) - delta_jd = x0_fit - sigma = x0_s # Uncertainty in delta_jd - bjd_min = BJD[0] + delta_jd - phs_min, oc = phase_diff(bjd_min, jd0, P) - - # generate a model for plotting - npts = 100 - xmod = np.linspace(BJD[jmin - w] - BJD[0], BJD[jmin + w] - BJD[0], npts) - ymod = fgauss(xmod, a_fit, b_fit, x0_fit, w_fit) - xmod += BJD[0] - phs_mod = ((xmod - jd0) % P) / P - if verbose: - print( - "Fitted minimum at BJD: %.5f +/- %.5f, O-C = %.5f days (%.1f sec +/- %.1f sec). Phase at minimum = %.3f)" - % (bjd_min, sigma, oc, oc * 86400, sigma * 86400, phs_min) - ) -else: - bjd_min = 0 - phs_min = 0 - oc = 0 - jd1 = 0 - sigma = 0 - xmod = 0 - phs_mod = 0 - ymod = 0 - -# Plot differential magnitude of target, check star vs heliocentric jd -pltname = "%s_%s" % (objname, date) -if suptitle == "": - suptitle = objname -Ymin = np.mean(obj) + ymin -Ymax = np.mean(obj) + ymax -tmin_args = (bjd_min, sigma, phs_min, jd1, oc, xmod, phs_mod, ymod) -obj_args = (objname, ra_str, dec_str, exptime, filter, telescope, date) -box_text = "%s\nDate: %s\nFilter: %s\nExp time: %.1f sec" % ( - telescope, - date, - filter, - exptime, -) -plot_lc_jd( - BJD, - Ymin, - Ymax, - jdmin, - jdmax, - obj, - obj_sigma, - ck, - ck_sigma, - pltname, - plottype, - suptitle, - jd0, - show_check, - solve_tmin, - tmin_args, - box_text, - line, -) - -# If requested, also plot vs. phase using user-supplied ephemeris -if plot_phase: - plot_lc_phs( - phs, - ymin, - ymax, - jdmin, - jdmax, - obj, - obj_sigma, - ck, - ck_sigma, - pltname, - plottype, - suptitle, - jd0, - show_check, - show_double, - solve_tmin, - tmin_args, - box_text, - ) - -# Write a 2-column bjd, magnitude output file(s) -outfile = pltname + "_jd.dat" -f = open(outfile, "w") -for j in range(len(BJD)): - f.write("%.5f %.3f %.3f\n" % (BJD[j], obj[j], obj_sigma[j])) -f.close() -print("Wrote file %s" % outfile) - -if plot_phase: - outfile = pltname + "_phase.dat" - f = open(outfile, "w") - for j in range(len(ut_hr)): - f.write("%.4f %.3f %0.3f\n" % (phs[j], obj[j], obj_sigma[j])) - f.close() - print("Wrote file %s" % outfile) diff --git a/_tba/analysis/plt-phot b/_tba/analysis/plt-phot deleted file mode 100755 index a516abf9..00000000 --- a/_tba/analysis/plt-phot +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/env python - -# plt-phot: -# Computes photometric magnitudes using sextractor, plots -# Optionally checks magnitudes using SDSS - -# N.B. Requires sextractor! -# [command line sex, config file location defaults to /usr/local/sextractor/default.sex] - -# v. 1.0 RLM 22 April 2016 - -import glob -import os -import re -import sys -import warnings -from optparse import OptionParser - -import matplotlib.pyplot as plt -import numpy as np -from astropy import units as u -from astropy.coordinates import SkyCoord -from astropy.io import fits -from astropy.io.fits import getheader, setval, update -from astropy.time import Time -from astroquery.sdss import SDSS -from matplotlib.pyplot import cm -from scipy.optimize import minimize - -# Avoid annoying warning about matplotlib building the font cache -warnings.filterwarnings("ignore") - -# Sextractor config file path -sex_path = "/usr/local/sextractor/default.sex" - - -def get_args(): - global parser - parser = OptionParser(description="Program %prog", version="%prog 1.0") - parser.add_option( - "-f", - dest="filter", - metavar="Filter", - action="store", - help="Filter name [no default]", - ) - parser.add_option( - "-s", - dest="sigma", - metavar="sigma", - action="store", - type=float, - default=5, - help="Sextractor detection threshold [default 5]", - ) - parser.add_option( - "-c", - dest="config", - metavar="config", - action="store", - help="phot config file name [no default]", - ) - parser.add_option( - "-d", - dest="datafile", - metavar="datafile", - action="store", - default="", - help="Data file: CCV file of wavelengths, intensities", - ) - parser.add_option( - "-p", - dest="plot", - metavar="plot", - action="store_true", - default=True, - help="Plot solution", - ) - parser.add_option( - "-S", - dest="SDSS", - metavar="SDSS", - action="store_true", - default=False, - help="Search SDSS for magnitude", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - parser.add_option( - "-y", - dest="ywidth", - metavar="ywidth", - action="store", - type=float, - default=2.0, - help="Differential plot width [mags, default 2 mag]", - ) - parser.add_option( - "-z", - dest="zp", - metavar="Zeropoint", - action="store", - type=float, - default=-1, - help="Zero-point magnitude, defaults to FITS header value", - ) - return parser.parse_args() - - -def get_hdrdata(ftsfile): - hdr = getheader(ftsfile, 0) - jd = hdr["JD"] - date = hdr["DATE-OBS"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"][0] - airmass = hdr["AIRMASS"] - if "ZMAG" in hdr: - zp = hdr["ZMAG"] - zperr = hdr["ZMAGERR"] - else: - zp = 0 - zperr = 0 - nbin = hdr["XBINNING"] # Assume same for y binning - arcsec_pixel = np.abs(hdr["CDELT1"] * 3600.0) - return jd, date, exptime, filter, arcsec_pixel, airmass, nbin, zp, zperr - - -def get_sexinfo(sexname, exptime, scale): - fn = open(sexname, "r") - lines = fn.readlines()[15:] - Nr = [] - Ra = [] - Dec = [] - Snr = [] - Flux = [] - Fluxerr = [] - Fwhm = [] - V = [] - Verr = [] - for line in lines: - ( - nr, - dum, - dum, - flux, - fluxerr, - x_pix, - y_pix, - ra_deg, - dec_deg, - profile_x, - profile_y, - pa, - fwhm_pixel, - dum, - flag, - ) = [float(x) for x in line.split()] - v = -2.5 * np.log10(flux / exptime) - snr = flux / fluxerr - verr = 2.5 * (fluxerr / flux) # Expanding log10(1+x) ~ 2.5x - Ra.append(ra_deg) - Dec.append(dec_deg) - Flux.append(flux) - Fluxerr.append(fluxerr) - Fwhm.append(fwhm_pixel * np.abs(scale)) - Snr.append(snr) - V.append(v) - Verr.append(verr) - fn.close() - # Trim list to stars by restricting fwhm values - fwhm_min = 1.4 - fwhm_max = 4.0 - A = zip(Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr) - B = [] - for j in range(len(A)): - if fwhm_min < A[j][5] < fwhm_max: - B.append(A[j]) - Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr = zip(*B) - V = np.array(V) - Verr = np.array(Verr) - return Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr - - -def get_sdss_magnitudes(ra, dec): - # Query SDSS online photometric catalog for u,g,r,i,z magnitudes; ra,deg in degrees (ICRS, 2000) - pos = SkyCoord(ra, dec, unit=(u.deg, u.deg), frame="icrs") - ids = SDSS.query_region( - pos, radius=5 * u.arcsec, fields=["ra", "dec", "clean", "u", "g", "r", "i", "z"] - ) # defaults to 2 arcsec search - u1 = g = r = i = z = np.nan - if ids != None: - for id in ids: - if ( - id["clean"] == 1 and id["g"] < 20.0 - ): # Only accept photometry with clean flags & reject very faint stars - u1 = id["u"] - g = id["g"] - r = id["r"] - i = id["i"] - z = id["z"] - return u1, g, r, i, z - return u1, g, r, i, z - - -def trim(indices, A): - # Trims arrays packed in A, dropping elements with given indices - B = [] - for a in A: - B.append(np.delete(a, indices)) - return B - - -def get_magnitudes(Ra, Dec, Ra_sex, Dec_sex, max_diff, Mag_sex, Mag_sex_err): - N1 = len(Ra) - N2 = len(Ra_sex) - Mag = np.empty(N1) * np.nan - Mag_err = np.empty(N1) * np.nan - for j in range(N1): - for k in range(N2): - dra = np.abs(Ra[j] - Ra_sex[k]) - ddec = np.max(Dec[j] - Dec_sex[k]) - if dra < max_diff and ddec < max_diff: - Mag[j] = Mag_sex[k] - Mag_err[j] = Mag_sex_err[k] - Mag = np.array(Mag) - Mag_err = np.array(Mag_err) - return Mag, Mag_err - - -def parse_config(config_file): - Objects = [] - Filters = [] - Ftsfiles = [] - Mag_catalog = [] - Ra_hms = [] - Dec_dms = [] - Ra_deg = [] - Dec_deg = [] - if not os.path.isfile(config_file): - sys.exit("Configuration file %s does not exist, try again" % config_file) - else: - fn = open(config_file, "r") - lines = fn.readlines() - fn.close() - for line in lines: - line = line.split() - if line == []: - continue # Skip blank lines - elif line[0] == "I": - Ftsfiles = line[1] - elif line[0] == "S": - object, ra_hms, dec_dms = line[1:4] - Objects.append(object) - Ra_hms.append(ra_hms) - Dec_dms.append(dec_dms) - c = SkyCoord(ra_hms, dec_dms, unit=(u.hourangle, u.deg), frame="icrs") - Ra_deg.append(c.ra.deg) - Dec_deg.append(c.dec.deg) - if sdss: - u1, g, r, i, z = get_sdss_magnitudes(c.ra.deg, c.dec.deg) - Mag_catalog.append([u1, g, r, i, z]) - elif line[0] == "T": - title = " ".join(line[1:]) - return Objects, Ftsfiles, Ra_hms, Dec_dms, Ra_deg, Dec_deg, Mag_catalog, title - - -# ======== MAIN ================ - -# Max difference: config vs Sex position [deg] -max_diff = 5 / 3600.0 - -# Define dictionary of zero-point values and extinction for filters [guesses except for G, R] -Cal_Apogee = { - "N": (22.0, 0.20), - "B": (21.5, 0.35), - "G": (21.65, 0.28), - "V": (20.6, 0.20), - "R": (20.3, 0.12), - "W": (19.8, 0.05), -} -Cal = { - "N": (21.5, 0.20), - "B": (21.0, 0.35), - "G": (21.15, 0.28), - "V": (20.6, 0.20), - "R": (20.3, 0.12), - "W": (19.8, 0.05), -} - -# Get command line arguments, assign parameter values -(opts, args) = get_args() - -if not opts.filter: - parser.error("filter (-f) not given, try again") -Filter = opts.filter[0].upper() # Filter name (convert to upper if needed) - -if not opts.config: - parser.error("config file (-c) not given, try again") -config_file = opts.config - -detect_threshold = opts.sigma # Sextractor detection threshold [sigma] -plot = opts.plot # Plot various things -csvfile = opts.datafile # optional CSV output filename -sdss = opts.SDSS # Look in SDSS for object magnitudes -verbose = opts.verbose # Print diagnostics, more -ywidth = opts.ywidth # Differentail plot width, magnitudes -zp_user = opts.zp # Zeropoint magnitude - -# Parse configuration file -Objects, Ftsfiles, Ra_hms, Dec_dms, Ra_deg, Dec_deg, Mag_catalog, title = parse_config( - config_file -) -nstar = len(Objects) - -JD = [] -Date = [] -Mag_all = [] -Mag_err_all = [] -# for Filter in Filters: -if 1 == 1: - for ftsfile in glob.glob(Ftsfiles): - # Get useful header info [NB not currently using nbin] - jd, date, exptime, filter, scale, airmass, nbin, zp, zperr = get_hdrdata( - ftsfile - ) - - # If wrong filter, skip - if filter != Filter: - if verbose: - print( - "%s: Wrong filter [expecting %s, got %s], skipping" - % (ftsfile, Filter, filter) - ) - continue - - # Run sextractor - sexname = os.path.basename(ftsfile).split(".")[0] + ".sexout" - if verbose: - print( - "Running sextractor on %s with detection threshold = %.1f sigma" - % (ftsfile, detect_threshold) - ) - os.system( - "sex %s -c %s -CATALOG_NAME %s -DETECT_THRESH %.1f -VERBOSE_TYPE QUIET" - % (ftsfile, sex_path, sexname, detect_threshold) - ) - - # Get position, magnitude info for each listed star in output file - ( - Ra_sex, - Dec_sex, - Snr, - Flux, - Fluxerr, - Fwhm_sex, - Mag_sex, - Mag_sex_err, - ) = get_sexinfo(sexname, exptime, scale) - nobs = len(Ra_sex) - if verbose: - print("Sextractor found %i stars" % nobs) - - # Get magnitudes for target objects using position match to sextractor output - Mag, Mag_err = get_magnitudes( - Ra_deg, Dec_deg, Ra_sex, Dec_sex, max_diff, Mag_sex, Mag_sex_err - ) - - # Convert to magnitude by adding ZP and correcting for extinction. Use user-supplied ZP if specified - if zp_user > 0: - ZP = zp - if verbose: - print("Using user-supplied zero-point (ZP = %.2f)" % ZP) - elif zp > 0: - ZP = zp - if verbose: - print("Using zero-point found in FITS header: %.2f)" % ZP) - else: - ZP = Cal[Filter][0] - if nbin == 1: - ZP += 0.5 - if verbose: - print( - "Using default zero-point for %s filter: (ZP = %.2f)" % (filter, ZP) - ) - k = Cal[Filter][1] - Mag += ZP - k * airmass - - # Add to array, but only if all stars detected - if not np.isnan(Mag).any(): - JD.append(jd) - Date.append(date) - Mag_all.append(Mag), Mag_err_all.append(Mag_err) - -nepoch = len(JD) - -# Sort by JD -JD, Date, Mag_all, Mag_err_all = ( - list(x) for x in zip(*sorted(zip(JD, Date, Mag_all, Mag_err_all))) -) - -# Convert to numpy arrays -Mag = np.array(Mag_all) -Mag_err = np.array(Mag_err_all) - -# Subtract reference star magnitudes -Ref_Mag = Mag[:, -1] -Diff_mag = Mag - Ref_Mag[:, np.newaxis] - -# Calculate median differential magnitudes -Medians = np.median(Mag, axis=0) -Diff_mag += Medians[-1] -Diff_err = np.sqrt(Mag_err**2 + Mag_err[-1] ** 2) - - -# Plot magnitudes -plt.figure(1, figsize=(12, 8)) -for j in range(nepoch): - mjd = JD[j] - JD[0] - color = iter(cm.rainbow(np.linspace(0, 1, nstar + 1))) - for k in range(nstar): - c = color.next() - if j == 0: - plt.errorbar( - mjd, - Mag[j][k], - yerr=Mag_err[j][k], - marker="d", - markersize=5, - c=c, - label="%s" % Objects[k], - ) - else: - plt.errorbar( - mjd, Mag[j][k], yerr=Mag_err[j][k], marker="d", markersize=5, c=c - ) -plt.title(title) -plt.legend(loc=2) -plt.ylim(20, 10) -plt.ylabel("%s Magnitude" % Filter) -plt.xlabel("Days since JD %.5f (%s)" % (JD[0], Date[0])) -plt.grid(True) -plot_title = "%s_lc-all.png" % (config_file.split(".")[0]) -plt.savefig(plot_title) -if verbose: - print("Saved light curve plot as %s" % plot_title) - -# Separate plots for differential magnitudes -color = iter(cm.rainbow(np.linspace(0, 1, nstar + 1))) -for k in range(nstar): - fig = plt.figure(j + 1, figsize=(12, 8)) - ax = fig.add_subplot(111) - c = color.next() - ymin = Medians[k] + ywidth / 2.0 - ymax = ymin - ywidth - for j in range(nepoch): - mjd = JD[j] - JD[0] - plt.errorbar( - mjd, Diff_mag[j][k], yerr=Diff_err[j][k], marker="s", markersize=7, c="b" - ) - plt.suptitle(title, fontsize=14) - plt.title( - "%s [%s %s] Filter = %s" % (Objects[k], Ra_hms[k], Dec_dms[k], Filter), - fontsize=12, - ) - plt.legend(loc=2) - plt.ylim(ymin, ymax) - plt.ylabel("%s Magnitude at Z=0" % Filter) - if sdss: - u, g, r, i, z = Mag_catalog[k] - if Filter == "G": - txt = "Sloan g = %.2f" % g - elif Filter == "R": - txt = "Sloan r = %.2f" % r - else: - txt = "Sloan g = %.2f, r = %.2f, i = %.2f, z = %.2f," % (g, r, i, z) - plt.text( - 0.05, - 0.05, - txt, - fontsize=12, - transform=ax.transAxes, - bbox=dict(facecolor="white", alpha=0.5), - ) - plt.xlabel("Days since JD %.5f (%s)" % (JD[0], Date[0])) - plt.grid(True) - plot_title = "%s_%s_lc.png" % (Objects[k], Filter) - plt.savefig(plot_title) - print("Saved differential l.c. plot %s" % plot_title) - plt.close(fig) - - -# write CSV output file if requested -if csvfile != "": - fn = open(csvfile, "w") - for j in range(nepoch): - str1 = " ".join( - "%.3f %.3f " % (Mag[j][k], Mag_err[j][k]) for k in range(nstar) - ) - s = "%10.4f %s\n" % (JD[j], str1) - fn.write(s) - print("wrote CSV file %s" % csvfile) - fn.close() diff --git a/_tba/analysis/rockfinder b/_tba/analysis/rockfinder deleted file mode 100755 index af00e972..00000000 --- a/_tba/analysis/rockfinder +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python - -# Version 12/6/2016 -vers = "rockfinder v1.0" -import sys -from optparse import OptionParser - -# import pyfits as fits -import astropy.io.fits as fits -import requests - - -def get_args(): - usage = "usage: %prog [options] image.fts" - parser = OptionParser( - usage=usage, - description="Program %prog references MPC catalogs for Asteroids and Comets", - version=vers, - ) - parser.add_option( - "-R", dest="RA", metavar="RA", action="store", help="RA to search hh:mm:ss.ss" - ) - parser.add_option( - "-D", dest="DEC", metavar="DEC", action="store", help="DEC to search" - ) - parser.add_option( - "-r", - dest="radius", - default=25, - metavar="Radius", - action="store", - help="Radius to search in arcminutes", - ) - parser.add_option( - "-m", - dest="limmag", - default=22, - metavar="LimMag", - action="store", - help="Limiting magnitude", - ) - parser.add_option( - "-d", dest="date", metavar="date", action="store", help="Date in yyyy/mm/dd" - ) - parser.add_option( - "-t", dest="ut", metavar="time", action="store", help="UT Time in hh:mm:ss.ss" - ) - return parser.parse_args() - - -def get_hdr_info(ftsfile): - im, hdr = fits.getdata(ftsfile, 0, header=True) - D = hdr["DATE-OBS"] - date = D[0:10] - ut = D[11:] - ra = hdr["RA"] - dec = hdr["DEC"] - return date, ut, ra, dec - - -def substring(string, i, j): - return string[i:j] - - -def make2dlist(string): - objlist = string.split("\n") - object2d = [] - for index in range(len(objlist) - 1): - object2d.append([]) - # object name - object2d[index].append(substring(str(objlist[index]), 0, 24)) - # RA - object2d[index].append(substring(str(objlist[index]), 24, 36)) - # DEC - object2d[index].append(substring(str(objlist[index]), 36, 47)) - # Magnitude - object2d[index].append(substring(str(objlist[index]), 47, 53)) - # RA Offset - object2d[index].append(substring(str(objlist[index]), 53, 58)) - # DEC Offset - object2d[index].append(substring(str(objlist[index]), 59, 65)) - # Motion/Hr RA - object2d[index].append(substring(str(objlist[index]), 70, 73)) - # Motion/Hr DEC - object2d[index].append(substring(str(objlist[index]), 77, 81)) - # Orbit - object2d[index].append(substring(str(objlist[index]), 83, 86)) - # Comment - object2d[index].append( - substring(str(objlist[index]), 87, len(objlist[index]) - 1) - ) - # remove first 4 rows because they are headers - del object2d[0:4] - return object2d - - -# MAIN PROGRAM -(opts, args) = get_args() -# check for image input -try: - filename = args[0] - date, ut, ra, dec = get_hdr_info(filename) - im, hdr = fits.getdata(filename, 0, header=True) -except IndexError: - # check for necessary options if no image input - if not opts.RA or not opts.DEC or not opts.ut or not opts.date: - print("Must specifiy at least -R, -D, -d, and -t if no image specified.") - exit() -if opts.RA: - ra = opts.RA -if opts.DEC: - dec = opts.DEC -if opts.ut: - ut = opts.ut -if opts.date: - date = opts.date -radius = opts.radius -limmag = opts.limmag -# convert RA and DEC to format readable by MPC website -ra = ra.replace(":", " ") -dec = dec.replace(":", " ") -# break date into year, month and day formats readable by MPC website -year = date[0:4] -month = date[5:7] -fullday = float(date[8:10]) -halfday = ((float(ut[0:2]) * 3600) + (float(ut[3:5]) * 60) + (float(ut[6:8]))) / 86400.0 -day = fullday + halfday -# create arguments to sent to MPC website -payload = { - "year": year, - "month": month, - "day": day, - "which": "pos", - "ra": ra, - "decl": dec, - "TextArea": " ", - "radius": radius, - "limit": limmag, - "oc": "857", - "sort": "d", - "mot": "h", - "tmot": "s", - "pdes": "u", - "needed": "f", - "ps": "n", - "type": "p", -} -# send request to MPC website -r = requests.get("http://www.minorplanetcenter.net/cgi-bin/mpcheck.cgi", params=payload) - -# check to see if any objects found -if r.text.find("No known minor planets") > 0: - print( - "No known minor planets within %s arcminutes and brighter than %s mag of RA:%s DEC:%s on %s at %sUT." - % (radius, limmag, ra, dec, date, ut) - ) -else: - # cut website output down to object table - objectstring = r.text[r.text.find("
") + 5 : r.text.find("
")] - # replace formatting problems - objectstring = objectstring.replace("°", "d") - objectstring = objectstring.replace( - 'Further observations?', - "", - ) - # make a 2d list of objects for manipulation by other programs - objectlist = make2dlist(objectstring) - print( - "%s objects within %s arcminutes and brighter than %s mag of RA:%s DEC:%s on %s at %s:" - % (len(objectlist), radius, limmag, ra, dec, date, ut) - ) - print(objectstring) - # for index in range(len(objectlist)): - # print objectlist[index] diff --git a/_tba/analysis/rocklister.py b/_tba/analysis/rocklister.py deleted file mode 100755 index 4f38d783..00000000 --- a/_tba/analysis/rocklister.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/usr/bin/env python - -# Rocklister - lists MPC objects [using MPC database query] in a region and date extracted from a FITS header or specified by RA,Dec, UT Date/time - -# Version 1.0 12/6/2016 RLM and Chris Michael -# 1.01 21 Jan 2017 update Optparser with usage string to show FITS image - -vers = "rocklister v1.01" - -import os -import sys -from optparse import OptionParser - -import astropy.io.fits as fits -import requests -from astropy import units as u -from astropy.coordinates import SkyCoord - - -def get_args(): - parser = OptionParser( - usage="Uasge: %prog [options] FITSimage", - description="Program %prog queries MPC online database for Asteroids and Comets", - version=vers, - ) - parser.add_option( - "-R", - dest="RA", - metavar="RA", - action="store", - help="RA to search [hh:mm:ss] (optional, default use FITS header)", - ) - parser.add_option( - "-D", - dest="DEC", - metavar="DEC", - action="store", - help="DEC to search [dd:mm] (optional, default use FITS header)", - ) - parser.add_option( - "-r", - dest="radius", - default=15, - metavar="Radius", - action="store", - help="Search radius [arcmin], default 15", - ) - parser.add_option( - "-m", - dest="limmag", - default=20, - metavar="LimMag", - action="store", - help="Limiting magnitude, default 20", - ) - parser.add_option( - "-d", - dest="date", - metavar="date", - action="store", - help="Date string [yyyy/mm/dd] (optional, default use FITS header)", - ) - parser.add_option( - "-t", - dest="ut", - metavar="time", - action="store", - default="00:00", - help="UT time [hh:mm] (optional, default use FITS header)", - ) - return parser.parse_args() - - -def get_hdr_info(ftsfile): - im, hdr = fits.getdata(ftsfile, 0, header=True) - object = hdr["OBJECT"] - nbin = hdr["XBINNING"] - D = hdr["DATE-OBS"] - date = D[0:10] - ut = D[11:] - ra = hdr["RA"] - dec = hdr["DEC"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"] - z = hdr["AIRMASS"] - return object, nbin, date, ut, ra, dec, exptime, filter, z - - -def substring(string, i, j): - return string[i:j] - - -def make2dlist(string): - objlist = string.split("\n") - object2d = [] - for index in range(len(objlist) - 1): - object2d.append([]) - object2d[index].append(substring(str(objlist[index]), 0, 24)) - object2d[index].append(substring(str(objlist[index]), 24, 36)) - object2d[index].append(substring(str(objlist[index]), 36, 47)) - object2d[index].append(substring(str(objlist[index]), 47, 53)) - object2d[index].append(substring(str(objlist[index]), 53, 58)) - object2d[index].append(substring(str(objlist[index]), 59, 65)) - object2d[index].append(substring(str(objlist[index]), 69, 73)) - object2d[index].append(substring(str(objlist[index]), 76, 81)) - object2d[index].append(substring(str(objlist[index]), 82, 86)) - object2d[index].append( - substring(str(objlist[index]), 87, len(objlist[index]) - 1) - ) - del object2d[0:4] - return object2d - - -def fix_signs(ra_off, dec_off, ra_dot, dec_dot): - # Crack crazy suffixs N/S and +/- on MPC offsets, rates - ra_off = ra_off.strip() - dec_off = dec_off.strip() - ra_dot = ra_dot.strip() - dec_dot = dec_dot.strip() - if ra_off.endswith("E"): - ra_off = -1 * float(ra_off[:-1]) - else: - ra_off = float(ra_off[:-1]) - if dec_off.endswith("S"): - dec_off = -1 * float(dec_off[:-1]) - else: - dec_off = float(dec_off[:-1]) - if ra_dot.endswith("-"): - ra_dot = -1 * float(ra_dot[:-1]) - else: - ra_dot = float(ra_dot[:-1]) - if dec_dot.endswith("-"): - dec_dot = -1 * float(dec_dot[:-1]) - else: - dec_dot = float(dec_dot[:-1]) - return ra_off, dec_off, ra_dot, dec_dot - - -def find_mpc_objects(ftsfile, radius, limmag): - """Query the MPC database for small bodies within a specified radius [arcmin] and limiting magnitude of the center of a FITS image""" - Objects = [] - Obj_Coords = [] - Magnitudes = [] - Offsets = [] - Rates = [] - Comments = [] - object, nbin, date, ut, ra, dec, exptime, filter, z = get_hdr_info(ftsfile) - year, month, day = [int(s) for s in date.split("-")] - uth, utm, uts = [float(s) for s in ut.split(":")] - day += (uth + utm / 60.0 + uts / 3600.0) / 24.0 - day = "%.2f" % day - coord = "%s %s" % (ra, dec) - Ctr_Coords = SkyCoord(coord, unit=(u.hourangle, u.deg)) - ra = ra.replace(":", " ") - dec = dec.replace(":", " ") - payload = { - "year": year, - "month": month, - "day": day, - "which": "pos", - "ra": ra, - "decl": dec, - "TextArea": " ", - "radius": radius, - "limit": limmag, - "oc": "857", - "sort": "d", - "mot": "h", - "tmot": "s", - "pdes": "u", - "needed": "f", - "ps": "n", - "type": "p", - } - r = requests.get( - "http://www.minorplanetcenter.net/cgi-bin/mpcheck.cgi", params=payload - ) - myString = r.text - # check to see if any objects found - if "No known minor planets" in r.text: - return Objects, Ctr_Coords, Obj_Coords, Magnitudes, Offsets, Rates, Comments - else: - mySubString = r.text[r.text.find("
") + 5 : r.text.find("
")] - mySubString = mySubString.replace("°", "d") - mySubString = mySubString.replace( - 'Further observations?', - "", - ) - # make a 2d list of objects for manipulation by other programs - objectlist = make2dlist(mySubString) - for line in objectlist: - objname, ra, dec, mag, ra_off, dec_off, ra_dot, dec_dot, dum, comment = line - ra_off, dec_off, ra_dot, dec_dot = fix_signs( - ra_off, dec_off, ra_dot, dec_dot - ) - mag = float(mag) - Objects.append(objname) - coord = "%s %s" % (ra, dec) - c = SkyCoord(coord, unit=(u.hourangle, u.deg)) - Obj_Coords.append(c) - Magnitudes.append(mag) - Offsets.append([ra_off, dec_off]) - Rates.append([ra_dot, dec_dot]) - Comments.append(comment) - return Objects, Ctr_Coords, Obj_Coords, Magnitudes, Offsets, Rates, Comments - - -def find_mpc_objects_cl(date, ut, ra, dec, radius, limmag): - """Query the MPC database for small bodies within a specified radius [arcmin] and limiting magnitude using user-supplied ra,dec,date""" - Objects = [] - Obj_Coords = [] - Magnitudes = [] - Offsets = [] - Rates = [] - Comments = [] - year, month, day = date.split("/") - ut_hr, ut_min = [float(x) for x in ut.split(":")] - frac_day = (ut_hr + ut_min / 60.0) / 24.0 - day = "%.2f" % (float(day) + frac_day) - coord = "%s %s" % (ra, dec) - Ctr_Coords = SkyCoord(coord, unit=(u.hourangle, u.deg)) - ra = ra.replace(":", " ") - dec = dec.replace(":", " ") - payload = { - "year": year, - "month": month, - "day": day, - "which": "pos", - "ra": ra, - "decl": dec, - "TextArea": " ", - "radius": radius, - "limit": limmag, - "oc": "857", - "sort": "d", - "mot": "h", - "tmot": "s", - "pdes": "u", - "needed": "f", - "ps": "n", - "type": "p", - } - r = requests.get( - "http://www.minorplanetcenter.net/cgi-bin/mpcheck.cgi", params=payload - ) - myString = r.text - # check to see if any objects found - if "No known minor planets" in r.text: - return Objects, Ctr_Coords, Obj_Coords, Magnitudes, Offsets, Rates, Comments - else: - mySubString = r.text[r.text.find("
") + 5 : r.text.find("
")] - mySubString = mySubString.replace("°", "d") - mySubString = mySubString.replace( - 'Further observations?', - "", - ) - # make a 2d list of objects for manipulation by other programs - objectlist = make2dlist(mySubString) - for line in objectlist: - objname, ra, dec, mag, ra_off, dec_off, ra_dot, dec_dot, dum, comment = line - ra_off, dec_off, ra_dot, dec_dot = fix_signs( - ra_off, dec_off, ra_dot, dec_dot - ) - mag = float(mag) - Objects.append(objname) - coord = "%s %s" % (ra, dec) - c = SkyCoord(coord, unit=(u.hourangle, u.deg)) - Obj_Coords.append(c) - Magnitudes.append(mag) - Offsets.append([ra_off, dec_off]) - Rates.append([ra_dot, dec_dot]) - Comments.append(comment) - return Objects, Ctr_Coords, Obj_Coords, Magnitudes, Offsets, Rates, Comments - - -# ====== MAIN ========== - -# Get params from command line -(opts, args) = get_args() -radius = opts.radius -limmag = opts.limmag - - -# Query MPC, parse output -if len(args) == 1: - ftsfile = args[0] - if os.path.isfile(ftsfile): - ( - Objects, - Ctr_Coords, - Obj_Coords, - Magnitudes, - Offsets, - Rates, - Comments, - ) = find_mpc_objects(ftsfile, radius, limmag) - ctr_coords = Ctr_Coords.to_string( - style="hmsdms", precision=1, sep=":", decimal=False - ) - object, nbin, date, ut, ra, dec, exptime, filter, z = get_hdr_info(ftsfile) - else: - sys.exit("File %s does not exist in current path, exiting" % ftsfile) -else: - if not opts.RA or not opts.DEC or not opts.date: - sys.exit("Must specifiy at least -R, -D, -d if no image specified.") - ra = opts.RA - dec = opts.DEC - ut = opts.ut - date = opts.date - ( - Objects, - Ctr_Coords, - Obj_Coords, - Magnitudes, - Offsets, - Rates, - Comments, - ) = find_mpc_objects_cl(date, ut, ra, dec, radius, limmag) - ctr_coords = Ctr_Coords.to_string( - style="hmsdms", precision=1, sep=":", decimal=False - ) -# Print results -N = len(Objects) -if N == 0: - print( - "No known minor bodies with V < %s within %s' radius of %s on %s at %s UT" - % (limmag, radius, ctr_coords, date, ut) - ) -else: - print() - print( - "Found %i minor bodies with V < %s within %s' radius of %s on %s at %s UT" - % (N, limmag, radius, ctr_coords, date, ut) - ) - print() - print( - " Number Name V RA(J2000) Dec(J2000) Offsets Motion Comment" - ) - print( - "----------------------------------------------------------------------------------------------------------------------" - ) - for j in range(N): - coords_hmsdms = Obj_Coords[j].to_string( - style="hmsdms", precision=1, sep=":", decimal=False - ) - ra_deg = Obj_Coords[j].ra.degree - dec_deg = Obj_Coords[j].dec.degree - coords = Obj_Coords[j].to_string( - style="hmsdms", precision=2, sep=":", decimal=False - ) - ra_off, dec_off = Offsets[j] - ra_rate, dec_rate = Rates[j] - print( - "%s %.2f %s [%5.1f',%5.1f'] [%3i\"/hr, %3i\"/hr] %s" - % ( - Objects[j], - Magnitudes[j], - coords, - ra_off, - dec_off, - ra_rate, - dec_rate, - Comments[j], - ) - ) diff --git a/_tba/analysis/sexphot.py b/_tba/analysis/sexphot.py deleted file mode 100755 index eddaffdf..00000000 --- a/_tba/analysis/sexphot.py +++ /dev/null @@ -1,743 +0,0 @@ -#!/usr/bin/env python -""" -sexphot: Computes photometric magnitudes using sextractor, plots light curves -Optionally checks magnitudes using SDSS -N.B. Requires sextractor! - -1.0 [command line sex, config file location defaults to /usr/local/sextractor/default.sex] -1.1 31 May 2016 - Fixed bug in Sloan lookup function -1.2 13 Jun 2016 - report median magnitudes on plots (if option -l) -1.3 21 Feb 2017 - skip stars with fluxerr = 0 -1.4 31 May 2017 - Correctly [?] account for airmass, average color -2.0 2 Dec 2018 - add BJD, solve for minimum time, set ZP mags for IKON camera, changed max FWHM to 5 pixs, remove SDSS -2.1 10 Jan 2019 - IF AIRMASS keyword not found, calculate using ELEVATION keyword (or give up, use 1.0), add fwhm_filter -2.11 2 Feb 2019 - Check: if sextractor couldn't find stars = skip (try/except) -2.2 17 May 2020 - Fix array integer index problem in time of minimum solver -2.3 22 Jan 2021 - add pDF plot format -2.4 11 Jan 2022 - check Maxim version: do not add 0.5x exposure time to time of observation if version = 6.22+ - since DATE-OBS and JD keywords are now == observation midpoint (Maxim v.6.22+) -""" -vers = "2.4 (11 Jan 2021)" - -import glob -import os -import re -import sys -import warnings -from optparse import OptionParser - -import matplotlib.pyplot as plt -import numpy as np -from astropy import coordinates as coord -from astropy import time -from astropy import units as u -from astropy.coordinates import SkyCoord - -# from astropy.time import Time -# from astropy import units as u -from astropy.io import fits -from astropy.io.fits import getheader, setval, update -from matplotlib.pyplot import cm -from scipy.optimize import curve_fit, minimize -from scipy.stats import chi2 - -# Avoid annoying warning about matplotlib building the font cache -warnings.filterwarnings("ignore") - -# Sextractor config file path -sex_path = "/usr/local/sextractor/default.sex" - - -def get_args(): - global parser - parser = OptionParser( - description="%prog computes photometric magnitudes using sextractor, plots light curves", - version="%s" % vers, - ) - parser.add_option( - "-s", - dest="sigma", - metavar="sigma", - action="store", - type=float, - default=5, - help="Sextractor detection threshold [default 5]", - ) - parser.add_option( - "-c", - dest="config", - metavar="config", - action="store", - help="phot config file name [no default]", - ) - parser.add_option( - "-d", - dest="datafile", - metavar="outfile", - action="store", - default="", - help="Output csv file name", - ) - parser.add_option( - "-F", - dest="fwhm_off", - metavar="fwhm_off", - action="store_true", - default=False, - help="Skip FWHM check, default = False", - ) - parser.add_option( - "-l", - dest="line", - metavar="line", - action="store_true", - default=False, - help="Plot median line", - ) - parser.add_option( - "-j", - dest="jdrange", - metavar="jdrange", - action="store", - default="0,0", - help="BJD range (BJDmin, BJDmax)", - ) - parser.add_option( - "-P", - dest="PDF", - metavar="PDF", - action="store_true", - default=False, - help="PDF plot format[default png format]", - ) - parser.add_option( - "-t", - dest="tmin", - metavar="Time_minimum", - action="store_true", - default=False, - help="Solve for time of minimum [default False]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - parser.add_option( - "-w", - dest="width", - metavar="width", - action="store", - type=int, - default=20, - help="Minimum fit width, sample times [default 20]", - ) - parser.add_option( - "-y", - dest="yrange", - metavar="yrange", - action="store", - default="0,0", - help="Differential plot yrange [default: autoscale]", - ) - parser.add_option( - "-z", - dest="zp", - metavar="Zeropoint", - action="store", - type=float, - default=-1, - help="Zero-point magnitude, defaults to FITS header value", - ) - return parser.parse_args() - - -def hms2rad(hms_str): - import re - - result = 0 - fields = re.split(r"[: _]", hms_str) - fields = [float(x) for x in fields] - while len(fields) > 0: - result = result / 60.0 + fields.pop() - rad = result * np.pi / 12.0 - return rad - - -def get_hdrdata(ftsfile): - hdr = getheader(ftsfile, 0) - date = hdr["DATE-OBS"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"][0] - if "AIRMASS" in hdr: - airmass = hdr["AIRMASS"] - elif "ELEVATION" in hdr: - airmass = 1 / np.sin(hms2rad(hdr["ELEVATION"])) - else: - if verbose: - print( - "%s: No airmass or elevation in header, assuming airmass = 1.0" - % ftsfile - ) - airmass = 1.0 - zp = 0 - zperr = 0 - if "ZMAG" in hdr: - zp = hdr["ZMAG"] - if "ZMAGERR" in hdr: - zperr = hdr["ZMAGERR"] - nbin = hdr["XBINNING"] # Assume same for y binning - arcsec_pixel = np.abs(hdr["CDELT1"] * 3600.0) - ra_str = hdr["RA"] - dec_str = hdr["DEC"] - Maxim_version = float(hdr["SWCREATE"].split()[3]) - jd_utc = hdr["JD"] - if Maxim_version < 6.22: - if verbose: - print( - "Maxim version %.2f (< 6.22), adding 0.5x exposure time to time of observation" - % Maxim_version - ) - jd_utc += exptime / (2.0 * 86400) - bjd = calc_bjd(jd_utc, ra_str, dec_str) - return ( - bjd, - date, - ra_str, - dec_str, - exptime, - filter, - arcsec_pixel, - airmass, - nbin, - zp, - zperr, - ) - - -def get_sexinfo(sexname, fwhm_filter, exptime, scale): - fn = open(sexname, "r") - lines = fn.readlines()[15:] - Nr = [] - Ra = [] - Dec = [] - Snr = [] - Flux = [] - Fluxerr = [] - Fwhm = [] - V = [] - Verr = [] - for line in lines: - ( - nr, - dum, - dum, - flux, - fluxerr, - x_pix, - y_pix, - ra_deg, - dec_deg, - profile_x, - profile_y, - pa, - fwhm_pixel, - dum, - flag, - ) = [float(x) for x in line.split()] - v = -2.5 * np.log10(flux / exptime) - if fluxerr == 0: - continue - snr = flux / fluxerr - verr = 2.5 * (fluxerr / flux) # Expanding log10(1+x) ~ 2.5x - Nr.append(nr) - Ra.append(ra_deg) - Dec.append(dec_deg) - Flux.append(flux) - Fluxerr.append(fluxerr) - Fwhm.append(fwhm_pixel * np.abs(scale)) - Snr.append(snr) - V.append(v) - Verr.append(verr) - fn.close() - # Trim list to stars by restricting fwhm values - fwhm_min = 1.4 - fwhm_max = 7.0 - A = list(zip(Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr)) - B = [] - for j in range(len(A)): - if fwhm_min < A[j][5] < fwhm_max or fwhm_off: - B.append(A[j]) - Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr = list(zip(*B)) - V = np.array(V) - Verr = np.array(Verr) - return Nr, Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr - - -def phase_diff(jd, jd0, P): - # Returns phase and o-c [day] at heliocentric observed date jd given ephemeris jd0,P - jd_diff = jd - jd0 - phase = (jd_diff % P) / P - if phase >= 0.5: - o_c = (phase - 1) * P - else: - o_c = phase * P - return phase, o_c - - -def fgauss(x, a, b, x0, w): - t = (x - x0) ** 2 / w**2 - return a * np.exp(-t) + b - - -def trim(indices, A): - # Trims arrays packed in A, dropping elements with given indices - B = [] - for a in A: - B.append(np.delete(a, indices)) - return B - - -def get_magnitudes(Ra, Dec, Nr_sex, Ra_sex, Dec_sex, max_diff, Mag_sex, Mag_sex_err): - N1 = len(Ra) - N2 = len(Ra_sex) - Mag = np.empty(N1) * np.nan - Mag_err = np.empty(N1) * np.nan - Nr = np.empty(N1) * np.nan - for j in range(N1): - for k in range(N2): - dra = np.abs(Ra[j] - Ra_sex[k]) - ddec = np.abs(Dec[j] - Dec_sex[k]) - if dra < max_diff and ddec < max_diff: - Mag[j] = Mag_sex[k] - Mag_err[j] = Mag_sex_err[k] - Nr[j] = Nr_sex[k] - Mag = np.array(Mag) - Mag_err = np.array(Mag_err) - return Nr, Mag, Mag_err - - -def parse_config(config_file): - Objects = [] - Filters = [] - Ftsfiles = [] - Mag_catalog = [] - Ra_hms = [] - Dec_dms = [] - Ra_deg = [] - Dec_deg = [] - if not os.path.isfile(config_file): - sys.exit("Configuration file %s does not exist, try again" % config_file) - else: - fn = open(config_file, "r") - lines = fn.readlines() - fn.close() - for line in lines: - line = line.split() - if line == []: - continue # Skip blank lines - elif line[0] == "I": - Ftsfiles.append(line[1]) - elif line[0] == "F": - Filter = line[1] - elif line[0] == "S": - object, ra_hms, dec_dms = line[1:4] - Objects.append(object) - Ra_hms.append(ra_hms) - Dec_dms.append(dec_dms) - c = SkyCoord(ra_hms, dec_dms, unit=(u.hourangle, u.deg), frame="icrs") - Ra_deg.append(c.ra.deg) - Dec_deg.append(c.dec.deg) - elif line[0] == "T": - title = " ".join(line[1:]) - elif line[0] == "E": - BJD0, P0 = [float(s) for s in line[1:]] - return ( - Objects, - Filter, - Ftsfiles, - Ra_hms, - Dec_dms, - Ra_deg, - Dec_deg, - Mag_catalog, - title, - BJD0, - P0, - ) - - -def calc_bjd(jd_utc, ra_str, dec_str): - """ - Calculate barycentric dynamical Julian date from UTC Julian date, source coordinates - Refs: http://docs.astropy.org/en/stable/time - Eastman, et al. 2010 PASP,122,935 - """ - object = coord.SkyCoord(ra_str, dec_str, unit=(u.hourangle, u.deg), frame="icrs") - lowell = coord.EarthLocation.of_site( - "Multiple Mirror Telescope" - ) # close enough to Winer - times = time.Time(jd_utc, format="jd", scale="utc", location=lowell) - ltt_bary = times.light_travel_time(object) - bjd_tdb = times.tdb + ltt_bary - return bjd_tdb.value - - -def calc_tmin(BJD, obj, obj_sigma, width): - jd_frac, jd_int = np.modf(BJD) - jd1 = jd_int[0] # Integer part of first BJD time - jmin = np.argmax( - obj - ) # index of maximum (minimum magnitude), use as guess for fitted minimum - BJD_min0 = BJD[jmin] - if verbose: - print( - "Found sample minimum at BJD = %.5f (jmin = %i, mag = %.2f)" - % (BJD_min0, jmin, obj[jmin]) - ) - w = int(width / 2) # Width of fit in points - # print(jmin,w) - x = np.array(BJD[jmin - w : jmin + w]) - BJD[0] - y = obj[jmin - w : jmin + w] - s = obj_sigma[jmin - w : jmin + w] - - # weighted Gaussian fit - init_vals = (obj[jmin], 0, BJD[jmin] - BJD[0], width * exptime / 86400) - [a_fit, b_fit, x0_fit, w_fit], cov = curve_fit(fgauss, x, y, p0=init_vals) - [a_s, b_s, x0_s, w_s] = np.sqrt(np.diag(cov)) - delta_jd = x0_fit - sigma = x0_s # Uncertainty in delta_jd - bjd_min = BJD[0] + delta_jd - - # generate a model for plotting - npts = 100 - xmod = np.linspace(BJD[jmin - w] - BJD[0], BJD[jmin + w] - BJD[0], npts) - ymod = fgauss(xmod, a_fit, b_fit, x0_fit, w_fit) - xmod += BJD[0] - # phs_mod = ( (xmod-jd0) % P)/P - return bjd_min, sigma, xmod, ymod - - -# ======== MAIN ================ - -# Max difference: config vs Sex position [deg] -max_diff = 5 / 3600.0 - -# Define dictionary of zero-point values and extinction for filters [guesses except for Sloan G, R, I] -# Cal_Apogee = { 'N':(22.0,0.20), 'B':(21.5,0.35), 'G':(22.68,0.28), 'V':(20.6,0.20), 'R':(20.3,0.12), 'I':(20.66,0.05) } -Cal = { - "N": (23.2, 0.20), - "C": (23.2, 0.20), - "L": (23.2, 0.20), - "B": (22.3, 0.35), - "G": (22.68, 0.28), - "V": (22.5, 0.20), - "R": (22.45, 0.12), - "I": (21.90, 0.05), -} - -# Get command line arguments, assign parameter values -(opts, args) = get_args() - - -if not opts.config: - parser.error("config file (-c) not given, try again") -config_file = opts.config -detect_threshold = opts.sigma # Sextractor detection threshold [sigma] -csvfile = opts.datafile # optional CSV output filename -fwhm_off = opts.fwhm_off # FWHM filter [on by default] -jdmin, jdmax = [float(x) for x in opts.jdrange.split(",")] # Julian date range -line = opts.line # Plot median line -solve_tmin = opts.tmin # Solve for minimum time -verbose = opts.verbose # Print diagnostics, more -width = opts.width # Width for finding minimum -ymin, ymax = [ - float(x) for x in opts.yrange.split(",") -] # Differential plot width, magnitudes -zp_user = opts.zp # Zeropoint magnitude -PDF = opts.PDF # Use PDF plot format? - -# Parse configuration file -( - Objects, - Filter, - Ftsfiles, - Ra_hms, - Dec_dms, - Ra_deg, - Dec_deg, - Mag_catalog, - title, - BJD0, - P0, -) = parse_config(config_file) -nstar = len(Objects) - -BJD = [] -Date = [] -Mag_all = [] -Mag_err_all = [] -FitsFile_all = [] -Nr_all = [] - -# Expand filenames if needed -if "*" in Ftsfiles[0] or "?" in Ftsfiles[0]: - Ftsfiles = glob.glob(Ftsfiles[0]) -if verbose: - print("Reading %i FITS image files" % len(Ftsfiles)) -for ftsfile in Ftsfiles: - # Get useful header info [NB not currently using nbin] - try: - ( - bjd, - date, - ra_str, - dec_str, - exptime, - filter, - scale, - airmass, - nbin, - zp, - zperr, - ) = get_hdrdata(ftsfile) - except: - if verbose: - print("%s header does not have required keywords, skipping" % ftsfile) - continue - # If wrong filter, skip - if filter != Filter: - if verbose: - print( - "%s: Wrong filter [expecting %s, got %s], skipping" - % (ftsfile, Filter, filter) - ) - continue - - # If not in user-specified JD range, skip - if jdmin == 0 and jdmax == 0: - pass - else: - if not jdmin <= bjd <= jdmax: - if verbose: - print( - "%s: BJD %.5f not in range %.5f - %.5f, skipping" - % (ftsfile, bjd, jdmin, jdmax) - ) - continue - # Run sextractor - sexname = os.path.basename(ftsfile).split(".")[0] + ".sexout" - if verbose: - print( - "Running sextractor on %s with detection threshold = %.1f sigma" - % (ftsfile, detect_threshold) - ) - os.system( - "sex %s -c %s -CATALOG_NAME %s -DETECT_THRESH %.1f -VERBOSE_TYPE QUIET" - % (ftsfile, sex_path, sexname, detect_threshold) - ) - - # Get position, magnitude info for each listed star in output file - try: - ( - Nr_sex, - Ra_sex, - Dec_sex, - Snr, - Flux, - Fluxerr, - Fwhm_sex, - Mag_sex, - Mag_sex_err, - ) = get_sexinfo(sexname, fwhm_off, exptime, scale) - nobs = len(Ra_sex) - if verbose: - print("Sextractor found %i stars" % nobs) - except: - if verbose: - print("Sextractor could'nt find stars, skipping %s" % ftsfile) - continue - # Get magnitudes for target objects using position match to sextractor output - Nr, Mag, Mag_err = get_magnitudes( - Ra_deg, Dec_deg, Nr_sex, Ra_sex, Dec_sex, max_diff, Mag_sex, Mag_sex_err - ) - # Convert to magnitude by adding ZP and correcting for extinction. Use user-supplied ZP if specified - if zp_user > 0: - ZP = zp_user - if verbose: - print("Using user-supplied zero-point (ZP = %.2f)" % ZP) - elif zp > 0: - ZP = zp - if verbose: - print("Using zero-point found in FITS header: %.2f)" % ZP) - else: - ZP = Cal[Filter][0] - if verbose: - print("Using preset zero-point for %s filter: (ZP = %.2f)" % (filter, ZP)) - k = Cal[Filter][1] - # correct for airmass, assume average color correction 0.1 - Mag -= k * airmass - 0.1 - Mag += ZP - - # Add to array, but only if all stars detected - if not np.isnan(Mag).any(): - BJD.append(bjd) - Date.append(date) - Mag_all.append(Mag) - Mag_err_all.append(Mag_err) - FitsFile_all.append(ftsfile) - Nr_all.append(Nr) - -nepoch = len(BJD) -if verbose: - print("Analyzing %i images" % nepoch) - -# Sort by BJD -BJD, FitsFile_all, Date, Nr_all, Mag_all, Mag_err_all = ( - list(x) - for x in zip(*sorted(zip(BJD, FitsFile_all, Date, Nr_all, Mag_all, Mag_err_all))) -) - -# Convert to numpy arrays -Mag = np.array(Mag_all) -Mag_err = np.array(Mag_err_all) - -# Subtract reference star magnitudes -Ref_Mag = Mag[:, -1] -Diff_mags = Mag - Ref_Mag[:, np.newaxis] - -# Target star is first listed -Mag_Target = Diff_mags[:, 0] -Mag_Target_Err = Mag_err[:, 0] -Name_Target = Objects[0].strip() - -# Solve for minimum if requested -if solve_tmin: - bjd_min, sigma, xmod, ymod = calc_tmin(BJD, Mag_Target, Mag_Target_Err, width) - phs_min, oc = phase_diff(bjd_min, BJD0, P0) - if verbose: - print( - "Fitted minimum at BJD: %.5f +/- %.5f, O-C = %.5f days (%.1f sec +/- %.1f sec). Phase at min = %.3f)" - % (bjd_min, sigma, oc, oc * 86400, sigma * 86400, phs_min) - ) -else: - bjd_min = 0 - phs_min = 0 - oc = 0 - jd1 = 0 - sigma = 0 - xmod = 0 - phs_mod = 0 - ymod = 0 - -# Calculate median differential magnitudes -Medians = np.median(Mag, axis=0) -Stds = np.std(Mag, axis=0) -""" -Diff_mag += Medians[-1] -Diff_err = np.sqrt(Mag_err**2 + Mag_err[-1]**2) -""" -# Plot light curves - -# First plot all stars on same plot -fig = plt.figure(figsize=(12, 8)) -for j in range(nepoch): - mjd = BJD[j] - BJD[0] - color = iter(cm.rainbow(np.linspace(0, 1, nstar + 1))) - for k in range(nstar): - c = next(color) - if j == 0: - plt.errorbar( - mjd, - Mag[j][k], - yerr=Mag_err[j][k], - marker="o", - markersize=5, - c=c, - label="%s" % Objects[k], - ) - else: - plt.errorbar( - mjd, Mag[j][k], yerr=Mag_err[j][k], marker="o", markersize=5, c=c - ) -plt.title(title) -plt.legend(loc=2, fontsize=8) -ymin0 = np.max(Medians) + 1 -ymax0 = np.min(Medians) - 1 -plt.ylim(ymin0, ymax0) -if jdmin != 0.0: - plt.xlim(jdmin - BJD[0], jdmax - BJD[0]) -plt.ylabel("%s Magnitude" % Filter) -plt.xlabel("Days since BJD %.5f (%s)" % (BJD[0], Date[0])) -plt.grid(True) -if PDF: - plot_title = "%s_lc-all.pdf" % Name_Target -else: - plot_title = "%s_lc-all.png" % Name_Target -plt.savefig(plot_title) -print("Saved light curve of all stars as %s" % plot_title) -plt.close(fig) - -# Plot target star differential magnitude with minimum fit [optional] -fig = plt.figure(figsize=(12, 8)) -BJD0_int = np.int(BJD[0]) -mBJD = np.array(BJD) - BJD0_int - -plt.errorbar( - mBJD, Mag_Target, yerr=Mag_Target_Err, ls="none", marker="o", markersize=5, c="b" -) -plt.suptitle(title, fontsize=14) -if solve_tmin: - plt.plot(xmod - BJD0_int, ymod, "r--") - title = ( - "BJD0: %.6f, P0: %.10f,\n BJDmin: %.5f +/- %.5f, O-C: %.5f days [%.1f s +/- %.1f s]" - % (BJD0, P0, bjd_min, sigma, oc, oc * 86400, sigma * 86400) - ) -else: - title = "%s [RA: %s, Dec: %s] Filter = %s" % ( - Name_Target, - Ra_hms[0], - Dec_dms[0], - Filter, - ) -plt.title(title, fontsize=12) - -# Set plot limits -if jdmin != 0.0: - plt.xlim(jdmin - BJD0_int, jdmax - BJD0_int) -if ymin != 0 or ymax != 0: - plt.ylim(ymin, ymax) -else: - mag_range = np.abs(max(Mag_Target) - min(Mag_Target)) - ymin = min(Mag_Target) - 0.2 * mag_range - ymax = max(Mag_Target) + 0.2 * mag_range - plt.ylim(ymax, ymin) - -UTDate, UTTime = Date[0].split("T") -plt.xlabel("Barycentric JD - %i (Start: %s, %s UT)" % (BJD0_int, UTDate, UTTime)) -plt.ylabel("Differential Magnitude (Filter %s)" % Filter) -plt.grid(True) -if PDF: - plot_file = "%s_%s_lc.pdf" % (Name_Target, UTDate) -else: - plot_file = "%s_%s_lc.png" % (Name_Target, UTDate) -plt.savefig(plot_file) -print("Saved differential l.c. plot %s" % plot_file) -plt.close(fig) - - -# write CSV output file if requested -if csvfile != "": - fn = open(csvfile, "w") - for j in range(nepoch): - str1 = " ".join( - "%s %.3f %.3f " % (Objects[k], Mag[j][k], Mag_err[j][k]) - for k in range(nstar) - ) - s = "%10.5f %30s %s\n" % (BJD[j], FitsFile_all[j], str1) - fn.write(s) - print("Wrote data file: %s" % csvfile) - fn.close() diff --git a/_tba/analysis/sexphot2.py b/_tba/analysis/sexphot2.py deleted file mode 100755 index e9544383..00000000 --- a/_tba/analysis/sexphot2.py +++ /dev/null @@ -1,626 +0,0 @@ -#!/usr/bin/env python - -# sexphot2 -# Computes 2-color [G,R] photometric magnitudes using sextractor, plots light curves for G, R filters and g-r color index -# Optionally checks magnitudes using SDSS - -# N.B. Requires sextractor! -# [command line sex, config file location defaults to /usr/local/sextractor/default.sex] -# v. 1.0 11 Mar 2017 -# v. 1.1 26 Mar 2017 - add fwhm_range in opts -# v. 1.2 31 May 2017 - check photometry -# v. 1.3 13 Jun 2017 - fix problem with some epochs having nan magnitudes - -vers = "1.3 (13 Jun 2017)" - -import glob -import itertools -import os -import re -import sys -import warnings -from optparse import OptionParser - -import matplotlib.pyplot as plt -import numpy as np -from astropy import units as u -from astropy.coordinates import SkyCoord -from astropy.io import fits -from astropy.io.fits import getheader, setval, update -from astropy.time import Time -from astroquery.sdss import SDSS -from matplotlib.pyplot import cm -from scipy.optimize import minimize - -# Avoid annoying warning about matplotlib building the font cache -warnings.filterwarnings("ignore") - -# Sextractor config file path -sex_path = "/usr/local/sextractor/default.sex" - - -def get_args(): - global parser - parser = OptionParser( - description="Program %prog plots 2-color (Sloan g,r) absolute photometric magnitudes and color index g-r using sextractor, ZP mag", - version="%s" % vers, - ) - parser.add_option( - "-s", - dest="sigma", - metavar="sigma", - action="store", - type=float, - default=5, - help="Sextractor detection threshold [default 5]", - ) - parser.add_option( - "-c", - dest="config", - metavar="config", - action="store", - help="phot config file name [no default]", - ) - parser.add_option( - "-d", - dest="datafile", - metavar="outfile", - action="store", - default="", - help="Output csv file name", - ) - parser.add_option( - "-f", - dest="fwhm_range", - metavar="fwhm_range", - action="store", - default="1.4,5", - help="FWHM max (pixels) [default 1.4,5]", - ) - parser.add_option( - "-l", - dest="line", - metavar="line", - action="store_true", - default=False, - help="Plot median line", - ) - parser.add_option( - "-p", - dest="plot", - metavar="plot", - action="store_true", - default=True, - help="Plot solution", - ) - parser.add_option( - "-J", - dest="jdrange", - metavar="jdrange", - action="store", - default="0,0", - help="JD range (JDmin, JDmax)", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - parser.add_option( - "-y", - dest="ywidth", - metavar="ywidth", - action="store", - type=float, - default=2.0, - help="Differential plot width [mags, default 2 mag]", - ) - return parser.parse_args() - - -def get_hdrdata(ftsfile): - hdr = getheader(ftsfile, 0) - jd = hdr["JD"] - date = hdr["DATE-OBS"] - exptime = hdr["EXPTIME"] - filter = hdr["FILTER"][0] - airmass = hdr["AIRMASS"] - if "ZMAG" in hdr: - zp = hdr["ZMAG"] - zperr = hdr["ZMAGERR"] - else: - zp = 0 - zperr = 0 - nbin = hdr["XBINNING"] # Assume same for y binning - arcsec_pixel = np.abs(hdr["CDELT1"] * 3600.0) - return jd, date, exptime, filter, arcsec_pixel, airmass, nbin, zp, zperr - - -def get_sexinfo(sexname, exptime, scale): - global fwhm_min, fwhm_max - fn = open(sexname, "r") - lines = fn.readlines()[15:] - Nr = [] - Ra = [] - Dec = [] - Snr = [] - Flux = [] - Fluxerr = [] - Fwhm = [] - V = [] - Verr = [] - for line in lines: - ( - nr, - dum, - dum, - flux, - fluxerr, - x_pix, - y_pix, - ra_deg, - dec_deg, - profile_x, - profile_y, - pa, - fwhm_pixel, - dum, - flag, - ) = [float(x) for x in line.split()] - v = -2.5 * np.log10(flux / exptime) - if fluxerr == 0: - continue - snr = flux / fluxerr - verr = 2.5 * (fluxerr / flux) # Expanding log10(1+x) ~ 2.5x - Nr.append(nr) - Ra.append(ra_deg) - Dec.append(dec_deg) - Flux.append(flux) - Fluxerr.append(fluxerr) - Fwhm.append(fwhm_pixel * np.abs(scale)) - Snr.append(snr) - V.append(v) - Verr.append(verr) - fn.close() - n1 = len(V) - # Trim list to stars by restricting fwhm values - A = zip(Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr) - B = [] - for j in range(len(A)): - if fwhm_min < A[j][5] < fwhm_max: - B.append(A[j]) - Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr = zip(*B) - n2 = len(V) - if verbose: - print( - "Warning, skipped %i stars because they exceeded allowed FWHM range (%.1f,%.1f), use option -f to change" - % (n1 - n2, fwhm_min, fwhm_max) - ) - V = np.array(V) - Verr = np.array(Verr) - return Nr, Ra, Dec, Snr, Flux, Fluxerr, Fwhm, V, Verr - - -def get_sdss_magnitudes(ra, dec): - # Query SDSS online photometric catalog for u,g,r,i,z magnitudes; ra,deg in degrees (ICRS, 2000) - pos = SkyCoord(ra, dec, unit=(u.deg, u.deg), frame="icrs") - ids = SDSS.query_region( - pos, radius=5 * u.arcsec, fields=["ra", "dec", "clean", "u", "g", "r", "i", "z"] - ) # defaults to 2 arcsec search - u1 = g = r = i = z = np.nan - if ids != None: - for id in ids: - if ( - id["clean"] == 1 and id["g"] < 20.0 - ): # Only accept photometry with clean flags & reject very faint stars - u1 = id["u"] - g = id["g"] - r = id["r"] - i = id["i"] - z = id["z"] - return u1, g, r, i, z - return u1, g, r, i, z - - -def trim(indices, A): - # Trims arrays packed in A, dropping elements with given indices - B = [] - for a in A: - B.append(np.delete(a, indices)) - return B - - -def get_magnitudes(Ra, Dec, Nr_sex, Ra_sex, Dec_sex, max_diff, Mag_sex, Mag_sex_err): - N1 = len(Ra) - N2 = len(Ra_sex) - Mag = np.empty(N1) * np.nan - Mag_err = np.empty(N1) * np.nan - Nr = np.empty(N1) * np.nan - for j in range(N1): - for k in range(N2): - dra = np.abs(Ra[j] - Ra_sex[k]) - ddec = np.abs(Dec[j] - Dec_sex[k]) - if dra < max_diff and ddec < max_diff: - Mag[j] = Mag_sex[k] - Mag_err[j] = Mag_sex_err[k] - Nr[j] = Nr_sex[k] - Mag = np.array(Mag) - Mag_err = np.array(Mag_err) - return Nr, Mag, Mag_err - - -def parse_config(config_file): - Objects = [] - Filters = [] - Ftsfiles_G = [] - Ftsfiles_R = [] - Ra_hms = [] - Dec_dms = [] - Ra_deg = [] - Dec_deg = [] - if not os.path.isfile(config_file): - sys.exit("Configuration file %s does not exist, try again" % config_file) - else: - fn = open(config_file, "r") - lines = fn.readlines() - fn.close() - for line in lines: - line = line.split() - if line == []: - continue # Skip blank lines - elif line[0] == "G": - Ftsfiles_G.append(line[1]) - elif line[0] == "R": - Ftsfiles_R.append(line[1]) - elif line[0] == "S": - object, ra_hms, dec_dms = line[1:4] - Objects.append(object) - Ra_hms.append(ra_hms) - Dec_dms.append(dec_dms) - c = SkyCoord(ra_hms, dec_dms, unit=(u.hourangle, u.deg), frame="icrs") - Ra_deg.append(c.ra.deg) - Dec_deg.append(c.dec.deg) - elif line[0] == "T": - title = " ".join(line[1:]) - return Objects, Ftsfiles_G, Ftsfiles_R, Ra_hms, Dec_dms, Ra_deg, Dec_deg, title - - -def get_star_info(Ftsfiles, Filter, Cal_vals): - JD = [] - Date = [] - Mag_all = [] - Mag_err_all = [] - FitsFile_all = [] - Nr_all = [] - - for ftsfile in Ftsfiles: - # Get useful header info [NB not currently using nbin] - jd, date, exptime, filter, scale, airmass, nbin, zp, zperr = get_hdrdata( - ftsfile - ) - - # If wrong filter, skip - if filter != Filter: - if verbose: - print( - "%s: Wrong filter [expecting %s, got %s], skipping" - % (ftsfile, Filter, filter) - ) - continue - - # Run sextractor - if verbose: - print( - "Running sextractor on %s with detection threshold = %.1f sigma" - % (ftsfile, detect_threshold) - ) - sname = "%s_%s.sexout" % (os.path.basename(ftsfile).split(".")[0], Filter) - os.system( - "sex %s -c %s -CATALOG_NAME %s -DETECT_THRESH %.1f -VERBOSE_TYPE QUIET" - % (ftsfile, sex_path, sname, detect_threshold) - ) - - # Get position, magnitude info for each listed star in output file - ( - Nr_sex, - Ra_sex, - Dec_sex, - Snr, - Flux, - Fluxerr, - Fwhm_sex, - Mag_sex, - Mag_sex_err, - ) = get_sexinfo(sname, exptime, scale) - nobs = len(Ra_sex) - if verbose: - print("Sextractor found %i stars" % nobs) - - # Get magnitudes for target objects using position match to sextractor output - Nr, Mag, Mag_err = get_magnitudes( - Ra_deg, Dec_deg, Nr_sex, Ra_sex, Dec_sex, max_diff, Mag_sex, Mag_sex_err - ) - - # Convert to magnitude by adding ZP. Use user-supplied ZP if specified - if zp > 0: - ZP = zp - k = Cal_vals[Filter][1] - else: - ZP, k = Cal_vals[Filter] - if verbose: - print( - "WARNING: Using default zero-point for %s filter: (ZP = %.2f)" - % (filter, ZP) - ) - # correct for airmass, assume average color correction 0.1 - Mag -= k * airmass - 0.1 - Mag += ZP - # Add to array, but only if all stars detected - # if not np.isnan(Mag).any(): - if True: - JD.append(jd) - Date.append(date) - Mag_all.append(Mag) - Mag_err_all.append(Mag_err) - FitsFile_all.append(ftsfile) - Nr_all.append(Nr) - # Sort by JD - JD, FitsFile_all, Date, Nr_all, Mag_all, Mag_err_all = ( - list(x) - for x in zip(*sorted(zip(JD, FitsFile_all, Date, Nr_all, Mag_all, Mag_err_all))) - ) - - # Convert to numpy arrays - Mag = np.array(Mag_all) - Mag_err = np.array(Mag_err_all) - - # Calculate median differential magnitudes - Medians = np.nanmedian(Mag, axis=0) - Stds = np.nanstd(Mag, axis=0) - # print 'Medians = %s' % ( (' '.join('%5.2f' % x for x in Medians))) - return JD, Date, FitsFile_all, Mag, Mag_err, Medians, Stds - - -def mk_plot( - j, - JD0, - Date0, - object, - Ra_hms, - Dec_dms, - mjd_g, - mjd_r, - G_mag, - G_mag_err, - R_mag, - R_mag_err, - ywidth, - line, -): - fig = plt.figure(j, figsize=(12, 8)) - ax = fig.add_subplot(111) - ym = np.nanmean(np.append(G_mag, R_mag)) - ymin = ym + ywidth / 2.0 - ymax = ymin - ywidth - plt.errorbar(mjd_g, G_mag, yerr=G_mag_err, fmt="bs", label="Sloan g") - plt.errorbar(mjd_r, R_mag, yerr=R_mag_err, fmt="rs", label="Sloan r") - plt.suptitle(title, fontsize=14) - plt.title("%s [RA: %s, Dec: %s]" % (object, Ra_hms, Dec_dms), fontsize=12) - if jdmin != 0.0: - plt.xlim(jdmin - JD0, jdmax - JD0) - plt.legend(loc=2) - plt.ylim(ymin, ymax) - if line: - xmin, xmax = ax.get_xlim() - xline = [xmin, xmax] - ym_g = np.nanmedian(G_mag) - ym_r = np.nanmedian(R_mag) - std_g = np.nanstd(G_mag) - std_r = np.nanstd(R_mag) - yline_g = [ym_g, ym_g] - yline_r = [ym_r, ym_r] - plt.plot(xline, yline_g, linestyle="dashed", color="blue") - plt.plot(xline, yline_r, linestyle="dashed", color="red") - txt = "Median g = %.2f +/- %.2f, r = %.2f +/- %.2f, g-r = %.2f" % ( - ym_g, - std_g, - ym_r, - std_r, - ym_g - ym_r, - ) - plt.text( - 0.05, - 0.05, - txt, - fontsize=12, - transform=ax.transAxes, - bbox=dict(facecolor="white", alpha=0.5), - ) - - UTDate, UTTime = Date0.split("T") - plt.xlabel("Days since JD %.5f (%s, %s UT)" % (JD0, UTDate, UTTime)) - plt.ylabel("Magnitude at airmass = 0") - plt.grid(True) - plot_title = "%s_lc.pdf" % object - plt.savefig(plot_title) - print("Saved differential l.c. plot %s" % plot_title) - plt.close(fig) - - -# ======== MAIN ================ - -# Max difference: config vs Sex position [deg] -max_diff = 5 / 3600.0 - -# Define dictionary of zero-point values and extinction for filters [guesses except for G, R] -Cal_vals = { - "N": (21.5, 0.20), - "B": (21.0, 0.35), - "G": (22.2, 0.28), - "V": (20.5, 0.20), - "R": (21.8, 0.12), - "W": (20.66, 0.05), -} - -# Get command line arguments, assign parameter values -(opts, args) = get_args() - -if not opts.config: - parser.error("config file (-c) not given, try again") -config_file = opts.config - -detect_threshold = opts.sigma # Sextractor detection threshold [sigma] -plot = opts.plot # Plot various things -csvfile = opts.datafile # optional CSV output filename -jdmin, jdmax = [float(x) for x in opts.jdrange.split(",")] # Julian date range -line = opts.line # Plot median line -verbose = opts.verbose # Print diagnostics, more -ywidth = opts.ywidth # Differential plot width, magnitudes -fwhm_min, fwhm_max = [ - float(x) for x in opts.fwhm_range.split(",") -] # Maximum allowed FWHM (pixels) - -# Parse configuration file -Objects, Ftsfiles_G, Ftsfiles_R, Ra_hms, Dec_dms, Ra_deg, Dec_deg, title = parse_config( - config_file -) -nstar = len(Objects) - - -# Expand filenames if needed -if "*" in Ftsfiles_G[0] or "?" in Ftsfiles_G[0]: - Ftsfiles_G = glob.glob(Ftsfiles_G[0]) -if "*" in Ftsfiles_R[0] or "?" in Ftsfiles_R[0]: - Ftsfiles_R = glob.glob(Ftsfiles_R[0]) - - -Filters = ["G", "R"] -for Filter in Filters: - if Filter == "G": - ( - JD_G, - Date_G, - FitsFiles_all_G, - Mag_G, - Mag_err_G, - Medians_G, - Stds_G, - ) = get_star_info(Ftsfiles_G, Filter, Cal_vals) - elif Filter == "R": - ( - JD_R, - Date_R, - FitsFiles_all_R, - Mag_R, - Mag_err_R, - Medians_R, - Stds_R, - ) = get_star_info(Ftsfiles_R, Filter, Cal_vals) -""" -print 'G' -for k in range(len(JD_G)): - print JD_G[k], Mag_G[k] -print 'R' -for k in range(len(JD_R)): - print JD_R[k], Mag_R[k] -""" - -# Plot light curves - -JD0 = min(JD_G[0], JD_R[0]) -if JD0 == JD_G[0]: - Date0 = Date_G[0] -else: - Date0 = Date_R[0] -mjd_g = [x - JD0 for x in JD_G] -mjd_r = [x - JD0 for x in JD_R] -marker = itertools.cycle(["s", "d", "p", "*", "8"]) - -# First plot all stars on same plot -plt.figure(1, figsize=(12, 8)) - -params = {"legend.fontsize": 10} -plt.rcParams.update(params) - -G_mag = np.transpose(Mag_G) -G_mag_err = np.transpose(Mag_err_G) -R_mag = np.transpose(Mag_R) -R_mag_err = np.transpose(Mag_err_R) - -# Correct for color - -for k in range(nstar): - mark = marker.next() - plt.errorbar( - mjd_g, - G_mag[k], - yerr=G_mag_err[k], - marker=mark, - markersize=8, - c="b", - label="%s [G]" % Objects[k], - ) - plt.errorbar( - mjd_r, - R_mag[k], - yerr=R_mag_err[k], - marker=mark, - markersize=8, - c="r", - label="%s [R]" % Objects[k], - ) - -plt.title(title) -plt.legend(loc=2) -plt.ylim(18, 12) -if jdmin != 0.0: - plt.xlim(jdmin - JD[0], jdmax - JD[0]) -plt.ylabel("%s Magnitude" % Filter) -plt.xlabel("Days since JD %.5f (%s)" % (JD0, Date0)) -plt.grid(True) -plot_title = "%s_lc-all.pdf" % (config_file.split(".")[0]) -plt.savefig(plot_title) -print("Saved light curve plot as %s" % plot_title) - -# Separate plots for object -for k in range(nstar): - mk_plot( - k + 2, - JD0, - Date0, - Objects[k], - Ra_hms[k], - Dec_dms[k], - mjd_g, - mjd_r, - G_mag[k], - G_mag_err[k], - R_mag[k], - R_mag_err[k], - ywidth, - line, - ) - - -# write CSV output file if requested -if csvfile != "": - fn = open(csvfile, "w") - for j in range(len(JD_G)): - str1 = " ".join( - "%s: %.3f %.3f" % (Objects[k], G_mag[k][j], G_mag_err[k][j]) - for k in range(nstar) - ) - s = "%10.5f %30s G %s\n" % (JD_G[j], FitsFiles_all_G[j], str1) - fn.write(s) - for j in range(len(JD_R)): - str1 = " ".join( - "%s: %.3f %.3f" % (Objects[k], R_mag[k][j], R_mag_err[k][j]) - for k in range(nstar) - ) - s = "%10.5f %30s R %s\n" % (JD_R[j], FitsFiles_all_R[j], str1) - fn.write(s) - print("Wrote data file: %s" % csvfile) - fn.close() diff --git a/_tba/codecov.yml b/_tba/codecov.yml deleted file mode 100644 index 850b3da5..00000000 --- a/_tba/codecov.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Code Coverage - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - run: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Install dependencies - run: pip install -e ".[tests]" - - name: Run tests and collect coverage - run: pytest --cov -cov-report=xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/_tba/observatory/calibration_config.txt b/_tba/observatory/calibration_config.txt deleted file mode 100755 index 61eb27ee..00000000 --- a/_tba/observatory/calibration_config.txt +++ /dev/null @@ -1,13 +0,0 @@ -D:\Calibrations\{Timestamp} #SAVE_DATA_PATH -86.96717 #TARGET_AZ -30.09397 #TARGET_ALT -5 #SETTLE_TIME_SECONDS -1 #BINNING -25 #NUMBER_FLATS -0 #NUMBER_DARKS -0 #NUMBER_BIAS -15 #EXPOSURE_LENGTH_SECONDS_DARK -L,6,V,B,H,W,O,1,I,X,G,R #FILTER_NAMES -0.075,0,0.4,3,2.5,0.25,10,3,0.7,1,0.4,0.1 #FILTER_EXPOSURE_TIME -254,0,254,254,254,254,254,254,254,254,254,254 #FILTER_LAMP_INTENSITY -D:\Calibration_images #MASTER_DATA_PATH diff --git a/_tba/observatory/calibration_images.py b/_tba/observatory/calibration_images.py deleted file mode 100755 index 5baf5a14..00000000 --- a/_tba/observatory/calibration_images.py +++ /dev/null @@ -1,355 +0,0 @@ -import math -import os -import sys -import tempfile -import time -from datetime import datetime - -import astropy.io.fits as fits -import ephem -import numpy -import pulsar_dimmer -import relimport -from iotalib import convert, rst, talonwcs -from win32com.client import Dispatch - -""" -This script attempts to put a target RA and Dec at a particular pixel position -(which may be outside the bounds of the physical CCD in the case of an off-axis -spectrometer fiber). - -The script slews to a target, takes an image, finds a WCS solution and offsets -the mount to place the target RA/Dec at the requested pixel position. This process -repeats until the target is positioned within a specified tolerance or the maximum -number of attempts has been exceeded. - -The target pixel position can be calibrated by manually positioning a star at the -target position and running the calibrate_target_pixel script. -""" - -### CONFIGURATION VALUES ######################## - -# configuration_file = sys.argv[1] -configuration_file = "calibration_config.txt" -config = [] -with open(configuration_file) as infile: - for line in infile: - config.append((line.split()[0])) - -# MOUNT_DRIVER = "ASCOM.SoftwareBisque.Telescope" # Use this for the Paramount ME (controlled through TheSky) -MOUNT_DRIVER = "SiTech.Telescope" -HOME_MOUNT = True - -OBJECT_NAME = "" # Provides the name of an object to search for in the SIMBAD Database (If empty string, use provided coordinates) -TARGET_AZ = float(config[1]) # RA coordinates of the target star, in J2000 hours -TARGET_ALT = float(config[2]) # Dec coordinates of the target star, in J2000 degrees -SETTLE_TIME_SECONDS = float( - config[3] -) # Pause for this many seconds after slewing to each target before taking an image - -BINNING = float( - config[4] -) # Image binning (higher binning reduces resolution but speeds up image readout time) - -EXPOSURE_LENGTH_SECONDS_DARK = float( - config[8] -) # Exposure length of each dark image, in seconds -NUMBER_DARKS = float(config[6]) -NUMBER_BIAS = float(config[7]) -NUMBER_FLATS = float(config[5]) -FILTER_NAMES = config[9].split(",") # ["L","R","V","B","H","G","W","N"] -# FILTER_EXPOSURE_TIME = [2,2,2.5,7,15,0,2,2] -FILTER_EXPOSURE_TIME = [float(x) for x in config[10].split(",")] -FILTER_LAMP_INTENSITY = [ - int(x) for x in config[11].split(",") -] # [1,100,150,254,254,0,150,1] - -MIRRORED = False # If image can be rotated so that North is Up and East is Right, this should be True to ensure that a WCS solution can be found. - -SAVE_IMAGES = True # If True, each image will be saved to SAVE_DATA_PATH -# SAVE_DATA_PATH = r"{MyDocuments}\Calibration\{Timestamp}" -SAVE_DATA_PATH = config[0] # Script data will be saved to this location. -MASTER_DATA_PATH = config[12] - -print(SAVE_DATA_PATH) - - -### END CONFIGURATION VALUES ######################## - - -def main(): - """ - Starting point for the center_target_on_pixel script - """ - - print("Launching mount control software...") - mount = Dispatch(MOUNT_DRIVER) - - print("Connecting to mount...") - mount.Connected = True - - print("Launching MaxIm DL...") - maxim = Dispatch("MaxIm.Application") - maxim.LockApp = True - - camera = Dispatch("MaxIm.CCDCamera") - camera.DisableAutoShutdown = True - - print("Connecting to camera...") - camera.LinkEnabled = True - - if HOME_MOUNT: - print("Homing Mount") - mount.FindHome - - if mount.CanSetTracking: - # Not all mount drivers support turning tracking on/off. - # For example, the ASCOM driver for TheSky does not support it. - # However, if it is supported, make sure tracking is on before slewing - mount.Tracking = False - - save_data_path = parse_filepath_template(SAVE_DATA_PATH) - master_data_path = parse_filepath_template(MASTER_DATA_PATH) - - if SAVE_IMAGES and not os.path.isdir(save_data_path): - print("Creating directory %s" % save_data_path) - os.makedirs(save_data_path) - - print("Slewing to Azimuth %s, Altitude %s" % (TARGET_AZ, TARGET_ALT)) - - mount.SlewToAltAz(TARGET_AZ, TARGET_ALT) - mount.Tracking = False - print("Settling...") - time.sleep(SETTLE_TIME_SECONDS) - - darknum = 0 - print( - "Taking %d dark images with %d second exposure..." - % (NUMBER_DARKS, EXPOSURE_LENGTH_SECONDS_DARK) - ) - while darknum < NUMBER_DARKS: - camera.BinX = BINNING - camera.BinY = BINNING - camera.Expose( - EXPOSURE_LENGTH_SECONDS_DARK, 0 - ) # The 0 indicates that the shutter is closed - while not camera.ImageReady: - time.sleep(0.1) - - darknum = darknum + 1 - print("Dark %d of %d complete" % (darknum, NUMBER_DARKS)) - - tempfilename = os.path.join(tempfile.gettempdir(), "Dark.fits") - camera.SaveImage(tempfilename) - - if SAVE_IMAGES: - filename = "Dark_%03d.fits" % (darknum) - # filename = "Dark.fits" - filepath = os.path.join(save_data_path, filename) - # print "Saving image to", filepath - camera.SaveImage(filepath) - - biasnum = 0 - print("Taking %d bias images..." % (NUMBER_BIAS)) - while biasnum < NUMBER_BIAS: - camera.BinX = BINNING - camera.BinY = BINNING - camera.Expose(0, 0) # Bias image take as a 0 second dark exposure - while not camera.ImageReady: - time.sleep(0.1) - - biasnum = biasnum + 1 - print("Bias %d of %d complete" % (biasnum, NUMBER_BIAS)) - - tempfilename = os.path.join(tempfile.gettempdir(), "Bias.fits") - camera.SaveImage(tempfilename) - - if SAVE_IMAGES: - filename = "Bias_%03d.fits" % (biasnum) - # filename = "Bias.fits" - filepath = os.path.join(save_data_path, filename) - # print "Saving image to", filepath - camera.SaveImage(filepath) - - filternum = 0 - for fname in FILTER_NAMES: - # camera.set_active_filter(filternum) - if FILTER_EXPOSURE_TIME[filternum] == 0: - print("Skipping filter %s." % (fname)) - else: - flatnum = 0 - print( - "Setting flat field lamps to brightness level %d" - % (FILTER_LAMP_INTENSITY[filternum]) - ) - pulsar_dimmer.dimmer(FILTER_LAMP_INTENSITY[filternum]) - time.sleep(2) - print( - "Taking %d flat images with %d second exposure in filter %s..." - % (NUMBER_FLATS, FILTER_EXPOSURE_TIME[filternum], fname) - ) - while flatnum < NUMBER_FLATS: - camera.BinX = BINNING - camera.BinY = BINNING - camera.Expose(FILTER_EXPOSURE_TIME[filternum], 1, filternum) - while not camera.ImageReady: - time.sleep(0.1) - flatnum = flatnum + 1 - print( - "Flat %d of %d for filter %s complete" - % (flatnum, NUMBER_FLATS, fname) - ) - camera.SetFITSKey("IMAGETYP", "FLAT ") - tempfilename = os.path.join(tempfile.gettempdir(), "Flat.fits") - camera.SaveImage(tempfilename) - - if SAVE_IMAGES: - filename = "Flat_%03d%s.fits" % (flatnum, fname) - filepath = os.path.join(save_data_path, filename) - # print "Saving image to", filepath - camera.SaveImage(filepath) - filternum = filternum + 1 - print("30 second cooldown...") - time.sleep(30) - print("Turning off calibration lamps...") - pulsar_dimmer.dimmer(0) # Turn off the flat field lamps when finished exposing - - -""" - print "Now median averaging the dark frames..." - darknum=0 - while darknum < NUMBER_DARKS-1: - darknum = darknum+1 - filename = "Dark_%03d.fits" % (darknum) - filepath = os.path.join(save_data_path,filename) - print "Loading dark image %s" % (filename) - - # if not 'dark_stack' in locals(): - # hdu_list = fits.open(filepath) - # prihdr = hdu_list[0].header - # dark_stack = fits.getdata(filepath) - # #dark_stack = fits.getdata(filepath) - # else: - # dark_stack = numpy.dstack((dark_stack,fits.getdata(filepath))) - - hdu_list = fits.open(filepath) - prihdr = hdu_list[0].header - dark_image = fits.getdata(filepath) - if not 'dark_stack' in locals(): - dark_stack = numpy.zeros(shape=dark_image.shape + (NUMBER_DARKS,)) - dark_stack[:,:,darknum] - - - if not darknum == 0: - dark_master = numpy.median(dark_stack, axis=2) - prihdr['IMAGETYP'] = 'DARK' - hdu = fits.PrimaryHDU(dark_master,prihdr) - darkhdulist = fits.HDUList([hdu]) - filename = "Master_Dark.fits" - filepath = os.path.join(save_data_path,filename) - print "Saving master dark image" - darkhdulist.writeto(filepath) - filepath = os.path.join(master_data_path,filename) - darkhdulist.writeto(filepath,clobber=True) - darkhdulist.close() - hdu_list.close() - dark_stack=None - dark_master=None - - print "Now median averaging the bias frames..." - biasnum=0 - while biasnum < NUMBER_BIAS-1: - biasnum = biasnum+1 - filename = "Bias_%03d.fits" % (biasnum) - filepath = os.path.join(save_data_path,filename) - print "Loading bias image %s" % (filename) - hdu_list = fits.open(filepath) - prihdr = hdu_list[0].header - bias_image = fits.getdata(filepath) - if not 'bias_stack' in locals(): - bias_stack = numpy.zeros(shape=bias_image.shape + (NUMBER_BIAS,)) - bias_stack[:,:,biasnum] - # if not 'bias_stack' in locals(): - # hdu_list = fits.open(filepath) - # prihdr = hdu_list[0].header - # hdu_list.close() - # bias_stack = fits.getdata(filepath) - # #dark_stack = fits.getdata(filepath) - # else: - # bias_stack = numpy.dstack((bias_stack,fits.getdata(filepath))) - if not biasnum == 0: - bias_master = numpy.median(bias_stack, axis=2) - prihdr['IMAGETYP'] = 'BIAS ' - hdu = fits.PrimaryHDU(bias_master,prihdr) - biashdulist = fits.HDUList([hdu]) - filename = "Master_Bias.fits" - filepath = os.path.join(save_data_path,filename) - print "Saving master Bias image" - biashdulist.writeto(filepath) - filepath = os.path.join(master_data_path,filename) - biashdulist.writeto(filepath,clobber=True) - biashdulist.close() - - bias_stack=None - bias_master=None - - filternum=0 - for fname in FILTER_NAMES: - if FILTER_EXPOSURE_TIME[filternum] == 0: - print "Skiping filter %s." % (fname) - else: - flatnum=0 - print "Now median averaging the flat frames for the %s filter..." % (fname) - while flatnum < NUMBER_FLATS: - flatnum = flatnum+1 - filename = "Flat_%03d%s.fits" % (flatnum,fname) - filepath = os.path.join(save_data_path,filename) - print "Loading flat image %s" % (filename) - - hdu_list = fits.open(filepath) - prihdr = hdu_list[0].header - flat_image = fits.getdata(filepath) - if not 'flat_stack' in locals(): - flat_stack = numpy.zeros(shape=flat_image.shape + (NUMBER_FLATS,)) - #if flat_stack is None: - # flat_stack = numpy.zeros(shape=flat_image.shape + (NUMBER_FLATS,)) - - flat_stack[:,:,flatnum-1] - - # if flatnum==1: - # hdu_list = fits.open(filepath) - # prihdr = hdu_list[0].header - # flat_stack = fits.getdata(filepath) - # else: - # flat_stack = numpy.dstack((flat_stack,fits.getdata(filepath))) - if not flatnum == 0: - flat_master = numpy.median(flat_stack, axis=2) - prihdr['IMAGETYP'] = 'FLAT ' - hdu = fits.PrimaryHDU(flat_master,prihdr) - - flathdulist = fits.HDUList([hdu]) - filename = "Master_Flat_%s.fits" % (fname) - filepath = os.path.join(save_data_path,filename) - print "Saving master flat image for filter %s" % (fname) - flathdulist.writeto(filepath) - filepath = os.path.join(master_data_path,filename) - flathdulist.writeto(filepath,clobber=True) - flathdulist.close() - hdu_list.close() - - filternum=filternum+1 -""" - - -def parse_filepath_template(template): - my_documents_path = os.path.expanduser(r"~\My Documents") - timestamp = datetime.now().strftime("%Y-%m-%d %H_%M_%S") - - template = template.replace("{MyDocuments}", my_documents_path) - template = template.replace("{Timestamp}", timestamp) - - return template - - -if __name__ == "__main__": - main() diff --git a/_tba/observatory/measure_filter_focus_offsets.py b/_tba/observatory/measure_filter_focus_offsets.py deleted file mode 100755 index 8b265460..00000000 --- a/_tba/observatory/measure_filter_focus_offsets.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -For each filter in Maxim's current filter list, perform one or -more autofocus runs to measure the focus offset required for each filter. -""" - -import logging -import os -import sys -import time - -import relimport -from iotalib import config, config_observatory, logutil, observatory - -# Loop through the filter list this many times -NUM_MEASUREMENTS_PER_FILTER = 3 - -# List of filter index positions to measure. -# If many filters are known to use essentially the same -# filter position, you can save time by skipping those filters -# and manually editing the config file and duplicating focus -# positions across those filters. -FILTER_INDICES_TO_MEASURE = [0, 1, 8] - -# The exposure length to use for each filter. -# This list must contain the same number of elements as FILTER_INDICES_TO_MEASURE. -# It can be useful to specify longer exposure lengths for narrowband filters. -FILTER_EXPOSURE_LENGTHS = [10, 10, 10] - -# Template used to generate config file. -FOCUS_OFFSETS_FILE_TEMPLATE = """ -# Focus values for each filter. -# This config file is parsed as a Python file, so any valid Python -# syntax (if statements, string formatting, etc) is allowed. - -# AUTOGENERATED BY %(script_name)s on %(timestamp)s - -# This Python dictionary maps a filter index to a focus value. -# Focus positions can be expressed in absolute or relative units. -# The important part is that the *difference* between any two filter -# measurements is an accurate offset that can be used when moving -# from one filter to the other. -# Note that it may not be valid to mix best-focus values that were -# measured at significantly different times or temperatures since -# the overall focus of the instrument may have changed. - -best_focus_value = { -%(focus_values)s -} -""" - -# Optionally write values to this config file -CONFIG_FILENAME = "focus_offsets.cfg" - - -def main(): - logutil.setup_log("measure_filter_focus_offsets.log") - - config_observatory.read() - - observatory.setup_mount() - observatory.setup_camera() - observatory.setup_focuser() - observatory.setup_autofocus() - - best_focus_results = {} # Map from filter index to a list of best focus values - - filter_names = observatory.camera.get_filter_names() - - for loop_number in range(NUM_MEASUREMENTS_PER_FILTER): - for filter_index_index in range(len(FILTER_INDICES_TO_MEASURE)): - filter_index = FILTER_INDICES_TO_MEASURE[filter_index_index] - exp_length_seconds = FILTER_EXPOSURE_LENGTHS[filter_index_index] - - logging.info( - "*** Iteration %d of %d, filter %d of %d ***", - loop_number + 1, - NUM_MEASUREMENTS_PER_FILTER, - filter_index_index + 1, - len(FILTER_INDICES_TO_MEASURE), - ) - - logging.info( - "Setting filter %d (%s)", filter_index, filter_names[filter_index] - ) - observatory.camera.set_active_filter(filter_index) - - logging.info("Setting exposure to %s seconds", exp_length_seconds) - observatory.autofocus.set_exposure_length(exp_length_seconds) - - logging.info("Determining best focus position") - focus_result = observatory.autofocus.run_autofocus() - - logging.info("Best focus result: %s", focus_result) - - best_focus_values_for_filter = best_focus_results.get(filter_index, []) - if focus_result is not None: - best_focus_values_for_filter.append(focus_result) - best_focus_results[filter_index] = best_focus_values_for_filter - - config_lines = "" - - relative_to_value = None - for filter_index in FILTER_INDICES_TO_MEASURE: - filter_name = filter_names[filter_index] - values = best_focus_results[filter_index] - average_value = None - if len(values) > 0: - average_value = int(round(sum(values) / len(values))) - - if average_value is None: - average_value_str = "None" - else: - average_value_str = "%.2f" % average_value - - values_str = ", ".join(["%.2f" % x for x in values]) - - line = "%s: %s, # Filter: %s, Measurements: (%s)" % ( - filter_index, - average_value_str, - filter_name, - values_str, - ) - - config_lines += " " + line + "\n" - - logging.info("Final results:\n%s", config_lines) - - focus_offsets_cfg_text = FOCUS_OFFSETS_FILE_TEMPLATE % dict( - script_name=os.path.basename(__file__), - timestamp=time.ctime(), - focus_values=config_lines, - ) - - print(focus_offsets_cfg_text) - - config_file_path = config.get_config_path(CONFIG_FILENAME) - - while True: - response = input("Overwrite %s with new values? (y/n) " % CONFIG_FILENAME) - response = response.strip().lower() - if response == "n": - break - elif response == "y": - f = open(config_file_path, "w") - f.write(focus_offsets_cfg_text) - f.close() - logging.info("Configuration written to %s", config_file_path) - break - else: - print("Invalid response") - - -if __name__ == "__main__": - main() diff --git a/_tba/observatory/slew_grid.py b/_tba/observatory/slew_grid.py deleted file mode 100755 index 41e768ee..00000000 --- a/_tba/observatory/slew_grid.py +++ /dev/null @@ -1,219 +0,0 @@ -import math -import os -import time -from datetime import datetime - -import ephem -import relimport -from iotalib import convert -from win32com.client import Dispatch - -### CONFIGURATION VALUES ######################## - -# MOUNT_DRIVER = "ASCOM.SoftwareBisque.Telescope" # Use this for the Paramount ME (controlled through TheSky) -MOUNT_DRIVER = "SiTech.Telescope" # Use this for the SiTech-controlled Mathis fork mount at Winer Observatory - -CENTER_J2000_RA_HOURS = ( - "06:00:00.0" # RA coordinates at the center of the grid, in J2000 hours -) -CENTER_J2000_DEC_DEGS = "30:00:00.0" # Dec coordinates at the center of the grid, in J2000 degrees, Jupiter target -RASTER_GRID_RA_COLUMNS = 36 # Number of columns (distinct RA values) in the grid -RASTER_GRID_DEC_ROWS = 18 # Number of rows (distinct Dec values) in the grid -RA_GRID_SPACING_ARCSEC = 10 # RA spacing between each gridpoint, in arcseconds -DEC_GRID_SPACING_ARCSEC = 10 # Dec spacing between each gridpoint, in arcseconds -EQUAL_SKY_ANGLE = True # If True, apply cos(dec) compensation to RA offsets so that the amount of apparent motion of the sky is the same regardless of declination - -SETTLE_TIME_SECONDS = 2 # Pause for this many seconds after slewing to each target (and before taking an image, if requested) -PROMPT_BEFORE_STARTING_NEXT_TARGET = False # If True, script will wait for user to hit Enter before moving to each new target - -TAKE_IMAGE_AT_EACH_GRIDPOINT = ( - False # If True, Maxim DL will be used to take an image at each target -) -EXPOSURE_LENGTH_SECONDS = 3 # Exposure length of each image, in seconds -# PLATESOLVE_IMAGE = True # If True, script will try to find the precise center RA/Dec of each image using PlateSolve2. NOT YET IMPLEMENTED -SAVE_IMAGES = True # If True, each image will be saved to SAVE_DATA_PATH -SAVE_DATA_PATH = r"{MyDocuments}\SlewGridData\{Timestamp}" # Script data (including coordinates.txt and FITS images) will be saved to this location. -RECORD_COORDINATES = True # If True, write each J2000 coordinate to a file "coordinates.txt" in the data directory - -### END CONFIGURATION VALUES ######################## - - -def main(): - """ - Starting point for the slew_grid script - """ - - print("Launching mount control software...") - mount = Dispatch(MOUNT_DRIVER) - - print("Connecting to mount...") - mount.Connected = True - - if TAKE_IMAGE_AT_EACH_GRIDPOINT: - print("Launching MaxIm DL...") - maxim = Dispatch("MaxIm.Application") - maxim.LockApp = True - - camera = Dispatch("MaxIm.CCDCamera") - camera.DisableAutoShutdown = True - - print("Connecting to camera...") - camera.LinkEnabled = True - - if mount.CanSetTracking: - # Not all mount drivers support turning tracking on/off. - # For example, the ASCOM driver for TheSky does not support it. - # However, if it is supported, make sure tracking is on before slewing - mount.Tracking = True - - targets = make_raster_scan_grid( - convert.from_dms(CENTER_J2000_RA_HOURS), - convert.from_dms(CENTER_J2000_DEC_DEGS), - RASTER_GRID_RA_COLUMNS, - RASTER_GRID_DEC_ROWS, - RA_GRID_SPACING_ARCSEC, - DEC_GRID_SPACING_ARCSEC, - EQUAL_SKY_ANGLE, - ) - - save_data_path = parse_filepath_template(SAVE_DATA_PATH) - - if (SAVE_IMAGES or RECORD_COORDINATES) and not os.path.isdir(save_data_path): - print("Creating directory '%s'" % save_data_path) - os.makedirs(save_data_path) - - target_number = 1 - for target_ra_j2000_hours, target_dec_j2000_degs in targets: - # The mount expects Jnow coordinates, so we need to apply precession/nutation/etc. - (target_ra_jnow_hours, target_dec_jnow_degs) = convert.j2000_to_jnow( - target_ra_j2000_hours, target_dec_j2000_degs - ) - - print() - print("Target %d of %d" % (target_number, len(targets))) - print( - "Slewing to J2000 %s, %s" - % ( - convert.to_dms(target_ra_j2000_hours), - convert.to_dms(target_dec_j2000_degs), - ) - ) - - mount.SlewToCoordinates(target_ra_jnow_hours, target_dec_jnow_degs) - - print("Settling...") - time.sleep(SETTLE_TIME_SECONDS) - - if RECORD_COORDINATES: - coords_filepath = os.path.join(save_data_path, "coordinates.txt") - coords_file = open(coords_filepath, "a") - print( - "%d, %f, %f, %f, %f" - % ( - target_number, - target_ra_j2000_hours, - target_dec_j2000_degs, - target_ra_jnow_hours, - target_dec_jnow_degs, - ), - file=coords_file, - ) - coords_file.close() - - if TAKE_IMAGE_AT_EACH_GRIDPOINT: - print("Taking %d second exposure..." % (EXPOSURE_LENGTH_SECONDS)) - camera.Expose(EXPOSURE_LENGTH_SECONDS, 1) - while not camera.ImageReady: - time.sleep(0.1) - - print("Image complete") - - if SAVE_IMAGES: - filename = "image_%03d_ra_%f_dec_%f.fits" % ( - target_number, - target_ra_j2000_hours, - target_dec_j2000_degs, - ) - filepath = os.path.join(save_data_path, filename) - print("Saving image to", filepath) - camera.SaveImage(filepath) - - if PROMPT_BEFORE_STARTING_NEXT_TARGET and target_number != len(targets): - input("Press Enter to continue to next target...") - - target_number += 1 - - print("Finished!") - - -def make_raster_scan_grid( - center_ra_hours, - center_dec_degs, - num_ra_columns, - num_dec_rows, - ra_spacing_arcsec, - dec_spacing_arcsec, - equal_sky_angle=True, -): - """ - Create a grid of ra/dec coordinates following a "raster-scan" pattern; e.g.: - - 1 2 3 4 5 - 6 7 8 9 10 - 11 12 13 14 15 - - where point 8 = (center_ra_hours, center_dec_degs), num_ra_columns = 5, and num_dec_rows = 3 - - If equal_sky_angle is true, cos(dec) compensation will be applied so that the target on - the camera appears to move the same distance in both RA and Dec regardless of your - current declination. - - Returns a list of RA-Dec tuples, with RA in hours and Dec in degrees. For example: - - [ - (1, 10), - (2, 10), - (3, 10), - (1, 20), - (2, 20), - (3, 20), - (1, 30), - (2, 30), - (3, 30) - ] - """ - - targets = [] - - for i in range(num_dec_rows): - dec_steps_from_center = i - (num_dec_rows - 1) / 2.0 - dec_offset_degs = dec_steps_from_center * dec_spacing_arcsec / 3600.0 - target_dec_degs = center_dec_degs + dec_offset_degs - - for j in range(num_ra_columns): - ra_steps_from_center = j - (num_ra_columns - 1) / 2.0 - ra_offset_degs = ra_steps_from_center * ra_spacing_arcsec / 3600.0 - if equal_sky_angle: - ra_offset_degs = ra_offset_degs / math.cos( - convert.degs_to_rads(target_dec_degs) - ) - - target_ra_hours = center_ra_hours + convert.degs_to_hours(ra_offset_degs) - - targets.append((target_ra_hours, target_dec_degs)) - - return targets - - -def parse_filepath_template(template): - my_documents_path = os.path.expanduser(r"~\My Documents") - timestamp = datetime.now().strftime("%Y-%m-%d %H_%M_%S") - - template = template.replace("{MyDocuments}", my_documents_path) - template = template.replace("{Timestamp}", timestamp) - - return template - - -if __name__ == "__main__": - main() diff --git a/_tba/pwi4/README.txt b/_tba/pwi4/README.txt deleted file mode 100644 index b4237586..00000000 --- a/_tba/pwi4/README.txt +++ /dev/null @@ -1,15 +0,0 @@ -This directory contains sample Python code for communicating with PWI4 -via its HTTP API. - -The main module is pwi4_client.py, which can be imported from other Python -scripts and serves as a reference implementation for the available commands -and status information offered by the HTTP API. This module can be used -as-is, or it can be adapted to other programming languages as needed. - -A few other sample scripts using pwi4_client are also included: - -pwi4_build_model.py: Shows how to build a pointing model using available API calls -pwi4_client_demo.py: Provides a basic example of how to control a mount using pwi4_client -pwi4_startup.py: Provides a sample script to help start up a PWI4-controlled telescope - -If you have any questions about using this API, please contact PlaneWave Instruments. diff --git a/_tba/pwi4/close_shutter.bat b/_tba/pwi4/close_shutter.bat deleted file mode 100644 index cc1e8122..00000000 --- a/_tba/pwi4/close_shutter.bat +++ /dev/null @@ -1,26 +0,0 @@ -@ECHO OFF -SET PWSHUTTER="C:\Program Files (x86)\PlaneWave Instruments\PlaneWave Shutter Control\PWShutter.exe" - -ECHO Checking connection -%PWSHUTTER% isconnected -IF %ERRORLEVEL% EQU 0 ( - ECHO Shutter is connected -) ELSE ( - ECHO Shutter is NOT connected -) -ECHO Trying to connect -%PWSHUTTER% connect -IF %ERRORLEVEL% EQU 0 ( - ECHO Connected successfully -) ELSE ( - ECHO ERROR: Connection failed - EXIT /B -) -ECHO Closing -%PWSHUTTER% close -IF %ERRORLEVEL% EQU 0 ( - ECHO Closed successfully -) ELSE ( - ECHO ERROR while closing shutters - EXIT /B -) diff --git a/_tba/pwi4/open_shutter.bat b/_tba/pwi4/open_shutter.bat deleted file mode 100644 index 32c816a4..00000000 --- a/_tba/pwi4/open_shutter.bat +++ /dev/null @@ -1,26 +0,0 @@ -@ECHO OFF -SET PWSHUTTER="C:\Program Files (x86)\PlaneWave Instruments\PlaneWave Shutter Control\PWShutter.exe" - -ECHO Checking connection -%PWSHUTTER% isconnected -IF %ERRORLEVEL% EQU 0 ( - ECHO Shutter is connected -) ELSE ( - ECHO Shutter is NOT connected -) -ECHO Trying to connect -%PWSHUTTER% connect -IF %ERRORLEVEL% EQU 0 ( - ECHO Connected successfully -) ELSE ( - ECHO ERROR: Connection failed - EXIT /B -) -ECHO Opening -%PWSHUTTER% open -IF %ERRORLEVEL% EQU 0 ( - ECHO Opened successfully -) ELSE ( - ECHO ERROR while opening shutters - EXIT /B -) diff --git a/_tba/pwi4/platesolve.py b/_tba/pwi4/platesolve.py deleted file mode 100644 index b1b5304a..00000000 --- a/_tba/pwi4/platesolve.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -This module wraps the PlateSolve3 command-line executable to provide -all-sky star matching of telescope images. It is used by the pwi4_build_model -script to determine where a telescope is pointing when building a pointing -model. -""" - -import os.path -import platform -import tempfile -from subprocess import PIPE, Popen - -# Point this to the location of the "ps3cli.exe" executable -PS3CLI_EXE = os.path.expanduser("~/ps3cli/ps3cli.exe") - -# For testing purposes... -# PS3CLI_EXE = r"C:\Users\kmi\Desktop\Planewave work\Code\PWGit\PWCode\ps3cli\bin\Debug\ps3cli.exe" - - -# Set this to the path where the PlateSolve catalogs are located. -# The directory specified here should contain "UC4" and "Orca" subdirectories. -# If this is None, we will try to use the default catalog location -PS3_CATALOG = None - - -def is_linux(): - return platform.system() == "Linux" - - -def get_default_catalog_location(): - if is_linux(): - return os.path.expanduser("~/Kepler") - else: - return os.path.expanduser("~\\Documents\\Kepler") - - -def platesolve(image_file, arcsec_per_pixel): - stdout_destination = None # Replace with PIPE if we want to capture the output rather than displaying on the console - - output_file_path = os.path.join(tempfile.gettempdir(), "ps3cli_results.txt") - - if PS3_CATALOG is None: - catalog_path = get_default_catalog_location() - else: - catalog_path = PS3_CATALOG - - args = [ - PS3CLI_EXE, - image_file, - str(arcsec_per_pixel), - output_file_path, - catalog_path, - ] - - if is_linux(): - # Linux systems need to run ps3cli via the mono runtime, - # so add that to the beginning of the command/argument list - args.insert(0, "mono") - - process = Popen(args, stdout=stdout_destination, stderr=PIPE) - - (stdout, stderr) = ( - process.communicate() - ) # Obtain stdout and stderr output from the wcs tool - exit_code = process.wait() # Wait for process to complete and obtain the exit code - - if exit_code != 0: - raise Exception( - "Error finding solution.\n" - + "Exit code: " - + str(exit_code) - + "\n" - + "Error output: " - + stderr - ) - - return parse_platesolve_output(output_file_path) - - -def parse_platesolve_output(output_file): - f = open(output_file) - - results = {} - - for line in f.readlines(): - line = line.strip() - if line == "": - continue - - fields = line.split("=") - if len(fields) != 2: - continue - - keyword, value = fields - - results[keyword] = float(value) - - return results diff --git a/_tba/pwi4/pwi4_build_model.py b/_tba/pwi4/pwi4_build_model.py deleted file mode 100644 index e62cb262..00000000 --- a/_tba/pwi4/pwi4_build_model.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python - -""" -This script demonstrates how to build a pointing model using the -HTTP interface for PWI4. - -It is necessary to implement the take_image() function to provide -an image from your camera upon request. The implementation could be -as simple as waiting for an image to be manually taken and for the -FITS image to be placed in the requested location. - -This script also requires the PlateSolve library and star catalog. -Please contact PlaneWave Instruments for details. -""" - -import time - -import pwi4_client -from platesolve import platesolve - -# NOTE: Replace this with the estimated arcseconds per pixel -# for an image taken with your camera. -# For PWI4 Virtual Camera, the default is 1.0 arcsec/pixel. -IMAGE_ARCSEC_PER_PIXEL = 1.0 - - -def main(): - pwi4 = pwi4_client.PWI4() - - print("Checking connection to PWI4") - status = pwi4.status() - - if not status.mount.is_connected: - print("Connecting to mount") - pwi4.mount_connect() - - status = pwi4.status() - if not status.mount.axis0.is_enabled: - print("Enabling axis 0") - pwi4.mount_enable(0) - if not status.mount.axis1.is_enabled: - print("Enabling axis 1") - pwi4.mount_enable(1) - - # Construct a grid of 3 x 6 = 18 Alt-Az points - # ranging from 20 to 80 degrees Altitude, and from - # 5 to 355 degrees Azimuth. - points = create_point_list(3, 20, 80, 6, 5, 355) - - for alt, azm in points: - map_point(pwi4, alt, azm) - - print("DONE!") - - -def create_point_list(num_alt, min_alt, max_alt, num_azm, min_azm, max_azm): - """ - Build a grid of target points in alt-az coordinate space. - """ - - points = [] - - for i in range(num_azm): - azm = min_azm + (max_azm - min_azm) * i / float(num_azm) - - for j in range(num_alt): - alt = min_alt + (max_alt - min_alt) * j / float(num_alt - 1) - - points.append((alt, azm)) - - return points - - -def take_image(filename, pwi4): - # TODO: Replace this with your own routine to take an image - # with your camera and save a FITS file to "image.fits" - take_image_virtualcam(filename, pwi4) - - -def take_image_virtualcam(filename, pwi4): - """ - Take an artificial image using PWI4's virtual camera. - The starfield in the image will be based on the telescope's - current coordinates. - - (NOTE: Depends on the Kepler star catalog being installed - in the right place!) - """ - - pwi4.virtualcamera_take_image_and_save(filename) - - -def map_point(pwi4, alt_degs, azm_degs): - """ - Slew to the target Alt-Az, take an image, - PlateSolve it, and (if successful) add to the model - """ - - print("Slewing to Azimuth %.3f, Altitude %3f..." % (azm_degs, alt_degs)) - pwi4.mount_goto_alt_az(alt_degs, azm_degs) - - while True: - status = pwi4.status() - if not status.mount.is_slewing: - break - time.sleep(0.1) - - # Confirm that we actually reached our target. - # If, for example, the user clicked Stop in the GUI during - # the slew, we probably don't want to continue building the model. - status = pwi4.status() - - azm_error = abs(status.mount.azimuth_degs - azm_degs) - alt_error = abs(status.mount.altitude_degs - alt_degs) - - if azm_error > 0.1 or alt_error > 0.1: - raise Exception( - "Mount stopped at azimuth %.4f, altitude %.4f, which is too far from the target %.4f, %.4f." - % ( - status.mount.azimuth_degs, - status.mount.altitude_degs, - azm_degs, - alt_degs, - ) - ) - - # Mount will be stopped after an alt-az slew, so turn - # on sidereal tracking before taking an image - pwi4.mount_tracking_on() - - print("Taking image...") - - take_image("image.fits", pwi4) - - print("Saved FITS image") - - print("Running PlateSolve...") - try: - match = platesolve("image.fits", IMAGE_ARCSEC_PER_PIXEL) - except Exception as ex: - print(ex.message) - return - - pwi4.mount_model_add_point(match["ra_j2000_hours"], match["dec_j2000_degrees"]) - print("Added point") - - -if __name__ == "__main__": - main() diff --git a/_tba/pwi4/pwi4_client.py b/_tba/pwi4/pwi4_client.py deleted file mode 100644 index 8e010b2b..00000000 --- a/_tba/pwi4/pwi4_client.py +++ /dev/null @@ -1,953 +0,0 @@ -""" -This Python module wraps the calls and status responses provided -by the HTTP API exposed by PWI4. This code can be called directly -from other Python scripts, or can be adapted to other languages -as needed. -""" - -try: - # Python 3.x version - from urllib.error import HTTPError - from urllib.parse import urlencode - from urllib.request import urlopen -except ImportError: - # Python 2.7 version - from urllib import urlencode - - from urllib2 import HTTPError, urlopen - -try: - import requests -except ImportError: - pass - -import time - - -class PWI4: - """ - Client to the PWI4 telescope control application. - """ - - def __init__(self, host="localhost", port=8220): - self.host = host - self.port = port - self.comm = PWI4HttpCommunicator(host, port) - - ### High-level methods ################################# - - def status(self): - return self.request_with_status("/status") - - def mount_connect(self): - return self.request_with_status("/mount/connect") - - def mount_disconnect(self): - return self.request_with_status("/mount/disconnect") - - def mount_enable(self, axisNum): - return self.request_with_status("/mount/enable", axis=axisNum) - - def mount_disable(self, axisNum): - return self.request_with_status("/mount/disable", axis=axisNum) - - def mount_set_slew_time_constant(self, value): - return self.request_with_status("/mount/set_slew_time_constant", value=value) - - def mount_set_axis0_wrap_range_min(self, axis0_wrap_min_degs): - # Added in PWI 4.0.13 - return self.request_with_status( - "/mount/set_axis0_wrap_range_min", degs=axis0_wrap_min_degs - ) - - def mount_find_home(self): - return self.request_with_status("/mount/find_home") - - def mount_stop(self): - return self.request_with_status("/mount/stop") - - def mount_goto_ra_dec_apparent(self, ra_hours, dec_degs): - return self.request_with_status( - "/mount/goto_ra_dec_apparent", ra_hours=ra_hours, dec_degs=dec_degs - ) - - def mount_goto_ra_dec_j2000(self, ra_hours, dec_degs): - return self.request_with_status( - "/mount/goto_ra_dec_j2000", ra_hours=ra_hours, dec_degs=dec_degs - ) - - def mount_goto_alt_az(self, alt_degs, az_degs): - return self.request_with_status( - "/mount/goto_alt_az", alt_degs=alt_degs, az_degs=az_degs - ) - - def mount_goto_coord_pair(self, coord0, coord1, coord_type): - """ - Set the mount target to a pair of coordinates in a specified coordinate system. - coord_type: can currently be "altaz" or "raw" - coord0: the azimuth coordinate for the "altaz" type, or the axis0 coordiate for the "raw" type - coord1: the altitude coordinate for the "altaz" type, or the axis1 coordinate for the "raw" type - """ - return self.request_with_status( - "/mount/goto_coord_pair", c0=coord0, c1=coord1, type=coord_type - ) - - def mount_offset(self, **kwargs): - """ - One or more of the following offsets can be specified as a keyword argument: - - AXIS_reset: Clear all position and rate offsets for this axis. Set this to any value to issue the command. - AXIS_stop_rate: Set any active offset rate to zero. Set this to any value to issue the command. - AXIS_add_arcsec: Increase the current position offset by the specified amount - AXIS_set_rate_arcsec_per_sec: Continually increase the offset at the specified rate - - As of PWI 4.0.11 Beta 7, the following options are also supported: - AXIS_stop: Stop both the offset rate and any gradually-applied commands - AXIS_stop_gradual_offset: Stop only the gradually-applied offset, and maintain the current rate - AXIS_set_total_arcsec: Set the total accumulated offset at the time the command is received to the specified value. Any in-progress rates or gradual offsets will continue to be applied on top of this. - AXIS_add_gradual_offset_arcsec: Gradually add the specified value to the total accumulated offset. Must be paired with AXIS_gradual_offset_rate or AXIS_gradual_offset_seconds to determine the timeframe over which the gradual offset is applied. - AXIS_gradual_offset_rate: Paired with AXIS_add_gradual_offset_arcsec; Specifies the rate at which a gradual offset should be applied. For example, if an offset of 10 arcseconds is to be applied at a rate of 2 arcsec/sec, then it will take 5 seconds for the offset to be applied. - AXIS_gradual_offset_seconds: Paired with AXIS_add_gradual_offset_arcsec; Specifies the time it should take to apply the gradual offset. For example, if an offset of 10 arcseconds is to be applied over a period of 2 seconds, then the offset will be increasing at a rate of 5 arcsec/sec. - - Where AXIS can be one of: - - ra: Offset the target Right Ascension coordinate - dec: Offset the target Declination coordinate - axis0: Offset the mount's primary axis position - (roughly Azimuth on an Alt-Az mount, or RA on In equatorial mount) - axis1: Offset the mount's secondary axis position - (roughly Altitude on an Alt-Az mount, or Dec on an equatorial mount) - path: Offset along the direction of travel for a moving target - transverse: Offset perpendicular to the direction of travel for a moving target - - For example, to offset axis0 by -30 arcseconds and have it continually increase at 1 - arcsec/sec, and to also clear any existing offset in the transverse direction, - you could call the method like this: - - mount_offset(axis0_add_arcsec=-30, axis0_set_rate_arcsec_per_sec=1, transverse_reset=0) - - """ - - return self.request_with_status("/mount/offset", **kwargs) - - def mount_spiral_offset_new(self, x_step_arcsec, y_step_arcsec): - # Added in PWI 4.0.11 Beta 8 - return self.request_with_status( - "/mount/spiral_offset/new", - x_step_arcsec=x_step_arcsec, - y_step_arcsec=y_step_arcsec, - ) - - def mount_spiral_offset_next(self): - # Added in PWI 4.0.11 Beta 8 - return self.request_with_status("/mount/spiral_offset/next") - - def mount_spiral_offset_previous(self): - # Added in PWI 4.0.11 Beta 8 - return self.request_with_status("/mount/spiral_offset/previous") - - def mount_park(self): - return self.request_with_status("/mount/park") - - def mount_set_park_here(self): - return self.request_with_status("/mount/set_park_here") - - def mount_tracking_on(self): - return self.request_with_status("/mount/tracking_on") - - def mount_tracking_off(self): - return self.request_with_status("/mount/tracking_off") - - def mount_follow_tle(self, tle_line_1, tle_line_2, tle_line_3): - return self.request_with_status( - "/mount/follow_tle", line1=tle_line_1, line2=tle_line_2, line3=tle_line_3 - ) - - def mount_radecpath_new(self): - return self.request_with_status("/mount/radecpath/new") - - def mount_radecpath_add_point(self, jd, ra_j2000_hours, dec_j2000_degs): - return self.request_with_status( - "/mount/radecpath/add_point", - jd=jd, - ra_j2000_hours=ra_j2000_hours, - dec_j2000_degs=dec_j2000_degs, - ) - - def mount_radecpath_apply(self): - return self.request_with_status("/mount/radecpath/apply") - - def mount_custom_path_new(self, coord_type): - return self.request_with_status("/mount/custom_path/new", type=coord_type) - - def mount_custom_path_add_point_list(self, points): - lines = [] - for jd, ra, dec in points: - line = "%.10f,%s,%s" % (jd, ra, dec) - lines.append(line) - - data = "\n".join(lines).encode("utf-8") - - postdata = urlencode({"data": data}).encode() - - return self.request("/mount/custom_path/add_point_list", postdata=postdata) - - def mount_custom_path_apply(self, update_wrap=None): - # update_wrap parameter was added in PWI 4.0.99 beta 26 - if update_wrap is not None: - update_wrap = int(update_wrap) - - return self.request_with_status( - "/mount/custom_path/apply", update_wrap=update_wrap - ) - - def mount_model_add_point(self, ra_j2000_hours, dec_j2000_degs): - """ - Add a calibration point to the pointing model, mapping the current pointing direction - of the telescope to the secified J2000 Right Ascension and Declination values. - - This call might be performed after manually centering a bright star with a known - RA and Dec, or the RA and Dec might be provided by a PlateSolve solution - from an image taken at the current location. - """ - - return self.request_with_status( - "/mount/model/add_point", - ra_j2000_hours=ra_j2000_hours, - dec_j2000_degs=dec_j2000_degs, - ) - - def mount_model_delete_point(self, *point_indexes_0_based): - """ - Remove one or more calibration points from the pointing model. - - Points are specified by index, ranging from 0 to (number_of_points-1). - - Added in PWI 4.0.11 beta 9 - - Examples: - mount_model_delete_point(0) # Delete the first point - mount_model_delete_point(1, 3, 5) # Delete the second, fourth, and sixth points - mount_model_delete_point(*range(20)) # Delete the first 20 points - """ - - point_indexes_comma_separated = list_to_comma_separated_string( - point_indexes_0_based - ) - return self.request_with_status( - "/mount/model/delete_point", index=point_indexes_comma_separated - ) - - def mount_model_add_artificial_offset_point(self, offset_degs): - # Added in PWI 4.0.99 beta 26 - return self.request_with_status( - "/mount/model/add_artificial_offset_point", offset_degs=offset_degs - ) - - def mount_model_delete_artificial_points(self): - # Added in PWI 4.0.99 beta 26 - return self.request_with_status("/mount/model/delete_artificial_points") - - def mount_model_enable_point(self, *point_indexes_0_based): - """ - Flag one or more calibration points as "enabled", meaning that these points - will contribute to the fit of the model. - - Points are specified by index, ranging from 0 to (number_of_points-1). - - Added in PWI 4.0.11 beta 9 - - Examples: - mount_model_enable_point(0) # Enable the first point - mount_model_enable_point(1, 3, 5) # Enable the second, fourth, and sixth points - mount_model_enable_point(*range(20)) # Enable the first 20 points - """ - - point_indexes_comma_separated = list_to_comma_separated_string( - point_indexes_0_based - ) - return self.request_with_status( - "/mount/model/enable_point", index=point_indexes_comma_separated - ) - - def mount_model_disable_point(self, *point_indexes_0_based): - """ - Flag one or more calibration points as "disabled", meaning that these calibration - points will still be stored but will not contribute to the fit of the model. - - If a point is suspected to be an outlier, it can be disabled. This will cause the model - to re-fit, and the point's deviation from the newly-fit model can be re-examined before - being deleted entirely. - - Points are specified by index, ranging from 0 to (number_of_points-1). - - Added in PWI 4.0.11 beta 9 - - Examples: - mount_model_disable_point(0) # Disable the first point - mount_model_disable_point(1, 3, 5) # Disable the second, fourth, and sixth points - mount_model_disable_point(*range(20)) # Disable the first 20 points - mount_model_disable_point( # Disable all points - *range( - pwi4.status().mount.model.num_points_total - )) - """ - - point_indexes_comma_separated = list_to_comma_separated_string( - point_indexes_0_based - ) - return self.request_with_status( - "/mount/model/disable_point", index=point_indexes_comma_separated - ) - - def mount_model_clear_points(self): - """ - Remove all calibration points from the pointing model. - """ - - return self.request_with_status("/mount/model/clear_points") - - def mount_model_save_as_default(self): - """ - Save the active pointing model as the model that will be loaded - by default the next time the mount is connected. - """ - - return self.request_with_status("/mount/model/save_as_default") - - def mount_model_save(self, filename): - """ - Save the active pointing model to a file so that it can later be re-loaded - by a call to mount_model_load(). - - This may be useful when switching between models built for different instruments. - For example, a system might have one model for the main telescope, and another - model for a co-mounted telescope. - """ - - return self.request_with_status("/mount/model/save", filename=filename) - - def mount_model_load(self, filename): - """ - Load a model from the specified file and make it the active model. - - This may be useful when switching between models built for different instruments. - For example, a system might have one model for the main telescope, and another - model for a co-mounted telescope. - """ - - return self.request_with_status("/mount/model/load", filename=filename) - - def focuser_connect(self): - # Added in PWI 4.0.99 Beta 2 - return self.request_with_status("/focuser/connect") - - def focuser_disconnect(self): - # Added in PWI 4.0.99 Beta 2 - return self.request_with_status("/focuser/disconnect") - - def focuser_enable(self): - return self.request_with_status("/focuser/enable") - - def focuser_disable(self): - return self.request_with_status("/focuser/disable") - - def focuser_goto(self, target): - return self.request_with_status("/focuser/goto", target=target) - - def focuser_stop(self): - return self.request_with_status("/focuser/stop") - - def rotator_connect(self): - # Added in PWI 4.0.99 Beta 2 - return self.request_with_status("/rotator/connect") - - def rotator_disconnect(self): - # Added in PWI 4.0.99 Beta 2 - return self.request_with_status("/rotator/disconnect") - - def rotator_enable(self): - return self.request_with_status("/rotator/enable") - - def rotator_disable(self): - return self.request_with_status("/rotator/disable") - - def rotator_goto_mech(self, target_degs): - return self.request_with_status("/rotator/goto_mech", degs=target_degs) - - def rotator_goto_field(self, target_degs): - return self.request_with_status("/rotator/goto_field", degs=target_degs) - - def rotator_offset(self, offset_degs): - return self.request_with_status("/rotator/offset", degs=offset_degs) - - def rotator_stop(self): - return self.request_with_status("/rotator/stop") - - def fans_on(self, roles=None): - """ - roles: if None, turn on all fans - Otherwise, can be a list of one or more fan roles to turn on: - m1: Primary mirror fans - m1rear: Primary mirror fans (rear fans only) - m1side: Primary mirror fans (side fans only) - m2: Secondary mirror fans - m3: M3 mirror fans - m1heaters: Primary mirror heat distribution fans - m2heaters: Secondary mirror heat distribution fans - m3heaters: M3 mirror heat distribution fans - cabinet: Control cabinet / electronics fans - """ - # Added in PWI 4.0.99 beta 24 - - if isinstance(roles, (list, tuple)): - roles = list_to_comma_separated_string(roles) - return self.request_with_status("/fans/on", roles=roles) - - def fans_off(self, roles=None): - # Added in PWI 4.0.99 beta 24 - - if isinstance(roles, (list, tuple)): - roles = list_to_comma_separated_string(roles) - return self.request_with_status("/fans/off", roles=roles) - - def heaters_set(self, role, power): - """ - role: can be one of the following: - m1: Primary mirror heaters - m2: Secondary mirror heaters - m3: M3 mirror heaters - power: Percentage of total power to apply, from 0 to 100 - """ - # Added in PWI 4.0.99 beta 24 - return self.request_with_status("/heaters/set", role=role, power=power) - - def m3_goto(self, target_port): - return self.request_with_status("/m3/goto", port=target_port) - - def m3_stop(self): - return self.request_with_status("/m3/stop") - - def virtualcamera_take_image(self): - """ - Returns a string containing a FITS image simulating a starfield - at the current telescope position - """ - return self.request("/virtualcamera/take_image") - - def virtualcamera_take_image_and_save(self, filename): - """ - Request a fake FITS image from PWI4. - Save the contents to the specified filename - """ - - contents = self.virtualcamera_take_image() - f = open(filename, "wb") - f.write(contents) - f.close() - - ### Methods for testing error handling ###################### - - def test_command_not_found(self): - """ - Try making a request to a URL that does not exist. - Useful for intentionally testing how the library will respond. - """ - return self.request_with_status("/command/notfound") - - def test_internal_server_error(self): - """ - Try making a request to a URL that will return a 500 - server error due to an intentionally unhandled error. - Useful for testing how the library will respond. - """ - return self.request_with_status("/internal/crash") - - def test_invalid_parameters(self): - """ - Try making a request with intentionally missing parameters. - Useful for testing how the library will respond. - """ - return self.request_with_status("/mount/goto_ra_dec_apparent") - - ### Low-level methods for issuing requests ################## - - def request(self, command, **kwargs): - return self.comm.request(command, **kwargs) - - def request_with_status(self, command, **kwargs): - response_text = self.request(command, **kwargs) - return self.parse_status(response_text) - - ### Status parsing utilities ################################ - - def status_text_to_dict(self, response): - """ - Given text with keyword=value pairs separated by newlines, - return a dictionary with the equivalent contents. - """ - - # In Python 3, response is of type "bytes". - # Convert it to a string for processing below - if type(response) == bytes: - response = response.decode("utf-8") - - response_dict = {} - - lines = response.split("\n") - - for line in lines: - fields = line.split("=", 1) - if len(fields) == 2: - name = fields[0] - value = fields[1] - response_dict[name] = value - - return response_dict - - def parse_status(self, response_text): - response_dict = self.status_text_to_dict(response_text) - return PWI4Status(response_dict) - - -class Section(object): - """ - Simple object for collecting properties in PWI4Status - """ - - pass - - -class PWI4Status: - """ - Wraps the status response for many PWI4 commands in a class with named members - """ - - def __init__(self, status_dict): - self.raw = status_dict # Allow direct access to raw entries as needed - - self.pwi4 = Section() - self.pwi4.version = "" - self.pwi4.version_field = [0, 0, 0, 0] - - self.pwi4.version = self.raw["pwi4.version"] # Added in 4.0.5 beta 1 - - # pwi4.version_field[] was added in 4.0.9 beta 2 - self.pwi4.version_field[0] = self.get_int("pwi4.version_field[0]", 0) - self.pwi4.version_field[1] = self.get_int("pwi4.version_field[1]", 0) - self.pwi4.version_field[2] = self.get_int("pwi4.version_field[2]", 0) - self.pwi4.version_field[3] = self.get_int("pwi4.version_field[3]", 0) - - # response.timestamp_utc was added in 4.0.9 beta 2 - self.response = Section() - self.response.timestamp_utc = self.get_string("response.timestamp_utc") - - self.site = Section() - self.site.latitude_degs = self.get_float("site.latitude_degs") - self.site.longitude_degs = self.get_float("site.longitude_degs") - self.site.height_meters = self.get_float("site.height_meters") - self.site.lmst_hours = self.get_float("site.lmst_hours") - - self.mount = Section() - self.mount.is_connected = self.get_bool("mount.is_connected") - self.mount.geometry = self.get_int("mount.geometry") - self.mount.timestamp_utc = self.get_string( - "mount.timestamp_utc" - ) # Added in 4.0.9 beta 7 - self.mount.julian_date = self.get_float( - "mount.julian_date" - ) # Added in 4.0.9 beta 2 - self.mount.slew_time_constant = self.get_float( - "mount.slew_time_constant" - ) # Added in 4.0.9 beta 6 - self.mount.ra_apparent_hours = self.get_float("mount.ra_apparent_hours") - self.mount.dec_apparent_degs = self.get_float("mount.dec_apparent_degs") - self.mount.ra_j2000_hours = self.get_float("mount.ra_j2000_hours") - self.mount.dec_j2000_degs = self.get_float("mount.dec_j2000_degs") - self.mount.target_ra_apparent_hours = self.get_float( - "mount.target_ra_apparent_hours" - ) # Added in 4.0.5 beta 1 - self.mount.target_dec_apparent_degs = self.get_float( - "mount.target_dec_apparent_degs" - ) # Added in 4.0.5 beta 1 - self.mount.azimuth_degs = self.get_float("mount.azimuth_degs") - self.mount.altitude_degs = self.get_float("mount.altitude_degs") - self.mount.is_slewing = self.get_bool("mount.is_slewing") - self.mount.is_tracking = self.get_bool("mount.is_tracking") - self.mount.field_angle_here_degs = self.get_float("mount.field_angle_here_degs") - self.mount.field_angle_at_target_degs = self.get_float( - "mount.field_angle_at_target_degs" - ) - self.mount.field_angle_rate_at_target_degs_per_sec = self.get_float( - "mount.field_angle_rate_at_target_degs_per_sec" - ) - self.mount.path_angle_at_target_degs = self.get_float( - "mount.path_angle_at_target_degs" - ) - self.mount.path_angle_rate_at_target_degs_per_sec = self.get_float( - "mount.path_angle_rate_at_target_degs_per_sec" - ) - self.mount.distance_to_sun_degs = self.get_float( - "mount.distance_to_sun_degs" - ) # Added in 4.0.13 - self.mount.axis0_wrap_range_min_degs = self.get_float( - "mount.axis0_wrap_range_min_degs" - ) # Added in 4.0.13 - - self.mount.axis0 = Section() - self.mount.axis1 = Section() - self.mount.axis = [self.mount.axis0, self.mount.axis1] - - for axis_index in range(2): - axis = self.mount.axis[axis_index] - prefix = "mount.axis%d." % axis_index - - axis.is_enabled = self.get_bool(prefix + "is_enabled") - axis.rms_error_arcsec = self.get_float(prefix + "rms_error_arcsec") - axis.dist_to_target_arcsec = self.get_float( - prefix + "dist_to_target_arcsec" - ) - axis.servo_error_arcsec = self.get_float(prefix + "servo_error_arcsec") - axis.min_mech_position_degs = self.get_float( - prefix + "min_mech_position_degs" - ) # Added in 4.0.13 - axis.max_mech_position_degs = self.get_float( - prefix + "max_mech_position_degs" - ) # Added in 4.0.13 - axis.target_mech_position_degs = self.get_float( - prefix + "target_mech_position_degs" - ) # Added in 4.0.13 - axis.position_degs = self.get_float(prefix + "position_degs") - axis.position_timestamp_str = self.get_string( - prefix + "position_timestamp" - ) # Added in 4.0.9 beta 2 - axis.max_velocity_degs_per_sec = self.get_float( - prefix + "max_velocity_degs_per_sec" - ) # Added in 4.0.13 - axis.setpoint_velocity_degs_per_sec = self.get_float( - prefix + "setpoint_velocity_degs_per_sec" - ) # Added in 4.0.13 - axis.measured_velocity_degs_per_sec = self.get_float( - prefix + "measured_velocity_degs_per_sec" - ) # Added in 4.0.13 - axis.acceleration_degs_per_sec_sqr = self.get_float( - prefix + "acceleration_degs_per_sec_sqr" - ) # Added in 4.0.13 - axis.measured_current_amps = self.get_float( - prefix + "measured_current_amps" - ) # Added in 4.0.13 - - self.mount.model = Section() - self.mount.model.filename = self.get_string("mount.model.filename") - self.mount.model.num_points_total = self.get_int("mount.model.num_points_total") - self.mount.model.num_points_enabled = self.get_int( - "mount.model.num_points_enabled" - ) - self.mount.model.rms_error_arcsec = self.get_float( - "mount.model.rms_error_arcsec" - ) - - # mount.offests.* was added in PWI 4.0.11 Beta 5 - if "mount.offsets.ra_arcsec.total" not in self.raw: - self.mount.offsets = ( - None # Offset reporting not supported by running version of PWI4 - ) - else: - self.mount.offsets = Section() - - self.mount.offsets.ra_arcsec = Section() - self.mount.offsets.ra_arcsec.total = self.get_float( - "mount.offsets.ra_arcsec.total" - ) - self.mount.offsets.ra_arcsec.rate = self.get_float( - "mount.offsets.ra_arcsec.rate" - ) - self.mount.offsets.ra_arcsec.gradual_offset_progress = self.get_float( - "mount.offsets.ra_arcsec.gradual_offset_progress" - ) - - self.mount.offsets.dec_arcsec = Section() - self.mount.offsets.dec_arcsec.total = self.get_float( - "mount.offsets.dec_arcsec.total" - ) - self.mount.offsets.dec_arcsec.rate = self.get_float( - "mount.offsets.dec_arcsec.rate" - ) - self.mount.offsets.dec_arcsec.gradual_offset_progress = self.get_float( - "mount.offsets.dec_arcsec.gradual_offset_progress" - ) - - self.mount.offsets.axis0_arcsec = Section() - self.mount.offsets.axis0_arcsec.total = self.get_float( - "mount.offsets.axis0_arcsec.total" - ) - self.mount.offsets.axis0_arcsec.rate = self.get_float( - "mount.offsets.axis0_arcsec.rate" - ) - self.mount.offsets.axis0_arcsec.gradual_offset_progress = self.get_float( - "mount.offsets.axis0_arcsec.gradual_offset_progress" - ) - - self.mount.offsets.axis1_arcsec = Section() - self.mount.offsets.axis1_arcsec.total = self.get_float( - "mount.offsets.axis1_arcsec.total" - ) - self.mount.offsets.axis1_arcsec.rate = self.get_float( - "mount.offsets.axis1_arcsec.rate" - ) - self.mount.offsets.axis1_arcsec.gradual_offset_progress = self.get_float( - "mount.offsets.axis1_arcsec.gradual_offset_progress" - ) - - self.mount.offsets.path_arcsec = Section() - self.mount.offsets.path_arcsec.total = self.get_float( - "mount.offsets.path_arcsec.total" - ) - self.mount.offsets.path_arcsec.rate = self.get_float( - "mount.offsets.path_arcsec.rate" - ) - self.mount.offsets.path_arcsec.gradual_offset_progress = self.get_float( - "mount.offsets.path_arcsec.gradual_offset_progress" - ) - - self.mount.offsets.transverse_arcsec = Section() - self.mount.offsets.transverse_arcsec.total = self.get_float( - "mount.offsets.transverse_arcsec.total" - ) - self.mount.offsets.transverse_arcsec.rate = self.get_float( - "mount.offsets.transverse_arcsec.rate" - ) - self.mount.offsets.transverse_arcsec.gradual_offset_progress = ( - self.get_float( - "mount.offsets.transverse_arcsec.gradual_offset_progress" - ) - ) - - # mount.spiral_offset.* was added in PWI 4.0.11 Beta 8 - if "mount.spiral_offset.x" not in self.raw: - self.mount.spiral_offset = ( - None # Offset reporting not supported by running version of PWI4 - ) - else: - self.mount.spiral_offset = Section() - self.mount.spiral_offset.x = self.get_int("mount.spiral_offset.x") - self.mount.spiral_offset.y = self.get_int("mount.spiral_offset.y") - self.mount.spiral_offset.x_step_arcsec = self.get_float( - "mount.spiral_offset.x_step_arcsec" - ) - self.mount.spiral_offset.y_step_arcsec = self.get_float( - "mount.spiral_offset.y_step_arcsec" - ) - - self.focuser = Section() - self.focuser.exists = self.get_bool( - "focuser.exists", False - ) # Added in 4.0.99 Beta 2 - self.focuser.is_connected = self.get_bool("focuser.is_connected") - self.focuser.is_enabled = self.get_bool("focuser.is_enabled") - self.focuser.position = self.get_float("focuser.position") - self.focuser.is_moving = self.get_bool("focuser.is_moving") - - self.rotator = Section() - self.rotator.exists = self.get_bool( - "rotator.exists", False - ) # Added in 4.0.99 Beta 2 - self.rotator.is_connected = self.get_bool("rotator.is_connected") - self.rotator.is_enabled = self.get_bool("rotator.is_enabled") - self.rotator.mech_position_degs = self.get_float("rotator.mech_position_degs") - self.rotator.field_angle_degs = self.get_float("rotator.field_angle_degs") - self.rotator.is_moving = self.get_bool("rotator.is_moving") - self.rotator.is_slewing = self.get_bool("rotator.is_slewing") - - self.m3 = Section() - self.m3.exists = self.get_bool("m3.exists", False) # Added in 4.0.99 Beta 2 - self.m3.port = self.get_int("m3.port") - - self.autofocus = Section() - self.autofocus.is_running = self.get_bool("autofocus.is_running") - self.autofocus.success = self.get_bool("autofocus.success") - self.autofocus.best_position = self.get_float("autofocus.best_position") - self.autofocus.tolerance = self.get_float("autofocus.tolerance") - - def get_bool(self, name, value_if_missing=None): - if name not in self.raw: - return value_if_missing - return self.raw[name].lower() == "true" - - def get_float(self, name, value_if_missing=None): - if name not in self.raw: - return value_if_missing - return float(self.raw[name]) - - def get_int(self, name, value_if_missing=None): - if name not in self.raw: - return value_if_missing - return int(self.raw[name]) - - def get_string(self, name, value_if_missing=None): - if name not in self.raw: - return value_if_missing - return self.raw[name] - - def __repr__(self): - """ - Format all of the keywords and values we have received - """ - - max_key_length = max(len(x) for x in self.raw.keys()) - - lines = [] - - line_format = "%-" + str(max_key_length) + "s: %s" - - for key in sorted(self.raw.keys()): - value = self.raw[key] - lines.append(line_format % (key, value)) - return "\n".join(lines) - - -class PWI4HttpCommunicator: - """ - Manages communication with PWI4 via HTTP. - """ - - def __init__(self, host="localhost", port=8220): - self.host = host - self.port = port - - self.timeout_seconds = 3 - - self.on_request_issued = None # Optional callback function can be assigned here - self.on_request_completed = ( - None # Optional callback function can be assigned here - ) - - self.use_requests_lib = False - self.requests_session = None - - def make_url(self, path, **kwargs): - """ - Utility function that takes a set of keyword=value arguments - and converts them into a properly formatted URL to send to PWI. - Special characters (spaces, colons, plus symbols, etc.) are encoded as needed. - - Example: - make_url("/mount/gotoradec2000", ra=10.123, dec="15 30 45") -> "http://localhost:8220/mount/gotoradec2000?ra=10.123&dec=15%2030%2045" - """ - - # Construct the basic URL, excluding the keyword parameters; for example: "http://localhost:8220/specified/path?" - url = "http://" + self.host + ":" + str(self.port) + path + "?" - - # Remove any keyword args with a value of None - kwargs = {key: value for key, value in kwargs.items() if value is not None} - - # For every keyword=value argument given to this function, - # construct a string of the form "key1=val1&key2=val2". - keyword_values = list( - kwargs.items() - ) # Need to explicitly convert this to list() for Python 3.x - urlparams = urlencode(keyword_values) - - # In URLs, spaces can be encoded as "+" characters or as "%20". - # This will convert plus symbols to percent encoding for improved compatibility. - urlparams = urlparams.replace("+", "%20") - - # Build the final URL and return it. - url = url + urlparams - return url - - def request(self, path, postdata=None, **kwargs): - """ - Issue a request to PWI using the keyword=value parameters - supplied to the function, and return the response received from - PWI. - - Example: - pwi_request("/mount/gotoradec2000", ra=10.123, dec="15 30 45") - - will construct the appropriate URL and issue the request to the server. - - If the postdata argument is specified, this will make a POST request - instead of a GET request, and postdata will be used as the body of the - POST request. - - The server response payload will be returned, or an exception will be thrown - if there was an error with the request. - """ - - # Construct the URL that we will request - url = self.make_url(path, **kwargs) - - if self.on_request_issued is not None: - self.on_request_issued(url) - - start_time = time.time() - - if self.use_requests_lib: - payload = self.perform_request_with_requests(url, postdata) - else: - payload = self.perform_request_with_urllib(url, postdata) - - end_time = time.time() - elapsed_seconds = end_time - start_time - if self.on_request_completed is not None: - self.on_request_completed(url, elapsed_seconds) - - return payload - - def perform_request_with_urllib(self, url, postdata=None): - # Open a connection to the server, issue the request, and try to receive the response. - # The server will return an HTTP Status Code as part of the response. - # If the status code indicates an error, an HTTPError will be thrown. - try: - response = urlopen(url, data=postdata, timeout=self.timeout_seconds) - except HTTPError as e: - if e.code == 404: - error_message = "Command not found" - elif e.code == 400: - error_message = "Bad request" - elif e.code == 500: - error_message = "Internal server error (possibly a bug in PWI)" - else: - error_message = str(e) - - try: - error_details = ( - e.read() - ) # Try to read the payload of the response for error information - - # In Python 3, the response is returned as bytes rather than a string, - # so we need to decode it into a string - if type(error_details) == bytes: - error_details = error_details.decode("utf-8") - error_message = error_message + ": " + error_details - except: - pass # If that failed, we won't include any further details - - raise Exception(error_message) # TODO: Consider a custom exception here - - except Exception as e: - # This will often be a urllib2.URLError to indicate that a connection - # could not be made to the server, but we'll handle any exception here - raise - - payload = response.read() - - return payload - - def perform_request_with_requests(self, url, postdata=None): - if self.requests_session is None: - self.requests_session = requests.Session() - - # TODO: Implement POST requests - response = self.requests_session.get(url, timeout=self.timeout_seconds) - response.raise_for_status() - return response.content - - -def list_to_comma_separated_string(value_list): - """ - Convert list of values (e.g. [3, 1, 5]) into a comma-separated string (e.g. "3,1,5") - """ - - return ",".join([str(x) for x in value_list]) diff --git a/_tba/pwi4/pwi4_client_demo.py b/_tba/pwi4/pwi4_client_demo.py deleted file mode 100644 index d4b0b04a..00000000 --- a/_tba/pwi4/pwi4_client_demo.py +++ /dev/null @@ -1,45 +0,0 @@ -""" -This script demonstrates basic usage of the pwi4_client library -for controlling PWI4 via its HTTP interface. -""" - -import time - -from pwi4_client import PWI4 - -print("Connecting to PWI4...") - -pwi4 = PWI4() - -s = pwi4.status() -print("Mount connected:", s.mount.is_connected) - -if not s.mount.is_connected: - print("Connecting to mount...") - s = pwi4.mount_connect() - print("Mount connected:", s.mount.is_connected) - -print(" RA/Dec: %.4f, %.4f" % (s.mount.ra_j2000_hours, s.mount.dec_j2000_degs)) - - -print("Slewing...") -pwi4.mount_goto_ra_dec_j2000(10, 70) -while True: - s = pwi4.status() - - print( - "RA: %.5f hours; Dec: %.4f degs, Axis0 dist: %.1f arcsec, Axis1 dist: %.1f arcsec" - % ( - s.mount.ra_j2000_hours, - s.mount.dec_j2000_degs, - s.mount.axis0.dist_to_target_arcsec, - s.mount.axis1.dist_to_target_arcsec, - ) - ) - - if not s.mount.is_slewing: - break - time.sleep(0.2) - -print("Slew complete. Stopping...") -pwi4.mount_stop() diff --git a/_tba/pwi4/pwi4_startup.py b/_tba/pwi4/pwi4_startup.py deleted file mode 100644 index 54ccbfae..00000000 --- a/_tba/pwi4/pwi4_startup.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -This script is an example of how to use pwi4_client to automate the -startup of a PWI4-controlled mount. -""" - -import time - -from pwi4_client import PWI4 - -pwi4 = PWI4() - - -def main(): - connect_to_mount() - enable_motors() - find_home() - - -def connect_to_mount(): - print("Connecting to mount...") - pwi4.mount_connect() - while not pwi4.status().mount.is_connected: - time.sleep(1) - print("Done") - - -def enable_motors(): - print("Enabling motors") - pwi4.mount_enable(0) - pwi4.mount_enable(1) - while True: - status = pwi4.status() - if status.mount.axis0.is_enabled and status.mount.axis1.is_enabled: - break - time.sleep(1) - print("Done") - - -def find_home(): - print("Finding home") - pwi4.mount_find_home() - last_axis0_pos_degs = -99999 - last_axis1_pos_degs = -99999 - while True: - status = pwi4.status() - delta_axis0_pos_degs = status.mount.axis0.position_degs - last_axis0_pos_degs - delta_axis1_pos_degs = status.mount.axis1.position_degs - last_axis1_pos_degs - - if abs(delta_axis0_pos_degs) < 0.001 and abs(delta_axis1_pos_degs) < 0.001: - break - - last_axis0_pos_degs = status.mount.axis0.position_degs - last_axis1_pos_degs = status.mount.axis1.position_degs - - time.sleep(1) - print("Done") - - -if __name__ == "__main__": - main() diff --git a/_tba/pwi4/shutter_status.bat b/_tba/pwi4/shutter_status.bat deleted file mode 100644 index d94c9650..00000000 --- a/_tba/pwi4/shutter_status.bat +++ /dev/null @@ -1,37 +0,0 @@ -@ECHO OFF -SET PWSHUTTER="C:\Program Files (x86)\PlaneWave Instruments\PlaneWave Shutter Control\PWShutter.exe" - -ECHO Checking connection -%PWSHUTTER% isconnected -IF %ERRORLEVEL% EQU 0 ( - ECHO Shutter is connected -) ELSE ( - ECHO Shutter is NOT connected -) - -ECHO Trying to connect -%PWSHUTTER% connect -IF %ERRORLEVEL% EQU 0 ( - ECHO Connected successfully -) ELSE ( - ECHO ERROR: Connection failed - EXIT /B -) - -%PWSHUTTER% shutterstate -echo Return code: %ERRORLEVEL% -IF %ERRORLEVEL% EQU 0 ( - ECHO Shutters: Open -) ELSE IF %ERRORLEVEL% EQU 1 ( - ECHO Shutters: Closed -) ELSE IF %ERRORLEVEL% EQU 2 ( - ECHO Shutters: Opening -) ELSE IF %ERRORLEVEL% EQU 3 ( - ECHO Shutters: Closing -) ELSE IF %ERRORLEVEL% EQU 4 ( - ECHO Shutters: Error -) ELSE IF %ERRORLEVEL% EQU 5 ( - ECHO Shutters: PartlyOpen -) ELSE ( - ECHO Shutters: UNKNOWN STATE -) diff --git a/_tba/reduction/align_images.py b/_tba/reduction/align_images.py deleted file mode 100755 index 41bd0af7..00000000 --- a/_tba/reduction/align_images.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python -""" -align_images: Aligns FITS image[s] to a reference image. - -INPUT: filepath1 and filepath2 pointing toward images -The first image is used as a reference, and the second image has its WCS information as well as size changed -to be pixel-aligned with the first image for easier comparison. -OUTPUT: Saves a copy of the aligned image as imagename_aligned.fits - -Version 1.0 -- Developed by Jacob Isbell, 9 April 2017, modified for batch operation by RLM 16 April 2017 -""" - -vers = "%prog 1.0 16 Apr 2017" -import glob -import sys -from optparse import OptionParser -from sys import argv - -import matplotlib.pyplot as plt -import numpy as np -from astropy.io import fits -from astropy.wcs import WCS - - -def get_args(): - usage = "Usage: %prog [options] comma-separated FITS files[s]" - parser = OptionParser(description="Program %prog", usage=usage, version=vers) - parser.add_option( - "-r", - dest="reference", - metavar="Reference", - action="store", - help="Reference FITS image ", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="Verbose output", - ) - - return parser.parse_args() - - -def round_to_int(x): - if x - int(x) >= 0.5: - return int(x) + 1 - else: - return int(x) - - -def align_and_crop(im1, im2): - ref_image = fits.open(im1)[0] - new_image = fits.open(im2)[0] - - # ---read the central ra and dec of the ref image --- - ref_ra = ref_image.header["crval1"] - ref_ctr_px1 = ref_image.header["crpix1"] - ref_dec = ref_image.header["crval2"] - ref_ctr_px2 = ref_image.header["crpix2"] - ref_px_scale = abs(ref_image.header["amdx1"]) / degrees - - # ------ read in the same info for the other image ------ - new_ra = new_image.header["crval1"] - new_ctr_px1 = new_image.header["crpix1"] - new_dec = new_image.header["crval2"] - new_ctr_px2 = new_image.header["crpix2"] - new_px_scale = abs(new_image.header["amdy1"]) / degrees - - # ----- find the distance between the two image centers ------ - dist_dec = round_to_int((ref_dec - new_dec) / ref_px_scale) - dist_ra = round_to_int((ref_ra - new_ra) / ref_px_scale) - if verbose: - print("Moving %i in x direction" % (dist_ra)) - print("Moving %i in y direction" % (dist_dec)) - - if dist_ra > new_image.data.shape[0] or dist_dec > new_image.data.shape[1]: - print( - "The images are separated by too much distance and cannot be aligned. Exiting..." - ) - sys.exit(1) - - new_im = [] - temp = np.copy(new_image.data) - - # sys.exit() - if dist_dec >= 0: # the new image moves up - temp = np.roll(temp, dist_dec, axis=0) - for x in range(temp.shape[0]): - for y in range(dist_dec): - temp[-y][x] = 0 - elif dist_dec < 0: # the new image moves down - temp = np.roll(temp, dist_dec, axis=0) - for x in range(temp.shape[0]): - for y in range(dist_dec): - temp[y][x] = 0 - if dist_ra >= 0: # the new image moves left - temp = np.roll(temp, dist_ra, axis=1) - for y in range(temp.shape[1]): - for x in range(dist_ra): - temp[y][-x] = 0 - elif dist_ra < 0: # the new image moves right - temp = np.roll(temp, dist_ra, axis=1) - for y in range(temp.shape[1]): - for x in range(dist_ra): - temp[y][x] = 0 - - """ - fig,(ax1,ax2,ax3) = plt.subplots(3) - ax2.imshow(np.log10(temp),cmap='hot') - ax1.imshow(np.log10(ref_image.data),cmap='hot') - ax3.imshow(np.log10(temp-ref_image.data),vmin=-100,vmax=100,cmap='hot') - plt.show() - """ - # ------ create the headers for the images based on their original headers - - new_hdu = fits.PrimaryHDU(temp) - new_hdu.header = new_image.header - new_hdu.header["crval1"] = ref_ra - new_hdu.header["crval2"] = ref_dec - # new_hdu.header['crpix1'] = new_image.data.shape[0]/2 - # new_hdu.header['crpix2'] = new_image.data.shape[1]/2 - # new_hdu.header['naxis1'] = new_image.data.shape[0] - # new_hdu.header['naxis2'] = new_image.data.shape[1] - - return new_hdu - - -# ------------ MAIN -------------------- - -degrees = np.pi / 180.0 - -# Get command line arguments, assign parameter values -(opts, args) = get_args() - -ftsfiles = args[ - 0 -] # FITS input file mask (either single file or wildcard - parsed by glob) -ref_image = opts.reference # Reference FITS image name -verbose = opts.verbose # Print diagnostics, more -for ftsfile in ftsfiles.split(","): - ftsroot = ftsfile.split(".")[0] - new_hdu = align_and_crop(ref_image, ftsfile) - aligned_name = "%s_aligned.fts" % ftsroot - new_hdu.writeto(aligned_name, overwrite=True) - if verbose: - print("Successfully aligned %s with %s" % (ftsfile, ref_image)) - print("Saved %s as %s" % (ftsfile, aligned_name)) - print() diff --git a/_tba/reduction/compare_fits_headers.py b/_tba/reduction/compare_fits_headers.py deleted file mode 100755 index b5562042..00000000 --- a/_tba/reduction/compare_fits_headers.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Given the names of two FITS files, compare the FITS headers -to determine which entries are unique to each image and -which entries are common to both. - -Useful for figuring out what needs to be added to a Maxim -FITS image to make it compatible with Talon tools. -""" - -import os -import sys - -from astropy.io import fits as pyfits - - -def main(): - if len(sys.argv) != 3: - print("Usage: %s file1.fits file2.fits" % os.path.basename(__file__)) - sys.exit(1) - - filename1 = sys.argv[1] - filename2 = sys.argv[2] - - hdulist1 = pyfits.open(filename1) - hdulist2 = pyfits.open(filename2) - - header1 = hdulist1[0].header - header2 = hdulist2[0].header - - keys1 = list(header1.keys()) - keys2 = list(header2.keys()) - - file1_key_lines = [] - file2_key_lines = [] - common_key_lines = [] - - for key in keys1: - if key in keys2: - common_key_lines.append("1: %s" % str(header1.cards[key]).rstrip()) - common_key_lines.append("2: %s" % str(header2.cards[key]).rstrip()) - common_key_lines.append("") - else: - file1_key_lines.append(str(header1.cards[key]).rstrip()) - - for key in keys2: - if key not in keys1: - file2_key_lines.append(str(header2.cards[key]).rstrip()) - - print("Common keys (file1=%s, file2=%s):" % (filename1, filename2)) - for key in common_key_lines: - print(key) - print() - - print("Keys in %s only:" % filename1) - if len(file1_key_lines) == 0: - print("(none)") - for key in file1_key_lines: - print(key) - - print() - - print("Keys in %s only:" % filename2) - if len(file2_key_lines) == 0: - print("(none)") - for key in file2_key_lines: - print(key) - print() - - -if __name__ == "__main__": - main() diff --git a/_tba/reduction/crop-image b/_tba/reduction/crop-image deleted file mode 100644 index 807f99bf..00000000 --- a/_tba/reduction/crop-image +++ /dev/null @@ -1,97 +0,0 @@ -#! /usr/bin/env python - -""" -crop-image: Crops FITS images in place, according to user-specified boundaries, or 1/4 original (default) -FITS filename can be wild-carded e.g. *.fts in current directory - -RLM 23 Oct 2014 -3 Nov 2014: Check if WCS solution exists before changing CRPIX1,CRPIX2 -""" - -import getopt -import os -import sys - -import astropy.io.fits as pyfits - - -def usage(): - print( - "Usage: crop-image [-X ] [-Y] [-W] [-H] [-v verbose] [-h = help] FITS_file[s]" - ) - sys.exit(1) - - -def help_usage(): - print("crop-image crops FITS images in place, wildcard spec allowed") - print("(X,Y) lower left corner of crop, pixels") - print("W,H = width, height of cropped image, pixels") - print("Note: Defaults to inner 1/4 of image") - print("Warning: Crop is in place (overwrites original files)") - print("Example of usage: crop-image -X 100 -Y 100 -W 512 -H 512 *.fts") - sys.exit(1) - - -def getargs(): - # retrieves filenames and optional arguments from command line - try: - opts, arg = getopt.getopt(sys.argv[1:], "X:Y:W:H:vh") - except getopt.GetoptError as err: - print(str(err)) # Prints "option -a not recognized" - usage() - if len(arg) == 0: - usage() - verbose = False - fnames = arg - X = Y = W = H = 0 - for opt in opts: - if opt[0] in ("-v", "--verbose"): - verbose = True - elif opt[0] in ("-X", "--xstart"): - X = int(opt[1]) - elif opt[0] in ("-Y", "--xstop"): - Y = int(opt[1]) - elif opt[0] in ("-W", "--ystart"): - W = int(opt[1]) - elif opt[0] in ("-H", "--ystop"): - H = int(opt[1]) - elif opt[0] in ("-h", "--help"): - help_usage() - return verbose, X, Y, W, H, fnames - - -# === MAIN === -# get params -verbose, X, Y, W, H, fnames = getargs() - -# crop images, overwrite original image, fixing CRPIX1 keywords -for fn in fnames: - if verbose: - print("Cropping %s" % fn) - HDU = pyfits.open(fn) - Im = HDU[0].data - Header = HDU[0].header - if W == 0: - Nx = Header["NAXIS1"] - Ny = Header["NAXIS2"] - X = Nx / 4 - Y = Ny / 4 - W = Nx / 2 - H = Nx / 2 - X0 = X - Y0 = Y - X1 = X0 + W - 1 - Y1 = Y0 + H - 1 - if verbose: - print("X0 = %i, YX1 = %i, Y0 = %i Y1 = %i" % (X0, X1, Y0, Y1)) - Im = Im[int(X0) : int(X1), int(Y0) : int(Y1)] - HDU[0].data = Im - HDU[0].scale("int16", bzero=32768) - if "CRPIX1" in Header: - Header["CRPIX1"] -= X - Header["CRPIX2"] -= Y - Header["Comment"] = "Cropped image using crop-image" - os.remove(fn) - HDU.writeto(fn) -if verbose: - print("Done") diff --git a/_tba/reduction/file_renumber.py b/_tba/reduction/file_renumber.py deleted file mode 100755 index 955a1572..00000000 --- a/_tba/reduction/file_renumber.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/python - -# Rename and sequentially number files - -import os -import re -import sys - -if len(sys.argv) == 1: - print(" ") - print("Usage: file_renumber.py basename file1.ext file2.ext ...") - print(" ") - sys.exit("Renumbers sequential files \n") -elif len(sys.argv) >= 3: - basename = sys.argv[1] - infiles = sys.argv[2:] -else: - print(" ") - print("Usage: file_renumber.py basename file1.ext file2.ext ...") - print(" ") - sys.exit("Renumbers sequential files with a new basename\n") - -# Set a clobber flag True so that images can be overwritten -# Otherwise set it False for safety - -n = -1 - -for infile in infiles: - n = n + 1 - - # Create an output file name - - inbase = os.path.splitext(os.path.basename(infile))[0] - inext = os.path.splitext(os.path.basename(infile))[1] - val = re.findall(r"\d+", inbase) - intval = int(float(val[0])) - valtxt = "%04d" % (intval,) - outfile = basename + valtxt + inext - os.rename(infile, outfile) - -exit() diff --git a/_tba/reduction/mvToDate b/_tba/reduction/mvToDate deleted file mode 100755 index 5a59fb3c..00000000 --- a/_tba/reduction/mvToDate +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# mvToDate: Sorts a directory full of ft[sh] files into year/dayxxx -# directories. -# Original version: KMI (Sometime in 2003) -# 03 Feb 2004: Various cleanups; only create dayxxx directory if there -# are images for that day; added to talon's crontab - -ARCHIVE_DIR="/mnt/images/archive" -#ARCHIVE_DIR=`pwd` -#fitshdr="/usr/local/telescope/bin/fitshdr" - -cd $ARCHIVE_DIR - -for file in `ls | grep '^...[0-9][0-9][0-9].*ft[sh]$'`; do - date=`$fitshdr $file | grep DATE-OBS | awk -F\' '{print $2}'` - year=`echo $date | awk -F- '{print $1}'` - if [ -d $year ]; then - true - else - mkdir $year - fi - - echo "file: $file" - daynum=`echo $file | awk '{print substr($0, 4, 3)}'` - echo "daynum: $daynum" - today=`date +"%j"` - - if [ -d $year/day$daynum ]; then - true - else - mkdir $year/day$daynum - fi - echo "$file -> $year/day$daynum" - mv $file $year/day$daynum -done diff --git a/_tba/reduction/rename_fts b/_tba/reduction/rename_fts deleted file mode 100755 index 55206276..00000000 --- a/_tba/reduction/rename_fts +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python - -# rename a directory of files to include their filter and exposure - -import glob -import sys -from os import rename as rn - -import numpy as np -from astropy.io import fits - - -def rename(files, obj, ftr, tim, loc="/exports/images/research/images/xjk/2022/"): - global day - loc_ = loc + day + "/" - for i, fp in enumerate(files): - rn( - fp, - loc - + day - + "/" - + obj[i] - + "_" - + ftr[i] - + "_" - + tim[i] - + "s_" - + str(i) - + ".fts", - ) - - -def FileSort(filepaths): - objs = [] - filters = [] - exptimes = [] - - for file in filepaths: - objs.append((fits.open(file)[0].header)["OBJECT"]) - filters.append((fits.open(file)[0].header)["FILTER"]) - temp = (fits.open(file)[0].header)["EXPTIME"] - exptimes.append(str(temp)[: str(temp).index(".")]) - return list(zip(filepaths, objs, filters, exptimes)) - - -day = input("Which Day? ") -dir_fp = "/exports/images/research/images/xjk/2022/" + day -fps = glob.glob(dir_fp + "/*.fts") -dir_data = FileSort(fps) - -objs = [] -ftrs = [] -exps = [] -for data in dir_data: - objs.append(data[1]) - ftrs.append(data[2]) - exps.append(data[3]) - - -rename(fps, objs, ftrs, exps) - -exit() diff --git a/_tba/reduction/retrieve_images b/_tba/reduction/retrieve_images deleted file mode 100755 index 666ad47e..00000000 --- a/_tba/reduction/retrieve_images +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python -import glob -import os -import sys -from optparse import OptionParser -from shutil import copyfile - -from astropy.io import fits - -# Perfomn recursive search of FITS files (*.fts) from specfified directory by object name; -# list, and optionally copy to a destination directory. -vers = "1.0 ides of March 2021" - - -def get_args(): - parser = OptionParser(description="Program %prog", version=vers) - parser.add_option( - "-s", - dest="source", - metavar="Source", - action="store", - default="", - help="Source name [no default]", - ) - parser.add_option( - "-p", - dest="path", - metavar="Path", - action="store", - default=".", - help='Search path [default "."]', - ) - parser.add_option( - "-d", - dest="destination", - metavar="Destination", - action="store", - default="", - help="Destination path [default no copy]", - ) - parser.add_option( - "-f", - dest="filter", - metavar="Filter", - action="store", - default="", - help="Filter [default any]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default=False, - help="verbose, default False", - ) - return parser.parse_args() - - -# Parse options -(opts, args) = get_args() -objname = opts.source -if objname == "": - sys.exit("Source must be specified, try again") -src_path = opts.path -dest_path = opts.destination -do_copy = not dest_path == "" -filter = opts.filter.upper() -verbose = opts.verbose -print(verbose) - -# Generate list of FITS images with -print("Searching recursively for FITS images starting at folder %s" % src_path) -ftsfiles = glob.glob(src_path + "/**/*.fts", recursive=True) -if filter == "": - print( - "Found %i fts images, now searching for object %s ..." - % (len(ftsfiles), objname) - ) -else: - print( - "Found %i fts images, now searching for object %s and filter %s" - % (len(ftsfiles), objname, filter) - ) -N = 0 -for ftsfile in ftsfiles: - # Ignore FITS files with corrupt headers - try: - im, hdr = fits.getdata(ftsfile, 0, header=True) - except: - continue - try: - D = hdr["DATE-OBS"] - Date = D[0:10] - UT = D[11:] - JD = float(hdr["JD"]) - RA = hdr["RA"] - DEC = hdr["DEC"] - obj = hdr["OBJECT"] - fil = hdr["FILTER"][0] - exptime = hdr["EXPOSURE"] - filter_ok = filter == "" or filter == fil - if obj == objname and filter_ok: - N += 1 - if N == 1: - print("File Source. Date Filter. Exp. time[s]") - fname = os.path.basename(ftsfile) - print("%-25s %-7s %s %1s %5.1f" % (fname, obj, D, fil, exptime)) - if do_copy: - d = "%s%s" % (dest_path, fname) - copyfile(ftsfile, d) - if verbose: - print("Copied %s to %s" % (os.path.basename(ftsfile), d)) - except: - if verbose: - print("%s: header does not have required keywords, skipping" % ftsfile) -if do_copy: - print("Copied %i images to %s" % (N, dest_path)) diff --git a/_tba/reduction/sort-files b/_tba/reduction/sort-files deleted file mode 100644 index ccd8778c..00000000 --- a/_tba/reduction/sort-files +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python - -# Sort images into subfolders by source name, filter, and focus position -# RLM 26 Nov 2013 -# Modify time stamp, remove focus -# Add filter sort -# 6 Nov 2016 add a digit to UT time -# 21 Feb 2017 skip if getheader fails - -import glob -import os -import shutil - -# import needed modules -import sys - -import astropy.io.fits as pyfits - -# make list of FITS file names in current directory -fnames = glob.glob("*.fts") - -# Spin through files, renaming and putting into subfolders -for fname in fnames: - # Get info from FITS header - try: - hdr = pyfits.getheader(fname) - except: - print("Could not read %s, skipping" % fname) - continue - filter = hdr["FILTER"] - source = hdr["OBJECT"] - date, ut = hdr["DATE-OBS"].split("T") - ut = ut[:-3] - - # Clean up source name if needed (no /'s, spaces) - source = source.replace(" ", "") - source = source.replace("/", "_") - date = date.replace("-", "_") - filter = filter[0] - # Remove colons from UT - ut = ut.replace(":", "") - folder = "%s/%s" % (source, filter) - - # Create subfolder named by object and filter - if not os.path.exists(source): - os.mkdir(source) - if not os.path.exists(folder): - os.mkdir(folder) - - # Make new filename from date,ut,filter,focus strings - fnew_name = "%s_%s_%s_%s.fts" % (source, date, ut, filter) - - # Move newly named file to appropriate subfolder - print("Moving %s => %s to subfolder %s" % (fname, fnew_name, folder)) - fnew = folder + "/" + fnew_name - shutil.move(fname, fnew) diff --git a/_tba/telrun/distemail b/_tba/telrun/distemail deleted file mode 100755 index 0f9a6712..00000000 --- a/_tba/telrun/distemail +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/perl -# distemail.pl: send email to everyone with new images update emaillog. -# Elwood Downey -# 6 Sep 96: first release -# 10 Sep 96: change mail format slightly and enable for all users. -# 16 Sep 96: tighten up emaillog -# 17 Sep 96: improve keywords around/for each feature -# 6 Nov 98: changes for iro/atf. no more obs log -# 19 Nov 04: fixed paths -# 24 Nov 04: fix telhome; add 24 argument -# 22 Oct 05: change paths for deimos, change emaillog destination to user/logs [rlm] -# 8 Mar 07: allow multiple email addresses for each code [bmp] -# 20 Oct 12: check student_*.obs files for student codes, new email format [bmp] -# 23 Nov 15 do not actually send email (replaced by email_summary) [rlm] - -# only send mail if images are no older than this many seconds from now. -# may be overridden with first argument. -$age = defined($ARGV[0]) ? 3600*$ARGV[0] : 1e10; # N.B. want age in seconds -if ($age <= 0) { - $_ = $0; s#.*/##; - print "Usage: $_ [age]\n"; - print "Purpose: send email to owners of \$TELHOME/user/images.\n"; - print "Default is to send mail to everyone regardless of file age.\n"; - print "First optional arg can specify a max age, in hours.\n"; - exit 1; -} - -# need TELHOME -#defined($telhome = $ENV{TELHOME}) or die "No TELHOME\n"; -$telhome = "/usr/local/telescope"; - -# dir with images to inspect -$imdir = "$telhome/user/images"; - -# dir with schedules to hunt for additional emails to notify -BMP -$scheddir = "$telhome/user/schedin/netin"; - -# file of user codes with email addresses -@obsinfo = `cat $telhome/user/obsinfo/obs.txt`; - -# student observer code files -BMP -@studentobs = `cat $telhome/user/obsinfo/studentobs_?.txt`; - -# file in which we append email activity -$logfn = "$telhome/archive/logs/emaillog"; - -# open log file for append -open(LOG, ">>$logfn") or die "Can not append to $logfn\n"; - -# add date stamp and auxmsg -($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(); -$mon += 1; -$year += 1900; -print LOG "DATE: $mon/$mday/$year $hour:$min:$sec MDY UTC\n"; -print LOG "\n"; - -# scan image directory for new files, get codes from filenames -$now = time(); -foreach $fn (<$imdir/???*.ft[sh]>) { - my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, - $atime,$mtime,$ctime,$blksize,$blocks) = stat($fn); - next if ($mtime + $age < $now); - $fn =~ s#.*/##; - $obc = substr($fn,0,3); - if (!grep /$obc/,@obscodes) { - push(@obscodes,$obc); - } -} - -# find new files for each code, get emails and send -foreach $obc (@obscodes) { - @files=(); - @emails=(); - foreach $fn (<$imdir/$obc*.ft[sh]>) { - my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, - $atime,$mtime,$ctime,$blksize,$blocks) = stat($fn); - next if ($mtime + $age < $now); - $fn =~ s#.*/##; - push(@files,$fn); - } - if (@x=grep /^$obc /,(@obsinfo, @studentobs) ) { - @y=split(/\s*\|\s/,@x[0]); - @emails=split(",",$y[1]); - foreach $sch (glob "$scheddir/$obc*.sch") { - foreach $line (grep /\@/,`cat $sch`) { - ($a)=($line =~ /([^\'\s]+\@[^\'\s]+)/); - if (!grep /$a/,@emails) { - push(@emails, $a); - } - } - } - &sendEmail(); - } -} - -# send email to @emails and tell them @files are ready -# also append to $logfn -sub sendEmail -{ - foreach $email (@emails) { - my ($msg,$fmtfiles,$n,$f); - $n = 0; - foreach $f (@files) { - $fmtfiles .= "\n " if (($n++ % 5) == 0); - $fmtfiles .= " $f"; - } - - # build the message - $msg = <<"xEOFx"; -This message is being generated automatically by the University of Iowa's Robotic Telescope Facility. The images taken at your request are now available. The resulting files are listed below. - -Images are hosted on the server deimos.physics.uiowa.edu. In the astronomy labs, these images can be found on the 'student-images' drive. External observers can connect to the server via web browser or anonymous ftp. Images are stored in directories based on the observer type, the first three letters of the filename, the year, and the day of the year (the first three numbers in the filename). - -Images will remain online for two weeks, after which they may be archived. Archived images can be restored by special request. Please direct any questions to talon\@deimos.physics.uiowa.edu. - -Thank you. -$fmtfiles - - -xEOFx - - # send mail - #if (!open (M, "| mail -s 'Your Rigel images are ready' \"$email\"")) { - # print STDERR "Can not send mail to $email\n"; - # return; - #} - #print M $msg; - #close (M); - - # append name, address and files to log - #print LOG "EMAIL: $addr\n"; - #print LOG "NAME: $name\n"; - #print LOG "FILES: @files\n"; - #print LOG "\n"; - } -} - -# For RCS Only -- Do Not Edit -# @(#) $RCSfile: distemail.pl,v $ $Date: 2001/04/19 21:12:16 $ $Revision: 1.1.1.1 $ $Name: $ diff --git a/_tba/telrun/get-asassn b/_tba/telrun/get-asassn deleted file mode 100644 index 19b9470b..00000000 --- a/_tba/telrun/get-asassn +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python - -from optparse import OptionParser - -import pandas as pd -import requests as rs - -url = "https://docs.google.com/spreadsheets/d/1QdWCXJ0wRlNpTK0Ux4EfAybYJKKm9EXAa_wamQisajA/export?format=csv&id=1QdWCXJ0wRlNpTK0Ux4EfAybYJKKm9EXAa_wamQisajA&gid=0" -email = "tyler-roth@uiowa.edu" -vers = "1.0" - - -def get_args(): - global parser - parser = OptionParser( - description="%prog creates a schedule file for ASAS-SN observations", - version=vers, - ) - parser.add_option( - "-f", - dest="output_filename", - metavar="Output File Name", - action="store", - default="", - help="output file name", - ) - return parser.parse_args() - - -write_file = False -(opts, args) = get_args() -output_filename = opts.output_filename -if len(output_filename) > 0: - write_file = True - -res = rs.get(url=url) -open("/tmp/asas-sn.csv", "wb").write(res.content) -if write_file: - output = open(output_filename, "wb") -if write_file: - output.write('Observer = "' + email + '"\n') -print(('Observer = "' + email + '"')) -if write_file: - output.write("epoch 2000\n") -print("epoch 2000") -if write_file: - output.write("\n") -print("") - -df = pd.read_csv("/tmp/asas-sn.csv", header=2) - - -def calc_exp(mag): - # calculate and return an exposure time based on magnitude - dur = 300 # replace this line - ###code here### - return str(dur) - - -for index, row in df.iterrows(): - if not isinstance(row["Object"], str): - continue - # if row['Active'] != 'TRUE': - # continue - # create string for duration - # needs to calculate exposure time based on magnitude from row['Last Mag'] - duration = "" - for i in range(0, len(row["Filters"])): - duration += calc_exp(row["Last Mag"]) + "," - # trim last comma - duration = duration[:-1] - # create string for filters - filter = "" - for i in row["Filters"]: - filter += i + "," - # trim last comma - filter = filter[:-1] - print( - ( - "source " - + row["Object"] - + " ra " - + row["RA"] - + " dec " - + row["Dec"] - + " filter " - + filter - + " dur " - + duration - + " /" - ) - ) - if write_file: - output.write( - "source " - + row["Object"] - + " ra " - + row["RA"] - + " dec " - + row["Dec"] - + " filter " - + filter - + " dur " - + duration - + " /\n" - ) -if write_file: - output.close() diff --git a/_tba/telrun/notification.py b/_tba/telrun/notification.py deleted file mode 100755 index 61cba9d9..00000000 --- a/_tba/telrun/notification.py +++ /dev/null @@ -1,86 +0,0 @@ -# Built-in Python imports -import logging -import queue - -# iotalib imports -from . import config_notification, gmail, logutil - - -def send_info(subject_suffix, message): - if not config_notification.valid_config: - logging.error( - "Not sending e-mail notification: don't have a valid notification config" - ) - return - - subject = "INFO - " + subject_suffix - - send_mail(config_notification.values.info_emails, subject, message) - - -def send_warnings(): - if not config_notification.valid_config: - logging.error( - "Not sending e-mail notification: don't have a valid notification config" - ) - return - - warnings = [] - while True: - try: - warning_message = logutil.warning_queue.get_nowait() - warnings.append(warning_message) - except queue.Empty: - break - - subject = "%d Recent IOTA Warnings" % len(warnings) - message = "\n".join(warnings) - - send_mail(config_notification.values.error_emails, subject, message) - - -def send_error(subject_suffix, message): - if not config_notification.valid_config: - logging.error( - "Not sending e-mail notification: don't have a valid notification config" - ) - return - - subject = "ERROR - " + subject_suffix - - try: - log_messages = [] - while True: - try: - log_message = logutil.recent_message_queue.get_nowait() - log_messages.append(log_message) - except queue.Empty: - break - log_messages_text = "%d most recent log messages:\n%s" % ( - len(log_messages), - "\n".join(log_messages), - ) - - message = message + "\n\n" + log_messages_text - except: - pass # Don't let a problem with our log message retrieval prevent us from getting the message out - - send_mail(config_notification.values.error_emails, subject, message) - - -def send_mail(email_addresses, subject, message): - if config_notification.values.simulate_email: - logging.info( - "SIMULATED EMAIL: Recipients = '%r', Subject = '%s', Message = '%s'", - email_addresses, - subject, - message, - ) - else: - gmail.send_mail( - config_notification.values.gmail_username, - config_notification.values.gmail_password, - email_addresses, - subject, - message, - ) diff --git a/_tba/telrun/obs-plan b/_tba/telrun/obs-plan deleted file mode 100755 index 43e63daa..00000000 --- a/_tba/telrun/obs-plan +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python - -# obs-plan: Calculates SNR, saturation times, etc for Gemini telescope observer planning - -# v. 1.0 14 Nov 2018 RLM - -import math -import sys -from optparse import OptionParser - -import numpy as np - - -def get_args(): - d_txt = "Program obs-plan calculates SNR, peak ADU count, saturation time given star magnitude, \ - and [optionally] sky brightness, seeing, airmass" - parser = OptionParser(description=d_txt, version="%prog v. 1.0 (14 Nov 2018)") - - parser.add_option( - "-m", - dest="magnitude", - type=float, - action="store", - metavar="App. magnitude", - help="Apparent magnitude [no default]", - ) - parser.add_option( - "-M", - dest="moon", - metavar="Moon phase", - action="store", - default="quarter", - help="Moon (dark, quarter, full), default quarter", - ) - parser.add_option( - "-f", - dest="fwhm", - metavar="FWHM seeing", - action="store", - default=2.5, - help="FWHM seeing (arcsec) [default 2.5]", - ) - parser.add_option( - "-z", - dest="airmass", - metavar="Airmass", - action="store", - default=1.0, - help="Airmass [default 1.0]", - ) - parser.add_option( - "-t", - dest="time", - metavar="Exposure time", - action="store", - default=10.0, - help="Exposure time,sec [default = 10]", - ) - parser.add_option( - "-v", - dest="verbose", - metavar="Verbose", - action="store_true", - default="False", - help="Verbose output", - ) - return parser.parse_args() - - -# Dictionaries -Moon_x = { - "dark": 1.0, - "quarter": 3, - "full": 10.0, -} # Sky brightness multiplier relative to Moonless -Sky = { - "L": 2.0, - "B": 0.7, - "G": 0.7, - "R": 1.2, - "I": 1.5, -} # Color dependence of sky brightness: roughly Sigma = RN + Sky * sqrt(t/sec) -filter_names = {"L": "Luminance", "G": "Sloan g", "R": "Sloan r", "I": "Sloan i"} -k = { - "G": 0.3, - "R": 0.15, - "I": 0.05, - "V": 0.3, - "B": 0.4, - "L": 0.25, -} # Airmass extinction coeff. -ZP = { - "G": 22.62, - "R": 22.50, - "I": 21.90, - "V": 22.5, - "B": 2.15, - "L": 23.2, -} # Zero-point magnitudes (accurate for Sloan G,R, I, but guesses otherwise) - -# Camera specs -pixel = 0.8 # Pixel size [arcsec] -RN = 5 # Read noise of the camera [e-] -DC = 0.05 # Dark current [e-] -params = [ZP, Sky, pixel, RN, DC] - - -def npixel(fwhm_seeing): - # Effective number of pixels - return np.pi * (fwhm_seeing / (2 * pixel)) ** 2 - - -def stdev(filtercode, moon, exptime, fwhm_seeing, params): - # Calculates standard deviation empirically based on obs. values - ZP, Sky, pixel, RN, DC = params - sigma = ( - RN + Moon_x[moon] * Sky[filtercode] * np.sqrt(exptime) + np.sqrt(DC * exptime) - ) - return sigma - - -def calc_all(mag0, filtercodes, moon, exptime, fwhm_seeing0, z, params): - ZP, Sky, pixel, RN, DC = params - Sigma = [] - Snr = [] - Saturation_time = [] - Peak_ADU = [] - for fcode in filtercodes: - zp = ZP[fcode] - mag = mag0 + k[fcode] * (z - 1) - fwhm_seeing = fwhm_seeing0 * (z**0.6) - R_star = 10 ** (0.4 * (zp - mag)) - Signal = R_star * exptime - npix = npixel(fwhm_seeing) - sigma = stdev(fcode, moon, exptime, fwhm_seeing, params) - noise = np.sqrt(Signal + sigma**2) - snr = Signal / noise - t = 10 ** (4.71 - 0.4 * (zp - mag)) - saturation_time = t * (fwhm_seeing / pixel) ** 2 - peak_ADU = 0.85 * R_star * exptime / npix - if peak_ADU > 65000: - peak_ADU = "" - Sigma.append(sigma) - Snr.append(snr) - Saturation_time.append(saturation_time) - Peak_ADU.append(peak_ADU) - return Sigma, Snr, Saturation_time, Peak_ADU - - -# MAIN - -# Get command line arguments, assign parameter values -(opts, args) = get_args() -if not opts.magnitude: - sys.exit("Magnitude required (option -m), exiting") -else: - mag = opts.magnitude -moon = opts.moon -fwhm_seeing = float(opts.fwhm) -exptime = float(opts.time) -z = float(opts.airmass) - -# loop through these filters -fcodes = ["G", "R", "I", "L"] - -print("Gemini telescope observing planner") -print("App. magnitude = %.1f" % mag) -print("Moon phase = %s" % moon) -print("Airmass = %.1f" % z) -print( - 'FWHM seeing = %.1f" (zenith) %.1f" (z=%.1f)' - % (fwhm_seeing, fwhm_seeing * (z**0.6), z) -) -print("Exposure time = %.1f sec" % exptime) -print() -print("Parameter " + " ".join([f for f in fcodes])) -print("-----------------------------------------------") - -Sigma, Snr, Sat, Peak = calc_all(mag, fcodes, moon, exptime, fwhm_seeing, z, params) -print("SNR " + " ".join(["{:7.1f}".format(x) for x in Snr])) -print("Std.Dev [ADU] " + " ".join(["{:7.1f}".format(x) for x in Sigma])) -print( - "Peak ADU " - + " ".join(["{:7.0f}".format(x) if type(x) == float else " Sat" for x in Peak]) -) -print("Saturation [s] " + " ".join(["{:7.1f}".format(x) for x in Sat])) diff --git a/_tba/telrun/offsetCalc.py b/_tba/telrun/offsetCalc.py deleted file mode 100755 index 7fa19b56..00000000 --- a/_tba/telrun/offsetCalc.py +++ /dev/null @@ -1,39 +0,0 @@ -import ephem -from iotalib import convert - - -def calcOffsetPx(x, y): - scale = 0.625 / 60.0 # arcmin/pixel for VAO SBIG camera - dRA_fiber = 0.87 - dDec_fiber = 30.58 # Fiber offset from CCD field center - xc = 1536 - yc = 1024 # pixel coords of CCD field center - - dRA_ccd = (xc - x) * scale # Correct to center of CDD and scale RA - dDec_ccd = (yc - y) * scale # Correct to center of CDD and scale Dec - dRA = dRA_fiber + dRA_ccd # Correct fiber offset RA - dDec = dDec_fiber + dDec_ccd # Correct fiber offset Dec - - return (dRA, dDec) - - -def makeObsStar(ra_j2000_hours, dec_j2000_degs): - ra_j2000_hours = convert.from_dms(ra_j2000_hours) - dec_j2000_degs = convert.from_dms(dec_j2000_degs) - obs = ephem.Observer() - obs.date = e.now() - obs.lat, obs.lon = "41.662195", "-91.532210" # Van Allan Hall, Iowa City, Iowa, USA - star = ephem.FixedBody() - star._ra = convert.hours_to_rads(ra_j2000_hours) - star._dec = convert.degs_to_rads(dec_j2000_degs) - star.compute(obs) - return star - - -def westofMeridian(ra_j2000_hours, dec_j2000_degs): - star = makeObsStar(ra_j2000_hours, dec_j2000_degs) - - if convert.degs_to_rads(180) > star.az > convert.degs_to_rads(0): - return false - else: - return true diff --git a/_tba/telrun/rebootnotify.py b/_tba/telrun/rebootnotify.py deleted file mode 100755 index 28020152..00000000 --- a/_tba/telrun/rebootnotify.py +++ /dev/null @@ -1,34 +0,0 @@ -import datetime -import smtplib - -gmail_user = "iota.alert@gmail.com" -gmail_password = "iowaTele1" - -sent_from = gmail_user -to = ["chris@iapcrepair.com", "robert-mutel@uiowa.edu", "caroline-roberts@uiowa.edu"] -subject = "Gemini ECC PC Rebooted" -body = "Gemini ECC PC Rebooted at " + str(datetime.datetime.now()) - -email_text = """\ -From: %s -To: %s -Subject: %s - -%s -""" % ( - sent_from, - ", ".join(to), - subject, - body, -) - -try: - server = smtplib.SMTP_SSL("smtp.gmail.com", 465) - server.ehlo() - server.login(gmail_user, gmail_password) - server.sendmail(sent_from, to, email_text) - server.close() - - print("Email sent!") -except: - print("Something went wrong...") diff --git a/pyscope/bin/gui/theme/dark.tcl b/pyscope/bin/gui/theme/dark.tcl deleted file mode 100755 index ab4e3d7c..00000000 --- a/pyscope/bin/gui/theme/dark.tcl +++ /dev/null @@ -1,484 +0,0 @@ -# Copyright © 2021 rdbende - -# A stunning dark theme for ttk based on Microsoft's Sun Valley visual style - -package require Tk 8.6 - -namespace eval ttk::theme::sun-valley-dark { - variable version 1.0 - package provide ttk::theme::sun-valley-dark $version - - ttk::style theme create sun-valley-dark -parent clam -settings { - proc load_images {imgdir} { - variable images - foreach file [glob -directory $imgdir *.png] { - set images([file tail [file rootname $file]]) \ - [image create photo -file $file -format png] - } - } - - load_images [file join [file dirname [info script]] dark] - - array set colors { - -fg "#ffffff" - -bg "#1c1c1c" - -disabledfg "#595959" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style layout TButton { - Button.button -children { - Button.padding -children { - Button.label -side left -expand 1 - } - } - } - - ttk::style layout Toolbutton { - Toolbutton.button -children { - Toolbutton.padding -children { - Toolbutton.label -side left -expand 1 - } - } - } - - ttk::style layout TMenubutton { - Menubutton.button -children { - Menubutton.padding -children { - Menubutton.label -side left -expand 1 - Menubutton.indicator -side right -sticky nsew - } - } - } - - ttk::style layout TOptionMenu { - OptionMenu.button -children { - OptionMenu.padding -children { - OptionMenu.label -side left -expand 1 - OptionMenu.indicator -side right -sticky nsew - } - } - } - - ttk::style layout Accent.TButton { - AccentButton.button -children { - AccentButton.padding -children { - AccentButton.label -side left -expand 1 - } - } - } - - ttk::style layout TCheckbutton { - Checkbutton.button -children { - Checkbutton.padding -children { - Checkbutton.indicator -side left - Checkbutton.label -side right -expand 1 - } - } - } - - ttk::style layout Switch.TCheckbutton { - Switch.button -children { - Switch.padding -children { - Switch.indicator -side left - Switch.label -side right -expand 1 - } - } - } - - ttk::style layout Toggle.TButton { - ToggleButton.button -children { - ToggleButton.padding -children { - ToggleButton.label -side left -expand 1 - } - } - } - - ttk::style layout TRadiobutton { - Radiobutton.button -children { - Radiobutton.padding -children { - Radiobutton.indicator -side left - Radiobutton.label -side right -expand 1 - } - } - } - - ttk::style layout Vertical.TScrollbar { - Vertical.Scrollbar.trough -sticky ns -children { - Vertical.Scrollbar.uparrow -side top - Vertical.Scrollbar.downarrow -side bottom - Vertical.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout Horizontal.TScrollbar { - Horizontal.Scrollbar.trough -sticky ew -children { - Horizontal.Scrollbar.leftarrow -side left - Horizontal.Scrollbar.rightarrow -side right - Horizontal.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout TSeparator { - TSeparator.separator -sticky nsew - } - - ttk::style layout TCombobox { - Combobox.field -sticky nsew -children { - Combobox.padding -expand 1 -sticky nsew -children { - Combobox.textarea -sticky nsew - } - } - null -side right -sticky ns -children { - Combobox.arrow -sticky nsew - } - } - - ttk::style layout TSpinbox { - Spinbox.field -sticky nsew -children { - Spinbox.padding -expand 1 -sticky nsew -children { - Spinbox.textarea -sticky nsew - } - - } - null -side right -sticky nsew -children { - Spinbox.uparrow -side left -sticky nsew - Spinbox.downarrow -side right -sticky nsew - } - } - - ttk::style layout Card.TFrame { - Card.field { - Card.padding -expand 1 - } - } - - ttk::style layout TLabelframe { - Labelframe.border { - Labelframe.padding -expand 1 -children { - Labelframe.label -side left - } - } - } - - ttk::style layout TNotebook { - Notebook.border -children { - TNotebook.Tab -expand 1 - Notebook.client -sticky nsew - } - } - - ttk::style layout TNotebook.Tab { - Notebook.tab -expand 1 -children { - Notebook.padding -expand 1 -sticky nsew -children { - Notebook.image -side left -sticky w - Notebook.text -side right -expand 1 - } - } - } - - ttk::style layout Treeview.Item { - Treeitem.padding -sticky nsew -children { - Treeitem.image -side left -sticky {} - Treeitem.indicator -side left -sticky {} - Treeitem.text -side left -sticky {} - } - } - - # Button - ttk::style configure TButton -padding {8 4} -anchor center -foreground $colors(-fg) - - ttk::style map TButton -foreground \ - [list disabled #7a7a7a \ - pressed #d0d0d0] - - ttk::style element create Button.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-disabled) \ - disabled $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Toolbutton - ttk::style configure Toolbutton -padding {8 4} -anchor center - - ttk::style element create Toolbutton.button image \ - [list $images(empty) \ - {selected disabled} $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Menubutton - ttk::style configure TMenubutton -padding {8 4 0 4} - - ttk::style element create Menubutton.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create Menubutton.indicator image $images(arrow-down) -width 28 -sticky {} - - # OptionMenu - ttk::style configure TOptionMenu -padding {8 4 0 4} - - ttk::style element create OptionMenu.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create OptionMenu.indicator image $images(arrow-down) -width 28 -sticky {} - - # Accent.TButton - ttk::style configure Accent.TButton -padding {8 4} -anchor center -foreground #000000 - - ttk::style map Accent.TButton -foreground \ - [list pressed #25536a \ - disabled #a5a5a5] - - ttk::style element create AccentButton.button image \ - [list $images(button-accent-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-accent-disabled) \ - selected $images(button-accent-rest) \ - pressed $images(button-accent-pressed) \ - active $images(button-accent-hover) \ - ] -border 4 -sticky nsew - - # Checkbutton - ttk::style configure TCheckbutton -padding 4 - - ttk::style element create Checkbutton.indicator image \ - [list $images(check-unsel-rest) \ - {alternate disabled} $images(check-tri-disabled) \ - {selected disabled} $images(check-disabled) \ - disabled $images(check-unsel-disabled) \ - {pressed alternate} $images(check-tri-hover) \ - {active alternate} $images(check-tri-hover) \ - alternate $images(check-tri-rest) \ - {pressed selected} $images(check-hover) \ - {active selected} $images(check-hover) \ - selected $images(check-rest) \ - {pressed !selected} $images(check-unsel-pressed) \ - active $images(check-unsel-hover) \ - ] -width 26 -sticky w - - # Switch.TCheckbutton - ttk::style element create Switch.indicator image \ - [list $images(switch-off-rest) \ - {selected disabled} $images(switch-on-disabled) \ - disabled $images(switch-off-disabled) \ - {pressed selected} $images(switch-on-pressed) \ - {active selected} $images(switch-on-hover) \ - selected $images(switch-on-rest) \ - {pressed !selected} $images(switch-off-pressed) \ - active $images(switch-off-hover) \ - ] -width 46 -sticky w - - # Toggle.TButton - ttk::style configure Toggle.TButton -padding {8 4 8 4} -anchor center -foreground $colors(-fg) - - ttk::style map Toggle.TButton -foreground \ - [list {selected disabled} #a5a5a5 \ - {selected pressed} #d0d0d0 \ - selected #000000 \ - pressed #25536a \ - disabled #7a7a7a - ] - - ttk::style element create ToggleButton.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-disabled) \ - {pressed selected} $images(button-rest) \ - {active selected} $images(button-accent-hover) \ - selected $images(button-accent-rest) \ - {pressed !selected} $images(button-accent-rest) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Radiobutton - ttk::style configure TRadiobutton -padding 4 - - ttk::style element create Radiobutton.indicator image \ - [list $images(radio-unsel-rest) \ - {selected disabled} $images(radio-disabled) \ - disabled $images(radio-unsel-disabled) \ - {pressed selected} $images(radio-pressed) \ - {active selected} $images(radio-hover) \ - selected $images(radio-rest) \ - {pressed !selected} $images(radio-unsel-pressed) \ - active $images(radio-unsel-hover) \ - ] -width 26 -sticky w - - # Scrollbar - ttk::style element create Horizontal.Scrollbar.trough image $images(scroll-hor-trough) -sticky ew -border 6 - ttk::style element create Horizontal.Scrollbar.thumb image $images(scroll-hor-thumb) -sticky ew -border 3 - - ttk::style element create Horizontal.Scrollbar.rightarrow image $images(scroll-right) -sticky {} -width 12 - ttk::style element create Horizontal.Scrollbar.leftarrow image $images(scroll-left) -sticky {} -width 12 - - ttk::style element create Vertical.Scrollbar.trough image $images(scroll-vert-trough) -sticky ns -border 6 - ttk::style element create Vertical.Scrollbar.thumb image $images(scroll-vert-thumb) -sticky ns -border 3 - - ttk::style element create Vertical.Scrollbar.uparrow image $images(scroll-up) -sticky {} -height 12 - ttk::style element create Vertical.Scrollbar.downarrow image $images(scroll-down) -sticky {} -height 12 - - # Scale - ttk::style element create Horizontal.Scale.trough image $images(scale-trough-hor) \ - -border 5 -padding 0 - - ttk::style element create Vertical.Scale.trough image $images(scale-trough-vert) \ - -border 5 -padding 0 - - ttk::style element create Scale.slider \ - image [list $images(scale-thumb-rest) \ - disabled $images(scale-thumb-disabled) \ - pressed $images(scale-thumb-pressed) \ - active $images(scale-thumb-hover) \ - ] -sticky {} - - # Progressbar - ttk::style element create Horizontal.Progressbar.trough image $images(progress-trough-hor) \ - -border 1 -sticky ew - - ttk::style element create Horizontal.Progressbar.pbar image $images(progress-pbar-hor) \ - -border 2 -sticky ew - - ttk::style element create Vertical.Progressbar.trough image $images(progress-trough-vert) \ - -border 1 -sticky ns - - ttk::style element create Vertical.Progressbar.pbar image $images(progress-pbar-vert) \ - -border 2 -sticky ns - - # Entry - ttk::style configure TEntry -foreground $colors(-fg) - - ttk::style map TEntry -foreground \ - [list disabled #757575 \ - pressed #cfcfcf - ] - - ttk::style element create Entry.field \ - image [list $images(entry-rest) \ - {focus hover !invalid} $images(entry-focus) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - {focus !invalid} $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding 8 -sticky nsew - - # Combobox - ttk::style configure TCombobox -foreground $colors(-fg) - - ttk::style map TCombobox -foreground \ - [list disabled #757575 \ - pressed #cfcfcf - ] - - ttk::style configure ComboboxPopdownFrame -borderwidth 1 -relief solid - - ttk::style map TCombobox -selectbackground [list \ - {readonly hover} $colors(-selectbg) \ - {readonly focus} $colors(-selectbg) \ - ] -selectforeground [list \ - {readonly hover} $colors(-selectfg) \ - {readonly focus} $colors(-selectfg) \ - ] - - ttk::style element create Combobox.field \ - image [list $images(entry-rest) \ - {readonly disabled} $images(button-disabled) \ - {readonly pressed} $images(button-pressed) \ - {readonly hover} $images(button-hover) \ - readonly $images(button-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 28 8} - - ttk::style element create Combobox.arrow image $images(arrow-down) -width 35 -sticky {} - - # Spinbox - ttk::style configure TSpinbox -foreground $colors(-fg) - - ttk::style map TSpinbox -foreground \ - [list disabled #757575 \ - pressed #cfcfcf - ] - - ttk::style element create Spinbox.field \ - image [list $images(entry-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 54 8} -sticky nsew - - ttk::style element create Spinbox.uparrow image $images(arrow-up) -width 35 -sticky {} - ttk::style element create Spinbox.downarrow image $images(arrow-down) -width 35 -sticky {} - - # Sizegrip - ttk::style element create Sizegrip.sizegrip image $images(sizegrip) \ - -sticky nsew - - # Separator - ttk::style element create TSeparator.separator image $images(separator) - - # Card - ttk::style element create Card.field image $images(card) \ - -border 10 -padding 4 -sticky nsew - - # Labelframe - ttk::style element create Labelframe.border image $images(card) \ - -border 5 -padding 4 -sticky nsew - - # Notebook - ttk::style configure TNotebook -padding 1 - - ttk::style element create Notebook.border \ - image $images(notebook-border) -border 5 -padding 5 - - ttk::style element create Notebook.client image $images(notebook) - - ttk::style element create Notebook.tab \ - image [list $images(tab-rest) \ - selected $images(tab-selected) \ - active $images(tab-hover) \ - ] -border 13 -padding {16 14 16 6} -height 32 - - # Treeview - ttk::style element create Treeview.field image $images(card) \ - -border 5 - - ttk::style element create Treeheading.cell \ - image [list $images(treeheading-rest) \ - pressed $images(treeheading-pressed) \ - active $images(treeheading-hover) - ] -border 5 -padding 15 -sticky nsew - - ttk::style element create Treeitem.indicator \ - image [list $images(arrow-right) \ - user2 $images(empty) \ - user1 $images(arrow-down) \ - ] -width 26 -sticky {} - - ttk::style configure Treeview -background $colors(-bg) -rowheight [expr {[font metrics font -linespace] + 2}] - ttk::style map Treeview \ - -background [list selected #292929] \ - -foreground [list selected $colors(-selectfg)] - - # Panedwindow - # Insane hack to remove clam's ugly sash - ttk::style configure Sash -gripcount 0 - } -} diff --git a/pyscope/bin/gui/theme/dark/arrow-down.png b/pyscope/bin/gui/theme/dark/arrow-down.png deleted file mode 100755 index 122ee4599d9d9eee5a4c8fb73d0f428ae7076c1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt;1XT^vI=X6E)9avgFIad|#F zS8UrSy>DSo4GEO|ZY$F%aCih3j5Ro_y@0xmC4t-Ks1@rL&%K3~yK%_I$sr;+d!7Upepi{4FY#0bXl2=~SQk zpm+LG##-eD4W;?}-kZl>U)#N?g0bPx@>$C+=H8BDJRmVSD*OK7Z>*L))>mKT@N5G* Olfl!~&t;ucLK6U9iewxB diff --git a/pyscope/bin/gui/theme/dark/arrow-right.png b/pyscope/bin/gui/theme/dark/arrow-right.png deleted file mode 100755 index 2638d885eb9be2833b009ef5b88081a4b22b34b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1g!3HF2ETDGVhgXJ?3QcUSGUi&$t1y1Z}2x(bF22!HCMuQ zI$p3OWUj4!E%8|9z|yT|74PF25?r3-^xU+1J}>zd^VQSFKbf9auLQb|!PC{xWt~$( F697n>UrPW0 diff --git a/pyscope/bin/gui/theme/dark/arrow-up.png b/pyscope/bin/gui/theme/dark/arrow-up.png deleted file mode 100755 index f935a0d32a699308758f10b1ccee21c92e7c6b64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=OCT^vI=X2$jzavgFIVSTQi z*}{5|xk5}oLD5M{aRSQ`Y(kAvd{KEfjKk-)x RB?6tw;OXk;vd$@?2>=XyVnF}^ diff --git a/pyscope/bin/gui/theme/dark/button-accent-disabled.png b/pyscope/bin/gui/theme/dark/button-accent-disabled.png deleted file mode 100755 index bf7bd9ba70f9371a9c3d98b8f97039e611018479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|;*h7sn8b-nZur`3@@xxLiDW z!YS&7&*{JVX}8u2I5|~J?otbV6Yxv^!OQ(YUir^ul6nLLCmfl2hG(tm3azU5H|7+3 zXvVgssR%kXbSOOMIVO>xeE-9~_X|4y^q$@G{Xy4mNzXNx)qXYb|7h6XkhYD-*m%R% zZ9HqXHm&Lso&LPiDfHky>!Ncjw%%(x%d=EO=6Itao1J61{YF2TkV2sI7(8A5T-G@y GGywpYLS~o% diff --git a/pyscope/bin/gui/theme/dark/button-accent-hover.png b/pyscope/bin/gui/theme/dark/button-accent-hover.png deleted file mode 100755 index 8aea9dd682773a2a5ed32f6a9c14a4db8f2c00ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 373 zcmV-*0gC>KP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Rl-x zK~y-6?bN+W!%!TC@#mZrlbRw0K~Ro^QncWv1YEoUXX)bTF1-OKZ$JkJXGg`wJ8*C) z4h}*mv5QFkz*dZslX(8l-Avjs&jtK=r&k$cB%EKO(P~p~MpUXHdCGobKDr$q&X0(n zd#KfDOEwP9aDENz1j)sL0;aUWOJZ&hchQYj8|T+Z3Sa;NBu>bJkma3ybiEm2r3f`z z>V(;~O?0IiG64?}@PZIMJ~dhimS73~3$*bkY66f_kbVu(Z}&HN?sxoUE~9^UjqY8Z zkPco@nWfO}h@dh{`rha9;uvL&kzj6-h3!3NR@Tw}bS}>&-weAQo~}-@gIE3l45nSi T5+#gb00000NkvXXu0mjf@z|C; diff --git a/pyscope/bin/gui/theme/dark/button-accent-pressed.png b/pyscope/bin/gui/theme/dark/button-accent-pressed.png deleted file mode 100755 index edc1114e5e03094ebe8796dd528ca5fb932ec9eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|*)h7sn8b-nUn6{g@nO+CDzF zx9*;udEr#r)!YL?N^{c#^cI$c$sIY?*5W;(fLphNmz7r~>{yG(qSJL}`&z|jMeIn} zd7iQEWb|I4wuQ#O+g`s687wYMU0!lt z4%JQPuG`#;Tf8_<{9(Jro53Y5417}iTRn$ z_kpST7yD~>(}-r%AL<)ClBVSFb0$nKzmjF$cdf6(yaQPpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10R~A# zK~y-6?bN+W13?sq;df?N$>0*f%3{PuB$bt@SXo&HyaDk7(pq>0iU@Z0f>_yDiC`o6 zgBBKIBg7`4kOXx#8{C~uW-P1}3)iIF_W~Y1PH}|R+68)o$>lXhrsnZgK$bcm5ihsr z-0!Z_etbZb<`&%S<{`FFB(>l&aUg&J310lpGEAgY~T(%)uP|6KJA0L?0jlm{y2v-kc)4Y0sdvDfR0U zWOaWF^IS)m1l&KKqO*`DB+VzPdm9L?wNsmmtT}V`<;yxP|+?=7sn8b-nUn**Bvq7alWYC zX%>^hdGkM4tVE}Mu^?#5Q&%xNvF6_YE-E-BXhO@(LiKFjs`p#2NhB}mNVsiQ zbk5~Sa)8j*un;fB=RGP_@6TA~H@@@p;xt}7rO8`KNiLD0*ston3yY@eoihfT-EYKy z`K?vI=FepDnuFqhIAgLmUOV)6=i7HNe((R@+N{1}w{oW;+w;mr*Q6#fOend#|C#lM ummtT}V`<;yxP|-q97sn8b-nUl{u4*O1 zsC*))jVhBP;;;%B={W8H-&&gXs@sj0Mt`_R8PZ22G9 z?3wQVH9B+GbL+L!2WszoE)}`+ce%W}QbDF^6Uc VrPTJsX8;|`;OXk;vd$@?2>?~eZKMDI diff --git a/pyscope/bin/gui/theme/dark/button-pressed.png b/pyscope/bin/gui/theme/dark/button-pressed.png deleted file mode 100755 index a1c525723e796c3070b3c319e7e9e70a06435650..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|;dX7sn8b-nUmAyILH0+#ZTg zZsL$T_Zj!g-yInS;MwD{tse`FwiUx|OSTSf{mh7zCvW32wReVsG-QQ=4>L zSbp!DJkxnW#|N{lwKH}L3%VQ;_)@vBIyZMN?9Bi75GgYw&Fx8DakoWax8&t;ucLK6VlYj_|4 diff --git a/pyscope/bin/gui/theme/dark/button-rest.png b/pyscope/bin/gui/theme/dark/button-rest.png deleted file mode 100755 index ec427edba67de887bffcb0673518c072742c194d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+?=7sn8b-nUaX_O%%Bu--2g zYMH`T_3!_84F^G$8~fgEiF)fPzAd$?|5)$Y)JU6S9dnF>yc9a+?#Qk+ee;_y)V0Sj zX_ndwt*GpR?|O=!EdmDTjjxrup0RWeUAQWC(e*>GcL_K-oRgds)HL(%q`gUL5^ao5 zo2t+5_wsbwXSVkvhx|k3KhrOq*_Ho5-Q&<1g{@&7HS%6U&nun0Gz-c*POJ@@w7+<% uh)d|vZMhQ1KVIWzXo%A8d)zSdAv-rqX!7?9^YVbcVDNPHb6Mw<&;$TnqIMJj diff --git a/pyscope/bin/gui/theme/dark/card.png b/pyscope/bin/gui/theme/dark/card.png deleted file mode 100755 index d87fefc3bf6327821608d7200f19bb10ecd9f348..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nlcPZ!6KjK;UO4t8lP3b;L7 z9Fe5dSl6)U|7X?ikoKSgG4XdvVa{m_RxjMQcJ_aTcRt^L*D_v-%3K@hrMR>uJzMwL zoHxJENia7s73_@@6b$+Lzp-4&>AkG6bLh#fVJTn>W<9 z>iY4x_2O$yi!`5xn18%0(Hz8e^Y`I@ol}HQZu?(LO%ZZ0>r@fsgK^Y8EV spwMS?V)VrGw%^XQo3~OcjqQN=osEgJ*1Y451co1jr>mdKI;Vst06Ai#e*gdg diff --git a/pyscope/bin/gui/theme/dark/check-disabled.png b/pyscope/bin/gui/theme/dark/check-disabled.png deleted file mode 100755 index f766ebafa3c9fa5b7dcf944e0a8c14961cb9c35a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 383 zcmV-_0f7FAP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Srk* zK~y-6t!pis{hQtTpX$cm-iIH|HLr4a>Y{eFvb$3Kgdvm}1 ziF0nsvW%iAu+}0XypyWZ_dVWwLI|W;mf@Ux8=4alw%aY%TH3ZnilSJI{e*YB9b&Eh z7@N!U99d)CoQSZVHls_Pj4>Pzhjl<>3}soe-|s&MjWL`~C#GqlX_|QOcs!D(>91Il zB-C}yIF9k4bB?ksztLFiC3v}97>0rK`OIdsiN;=ns!G!|gb=8zDjItU&S+iN+-|pM z3?TjMsH$AA*C^=Ug-a7vT@6!Jr0@IHz^>~M?>%d*9LEvwJrY8oZCkpoTLh~rkH>@i d{m%3GpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10cS}> zK~y-6tm>%sQxjsVJvc0EU6MnKcjAF7lwN(ubAy zmZ8%w!pUQ>oXb4ec(}%WYT*|(iy4CA#vxd@7sbRpR9P~O8G^*&L})t;=co@?t22P6 zA+nPnh#7)ED2Gtu2xH+ngp)ZCCpQoS0493=uA7=Pnf*V7DlOYE+HQi#)v|5i=VKA1 zUdVv9Wy@H%`GNZBKG+eWrKHLYIDY^xe*lcb+%BS6Dv Q3;+NC07*qoM6N<$f;?EuF#rGn diff --git a/pyscope/bin/gui/theme/dark/check-pressed.png b/pyscope/bin/gui/theme/dark/check-pressed.png deleted file mode 100755 index 4f9d1fc42c780c8e259064075cbd9860a8364fea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 460 zcmV;-0WpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10a-~z zK~y-6t*@gx0wzLfh4$rF{$OD)z^;@p5ti${WpvF#upG3B0}sD&>m{W78~cZh=O<*?*y8 z8Z z2=(vc3+f+zsEYiHhQ(%r*-{NuNMLY#0oO7^V>3Yz3SO#$B}MdZ&L>#85s1wO0T9Z- zOEpM*V}dajhyej#CX``qXEziS*l^xZ$1-BWTvG=fciXXnqvw0jq1=LF>I-AGF@kz| z3ML6S)E*QhGRsJ2RzQSAB*?W4Xpais&W`Y{ec=a+8+x@xd5_ru0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10cc4? zK~y-6t21Mxu(0 zQZ|KLECMC}3TAA2PMr)V!1neTg4*C}8P5SPnE8ECbxbXEh5aTyUW4)7R8*rgwW5j) zVipDftW5whP5bFzAZ^s}etBpPNENXs$fjZRvjS4erc48i zPq!esSOm0M?iy=WipU)W!P*(RBV|PJ4kxUGP7sba1W`rCM@Ga;bOUv%fNy)~je;~? RX`%oC002ovPDHLkV1i|U#n%7; diff --git a/pyscope/bin/gui/theme/dark/check-tri-disabled.png b/pyscope/bin/gui/theme/dark/check-tri-disabled.png deleted file mode 100755 index a9d31c760240a0deceaf99ab0f3a622de75aab08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|;>j7sn8b-lrE0xegofv_1?M z1bVY|fRyO!+&Ofw{1T8w8(7M&9i zoDy`w^0`2=4e!+sj(gPx#z9^S1P-OXk(6s=JaQ&KdF#ISj=I-?a&NbE{gb~TD|@W4 z<$K-tWvgZ^%l!UrXRAAR+}FSUKN&BanU$EvcFNM6IaE|dDPv>NZLmmtT}V`<;yxP|rq34lPpgIb0nC7z*t?s*(ZUIS$KV$=Fzr;4Ev|CG7GOvY~wd5EHE|*JZmyz zq4IeLu^1uqX=TPW^}m@`%Pil(((G2E8D{w@=ERAPXsv?Zum7h=b}{q(^N?g>`}gmmtT}V`<;yxP|;^k7sn8b-sCA0C;mLIsCHW6 z<8Y+ z<0bR_&!s0!`r@_hh5;K8r19mkd7gJ+Q_D-O?c!}_j0<&UQwuuGBcG7;JSkz(tV{!E z_j!#vJ5=t)S)cvz{CN1ASr$7aTekXaJT>vTliQsji`b6StStu~^iNStcyNFI{S|39 z_5-b%vG2}K2UBMIyEcape5tu@)3)D2$UyVK`R)EZ=eM0nN_du*nDL|HsRXn3egkId z{T$57+}fUOe}Dh%efaolG7rC)so|f0KiU`>bV3~Ue19Dl26~^t)78&qol`;+0A4kZ AaR2}S diff --git a/pyscope/bin/gui/theme/dark/check-tri-rest.png b/pyscope/bin/gui/theme/dark/check-tri-rest.png deleted file mode 100755 index 26edcdb13c66fa429b933f7af6a8ae04a2cc17a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmV-x0hIoUP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10QgBn zK~y-6W0aAR`OhV-!XO`0$-u8;#lXzT!$1o7`~3^U$IC|<9xQEV`2O@R1EZR`!+*WD zjSS4(d?Xo!Ver>a3>T~X7#LkA@BA-h>PC@aF!1)|b_T|f{h$An>P=kW@AoeZOw=-r zfq{X6nUjZsiE5@n!3Z>v1((FtIgJe8-#o)A$0;Jkpq)~WRgOFtGBVmmtT}V`<;yxP|;CO7sn8b-nUmbcC{D?G(4;@Y%WV|Kvzc#YHU*Gwy`4hsSJu7x(f%gS@eiVSnbVeY@5vw=@{C2|n~%YwD$W zM8dL<`EIG~`R9|T2%X*(kiC~hYw3n-M!v$IZ5BIoESVB<{j1QI;I&hy2r)diP&y{@ zt+t-~_uXx|Uuye1PCPaJzv^07N5NYr{gP($ie~-BwWd>osw!q^?+1E_!PC{xWt~$( F69Ag1dV&A| diff --git a/pyscope/bin/gui/theme/dark/check-unsel-hover.png b/pyscope/bin/gui/theme/dark/check-unsel-hover.png deleted file mode 100755 index 6d00402ad42910895fa8ec5617d5e50f9244e6b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-V27sn8b-nVBA``jED*e>Mf zBz0X}IN9fq>{XW?4d)bN4sm|d;(NgMh)GCR^yC&UR!$?4ZU0w`t}SnHmpQ&u=d_c| z@dtC7gSxDz1WgE9v`Wk8GK*5nj!P!4mrY*m;(NX89+zbD2OIuG38nMJ6Le0mE0hVm z{&nq(Xc5;9xzbY4WpZ!Zonly!8(m{3pD`sU=k_;?JKK17t#hunsjWP0UURs<)l1Xh zn4EiCqJt)@uNUX}&vNbi-X}}2{dx2I-)t354#gSIC7)N`>yk(oa8lr45^-gGWN|Ka wmf9nWw%5BZl>QB?kYWF|&pmQl#5C6WJ;|Y;8LQ-;1AWfm>FVdQ&MBb@0B_=m>i_@% diff --git a/pyscope/bin/gui/theme/dark/check-unsel-pressed.png b/pyscope/bin/gui/theme/dark/check-unsel-pressed.png deleted file mode 100755 index 67d6bb246ff2d2e9d072f90480c5ebb3279d6faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|{9^wwTk8g#epjQs&&4#vZqbaG|fmrN0GVh~)YzBO!xmJ7@A z!i0$HThD&(3F11#v-O(ZgpLDe64G*Rw?%t#E)`jkCH^Jq;lB47YZGUw1#mE?eG~mA zW$fG|=;CrWci-wM9T&_S`HLFGD;nh+&&&Rith&Pz6l(DK{DSXtmgzvFs{19B=M>+a tP|T;uVqomTa@ao3;hdyW(`TDPb|;olzx`f~a902T diff --git a/pyscope/bin/gui/theme/dark/check-unsel-rest.png b/pyscope/bin/gui/theme/dark/check-unsel-rest.png deleted file mode 100755 index 10cd31b102682ca0b2fcef0f6057191833ce7194..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-V27sn8b-nVBA``jED*e>Mf z7|jX{>~#6_x5G(kN>q9Qr}~Z677fxJ)5LT;U33KvCtkeqZ++O7dyH}$bzU3sFc-=k z-j=&!RhRXWDKnOFhK43aiV2+myw1e;q6zP>eaH8`-|low!s0o{VS&nd%_)&(XFvDs zT9@y7f00I0=B$96+iurO`;8bfZi~%7&)&6am4K5CU#q}^DNdC(wUvj>YYx}926-7A zlXIW7%+ZT8b(UJeIr;W=uajil|HvHwI9r92LowpI=<~{ZT@uM3YULdT7HDt@oZh4t vdtLCjq0Di{rC-7-=Bc+OIz&#pv5mLh-5^ZFF6mDy(B}-Eu6{1-oD!M diff --git a/pyscope/bin/gui/theme/dark/empty.png b/pyscope/bin/gui/theme/dark/empty.png deleted file mode 100755 index 22183634d5e36298e12ed067750da6c7d2fcdea9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0TuCix;TbNOifOZU~Lv=U|^iZ Vz$kX=awbrQ!PC{xWt~$(699LkAlU!_ diff --git a/pyscope/bin/gui/theme/dark/entry-disabled.png b/pyscope/bin/gui/theme/dark/entry-disabled.png deleted file mode 100755 index 9d25dc8b95a58511cf0912ec600abf356eeaee56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-Y37sn8b-nSPF`C1HkSTA-8 zPw19u{5{{mL$LGh+7;!Ex-;+j$NSyh=6X&tbm|-;b-0VZrr)2mKTH2dll~8F3Bmft zATJjdqvtM9eg+68_B4LEchb_{>HP0|)<@4|tX;Tj&Fd;|o4)4S`-SCp?;YJTqj(mB PT*~0->gTe~DWM4fa`k4= diff --git a/pyscope/bin/gui/theme/dark/entry-focus.png b/pyscope/bin/gui/theme/dark/entry-focus.png deleted file mode 100755 index 30310fb37156559369b5e99bdfc34f7eb568be5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+Pv7sn8b-nUl_y;uxI+CRQm zohhIy)}^3R+-2J;{*mLGnc2N#VISCr@zsC%IyB-?1z4~z5Bv+dF$G>zZ>s#PX5BpY diff --git a/pyscope/bin/gui/theme/dark/entry-hover.png b/pyscope/bin/gui/theme/dark/entry-hover.png deleted file mode 100755 index 6b93830ab5908d7af96c755acd111761524e0f4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|++;7sn8b-nUmaiZ&bYv^<>L zs&QolLx1LsTc<17q(oob^b(#GQuyf7e}?{99(~(L!!-< zWzueb=J&tdjEs9CZbmiFOkBCOX=dR(^}fdr@9lS4dug7qT)eeSyndef6wjY#F+f)` Nc)I$ztaD0e0suTIW;6f* diff --git a/pyscope/bin/gui/theme/dark/entry-invalid.png b/pyscope/bin/gui/theme/dark/entry-invalid.png deleted file mode 100755 index 7304b247cd136a2042d9ef347406655172be6537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-zC7sn8b-nUl{`W`V5aC>OJ zEx9tX`*fT|M2Lg`76;iY4#86x+4%%El_jO`SSR2yBjz*Lk1yv_RhBNf7UjfY@mxT# zDkwB-?UOl~Yag!JdPL%+rLnK@s#~mG9QUe|dU_sra4SwIUN+?tYpkBmQU#?J0fTdv z&no|kmqlu}CQ2A6U8;SS@6C87?Pav}j%Mp0>H(p<>mT$_FkGRv>{{3NI^!36(<8<1 zyi9)b@oDesYi5m$x7ys9Ghxc4d+Sd9{jFXe{A|Oz*b6r$=Jm7BTi3sbtA_D~anSBl Ths@^zJ;&hb>gTe~DWM4f{>_9> diff --git a/pyscope/bin/gui/theme/dark/entry-rest.png b/pyscope/bin/gui/theme/dark/entry-rest.png deleted file mode 100755 index e8767526ae057387f873d60cc1ec1ae578b77cec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-F|7sn8b-nUma_O>|iv_4Fi zR`<(bxX5RFna6a3#es^NlZt|K{H%9Ne|rA;WhT>Q6JJ#&1LGB19UQiyqDoBf`pz6t zP?9(a0>V{Rj@*l(t3a;Xk5lr}3qhb8mf^Wm_=9!90`MP`gbOaCHHB3=+VL81i pVP5)*EO80J8{2qa?^?9AP2QBpXKBq|V~{r)JYD@<);T3K0RWGPZ215H diff --git a/pyscope/bin/gui/theme/dark/notebook-border.png b/pyscope/bin/gui/theme/dark/notebook-border.png deleted file mode 100755 index 0827a074e2330c873964133f877788a1fb4cbeb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?4G5)fv*mnL5U6qGD+ zjVKAuPb(=;EJ|f4FE7{2%*!rLPAo{(%P&fw{mw=TsOX-ji(^PepF;&n47$?_anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=AJE{-7)t#8j6G9FM6U@&wz z{(F0*O38@}eB!J(jX$rEw~lAOClbOM>KGazijyj340+DDcylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=hPZ!4!iK)p`CQkf$-of?2 zBB|(e3h$>MI&k8U*F?wv|No>_Y$z0CPJXkn|9_;#&XtdL+27x9U|@T1Tf@Wu^Y4pD mfBSzvzwLOmxPd{CH4kI2rj)?bY!MZp$qb&ZelF{r5}E)bNJUcs diff --git a/pyscope/bin/gui/theme/dark/progress-pbar-vert.png b/pyscope/bin/gui/theme/dark/progress-pbar-vert.png deleted file mode 100755 index 3d0cb29758f0c6b066031b946818eeff9731dba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTn!3HEs#ylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=PPZ!4!iK)p`CQkf$-of>N yi|=6xAc)wsX_G`tOUsI+8@tQjOGvQqoz5s_&i0<^Jj+g?E(T9mKbLh*2~7YrkuMhj diff --git a/pyscope/bin/gui/theme/dark/progress-trough-vert.png b/pyscope/bin/gui/theme/dark/progress-trough-vert.png deleted file mode 100755 index 22a8c1c640586e6d8af59d584ff97089b570fec8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTn!3HEbP0l+XkK Dj5ILM diff --git a/pyscope/bin/gui/theme/dark/radio-disabled.png b/pyscope/bin/gui/theme/dark/radio-disabled.png deleted file mode 100755 index 965136dc72ed7492600cd63e83e1a2b218717b3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmV+^0@nSBP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10k%m* zK~y-6rIp>TsxT0R7Y#p&Qxgm|K;kR#{tv=C&_Ey>(yA3w_k~-fVAOrix|`MEYi4Mt z-fp+MVkXb?Kt$l2BMd|Mz7GHx#}Svy1zKw;rEtA2K7}_2a?X(?34$P4Bu_OA1G=t* zQp)k!G!Q}{%d(F!00@ErS(ZTv@fs+EKpe*(L371%j3h}Ou@BtJoWrcOsHzH0(?Dwt z03afyX^K40!Pv97&1Qr1`3$Y~4LIEa0E{sx%W~15zVFfZJ*1QOtG#_Di)phN} zQ51pQ5A9v|AMlOlxxVka=kTxcJa6H#aU30E-qUwltyWGvjw6^c#xc`0{TevWHO7F= z`;g~3IOiW>oO9%P?s!^jFr^fRVR)E~F%(7d7RotCQ54Q?8HNE`>)W&;qQ!33T1Y8T z*Y(G%2LMvaClL7e;Q#>p{T^M{-737BZQJ5-IQ$n3_?pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10^&(T zK~y-6m6cCy3{f1%Kkv=XZfxz^k*cPhwrNt)y4Z3+Rdfjs{)DJFAt59#6~e&<3C9xB zIH*G#H&Gf+Qju*#8ZPXqZ9<6dpo;9Kt<^O<^BiUu{iF1IdNXO~yo*wc?9rUQ-Do2h6%NuaoV#^yhj{cMr! zx=QNBl#XVZ+r!E0VWXm zHE_qiGSJDt2R?}ycK|;Cb6E} zr~h&bc4lJMaP{Cq8cw%iHtr(hc+Os3KSeqbM+(hH#oiMD1T=y5r0q1fXjFX@sUDja8cnlvE(`!tB4?;w9@}Qi}f87TobKNCE!hGK~5rq!EpI zVQ7M(?)EvI&2&1>Q1?xA=~XQfZ9)dC>Hx2r*0Y}fh81Kz>%o@?7FDf4>Wh{DAO(zl zcr$O{_=h)$e1`N$2@<>~h1ZMomjF>%DWskL>;V;o%Bts$3zbz_g;BMf<=YRS1r}g`pCC8+4e8T~RM)X= z`$0CGy+NpEC6jsB$A=w^#&3JZr;M+?EtGCM1bBU^OMy=c3eDsKIagu3ihSA_dDw|{ zukF|PnlWG_(HNEcP7)}om@|dh(Imrnu8>ajPEGCXSAY+rK1L+kM6j}M+7&QJ`emF{ fq8H`lW_;);9`znQ3JgsY00000NkvXXu0mjf>yCx# diff --git a/pyscope/bin/gui/theme/dark/radio-pressed.png b/pyscope/bin/gui/theme/dark/radio-pressed.png deleted file mode 100755 index 87bf8718e30763e2aabe9ba28f5ca3c5f735df48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 786 zcmV+t1MU2YP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10-s4l zK~y-6os`Q@6Hye0zk8=sQ&0wZ(~=kh1hpkZSSXJKT)0rsjYM}wf<*lvj3NF5u3U%@ zSh+x>COljMiJ&p+#)9}D8bc}aXqZMUw4E6jKxL*>5`W1o?wNDG`JH>`9#K|SX5o-f zTxfwNG=C!=Uj=Th5P-P8L@YE*bZU~--~^VDbe1BVz}%W6vD))GJqH0=sgrNE;~ zvfT>VZUv7jaVsJn+Ij)a-w1#Uso-t9VoMN!N0o%%j`C@!pUBq_0OVEHP<^(OlKNKG z6DB)1-*@gR(U~_Whnm}a@{gaf0kc%ehex-0cmEb^OCd}niD@KRTMF^xthySHO;G6CQcED|9a{jtvlg(U)4M4Oq?Jh_}6)efi#h zf;Lv>nIG&;Im-y#^sSH^XhQQh;PF+VY6So!^krh9FX&T~tOh5+G}21{0G-nL`_GUO Q^#A|>07*qoM6N<$g44ri2LJ#7 diff --git a/pyscope/bin/gui/theme/dark/radio-rest.png b/pyscope/bin/gui/theme/dark/radio-rest.png deleted file mode 100755 index a86b523f78ed0497d3d081a5cc9bf9b5c2c41f78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 830 zcmV-E1Ht@>P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10?SE6 zK~y-6jg?Jo3}G0@fA2fHvt`+^gOZl6MOsqQP+JL!6YU~2y;eyaiVNZ*Q3r`2ZXA3x zPJ|v2k+jRfK^$x~p(<(hLra^sl9?iHbhf)Q4%?lbZfW}@Gnx0FXMXQ9PoDQbqOh>g zCMSf)ix#Oui&WxYUy7=&0U)6dGx4P*k?EoOSp_Gl$T1sl_Nhm(1 zrpX1>r}_P*4I>^ytSt)J+i$%lZ*|Cl*~SnpXAh!(`S{Pz)QZcfy7>a36*y4y$9LM! z>_uu7Q3ocbE1L@jpm%+s<=i3El!*{&&9`bTTBH)GMJk;{RY2#H#yMebXxG#G_+(ZK zmhVIQ!&|exlhD)LG%qX@-OZ2ji0pbvcpFj;<~xD}#`-=l7&zA3fph^?Z8Z|xHP^zq zEhvPLGX>j5CX7*MZP~Cqyk)__W#KJYu6j&k1UcUK$q|w^Y>d_}7`Q1~OVS0L=xs+D zt+DJ57L?S~qZOCU3)70rD5pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10kug) zK~y-6t(9G?;xG_EPrq!N#21SNq5uDnDF~YSU=yP;w)-IMu3neBUiUnOFgcllVJ2#t zX5fh^ijY#GlwvlUk)|mCAq2elSZguHFbrRwl0IK3rKqZk5CUD-aX1_ZA>4!0l%gn@ z&*v-_3);5D81q^fMG;k1A%tMJ+u@x1^1JsQ?>)!kk+LkQ>zbk{&{|J1@p!1JiagKR zY&I`t&p78;uh$esK~+_cLZuW!2)5fTecyj88|nL=?RJY&3Mu7XIF`EK@86P*+-^5^ zyB*8rk|>IZrIdsa{;ABr;he*JkCc*FDMi61qWD-Sgy3?y5L;_eN_{MpQev$o?z)aF%Me2RSXP!L zS(e>}0Bze+*ELC!ye&(Tgu1TLS~Cp8U1*HK7{h9{dP|xl39Ho#V+__>;E!pewdQ<2 z(=-ibS$-=k%aW#PaL&=T?R5XpFIsDybJTUs^?Jox%jtCDcDqd(2_cYDl4TiM>nXF5 q=+z1UrjeBA`NImhTrSfJc*QS>7}QeWq3D4C0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10q03X zK~y-6t<}A5;y@IJ;TO9AB-;g*BgUbhpX-vi*fF2muFFbwfLk37#QisJX6Wm&{=jEFFs&8X}8%jv%+2m;bH zr6>yWJm+*eeFT#vK`F&#GNEajzhdWfyIry@8%spcTJ!Sqay52NUDs%>5fQ8?iYUwS+t@i( zRS||Emhbz#zP{cJ)^*L@-5r+WIG1;JON=qNuG@C`f27mt0ALwoa2)4m&~Y5J)>xa( z2Bp-^pi+vrw>PY^ED6K#W>7?^stT(p3Ovsv2!d~8K@i}&ZVMidNAf&pJRZ~U_pio! zy&h?rGM!F291d-li=tq)TJiAkK(E)k3VVEfWVu{YRTTiX=XpN>o}Qi<3yf5{xmI0c(r_+5xMSLPT&JhuLg)sqBv+w8J)vBEm4l o_kBFi13+v2;R`q(k6$YP2{rQD5mPw%UH||907*qoM6N<$f;>wMqyPW_ diff --git a/pyscope/bin/gui/theme/dark/radio-unsel-pressed.png b/pyscope/bin/gui/theme/dark/radio-unsel-pressed.png deleted file mode 100755 index 1534a969184e56e4f039e4fe143eb2897008bed7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmV-u0+;=XP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10rg2l zK~y-6rIlNA>L3(G7c`eDpfio=i~s+>dC5#BLR*rU^uZy=L~S3=_mm-<#lEo-`~7}k zCLsh$DYVvPSw@m10CZhP(=>SRan3Oe4_gs)pw^nAC;$i{(6%jo-vc0}Bu!KDJO`jG zOPq5PU~=zOQ4|y_*E%J=s-X_{iK<^BDg^Z7j5$oNqd1xhKZs(K2W zt8H7Vs+vSJ0=3o{W2o!;X2#W`dGGPwW36Sk+fBkBh5@A%Qfo~J;lZ;x>|UqS ziOc0OSqmZDfO(!jcvqI?OIXx>WuupQo+IDh-o`$QiaGmDbDO^JNs{DW+x)MD5CF)o z>yUEFT`U%V0_HYC2)eFAHcdmCrjwtW&E`+w+$K#^LI_Cj{dhN{j4@biU&5@llU@YK zvJCG%$>nn4av4XD# zY?fsRAvhk7qi0bM6FW+4jZ%s{&nHhn2w{8zp6LhE$PY75OJAA*0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10s2Wq zK~y-6t<}Mbs!$w&@h{zsk&-YAgCT2CNAN8gYggN*x4DngHexn2NH3A3Zp0vp78Sb4 zO?4Mi%&mI|n(nt0{CVVf{uW+dUQS#k%Q8X;gb)~pfvTziq-n}(wIT=tf*{~KYNS0;aV&|Too)`=UEEWq~*9Aa2zhpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10%1u+ zK~y-6ot4i|)Ib!+Kb@KFPg)y7Jdjmm!c9YrCr^+VkN&+}YYO-a*~FbvmTZ&gxC(lli>8X=`5%QD)w{R?&+ha^cJ z1zl7WMZkTUCX)$W*C7CcARvmOCqWnCTU%QML2!R!$8iY5kR(ag26(UE66_zMc3uE5 zzxhFVeoAw7Iq;3+n5wGKTBAJABM5^2)fct(;WNRzk4v`E?jFx}_bAVfnSDK4`X;5s z_kCQ~Mc7(hd(su?qQdu|@Ls(c#(5r|=OLt&j7Fn@L$H6i5o+b{9}H}^LWB^a@2W+q zo$-U<(esxBo9)se@iE%a&H%#p{;K9T*AH^%H$Mk9%M-TuS5=;$J_s%^P6oE7X=vLP zQP(w9RSg}kE-BBBH$%UDp}G1#u-mDpnx;W(O&rIATAqD9V*cw2fB&F=sQK?}%8Qe= zP&@VNy2crg$6XkPY;SLqrs+es|EIDnN6E(9$1n90000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10<}p* zK~y-6m6f}a(?}GCziwT+CHbOpuq2LPYc~W6_6>$A2;e0qTb_rE+J_*4DtH8-*pwln*aJ%`nNFu=iDJ!O3BH|3DfD6G)<4b-uDWEfIQEMqKF^}SS%KF zUAF}bA(%`i2T++#NbfyB-Mme3a${49<-gcZe`DUip))H0vMfUgfpdcI(_`-51{}e z*5pr~qJF#Y*G8j}&t58}&|3QsCDRF$hi4$f{})*ZrjO2$>Dbp?YfT)-8?aK!?@jML zxH8kj%oCEkr~W>UW0X?JAP9(}X#3#i?VpAY(!bvE*HIJ^h9Oc)>AMOb%zwEyILL2o zufKJvykh?~blBE%=>0*i_;PLRZp^@wL+=l=pFUh0yc=_Iaj^kgYiXLsKQQm#(6`O8 z_@nBqp?ddvdx*8PZMy{z<><_c^5t{*fgSFt?^(b4i@yF3*f`(ddb00(j4`a&>+Pu@ z-%`AIM&H&~W?H^@Mzhn>>-Czds!0pjeb|o zIsf{r)yhNnzyrJ}3aqu%bxo2a#Bsbam7ST_wk>VjQr9)c7>qFohaSwl;bYcyO&rH) mt&vjh%{=EEZQEWsOuqv99$IH#>fXfw0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10&Gb{ zK~y-6ot4jT(=ZgrKe4^UX_{4;;DQQ5;)Ia+apE?CIPwSYfOdc*5}LG0oI!9vpjAm% z$4*-(97aOBq*UyCbl&^;Iezc!U^<;P3@8W!q9`JYBBYcEApofB8t*+-RZ&$HO*5E| z8Un^~Op+vMt9v=P| zbW?4P_nth@X_^KB5XUiTn%)Y!2_KKg#BqFGaS#L~NkW>Yg9+ioCybsRqwd@XVD;-8 z_WV7|ub+FdX`14k!&-}qq6n?^;6)`b-!gjsdV8aLj|lHQqC7jrd_39uCZ$AcO&ErV z7Ccz#4d|vuFWwM7c-&iWdtXQ?QL5L@jGi9vgmy8{UiH=v4h~RCA%qaa{d#A*7p(5~ zW7;Vdd$a$g9pelj+S$LU)vxb+x$A+LmM7ZTzbSkEelOTweCQXnT3v&ys=_(f3s`>r zOnG*?6KX!aqZ;UC=N!xB5>-_d*4jbSF&|G@{rJM@*{klN)<3^tFFp)Hz4tihsH%!! zI-NG7(TJm?BeE>pd;EVX&vPy>FDc9Nx+|=;7-KNT+!|_(VX;_Xtp)zLThlZYMbZ6= z|1~3H3`J2;6h&wF-N@B-&1^Qqd(UdM+G+Hg^4@cGb;V+_U_PIBUc3cv;d!3ZUT7T0 zL{W5YDjRR!a=Gkc%Ce*^%ig9vlW3ZTvMjOI5{4mKYc|K;dyn_NgYSAwe*=@2Fh43d RT<-t?002ovPDHLkV1jvZPDlU% diff --git a/pyscope/bin/gui/theme/dark/scale-thumb-rest.png b/pyscope/bin/gui/theme/dark/scale-thumb-rest.png deleted file mode 100755 index f6571b9336e9fde5c8b92387bca71c4b0ede340b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 771 zcmV+e1N{7nP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10+2~W zK~y-6m6f}0)Ib!5zq$CfW*vvHh+>1p3s9w}gQwtC5aK0xfbmu4{7e=u+r9JUn+sp7@V-DWe^1-HUR717D2kp6A%@32!0Uq5r#ILanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-vZE{-7G896g;3H;Bs-V z3Rf``r|}i%@QEfR#lDRWik)3O%`1Mh+H6eHJZ7zopr08NrgdH?_b diff --git a/pyscope/bin/gui/theme/dark/scale-trough-vert.png b/pyscope/bin/gui/theme/dark/scale-trough-vert.png deleted file mode 100755 index 205fed89725e9c6399776707dff139f02916abc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(B@E{-7HKKbo$Pw?#jYb+z^;UtvYqx^#{;HXxUHx3v IIVCg!0NgxI_W%F@ diff --git a/pyscope/bin/gui/theme/dark/scroll-down.png b/pyscope/bin/gui/theme/dark/scroll-down.png deleted file mode 100755 index 4c0e24fa06c2478235e21ac90290d52cbe893c0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_G1(%kFERZ*H}=vaekL7T zsTuPK4U5vV2y0-MR<@)j{#s;mWYtHOBA<(qDxmJDYlVy1)&gck!oWZK} zC|dC1;VA7r-;YTYuQU{#o7@xrzE=Ey`5S%)T|KpL{*DuX&SLO%^>bP0l+XkKH`8Hy diff --git a/pyscope/bin/gui/theme/dark/scroll-hor-trough.png b/pyscope/bin/gui/theme/dark/scroll-hor-trough.png deleted file mode 100755 index 89d04035b472f791ab57dc0443a8b7f70b39d3e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W;!3HFgc;@~FQk(@Ik;M!Qe1}1p@p%4<6riAF ziEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0xbJzX3_G$yV+W9Y@~DAMro z|K6FZ8=@mlCfI9Lv+@fb^jcfYtFIK1uiRnTP%crxGlQF>*>{3S)9;+1j*iBZx*xaw zZ-^w%sd&HMB_%8ExWnoHYa7E8-p!FNIyawz>B9HFf(7Ym%cCdQ3r}oab*|`KzEJWU z6W`Apw7+rh4S0U%>pqEwnO`4U#I0K8_x)(yg6COj8^t*0WVrGqga}54u}sfO>)&c3 zICFJzp1?#`*7r+uZy%f*w9kpd|A1t{4((@q=P#WS#$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt*MqE{-7_Gjsbk@*Xk}VSRs7 zFnfpNDFzRdH3uZ_HK;8;D%1C*jdw{vzN_rIIuoH)YEsP b2Ojgjc&2X4#`4$+=mG{$S3j3^P6$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt%nyqq^H$w5`ut?h?T-$MEVZ9F z4@76D3tH}b6eqNF&gT<97Ot{2)}LCjl0|T7(5VYGyM!CekDXB}GGPc~xcmPdYv^6o V(3_U06oGa#c)I$ztaD0e0su$+QKSF> diff --git a/pyscope/bin/gui/theme/dark/scroll-up.png b/pyscope/bin/gui/theme/dark/scroll-up.png deleted file mode 100755 index 7ddba7fff7796e7f279467b5fc1acbfbe765ad53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_GX%tdV$$gfy*IINX}`O{#?QQcXpFYCW`F+o~!wk zErDU{HN~YRlT6sf(#rDIK7DV{_t;M9+MhK==d2la{=Rf(ox`*H!IzGmd1E2S*w$eo f@nJ@X{CDw5#j>p3*0Usmj$rU~^>bP0l+XkKK4()N diff --git a/pyscope/bin/gui/theme/dark/scroll-vert-thumb.png b/pyscope/bin/gui/theme/dark/scroll-vert-thumb.png deleted file mode 100755 index 572f33d8df6df17dd658503f9e26acc2760b2d42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrkx8<5=cZcP}F;wEaloG4buBjl9hU0$TW>Td&>S_rB+G1@j8y6VuNHddc2ZH1jRiJX#z+{X_J>NBmv4 zx7m70B>TuOD%P8_sQBHy=Z_CB&-}y?bV@8Ca+<;U&FBBH-o9@jy4KwA7|?+Xp00i_ I>zopr0OB2G#sB~S diff --git a/pyscope/bin/gui/theme/dark/scroll-vert-trough.png b/pyscope/bin/gui/theme/dark/scroll-vert-trough.png deleted file mode 100755 index c947ed1e9bc6585713c978ed6c50ba8c90921163..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343 zcmV-d0jU0oP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10OUzT zK~yM_?aMJr13?sr@$bEvC9qpq$zVS!dj$^>!Ah-j4KE^CrxAiVfOi;h444ZDgG~q) ziu-J~5HV!8_Ybdm{63*yjB(<wDnH~4<7hvg}o9CQA00f|I+auu4%ue}ZBC=A| zS%SaJUP5?`J8cjRPp9Y(oh5 pz#DMT-zV@SBDYP`+@zE?0N+L>O0OTY#%}-s002ovPDHLkV1jbciuM2i diff --git a/pyscope/bin/gui/theme/dark/separator.png b/pyscope/bin/gui/theme/dark/separator.png deleted file mode 100755 index 6e01f551a104e787194301b25e8d12128bb520f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}bl&H|6fVj#T-gc;Avs>lHuk|nMY zCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+>Bu@p`&AhH%VGzHt5ee|`q0)r`GU TeO=c8B^W$i{an^LB{Ts5>Ixx& diff --git a/pyscope/bin/gui/theme/dark/sizegrip.png b/pyscope/bin/gui/theme/dark/sizegrip.png deleted file mode 100755 index 7080c04c67807e08452264809cbacf35b89a0503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0TnIuba4!km|A<@H|v-IPs_vW zM}Hmco}=*Het~&k%Ix*smmD-!UOQ~OxM<$<_UXmtQQ0mm@%f4jp-ZPU%?$j{-Dk*f z;>`X<9Sc@5tl8SZVf0zEi{p49!-eYOcLPJe-Sgi#<&)K8#Wc0OpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10%}P_ zK~zYI#g<)fqCgaeA4ZHY4wVa>-=DddQ`#X-vvMiKRSeC_bIK(iFV`+b_-EK$IG*neZp68Thc{I;F2IhGlhGCGV zDNz*BG!1RrUKLOX!EiXlwrxDmW4GI}*=(q)>LAbf+(FlMab1@vipcZ)sz85|QVP%W zcz=IqyWRHR`wqIUi{m(~*K4%a-wFqiWf@gf@$m3~VHm`5{1Z@0$z(ENy0r?%Cf`=f?yW}0Z|n7gKFD0&(F_H zCX=Iu;y7lpSp0Ud|5Z{-JkMjbS|OyAXs!F72_a6x!G0W`o}Lgw+yt;F3bfWpDG^F3 zvMlR^^ZEQF+-JzyY<3esfHX}RkH?77XmoH?K@gl>WICPRf(?MOEU_#LVVWjQ)AWJv zLY$*ZaxazE8q+ipz}2_JHPkVQjz@T#|dK zq?FWkjmWYL$8q{V7>4~TeoAp16Ncf<0K3;k6h*;cFhEKPK-;z~7K@XR-9vqgZ36r5 znWjlm6nnEErR4tp{>!SH&1Oue)Bd(@w_8@L)y-BN>}M|s0^Z)<_N%UQGp_5Rl;ZXE z^}m<}0Mj&ie0(HHlHSquyDE-j(lo{QeUwt)4yTmD_x%Gn_{9$bj7B3o&qD}7nx>Rx zd2_?+I1X*wve|5?>-r$?3t%VPwi%Dd*tU&nnm4ihT5F1;AkXtJPP)?%6C*4`N&^#H P00000NkvXXu0mjf=LlB> diff --git a/pyscope/bin/gui/theme/dark/switch-off-hover.png b/pyscope/bin/gui/theme/dark/switch-off-hover.png deleted file mode 100755 index 5a136bd3f4c6fd280134f300e5cf50136e217b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 945 zcmV;i15W&jP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H113pPa zK~zYIy_QW&a$6XNuf023+x%!)<`lSP_n@SJa?RGpK4=$Gr zlgWf$ujl3ErB~^<)z{ZYr_-U)XizSf$>;M_tJR*h!biZ-(NR`bR_O2V=j!T;N~J=* zUg!DwSrfQ7>2x|Q77I?N6Q9pVyWJ+4Od`wj?*ZrL<_H7=93LNZb8~~L_H3@^5(I&X zi3x(iAZKT1TwY$j@BQ_*n4h0#YHEu8{e2Wg!D6ux4u|pk{fvx^03gdUnM{U6B0;@g z*9=fqRj#kEk!6|n^>sv14n1@7T4j2A8l6tp)1cXGvbVSQ{$FY-gTcVs+8XhAoLa3$pV@5wS*z6m2nK_c zN+pWLBB@l0Znw+8zyOjY(d~A*y}c!sO0m1UOR-qg9L{Jovazv2I-Mq)%>p0d*6i#o zi;Ih#oSaZ76#i)3a=YCuEiG|+diu-5`xIEMRx8WP%Se($KA)#tE>o}9>2x}pLh1E- zhK7c4I-Pht9$Kvy$z<}=*!l$cCcE9vj~_p9xm=jdX3ZmiyWRdJ^55Ov^{o9H?WJ3w Tsq6+i00000NkvXXu0mjfS6H?B diff --git a/pyscope/bin/gui/theme/dark/switch-off-pressed.png b/pyscope/bin/gui/theme/dark/switch-off-pressed.png deleted file mode 100755 index 040e2ea30c96c8fcf103abdb749a706ec9b01975..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 963 zcmV;!13dhRP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H115ims zK~zYIy_QW&GHn=!562|6Mq7oJMv;@!rcWw2(MEI;En4&sY}JqGPecoULIlw&anT1A zMr5sGWOA}n3n9p68q2_W7cV`~lkdw@%5$y;=Hi%RxVgDbi_hmXX%N5PPcRsyzrP<* z6mdG8SS*%Cqu-8cniz(GuIpT1Uz5#dxx2e-IBWS`u&1YoNF+i>M+b_caCdjd^Yb&+ zY8BIL_%Gj%#bUwXa3G2zeSLjMlEll)3#n9!QmORcf|HYz%+AhoczB4SC@mBE6X9Sm zNH`qk;NXC>v$J3C+CCnpr>7YoA7^7@15ML#yWPZMF-Av6@pwG6x3_-*zrDS2e}B)# z#RbV^l1ioWBSF(Nk!6`usl@#JJc1x_dV2b=ywB$|2L=XMSXf|la}!Fu)up8+j*gBfm&=61VOCaFu-R%#UBgSYBe-XV{UGaOeTX62n49rYUJ~I+-^7Vc>Kq=)nc)*yu6Id z<@&K8fU2t0YBd6Z0K)L_Fp8o85R1jWb*zoza5#uYqfIdYuCA^Kg+d5kulLuHjE#*o z4Q>^uP$z6k^_Fl<(?rlTjgF2E z0LtZZOTew*bar;4>pDWIRKn-;0g%aLng#=$pPx6)@cDcc3I&AA%S$9l0w9@8Qms~- z0vm?G&dyHLf|4X5%Q8YPm&0bWF)%PdrBWf0Nc?yQo2E%3kwDY5rsM_(2eDeM)~!$|#LUbL`}_N!F6)=V>h*ewL?Vcyh@vRm z+}xn+I)-7iw6HpzPFyY*e!m|{l6Zc8CY4I@@bK_?_WvsX(9qDQ$}b2)O9bonI)-7O lX&U)_o@_Qtp-^Zz`wvZHY;hpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10}M$- zK~zYIz1Gi5Dr+3a@welhTr?v^t1Pq0q@<=*SWBZIqeZJATn7Ce=^xQbXjz-qB^cx` z`mqTMG0Y#CQjj3ygycxx#ktz);PH03&iSl{`96H-#RJbXU(4v|Xp;_cI-N{SP2qC6 zu-om3qR7X`N2jI#If5Wy7zUcAk$2i3I6%no6ZY zyJDwTc@^!a=&E-rF%azZMVqS@@Yxjm1?VqtoE znm{1H`T03lS6A)YU#*9Qg#~73XNg22=(_$#V68NpO_IqZg+hT~Fo+-sTwGjy0w*RW zn4h0#e}BJ?6Gf3gAb{WR$K`SXkk99d$Kyn!Q4GWAIaJqm4h|03-rnZr<%L`>hh=PR zthuwZLo^yClgR)uG&Dpg6hcvyj*T*z4B>E?a=F}dB!HqQtgWpPi9`@Q9uJzPwQ-^- zcE!Pu9pByEMHEF-fZ1%8YPE{n?MCo=y(AKeb}O zpzAu4B(-1u?w8XkkB^ULxB!r48C};AN~O}5LveO?_U$I|c-#cqkBpCxQ!Ew{lF1~h zs&0uh*$ot7NlT?(gq;dV1=(_HUD! VO;&6PK$8Ff002ovPDHLkV1k-`qrU(E diff --git a/pyscope/bin/gui/theme/dark/switch-on-disabled.png b/pyscope/bin/gui/theme/dark/switch-on-disabled.png deleted file mode 100755 index c0d67c567752d96e322b3f3dcea9cd95bbf8efec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 623 zcmV-#0+9WQP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10sKis zK~zYI&6cr}qCgZy4^0bI(;cV~5I9Cyg@2-d?4OWmFp&xE7|>HHC>vO^qEqh7j=i-v z;$2=9r=TD2dZW>(;YOb4p_D>vO%Mb~DG@^4w*5$_(}}vSIUEj@Wr?+xrny5w-WpnK z;y6aiyIW6{5P~2G2!a5uHC0t%jG-*cZr|m?^E~1>CP@yac0aUA1$-aTLx zMfCgqe-3u5e!ou?MQttN{8?SY`MytN}R$|;=h`w!vZe7t6}8NTng zo|F=$6hdq53=Sa6vWIXNMNveSWgSl`g$TpY6&nDP$>htuX_|KGFbombuhoUVGkk-= zpi@gJdG2~}AW_$~(+6y~+b{RNzrT0tx~>t2!{OOX9W9s3zwX`d_lVow_iW{Pjxpws zea0B_Jnwkc+B0BPRj&9r9*-;*i-(YxANA>UYQ3teu-4*@Mx#b+O_C(eAmN-|rqd~D znzqw=z3Q&v0M_gEZ$N-Jj{l3W0O!FfiUPPwGsc*2O`I!jx7*HfXoFwVG!#X_ZnqpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H111w2I zK~zYIwU$k66lEC4fA2dVrQ7agw-gIyT5W*1y=eJJ)FQE!EFLff1aFlCM!eO=)B^`2 z37m)^+5>4w2pSU-KGNXLqAew+=>=8~S`LcAPJvo*pl!BYXJ=jxv)jZiUmIutr^%cD zfAX6r&-=_HY};0pNN5Jh^_?U)yh*I-Ma*OavZ}h&_P-S5=K0s1BKy~M+{`d;W*Fsr zOMIdfux?YDrrkZ5^^K7NJx#g0lQ`#(^Uu{E9t|j@hP|hUmFcdE0BAy>OVEWti117) z$SZ~CD}sV=>e9DNoIj2Vd;s*~3n~RC5SSVSGxzv)pr6UHOUz9E0f1$%#eOACYS(^D ztDXfvpdsCbQi_Rl$HKgA+p2C;nl&FDsR&%PE^w|~Wb|k^UUu%0J|k93dv721>pRH$ zVJ&|@(TjWSN2JgU?032&hXbh6z`1gfk;Csl77oD6&M|UmAI?wTQKd-$n&15xp&Q8L z`cA4=*pb6&Lg3HbWAtcu*lbUeQjC7yO)z^GDZs36B-z%1Om5s7IUIm4VSJ$fiIppr zm!0MMz)1`l-l9ZXC$grwwK9N0;EWBHopZ(p(F=ez&$l9VYkAac07wC|xBe_UH+$;_ zqR|%Nq>$c&=}5tfQb2rFOWC=2V++MAm2>|_W^ay13RYBJ zDePCbm7TL+dy9M!B4%&?hIBK-QL_Qy`HIx8{TQ*@XU>>47Ogu!z$*aW|78T}W`@X3 zJCOqhfM(Ux-rEPy(u+dS{z)GqUPqw9<-yHdMk+s#bK%R#0Ra~LfYn=e@W!X7FkAn3UGJ6|C*_xGeGIOwfiGhZhJ3sNC@YmAqBb=i?b~# ze1I1y3T?-o!EbObe6_f4Em>HT8@AH4r>7$S=O-yQ?J#lf3*5}mf6kWxLUHBqAko%I ztT~05Y(QHUixkLT$QD-qaqee^aeuyy^7Bi44*@HzBP+<;3C;ij002ovPDHLkV1m(s Bw9^0p diff --git a/pyscope/bin/gui/theme/dark/switch-on-pressed.png b/pyscope/bin/gui/theme/dark/switch-on-pressed.png deleted file mode 100755 index 00e87c68a1b37cde51142d964e4ac1374a69de4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 936 zcmV;Z16TZsP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H112suR zK~zYIt(HG*6K5F4f8Td!J6Aio1W3V-qk?h^I*HjWdzO86QJ=?vgrD z3)sA|kHpSF!YymG0=-Y=XOGB_oMQUsrMCkLX%HXyh7bF8YkQ~=U74|JiwhVE?zey7LD<>#6SI< z&i=g^jVq{lKCN4JgHq(qpA23ynM|tYjeV@$vtJvWWlC~aGF(4#0Jk*v-g?uD(!K8x ziJl#lTpxh@!v}H3$B@D>N%ViM4K88|a#u2BkNvnTH~_aeOZMmxg=@bPF(m+PyS_u1 zA*7v36J8nD0w;vPd-0s>Ck}vGG92D5rMPx{Kih`*N#8$U2{$FKh3OSeiO9NV< ziVulRTkGnP*t8Y5x~&u+-bFeSSsiQuxW1yZe=nvL{a^QotQegGd#L!quy$@=MNZ$k zOnIuH1<+Rt*|>u4eTRaL_`gU9y1ze+h_;|q5FVU~EE3-%pE<1s5TNY&Bzks`9y+>g zCqq_@^w2Tlo3~T;0{3#~Pog{*qp7Lsr_$qpFdCvnKl)e;r0Xl}_6|C>eTfl{;1=g_ zOSAZHWwFL=_yD`TgZ10KqUVPrG_LDn-V4^=lhI$896JZp#I3}@Agz60Xn}+vT$zTz z5Tg3U-;7&If#(NtYhkoJ8U2+)=FDQ;S`t|8RGPM(-)h^Rm#gwrf!u|kamL5qIKKoC zs*%5|7rUz$t8D`zdlg2+(hB6w7pXXt6m$1*CbCSAXQAra>v{!PR4Lbkib03~0000< KMNUMnLSTZP*s$RM diff --git a/pyscope/bin/gui/theme/dark/switch-on-rest.png b/pyscope/bin/gui/theme/dark/switch-on-rest.png deleted file mode 100755 index 52a19ea658446caede4fb410192b05bdc78353ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 859 zcmV-h1ElpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10_aIZ zK~zYIz1B@g6k#04@$d7@zUAtYPFj}YYA>1@NRowg=#*<2gjel_Adl4{2$aG@MMpc8 zc&qGK6qYU>f(XK;pce@(Enl$OXjYR}cGlgQ4(rRhnhH7nU>N4%cla=G&tK%_<(Z^J z*zBm4KGc0a+}>R{)Lax-T59c|5|2y~)xHt_(u)@GqXoJ#W7CUzA{DrysupiU6V9Cc zH3hAdNN|MVyVv;n^6}C@Ar%UaH?!sFng0q)&DYL$hHhWQG~xg#9*?I5z;@YR^sde9EMKc{Wb==?hzX71t4o{8QW^= zDXD8DsyLaRk#0WP$YkFuw4N?RzPHfabK@DVjRltB%+MI!=Z>I#e7j^vEiT8`+KEuN z%pvt3qr5(Q5UE!BEW?SJJGUGTKn)8c~bOSL~|A<&@MlqQ~dgLa&}8wVo~_ z!C^~)rU_BSiLbSDS>)`GT9*!HZ(t-if)?3h|7Xe{*!B{s45B2apG5)R_PP002ovPDHLkV1jPWmj3_% diff --git a/pyscope/bin/gui/theme/dark/tab-hover.png b/pyscope/bin/gui/theme/dark/tab-hover.png deleted file mode 100755 index 43a113b35c4019897390acd452cda5aab135512a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprUD>E{-7)t#7Y+3msPAVGc;# z;s37Q{Pe47lYDL&d7eBJb;zmzyup;*+byjdr#|JZU|BHNcbBz_Ko>H2y85}S Ib4q9e0C6#5TmS$7 diff --git a/pyscope/bin/gui/theme/dark/tab-rest.png b/pyscope/bin/gui/theme/dark/tab-rest.png deleted file mode 100755 index 9753e067c6ba2e08b90d28da491836864a47eedb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdv$07srr@*0<*j85siF&&FxvXEnW$OKlEFe7>G-FC};|YBqbt`|hK~z4{J48!Zy<-k8&PDcU^mzkp!_ zBToVYv&4ah=M3w9NZ!n@Wq3XR=%navucin+t2}1nJDu^K?oyFBFaILBy_Of5fc|3e MboFyt=akR{0ByQ@3jhEB diff --git a/pyscope/bin/gui/theme/dark/treeheading-hover.png b/pyscope/bin/gui/theme/dark/treeheading-hover.png deleted file mode 100755 index beaaf1353fe62c3607118cbabce26b78ec5ceea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt)?TT^vI!df#5%$kl8h;`Xpu zHGG23uHXOGt2;GG>|ECD+v?Q5YH7DU%N#pDG3DC(>2(I*e--e(w_VlMQPcWlPUN&! z0j9KVhtFBw+NS#akJWv@(3iXV5+w|bwHV(2ReZB|SHuA}`JMlkUoG8xSgB~2#I{#L z5p%EEy=lw;(e|G${^NWN?J(8}LAzK)1)o*kdnoZ(^=g`1k6^_anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-oKT^vI!df#4g>}xjQakwb| z#>4Ru$DRKzj7t+HM=lTxIUB+ha<*;%DK-6b#}>Z-eM5jw!N`qgXha3kZS z9%&gT3)7d?)r)to`FoM~{s-Rw(lOb#dz$OB_ZLe=Y@gH-(-u0la-R8)DNU;ut&-QT z(>m7C5#SXm&sb1ibZ*I%jN9KtYanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt<;~T^vI!df!ex*wtbnz`8nF zL{WV2@BjPNJDQ~C-#lzkC^4h?v0n8?zs3otvYmFVeZA|5{DaSjs+~Bdq;}4nJ88+3 z4nw0pgcsqI&hao}L|^sv&cRlB#1*Rw@{LGIBe zotxkPZuu~`_WsIMuLNe_4EMb6srmGK;q+Yj9Y?qS5c@y-$JhS&hg+XpG?c~`zN@Nu zE^~U5%%jX%pN^TIJgYOmdY4UuN?YQBb+6aE3EQli_uTUPSDVi&PjA;X%g1a>RN*`` R;|9=Y44$rjF6*2UngA+#iNF8= diff --git a/pyscope/bin/gui/theme/light.tcl b/pyscope/bin/gui/theme/light.tcl deleted file mode 100755 index 22027389..00000000 --- a/pyscope/bin/gui/theme/light.tcl +++ /dev/null @@ -1,489 +0,0 @@ -# Copyright © 2021 rdbende - -# A stunning light theme for ttk based on Microsoft's Sun Valley visual style - -package require Tk 8.6 - -namespace eval ttk::theme::sun-valley-light { - variable version 1.0 - package provide ttk::theme::sun-valley-light $version - - ttk::style theme create sun-valley-light -parent clam -settings { - proc load_images {imgdir} { - variable images - foreach file [glob -directory $imgdir *.png] { - set images([file tail [file rootname $file]]) \ - [image create photo -file $file -format png] - } - } - - load_images [file join [file dirname [info script]] light] - - array set colors { - -fg "#202020" - -bg "#fafafa" - -disabledfg "#a0a0a0" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style layout TButton { - Button.button -children { - Button.padding -children { - Button.label -side left -expand 1 - } - } - } - - ttk::style layout Toolbutton { - Toolbutton.button -children { - Toolbutton.padding -children { - Toolbutton.label -side left -expand 1 - } - } - } - - ttk::style layout TMenubutton { - Menubutton.button -children { - Menubutton.padding -children { - Menubutton.label -side left -expand 1 - Menubutton.indicator -side right -sticky nsew - } - } - } - - ttk::style layout TOptionMenu { - OptionMenu.button -children { - OptionMenu.padding -children { - OptionMenu.label -side left -expand 1 - OptionMenu.indicator -side right -sticky nsew - } - } - } - - ttk::style layout Accent.TButton { - AccentButton.button -children { - AccentButton.padding -children { - AccentButton.label -side left -expand 1 - } - } - } - - ttk::style layout TCheckbutton { - Checkbutton.button -children { - Checkbutton.padding -children { - Checkbutton.indicator -side left - Checkbutton.label -side right -expand 1 - } - } - } - - ttk::style layout Switch.TCheckbutton { - Switch.button -children { - Switch.padding -children { - Switch.indicator -side left - Switch.label -side right -expand 1 - } - } - } - - ttk::style layout Toggle.TButton { - ToggleButton.button -children { - ToggleButton.padding -children { - ToggleButton.label -side left -expand 1 - } - } - } - - ttk::style layout TRadiobutton { - Radiobutton.button -children { - Radiobutton.padding -children { - Radiobutton.indicator -side left - Radiobutton.label -side right -expand 1 - } - } - } - - ttk::style layout Vertical.TScrollbar { - Vertical.Scrollbar.trough -sticky ns -children { - Vertical.Scrollbar.uparrow -side top - Vertical.Scrollbar.downarrow -side bottom - Vertical.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout Horizontal.TScrollbar { - Horizontal.Scrollbar.trough -sticky ew -children { - Horizontal.Scrollbar.leftarrow -side left - Horizontal.Scrollbar.rightarrow -side right - Horizontal.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout TSeparator { - TSeparator.separator -sticky nsew - } - - ttk::style layout TCombobox { - Combobox.field -sticky nsew -children { - Combobox.padding -expand 1 -sticky nsew -children { - Combobox.textarea -sticky nsew - } - } - null -side right -sticky ns -children { - Combobox.arrow -sticky nsew - } - } - - ttk::style layout TSpinbox { - Spinbox.field -sticky nsew -children { - Spinbox.padding -expand 1 -sticky nsew -children { - Spinbox.textarea -sticky nsew - } - - } - null -side right -sticky nsew -children { - Spinbox.uparrow -side left -sticky nsew - Spinbox.downarrow -side right -sticky nsew - } - } - - ttk::style layout Card.TFrame { - Card.field { - Card.padding -expand 1 - } - } - - ttk::style layout TLabelframe { - Labelframe.border { - Labelframe.padding -expand 1 -children { - Labelframe.label -side left - } - } - } - - ttk::style layout TNotebook { - Notebook.border -children { - TNotebook.Tab -expand 1 - Notebook.client -sticky nsew - } - } - - ttk::style layout TNotebook.Tab { - Notebook.tab -expand 1 -children { - Notebook.padding -expand 1 -sticky nsew -children { - Notebook.image -side left -sticky w - Notebook.text -side right -expand 1 - } - } - } - - ttk::style layout Treeview.Item { - Treeitem.padding -sticky nsew -children { - Treeitem.image -side left -sticky {} - Treeitem.indicator -side left -sticky {} - Treeitem.text -side left -sticky {} - } - } - - # Button - ttk::style configure TButton -padding {8 4} -anchor center -foreground $colors(-fg) - - ttk::style map TButton -foreground \ - [list disabled #a2a2a2 \ - pressed #636363 \ - active #1a1a1a] - - ttk::style element create Button.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-disabled) \ - disabled $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Toolbutton - ttk::style configure Toolbutton -padding {8 4} -anchor center - - ttk::style element create Toolbutton.button image \ - [list $images(empty) \ - {selected disabled} $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Menubutton - ttk::style configure TMenubutton -padding {8 4 0 4} - - ttk::style element create Menubutton.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create Menubutton.indicator image $images(arrow-down) -width 28 -sticky {} - - # OptionMenu - ttk::style configure TOptionMenu -padding {8 4 0 4} - - ttk::style element create OptionMenu.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create OptionMenu.indicator image $images(arrow-down) -width 28 -sticky {} - - # Accent.TButton - ttk::style configure Accent.TButton -padding {8 4} -anchor center -foreground #ffffff - - ttk::style map Accent.TButton -foreground \ - [list disabled #ffffff \ - pressed #c1d8ee] - - ttk::style element create AccentButton.button image \ - [list $images(button-accent-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-accent-disabled) \ - selected $images(button-accent-rest) \ - pressed $images(button-accent-pressed) \ - active $images(button-accent-hover) \ - ] -border 4 -sticky nsew - - # Checkbutton - ttk::style configure TCheckbutton -padding 4 - - ttk::style element create Checkbutton.indicator image \ - [list $images(check-unsel-rest) \ - {alternate disabled} $images(check-tri-disabled) \ - {selected disabled} $images(check-disabled) \ - disabled $images(check-unsel-disabled) \ - {pressed alternate} $images(check-tri-hover) \ - {active alternate} $images(check-tri-hover) \ - alternate $images(check-tri-rest) \ - {pressed selected} $images(check-hover) \ - {active selected} $images(check-hover) \ - selected $images(check-rest) \ - {pressed !selected} $images(check-unsel-pressed) \ - active $images(check-unsel-hover) \ - ] -width 26 -sticky w - - # Switch.TCheckbutton - ttk::style element create Switch.indicator image \ - [list $images(switch-off-rest) \ - {selected disabled} $images(switch-on-disabled) \ - disabled $images(switch-off-disabled) \ - {pressed selected} $images(switch-on-pressed) \ - {active selected} $images(switch-on-hover) \ - selected $images(switch-on-rest) \ - {pressed !selected} $images(switch-off-pressed) \ - active $images(switch-off-hover) \ - ] -width 46 -sticky w - - # Toggle.TButton - ttk::style configure Toggle.TButton -padding {8 4 8 4} -anchor center -foreground $colors(-fg) - - ttk::style map Toggle.TButton -foreground \ - [list {selected disabled} #ffffff \ - {selected pressed} #636363 \ - selected #ffffff \ - pressed #c1d8ee \ - disabled #a2a2a2 \ - active #1a1a1a - ] - - ttk::style element create ToggleButton.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-disabled) \ - {pressed selected} $images(button-rest) \ - {active selected} $images(button-accent-hover) \ - selected $images(button-accent-rest) \ - {pressed !selected} $images(button-accent-rest) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Radiobutton - ttk::style configure TRadiobutton -padding 4 - - ttk::style element create Radiobutton.indicator image \ - [list $images(radio-unsel-rest) \ - {selected disabled} $images(radio-disabled) \ - disabled $images(radio-unsel-disabled) \ - {pressed selected} $images(radio-pressed) \ - {active selected} $images(radio-hover) \ - selected $images(radio-rest) \ - {pressed !selected} $images(radio-unsel-pressed) \ - active $images(radio-unsel-hover) \ - ] -width 26 -sticky w - - # Scrollbar - ttk::style element create Horizontal.Scrollbar.trough image $images(scroll-hor-trough) -sticky ew -border 6 - ttk::style element create Horizontal.Scrollbar.thumb image $images(scroll-hor-thumb) -sticky ew -border 3 - - ttk::style element create Horizontal.Scrollbar.rightarrow image $images(scroll-right) -sticky {} -width 12 - ttk::style element create Horizontal.Scrollbar.leftarrow image $images(scroll-left) -sticky {} -width 12 - - ttk::style element create Vertical.Scrollbar.trough image $images(scroll-vert-trough) -sticky ns -border 6 - ttk::style element create Vertical.Scrollbar.thumb image $images(scroll-vert-thumb) -sticky ns -border 3 - - ttk::style element create Vertical.Scrollbar.uparrow image $images(scroll-up) -sticky {} -height 12 - ttk::style element create Vertical.Scrollbar.downarrow image $images(scroll-down) -sticky {} -height 12 - - # Scale - ttk::style element create Horizontal.Scale.trough image $images(scale-trough-hor) \ - -border 5 -padding 0 - - ttk::style element create Vertical.Scale.trough image $images(scale-trough-vert) \ - -border 5 -padding 0 - - ttk::style element create Scale.slider \ - image [list $images(scale-thumb-rest) \ - disabled $images(scale-thumb-disabled) \ - pressed $images(scale-thumb-pressed) \ - active $images(scale-thumb-hover) \ - ] -sticky {} - - # Progressbar - ttk::style element create Horizontal.Progressbar.trough image $images(progress-trough-hor) \ - -border 1 -sticky ew - - ttk::style element create Horizontal.Progressbar.pbar image $images(progress-pbar-hor) \ - -border 2 -sticky ew - - ttk::style element create Vertical.Progressbar.trough image $images(progress-trough-vert) \ - -border 1 -sticky ns - - ttk::style element create Vertical.Progressbar.pbar image $images(progress-pbar-vert) \ - -border 2 -sticky ns - - # Entry - ttk::style configure TEntry -foreground $colors(-fg) - - ttk::style map TEntry -foreground \ - [list disabled #0a0a0a \ - pressed #636363 \ - active #626262 - ] - - ttk::style element create Entry.field \ - image [list $images(entry-rest) \ - {focus hover !invalid} $images(entry-focus) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - {focus !invalid} $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding 8 -sticky nsew - - # Combobox - ttk::style configure TCombobox -foreground $colors(-fg) - - ttk::style configure ComboboxPopdownFrame -borderwidth 1 -relief solid - - ttk::style map TCombobox -foreground \ - [list disabled #0a0a0a \ - pressed #636363 \ - active #626262 - ] - - ttk::style map TCombobox -selectbackground [list \ - {readonly hover} $colors(-selectbg) \ - {readonly focus} $colors(-selectbg) \ - ] -selectforeground [list \ - {readonly hover} $colors(-selectfg) \ - {readonly focus} $colors(-selectfg) \ - ] - - ttk::style element create Combobox.field \ - image [list $images(entry-rest) \ - {readonly disabled} $images(button-disabled) \ - {readonly pressed} $images(button-pressed) \ - {readonly hover} $images(button-hover) \ - readonly $images(button-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 28 8} - - ttk::style element create Combobox.arrow image $images(arrow-down) -width 35 -sticky {} - - # Spinbox - ttk::style configure TSpinbox -foreground $colors(-fg) - - ttk::style map TSpinbox -foreground \ - [list disabled #0a0a0a \ - pressed #636363 \ - active #626262 - ] - - ttk::style element create Spinbox.field \ - image [list $images(entry-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 54 8} -sticky nsew - - ttk::style element create Spinbox.uparrow image $images(arrow-up) -width 35 -sticky {} - ttk::style element create Spinbox.downarrow image $images(arrow-down) -width 35 -sticky {} - - # Sizegrip - ttk::style element create Sizegrip.sizegrip image $images(sizegrip) \ - -sticky nsew - - # Separator - ttk::style element create TSeparator.separator image $images(separator) - - # Card - ttk::style element create Card.field image $images(card) \ - -border 10 -padding 4 -sticky nsew - - # Labelframe - ttk::style element create Labelframe.border image $images(card) \ - -border 5 -padding 4 -sticky nsew - - # Notebook - ttk::style configure TNotebook -padding 1 - - ttk::style element create Notebook.border \ - image $images(notebook-border) -border 5 -padding 5 - - ttk::style element create Notebook.client image $images(notebook) - - ttk::style element create Notebook.tab \ - image [list $images(tab-rest) \ - selected $images(tab-selected) \ - active $images(tab-hover) \ - ] -border 13 -padding {16 14 16 6} -height 32 - - # Treeview - ttk::style element create Treeview.field image $images(card) \ - -border 5 - - ttk::style element create Treeheading.cell \ - image [list $images(treeheading-rest) \ - pressed $images(treeheading-pressed) \ - active $images(treeheading-hover) - ] -border 5 -padding 15 -sticky nsew - - ttk::style element create Treeitem.indicator \ - image [list $images(arrow-right) \ - user2 $images(empty) \ - user1 $images(arrow-down) \ - ] -width 26 -sticky {} - - ttk::style configure Treeview -foregound #1a1a1a -background $colors(-bg) -rowheight [expr {[font metrics font -linespace] + 2}] - ttk::style map Treeview \ - -background [list selected #f0f0f0] \ - -foreground [list selected #191919] - - # Panedwindow - # Insane hack to remove clam's ugly sash - ttk::style configure Sash -gripcount 0 - } -} diff --git a/pyscope/bin/gui/theme/light/arrow-down.png b/pyscope/bin/gui/theme/light/arrow-down.png deleted file mode 100755 index 45fc33bd33a3d6408eefb367ff285967dec941dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt+o^T^vI=X6E)9avgFIaQ*+r z!C}H3RhDXP?X@a%7o?c`>Lp~pWn{UT`*xCX1*4~z{{z-d9P7^Zg*qI$zwi5xC0qAt zE&X!;{#dF%Zo$S4(vDYsPIC;D1Hkdc)8n0iwm6_qf_s~F*$tJ$~3=j6+H}U-}b(4MN XIqN;$bHdI5oy*|q>gTe~DWM4f<}hm{ diff --git a/pyscope/bin/gui/theme/light/arrow-right.png b/pyscope/bin/gui/theme/light/arrow-right.png deleted file mode 100755 index 6461ffc94e88836ac40661d9ac943ce63592464e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1g!3HF2ETp?{#H&-P9X!wXP=*+-)~}Kvgh!TBPQ1m9ZGsT(>T4(;84osNt6Ca zNJ@6s|NZs!D37pB{l7oH$;ruXX=%@j)Yx|;L~N zjo*-Pkp0<}DanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-!OT^vI=X2$jzavgFIaQ%NK ztV!SlgPf9Y)O&%T$OsjkNzxTu-l{oGtt*8Dj+{Eh%)%7svP~d)_+WY2W|= diff --git a/pyscope/bin/gui/theme/light/button-accent-disabled.png b/pyscope/bin/gui/theme/light/button-accent-disabled.png deleted file mode 100755 index c3845a54e8991cd4b466606b5c50396360cffc5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+Mu7sn8b-nUmb@-`cYxL)l3 z@4jf0`i483|LgE1?zDAsoU5*vmT)?7R3h-|=^3jg1tnSNEtt|Y^KU7mB1@20&*Kx^7S4|MmIhb< Q2D+2M)78&qol`;+0E^6Rj{pDw diff --git a/pyscope/bin/gui/theme/light/button-accent-hover.png b/pyscope/bin/gui/theme/light/button-accent-hover.png deleted file mode 100755 index 054d56c0d2e22cc654f0dd1586e1dd8842f18df0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 374 zcmV-+0g3*JP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Ru@y zK~y-6?a!@C17QGw@#lSyefY5%78DUCR#6w$O^XJzMZsvX*aV~9DklF7!DbK^L`;It zg`u*AyK#4W-@P9OlVI>T<2`@C4<4bl_HV^(UPP=cS(>W^v{&mKb(%CARmAfk z^aqzud`1dw9M1bppcH~~NwQckb9J~uAh!0-9{OV@Q%g4LI}aG^QBzNt7-Y8>p>4L! z-_q7{{~97=WpOUIhjS1si_A~`tN;tJ0RIG5(TM+m@XQE}Y%K=_HqH;4l=eyqdAzgE z{Nw|Xj=-7hk~4^8$bxz2WOoyxwe~NA5$D&p+&%UfCn}Ryo2{_jTH|!T!&0Nh7iiII U9<_Fs3jhEB07*qoM6N<$f?4UA%K!iX diff --git a/pyscope/bin/gui/theme/light/button-accent-pressed.png b/pyscope/bin/gui/theme/light/button-accent-pressed.png deleted file mode 100755 index 9da8b53613ea07818317a7db21a0c144e0ed5e5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367 zcmV-#0g(QQP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Q^Zr zK~y-6?bR_#13?sr@$b#yBq_a(-U>9-@8;d|{kKh%&KuRx?1852h3#-*& zA&R(JnQ>>wN>H$Hv+aHd`0;<$6KkzcUkr=oBdc^nZXH$Xv;xM%nAxPy!LCN`pWglX z)g8}SfpsO``%0jQphKY-x41k#BuGxqE^gO3rl_{;XGQ7xEK1%!3e+myVBPlEcgB@m zrx_}@j*3_QnfH(zhpJEgt^pgc0sjSdS^*+b9}?*505u-Q=&(K*g^Ht5f|^bG^tuu4 zNTIe^szhjqiv1nUd^$j^wf^DNuvo6SNi%X&@dfCB<59wVI$$r3_yWgoS4rv;lwtq? N002ovPDHLkV1nu-o45b~ diff --git a/pyscope/bin/gui/theme/light/button-accent-rest.png b/pyscope/bin/gui/theme/light/button-accent-rest.png deleted file mode 100755 index 3b7959a323100057dddfa4edd26e18399d1c8c03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmV-`0e}99P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10S!q+ zK~y-6?bN?Y!%!H8@h7K=fl@+21#u88>MH6W=;-1iI4F1p?rwrhCvo)(yavI|O^_}k zZVsX1(7}JjriPGna+1&4PNrG=T)>Za`98-OBi)aLi<>T;Fe28LGBtdct>q@Cdo5bC zb)4tkK=v;md5bjK3VBh=qo%-j6?4H9*C*@L;+4bp!|Ra52qNV$01?P?L1zWuu|ajh zh%_q?jh0&BJ{+K8Z7If?Hc}zNPij%sxBYDbPQVHHFR-pMlvDkdl>?2MMQtv4czIG8 z^lZ%bqK`T|Sf!c2})J? ee!R>4Ou#Qx(r!GF=i%T00000mmtT}V`<;yxP|*QT7sn8b-nZuuX0;lKxL%yQ zxQxh>Y|Br@x#!%7FzYmmtT}V`<;yxP|bP0l+XkK&S7+& diff --git a/pyscope/bin/gui/theme/light/button-pressed.png b/pyscope/bin/gui/theme/light/button-pressed.png deleted file mode 100755 index 920bf70fb9f7aabc4a04b70935e97bc69246d1b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-S17sn8b-nUl`^V$q}93G~t zGrB(dcRj@^q5X*LD!u(yCX1BcGt7T;RII$;knMTprD}Dh8J;d4nt|D_p%Y*4dSt;j zEp_3Prd588$vjcnGbC?sQ~bTp{oKsb*yRh(pXvF%@8Uh{rBmD`95h?+SxX6C&i*%T zm5`v#bi0S*H3!B2aBC>_7|J;dHg+t~RE^5+cPv+b-g7Ogu>4HUuc-e%X=yVgw}w5j hk8?QpQ(E{Tw^DiXl)8_0CxI?!@O1TaS?83{1OV`cZc_jN diff --git a/pyscope/bin/gui/theme/light/button-rest.png b/pyscope/bin/gui/theme/light/button-rest.png deleted file mode 100755 index 1b211884bd062672de26718e641fb1b264a3c251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+Sw7sn8b-nZv>uWK>jX?fV` zIPvJx$A9MwGzKwD^L(>5LRL0+V`S97I=jrKRl8<7x&(MN?q0l1fN9n;M&srV0hc4& zbD52mk8voiVmiySic8S({`U-5mNPtQ>zdYeyx1!$7@)~&cx?WL{YNDNy%={^e?99j zD0oNq`_lg(nC%`g*D=qMJoCn^#FB;K_rCjV`ps>L2R7+=XevIB;e9%1V$h*o>m-f~ wI)?tW@jvsq=ds1A_rij^)*UwS&A2V5AJdy^Ep$ND0q73~Pgg&ebxsLQ0RK*M>i_@% diff --git a/pyscope/bin/gui/theme/light/card.png b/pyscope/bin/gui/theme/light/card.png deleted file mode 100755 index 78ac82ebe41fcbc4b444f7c927d2eadfd1bd0338..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nlPPZ!6KjK;UO9J7uX@VGrx zPp+O+G4Z#&F}HtHPJp=T>^!ce23#+!8|9;ZiHDjpgg)J+_iWA#$$6a|mg(0@mrh}F zV5``Fe}&ds)1q@7C*0+KNgF4h^L#%yO8fSaqGpmhCPv+*K25T{&V2r&8d+qwlaNSeYU9FNz2`c4;YRNp00i_>zopr09-Ml Au>b%7 diff --git a/pyscope/bin/gui/theme/light/check-disabled.png b/pyscope/bin/gui/theme/light/check-disabled.png deleted file mode 100755 index 2c59e08fde6b9d9b35b23a844da6753026662ed5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381 zcmV-@0fPRCP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10SZY( zK~y-6t<}AXg)k6?;V+X2r!t73l~_u&^8W8b6g#~DAvPk3*=AkX1>N}bwgV5tG{e?) zb<46~7zWJq3~Mc}#5qS8hRCuEpU($OYwen*fidP1^fw^{%CZCv!|)gb0AmcsaRklt z{21HQT7$0M>?~_7=r!%TTObi3Ns@O!B7#y1aU4Gfi3mkez&VGm>-@nqO+oZ~v}24h zv~7!R+x)>OilCJGL4C19u-V6G7!@CjZ{56bo4hqBYHBd?k%CZbXoSxDvAq4U~2isr3IF8U--+BR3O5}Nt bAPDdUnj(YdxTMpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10clA@ zK~y-6t<*nA0$~`(@!!4Pc^;a7qChtdNy2n2a;U}zk+z5?E$PqTZXmSP-d35ov^O|4 zG(@>ao+|v%9(Z1cQmWiNOV}+`QLNPP*6u<}Hr|AY z-+^@0i?LAwO!=XvEbd>UF3Fmp-`NEgr>QVZjll9w`LU@B1Aw;N!&9At6e~5|*jJag zH4v>k`>RGv1~E+g>|Y?y5iCu+jRAR%Ah$M&h0ut8(99AX#Dh>2U?VT82Uli1uy8*| zW|rVE5d=#ZB(gVXcY5l<`H5lVlc8@^E!G#@%8O{yE>1SXa9KIkSYNQyRj{5DQI}*~ zY)^qOK>TPh)))Lhdq^IOI4iwkHCqCe2V(%hcv9zzs+TG={!(nb$uP_=upkk?!!U4m z+zFD7dSMq>-I&YDArtd~p_D53FB0|&Ra{l-XtcEb+(Q-?a{(tZF(2FxGu{ExU4WbI SxXB#=0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10bof) zK~y-6t<*hB!$2H>@h7>IhS<=8Ac!_Xbx?2-igvLG(#?dUC*a_Ve~h3PcAC4(_N!3Q4mfHUia7)k&D002ov JPDHLkV1f>4&!PYT diff --git a/pyscope/bin/gui/theme/light/check-rest.png b/pyscope/bin/gui/theme/light/check-rest.png deleted file mode 100755 index 4f8d140379ca90dcee2d82e8b17da7be5018750e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmV;~0Ve*5P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10cJ@= zK~y-6t<<|OLtz+z@#lO!r7fjO7%HZcCXJ-UNTdVSlDbVSVjv8*T1+MBM1ntpCc9TmJ$|3h(S|LFQ=Dd5d&$Q>Urijzr648eUhRm>di}!|>D*Si5>mRfPd$^Ch0&b4b?t6SdMa%hh0( z5qe<&$xH#EoA%YeK$B50+waf^G#Ld4OJ4jvt<{6IvS4q)g9=RT#FPRZWu`)BmMdO|ouSqgh7yTsGanA-5e7 z3OKP@s^7A?-bi?&6G>4N_4YN*YUG~N_&do=W#3tYS!UR6Cp^)Kqp6lpn(BYD<0X+p P00000NkvXXu0mjfAj!+q diff --git a/pyscope/bin/gui/theme/light/check-tri-disabled.png b/pyscope/bin/gui/theme/light/check-tri-disabled.png deleted file mode 100755 index 5c796c07d129e7a97cea9575c7b2112f0ed4b78f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|*%g7sn8b-ltOyxegofxYp~a zrfl+8*z^2f`bow$(kHwP%-&woTbemrZOXKLUk^W4=DadB$nW$fg{3WXjH9w|FbN*4 z<`+C@(f8ua8F!QSD~L}2)^SbhPU1H1 zdW|Pr9b61}4&Plfg(-CJ?Amn?qngue{s+}Lc(}Bm?BF<8T|dL1g+W~@A#U%y>4|Aa rBre9rH>^5z=3W(}A`6iB-S(WZx>q;DiF-g_Gcb6%`njxgN@xNA=3R6R diff --git a/pyscope/bin/gui/theme/light/check-tri-hover.png b/pyscope/bin/gui/theme/light/check-tri-hover.png deleted file mode 100755 index a11cd661cdb79afbf83bb2415f2cac3f21a94f14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 365 zcmV-z0h0cSP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10QyNp zK~y-6WBm2&*Z;e(zB9CMeZX-1&PRqXKmRh20(jV&8LU+J87h4g7?eag85plW`S?Fz z@+F2(-~W(g5IW%FWMSBtr^mqjSv|7h`m?VT8OFfC!0_|WKZci|elai|zw?n|!(ia( z?T-vhq=p$j@cG9d1}3VR1_dL~Ko(pQ#U2U_;{0q_#>dIRz+|PuPc;MW)C3rqDt#0f_&9NpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10QX5m zK~y-6WBm2&*Z+rae=tnl`%QImn0*)XQz_RDV+8OFfC!0_wOKZe(ze={(hdH9)P!(ia_ zgHH@hq=p$j@b%|k1}3VR1_dL~Ko(pQ?z*B3{G8aOKYstiu=Db3ta4mmtT}V`<;yxP|+Vx7sn8b-sC@DUtj+}TYsNn z$lAS!3mzO}Z#!@Q&tL*mg=K_s5(_SiCB5JaPMsi@lFq=u=I4|1S^qc>FdP^>UHx3v IIVCg!0K31G#Q*>R diff --git a/pyscope/bin/gui/theme/light/check-unsel-disabled.png b/pyscope/bin/gui/theme/light/check-unsel-disabled.png deleted file mode 100755 index a0f31320c9ffa70bc3a3aef1bd4b890ba3a5023e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-zC7sn8b-nUl`xmy$@j(t>D z&Gck%&JcLM?|*n^hTaC1m?;V(FPBC4Nw3R(`=7CH&-*VThQ~fz@cCSB5nw7hw?k;D z$ed!0r2(O$T<`x{_9@@m);dL~XQOvQkKj4WHLtt&y$|F$TyU+lcin3}$N7@U8{S)c za4-g)ik4t7@nwE*>v8Vqr`h+DdK91Y6h;4f!6^TMv7SkDX@}vxwji$sQ<@6SJ)EmmtT}V`<;yxP|(t_inA9gUj_onHRhG zu0;t5zB?k(EWqTYsp8p`aR2-2OsOfUoSLc@&$m0gx9u^U@m#VrRypJLH?PYgp`mx1 z?4;WmpI0hvx#r~clY_Zok;aB~+D=Nl{yvK@Jm=`bA@41}_W}352m2k4N$mOFulQVM zk%p5-OM~Gv!;BusX{j7jgJvw_d~9*Z^83})!v+bFVi{`%|M)rF|9)fJ+tr*WKFdU9 dA1UmS-!7NmmtT}V`<;yxP|mmtT}V`<;yxP|+<<7sn8b-nUm3`&tb|S|2hm zQsHH5)jIKrW1naEL9Imb1>Um~vKXD6nzD|X_DFp;N~(zX$6B*9d~Qd_y3aO-Jj~m2 znFSyE^%!y}E|}spEw$rY?fzZsoMW#~2x=;>v)9k7aiq{;hZ|pH_5s-7fSFPgN z6v4sx{7&)(%l1VY0@_Ox)*dzS{cx|obfzKOXB+42-WTOe#|n?^S{Lu@5IOBal(KK| a9|nQS#J#~6B87myWbkzLb6Mw<&;$VY3x65_ diff --git a/pyscope/bin/gui/theme/light/empty.png b/pyscope/bin/gui/theme/light/empty.png deleted file mode 100755 index 22183634d5e36298e12ed067750da6c7d2fcdea9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0TuCix;TbNOifOZU~Lv=U|^iZ Vz$kX=awbrQ!PC{xWt~$(699LkAlU!_ diff --git a/pyscope/bin/gui/theme/light/entry-disabled.png b/pyscope/bin/gui/theme/light/entry-disabled.png deleted file mode 100755 index 920bf70fb9f7aabc4a04b70935e97bc69246d1b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-S17sn8b-nUl`^V$q}93G~t zGrB(dcRj@^q5X*LD!u(yCX1BcGt7T;RII$;knMTprD}Dh8J;d4nt|D_p%Y*4dSt;j zEp_3Prd588$vjcnGbC?sQ~bTp{oKsb*yRh(pXvF%@8Uh{rBmD`95h?+SxX6C&i*%T zm5`v#bi0S*H3!B2aBC>_7|J;dHg+t~RE^5+cPv+b-g7Ogu>4HUuc-e%X=yVgw}w5j hk8?QpQ(E{Tw^DiXl)8_0CxI?!@O1TaS?83{1OV`cZc_jN diff --git a/pyscope/bin/gui/theme/light/entry-focus.png b/pyscope/bin/gui/theme/light/entry-focus.png deleted file mode 100755 index 56309029ffceb7d1799eecde1146f748356de8d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|*!f7sn8b-nZusy;u!JS|8e* zH}2_HKC$}kQQjLjcXC`$O3Z%hcc-OwF1NtO%^T{hoP89&M`T!6{YEksmMai-*DlZC6E%n_UzxnO2scB+hfTcX0$pn%YjQn4>HJ8E*h zH9g+Xo%4DBhWAhByxgn&&hGcK{7#N{=Zapc?`hKB(Nxdq%rWC;(up%1iVB8qS!=rw zMrmuRUi!ScxT^H{x-YH|%Jd8CE1fdd{ycp8#L=_D+xxBW-FtrU_v{llKQdl_?kmmtT}V`<;yxP|^y?ZlqORdubk%U1Oma=fei zJ*!?-X@~X0jbB!a*Bli8BVMuh;PTjdrw+qQ)%85z?s;kodTGv(O#5~S3j3^P6mmtT}V`<;yxP|-zC7sn8b-nZusy;vLtS|9FT z9ewN2%#`5X6i5AyoeMc0wfgdy-4vd&OFUx6!k3NfZg~hA7R)L6_ow#5Ars%cv?)Ou zYahNlquJunBN*!H;*oaDqHl$kWx7j>SVr2FD9N_Qb+0{^iiC)U&SkAtQ;NSG#C0i3 z`S~CHH`SKuQP~~4&+q#gt<Ia5%=TzXr(ZBb?Y@wd0N!~Od`?w@a4^8RJqyqj|0V(!EgvweN$)y5|D RjT`7W22WQ%mvv4FO#ouqfad@J diff --git a/pyscope/bin/gui/theme/light/entry-rest.png b/pyscope/bin/gui/theme/light/entry-rest.png deleted file mode 100755 index d347a65a9c36ef61e592f66bfa2e34660d47f5c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-n87sn8b-nZu+^I8me+#aT@ zyL28AwcufYZgD3eKxwhX=B&`r*eQvpZ~wlWZ@Bs4yDL#I_ZlC2;HBim(HO+F%B!Tj z@3F#jA1}_(y)3(|(~iCVnlvvxY36uU!p(}EU#@9Q(v z+y66s)hUVQ-m{LkFQ@vcs~sN98xS1}=NyQ8u4v5sj$8EY>_{mxjq`*PIc zYhIeID^|^ryuEGXd+XanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&KtA z7E`k)|DN;QXuPp;Th-a-H)2{-obGehP1rT@^{zMj^0!a@IU3w+WQBu-Fp3qAz^LnJ!^~SJ$aQT#~2pZzmqm*UaVYq(qTQ%ms11? z{VtD^!^(n;p{BbYPh8csRAj>Y-6oz_q7>_mUEa$k_gp_Cur+ML5q7?EC;k=Xi+zD! OVDNPHb6Mw<&;$S}eSJOv diff --git a/pyscope/bin/gui/theme/light/notebook.png b/pyscope/bin/gui/theme/light/notebook.png deleted file mode 100755 index 255dee8e2309dba442cbcd78a38328d69de628ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt*nzE{-7)t#8jQWNa`HVA#0r zxBs8EzJ?Bl!kG%yY4g4&KYx8-cGIc_S}V9#;imMCPGYgm%{I#bn#kbk>gTe~DWM4f DG!H=O diff --git a/pyscope/bin/gui/theme/light/progress-pbar-hor.png b/pyscope/bin/gui/theme/light/progress-pbar-hor.png deleted file mode 100755 index 9806e3d5fc81faf66368913c69487ab94563419c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W_!3HEylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=>PZ!4!iK)qdzP`R*&!)vR z?bp|GiDhppk9>H2-oA?`DLFCeMDf)01d($$e*Qmwet$@ztUfz8b8y>a8D?p2;iGLE kHyRkGSe-O72(sp3JYgnw;To%SA<$q3Pgg&ebxsLQ00#v>bweIk=>!utY)$y{>b|Sh5TFw(>>EBq`cj5WBZ&n(&3X+8dbbk zNxo&bE6J&CW4=(w@L~7cABUyok8A9ixBo4}D;w2s^0Sm!AF$-lR6Lub$-fb3A%mx@ KpUXO@geCwXjZ9wv diff --git a/pyscope/bin/gui/theme/light/progress-trough-hor.png b/pyscope/bin/gui/theme/light/progress-trough-hor.png deleted file mode 100755 index 6999a37814de71d6b09fe3ffc3e924021ebd110d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W_!3HEylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD>4PZ!4!iK)qdzP`R*&!)ws zmL+xU01#|SILOq-FK-uc)PRwZk?ok{`&0&X1)ifTTXS~lN zf9IQKU#pjx-O0#hSM!4*+2{c4#DgmodRbUmSROPnFi#R)5&3YZA5bTQr>mdKI;Vst E06Q)-zW@LL diff --git a/pyscope/bin/gui/theme/light/radio-disabled.png b/pyscope/bin/gui/theme/light/radio-disabled.png deleted file mode 100755 index d44a9bf6ad73588f1421a14ba588c8ca861bdcc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 523 zcmV+m0`&cfP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hmcd zK~y-6t(CEkt1u8n57rV(aSA{}geW9k3X1&ycch?6w-kUVTp0ZI}>(D2fQfko9^EK+`nT zbxl=O48yR1ZZ?~#u5H_nm&;{Qv(;)vnx-hFo_>`vhAhkIx^DKQl$_6Jgw}ci6GFU% z0Vt(N(-a}*iqW<$TI&h;s3?kF!XBYf6fHchHNqIPFvBqX5%yiIF$QrP+M8|-ct>_@ z-}m>i`Ck&Q>n@C@Y2Hq@V_nxp*bS)b`fZ>ctCT|6cd@D}>_$D4F@~}%7hVtq6HrPy zo5L`WW!X#UZLr7jzE`A_c%C;c2dy>N>vfUjI1bzG_UY9t%W?q&IGs*+ApHAs0NCwz z?Du=%VcR50LLA3`4U6M=hQTiv?D2TS^Sp)s1qeSs0YMN<^?LFI`~e-LKFh3 diff --git a/pyscope/bin/gui/theme/light/radio-hover.png b/pyscope/bin/gui/theme/light/radio-hover.png deleted file mode 100755 index af45ede55aba74100cca821146c846c44f7f6dad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 837 zcmV-L1G@Z)P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10@6uD zK~y-6m6gj+6Hy$6zk7$lL8``10wp%o1~;Y!qbzjktJH|-zks3vce?TwT^QqI#nJ~M z=%1j1N|nfxg+_&vxDYK0;h`-N+fX{4xh|&isv?}-%-s9UnP2WXBXYT%VudW*p*NnQ zH=ZOumSOT|4glYlQo{anqG6M0*o&^om0J-jfzOAM+ z9q1^wMHydH@)iO7m)PlFq(Q{FoES(sJ(ik0XTOQe$Az;ftXpt&cu}0w?>EraeB| zo9poy3-4Q|bT-eqt~YeQn?f2}KtRXIoiv2JNXu5->`u7Oav7TT_;}E|_n$BTk5NKL z+dhu%u0&ZG0Iom&jAc8>-guJHWELD7vrN(6Tu)&ft4auHZ?4B{C~$1PrRIqZ&$vJU zmCw?0XveC~R%%n3PRpShRDK?SSUiaw8gnfx+aj{dw`SNMMe3?hwtG$ozon7Wv+f0s zL$JcLZeXy&vv91YW^>4eaS3poqR{+TN-ge@LLe(PmllAM#P`g)fr;-K5N-r3HtWb* zzx$a&(|I{Kv2I}O!vsRp0jTqPkkLXGMTtRY-w40vtTn@C=PWw=M%*X@4Pg`65b_c* z-E5|^g7XjGqSi13jiCN?-hXWs}j zSy6xq&K~r0@#vCr0JpyGJR5bP0FGU#uN*jq`jW-<)po$og`?FA^;J}`vEdXqo_ulh zy@~>+!PVnIA|dax`$a$i%T~mOQ^ev)hQ`v%egU<957DqmB;-Zcid*D2$)q_OpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10*OgP zK~y-6os~^b6Hyd~pSkT&KBXg&P%NMjXhD~3Af*MO@gKOMniw}mBU}H2E29t>>dLb6 z9|!^48uqDPZ|1bjC$LV5Ti3zR4`+z2`nT_ug~vm9p8afkme|gRrU* zQ8i+lhin~W0cdNg!P{O>z@y;zD9AR+Iu*$ZT#RYFolmflJ}C>ns2hrd7k4`u^C*>p zx&bo_31*izF9lgOIo!_kiB3czSy3GL9~i*w(kAZ~lcj$F@Kx0+!i?N0M)nB(B!hmE z!N?v#?yM~C)AtmScp3ovSXAAp`cX6k+~kH>DB{GlF&p&o^gi>pk}tsvyO(%44&glIgC2&-Dz zG6MrGWw48SGJXrcrv<~Z{D`U{VyQ!Obnc9xue&O&sGzS4Lq9cnzf&2+_FuDU7&?Q6 zJ-bYU?rWgyCU5)T6oIAKB_s@^ta+-)JXw-Jv^L6E-CxQ5D^bhIeM;3_P-~-%@Ve^F zJey4D+xnHjP;?DRn3EakXh4j4uA3umb|UHy9~M@t!rm{e5RLC5*=#0%)T3bc_d9Vb z4pS`(xt5vv6@Dc4nDljVr?VM=pNTy}(KVv+-Lk^&a5@<6bLKCHu$t!O=RcM_42)bZ zUmC^JlmwDZE@WojzgG|X@b^3Oy&Uj32@bm~KnclaN62*uM??OrsY9~a%V3X2T=@H) z`PCK8XA|xU(5v-u#KqHb_jwxD3pNr@^JZ?ntiP>tD-Ndab(J!!3IsUI8ARe~!m36r um0{~3-%V{zGG12$0k49;&xyUl6Yvjbu>X#2yHO7S0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10+LBY zK~y-6jg>J>6Hyq)fA6)lWvGzYk`R;ws*?sb`vQC zBi#Y}4d2F7+)HtxDB_`L78{Dk=|qGOWFil%IWmC2abgmL+`aWLSDDlX5uO2VsgjMGII2W>raFo zs4uKRK4-=86#Lxak<3KNfn>*ja1~Hu^ktw461qHyuyWQ(`@r z(z$=Fk-o5IS#N1G!|RVpqI2se%wbz^YM_}&&}UDZr;}^EoK4zLrOc)+9u881JUko# z9^KR!zGDst>qLjUn*eO65a9Hjc$N=uXbjzMvf{D<8%v}aoLDXL_t6T}R5IFgkx0;Y zXuS*wu%B1N5-DQw6pJOlfL6^*SEzpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hUQb zK~y-6t(9G`qA(CZ4+Me&z+=x1T0DS!C>xs-F3b0dGF~=XF3yH zuUF3l-g`s@5#fA3CIF12u&T%{* zF~-pMJ*(9UrPOnw_nyPyfQYc!Y{;_g+wEVm*0SI4QA)8~F24)AuH$q%QB@UD6#Z2; zF^(g3T{EB0@6vEeQ$*g90Yp(mRaF>c5RseEdr#Z8lx6vr?5lLU-O{!#-g`n3VK$qQ z=lR>Ri7d-Vk^~VUG{&H{{;_OAYkd=5E|-@T{TK2)=X^dBI_K_v_6czu*C8U5W%=?eT5D;V2Cen; z@ij3G*d$5NT9fDbZv)m^i!p{_7+9~@_sISzgx4!TYfVuUPglS%+OO)M8pId`00000 LNkvXXu0mjfSkvT7 diff --git a/pyscope/bin/gui/theme/light/radio-unsel-hover.png b/pyscope/bin/gui/theme/light/radio-unsel-hover.png deleted file mode 100755 index 7abe53eba6e8608a5f2029f18ccf35e5e120e807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 573 zcmV-D0>b@?P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10m?~4 zK~y-6t<}Mb>M$6F;n%nj+O8X22u@q|9yIF0>vkcvUPLu^Bh&tE;k-hRTWxmwALJtM^2{`0G{XJ`#w<=5d;CvX7h0=jYn|1-4e$!p6Ai) z^^j8Hx-J0LT8uGdS%$Ti$z(zlMgInGw;QX~ip^$&QVJ>M)9ycWI2=flgnqw|Qi?{S z@i!R9G5h_V`Fu{R)q085Da(?@VnMgtMJWYPt?MM0kDzs3%S14U5~1OY+_fl`XqYDHO=Z)0Uyk|YVU*^Fkhd7p9= zMGS{S7K_DOXk8A&;gBE*fQR}@l7uu(8IQ-b+wGTFp69IBYX*Y>pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10trb( zK~y-6m6cs@sz4BiAGSqMqGeI+jkZbu|9@(NUf8g4BMPn%*>hna1!_Hg@0b}LW|@z5 z%CfZdWUWPOjnA#B82WqWJk_5+bm`o;kp2ySE699Ex zV~n9F3aqufy}co&{26GiWxL&?lp=~EJkJ|^{kx1YXsuZ+7OdCnA#k(Vu;1^A;}{{t zudqgSU6ZCMQ53aM(Hv7L^(PpB5P~?4Nz)XqH2}}nT9PC|N;!ay$780`DWlN{fU+z( z9*>;Q=MHo}pOYjBVHonPwZ?HAeBbZDUS3|9&1M~&=XrRZN0wzA4u_7tIjPw1_e>^} zL1ul}cg^6@B8F=j;N}t?XyPBKl@Gn7$F2zRq_8N z?aA8ifHhMiT-W7#y>^^sSv~~zW3JaLuInO#AmDPjbezZI@geYZI(43nF$6(?kWx|< zMaOwQpUJZ9E-cHkPA>xFc}^5XH=wn4kmccUKq#YpS-@l4l4dgbzQUD?fCfkAPmD>m8F!#ag0)mx~~5OOVgC)a!DA506aI&W?OuH zeIcbBjACELn07m@R;$*2cfrDKoP6JJmqxQ-^E?OO{r$bo>@E-AxpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10nJH7 zK~y-6t(Cow;xHIR52ipOQA#367aLxJ@RyPYhBBbQQ^cmj60{_UAO*HSpjlYWEX$tW z$F}b0>)QEzzVbv>RTyJ1#<1V-$@3fl*L7*NS}3Im!;pHt{=AghvrubInx=T3hm?{| zr-S1-02D=mwHB>4hr@xvV1QEU&%*2V%4)SjYfT)-wA<~M-M?b3B}o#LQbbWitycS8 zn5HS#TBg$}jYi{J*`2a1naySdK|mBm0EqiFwbpOR02+-3)9I8f%P_{=gjH3MrYYm` z_$}F2X&lEamrJUuLKtK4JdYp<-j>~Ix7&E0$7ZuZXsut8`WK{>7-JCo{r<;9|Art4 z*zI1|nAmLy5SXf&c;uisKuO403hna}5x zW%-tDHk;Aw^$5cdc&e}UdQFyPOePaLozAzi?RLvzv7p=S((m_Q>+6m&hBQraUH4J2 zuImD~(YP0EQ4|b^!^g?)ya)jpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10v}04 zK~y-6t(9SKsxTCWAE02N42R1Wb@BiIuq8^iFlS|?LMivdqSrgnCGNf-Pv7&T?P<^P znx^R)k#i1X48|C&wRBwvKoA57A&^ocrNsCBv2?ue;J)vvs*1X$AW0I^G{y70%V6gmWmyu2A$gvkV3%aAWxL(dwk@;Sj35YxVCNjKudhs} zQ?e|(3j8juHM`x8r>CdW)%eHbmt}bq6d=norqd~9Sc5}2t8`E z*$iS)6k)B!cg~Go!{_JckKi$n5Q47j82^vIP~Z1>o`)X<0oK}qK`HekWX#ib9lr15 z3n6HlW?;DV{LZylEC#WrX%Ir-ODU=AdSJ-&oaJ(P5&Hc6%>DiSAYRusaU3J0le!r*dIvyV%e?0y*y}i95gdk1R zuUC<#DQ(-LwZ0juwZ>X|DyR?Oc^(fB5A1fko8ZT9KAX)>4S&$+-{0R6Lj1o*f7JPC snoK4nNpf{GDWyHaK>z>% diff --git a/pyscope/bin/gui/theme/light/scale-thumb-hover.png b/pyscope/bin/gui/theme/light/scale-thumb-hover.png deleted file mode 100755 index 34664b4365a089799f52819841eca8e0526e8528..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 749 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10(wbA zK~y-6wUxV08$lF?zuDWYy~ag=G!&6&f?_GW0Zm8~-he8hNqXLcK9N94#S0`|C=f4@ z6d=;%B0*)G!r8SwGrKz}Rvuqi5GcxD&Fy@1G?)Jft+k~_)>@1)7-J}k0&A;fLI`}{ zCk#WvFhmGZ7fw(IR7#3bHIC&vRO>)(qT%t+n*~eUc=h(P-QXoY!D5U@#caZnqsgn9g(_ zdX;MSk1sho>+z#I1Yq@HgJ(|`+1XfOsTBdxY&HQ%)6``ywAOYs8ey%a-EKSR$1h!W z56?+eQJZB;`s{75v9-SJ#`^s}LI~nG#v5N&k|ZTtaoRqi#eUd1VKPi6xA7zA3wmatKq%DKf99{&-1D?o?le| zL2p;xxAue(lx67#9xdJPb5t?0YnNq-5CYHleeyha6PumIdxM{MmR#EygYWxzVHl#6 zauYinE41Y89x<(jIACXE#r3t;gke~Lwbm3x^~9E15qsNf+~9^Y5(4(NAM+pyfy$H| zSO|fXk}S(yz}EUQZ(nv;h_AWoOs4N%b$Ib~@};vZa~3MJ)^=<{aU45C0qCWMgOdx6 zPJi>gI{;vHxyfdyGNEy%P&wRaG@3ObnAm2s*_?sjMRm6M|BZecK3$rmlsA?pt@YhX f)BFbiQcV8>eYs-Ep8zIn00000NkvXXu0mjf2U1Af diff --git a/pyscope/bin/gui/theme/light/scale-thumb-pressed.png b/pyscope/bin/gui/theme/light/scale-thumb-pressed.png deleted file mode 100755 index b0de0d07965dc7eacaf15119b05001beacb29d7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 675 zcmV;U0$lxxP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10x(HL zK~y-6t(Cz}+dvdWkH_P&ViOdWgsRJ;(l3Zq*z_~9qr3i2_m#TfFS@Cy1YbZA%Synm zL>R|-?3vd^h!wDtv{CPJeCNEe=lQ|Vy(p(gE0oJHA*P}y!X^~jkUJ%#Btmz@C+;> zR8@uY-lMf{8BWe`mqHuQS=*zv#(R$vk*4_$aBnH} z-tBEM9_@BQopVeklUZh1Yvu9r z5fNc~dwb=puhaegJxVF!IG%My9LG54D2ifZXi*e6=bE2Q!E7p|Y07ju-3UI9&BaXa z4Eit(Nz?TI1pOl1T1#1$e>IxoIG%OEs?qeaz^Y+7FSu-&{sENzIE*?h&~g9(002ov JPDHLkV1msrJEZ^s diff --git a/pyscope/bin/gui/theme/light/scale-thumb-rest.png b/pyscope/bin/gui/theme/light/scale-thumb-rest.png deleted file mode 100755 index 46bd9ed0075b48ed4c008adb569292a5e01da686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 701 zcmV;u0z&pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10!m3l zK~y-6t(CD-+dvS8Kb=k|TS5#eAs!welguzwA(i{&2|{Z3yhu`$)Z_{1p(7QO%rJR? z0HF+nkZhfFcPTtpe8x5&CfjLu_3i3T`){DNcFZy79Cck&*EQB!oExVFL4Xi~D2j-p zh#&~&y-qL-cFs{%6l&@~_{^)Sa(SM!v9aNu z>GhuxZ$F;%<1(My<<54-@xd<7pLJTFF^1dQTe2)e)OC$g%7a#oT?z&G{wwF*r*o>t zw!Sb7QA$zQ^*`{m9iCh+g-*l2oL+gp7Hq9WO6diU&TbxsF3OcsVy#6u=h~ltVn2W1 zuMEDp_P`hy-n}A zX&QtOh$xECT6@7kzsJsY_9%2`J7duAdA^A-wP0gR8;qsk_+WP_d@>+vYWj>ZXswY_ zjzwrR8sVH{eSO`p-dH}LUU77G)8788UdH>^n+*Cr4+?O1cZU#SJknZgHxZ#ur}HEc zg0aGlMx$0f1&p^snx>Rx$#6J)60|H!Pm|{geHezMY5ISJej2W|rmCuyR#P0uD5aLK jrr83^mg%J6qGkFQU&&dP858c)00000NkvXXu0mjf7>+yo diff --git a/pyscope/bin/gui/theme/light/scale-trough-hor.png b/pyscope/bin/gui/theme/light/scale-trough-hor.png deleted file mode 100755 index 7adbe2d02f6b77d3d5d9e278a0038d51227a3253..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-{hE{-7G89K2hJm&Nt`HUVf%sN*7v$|4HsU%ahD=#xmAn(u%E4 zRqxZ+K5g1JHHgVPEObH%v*)6%Y$t`iEFQj>i!GCATX8f{4`>mCr>mdKI;Vst06@M+ AasU7T diff --git a/pyscope/bin/gui/theme/light/scale-trough-vert.png b/pyscope/bin/gui/theme/light/scale-trough-vert.png deleted file mode 100755 index 924dfa9c60ae6cf6fdbecda91b5d7db42401e4d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt;)(E{-7HKKKxDVW&Su7;D zgTe~ HDWM4f5vffb diff --git a/pyscope/bin/gui/theme/light/scroll-down.png b/pyscope/bin/gui/theme/light/scroll-down.png deleted file mode 100755 index f4dd741ad41fef3f49d476ac11c38f91bb2dcf58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_G@Mt{BJut(gJ>Hjh*KX-8{lZYe Yd~mL8f}kTaGthPhPgg&ebxsLQ0L7G89{>OV diff --git a/pyscope/bin/gui/theme/light/scroll-hor-thumb.png b/pyscope/bin/gui/theme/light/scroll-hor-thumb.png deleted file mode 100755 index 989bc941ed811786635262e2fe62500b63b1da64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W;!3HFgc;@~FQk(@Ik;M!Qe1}1p@p%4<6riAF ziEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0xJo-U3d8WW#hQsg^qAi&~q z+t6U+l|Kpl6Mp{px5x|1oVn1EtM3SBhnA>$_?6lN8C%Queg9X|#yRbV{{f5VUz9dY zIPK9S+nB1Z>b(CFI4)#IzcDbb7BM!E2r#|6Z@Wu^e>ws_FM9Cc)S#+_V8-a> zQ>vyH->Z7RTlnJE;&qd^TuwCVdmPzQ$hzgvou?PC8*euEboN>~eGBssw&-sT|8fmy RT>|=y!PC{xWt~$(695mnf;0dC diff --git a/pyscope/bin/gui/theme/light/scroll-left.png b/pyscope/bin/gui/theme/light/scroll-left.png deleted file mode 100755 index 498d3caf57a8f0ed8c16c7b494eaf452ad955cce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJx0U~c5>$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-jVE{-7_Gh_QUayl7`xSs#Y zs#wjzr*MO1&Vk$*g&Rvwa&VdDCCp}(d3O7z>Ulpq%cr(7JCa=2etqz+y!=0h!gHCV z9&yj`1nvbvr_P<3cc3tq-ME^uTFvb92l2h%jX%t5-Fj`>luODjAEuTt@i=_GYj}(| aqON1M{CfGR=U)OHz~JfX=d#Wzp$Pzrj#Op< diff --git a/pyscope/bin/gui/theme/light/scroll-right.png b/pyscope/bin/gui/theme/light/scroll-right.png deleted file mode 100755 index 7f771bf82807b5456cdd6d4710c76e1f9062faa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJx0U~c5>$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(y8E{-7_Gn3!Ed1G?|2#y~) zG9@S|sAw7^m&EQ}yZ$-fte23KtlnSuH|qL@3m>>6nb`dP{{3w}tCLZ9hM}=>{xP+K z2Y{f#_tBO6`|E2bs{S)DGAgV4{q5|wO`AUPaWgX?u47|kb1Zqz#BfznRVv)A$qr~Q NgQu&X%Q~loCIBP>Q%(Q? diff --git a/pyscope/bin/gui/theme/light/scroll-up.png b/pyscope/bin/gui/theme/light/scroll-up.png deleted file mode 100755 index 09ef917ab4e4bcf12c3bca7a1333ce34842a4b6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_GI(6x;{(%$w+gIgXxyvssnDAon gd(j>KNBVDbzvxi%I2xj92y_L5r>mdKI;Vst09poCGXMYp diff --git a/pyscope/bin/gui/theme/light/scroll-vert-thumb.png b/pyscope/bin/gui/theme/light/scroll-vert-thumb.png deleted file mode 100755 index 6f84abf0c516279f8b6b3bdd59a6b327ddffeb04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eM!3HFEez+qDq&N#aB8wRq_zr_GE(z-`$&|{C-N%uEXj}r<|17d{jc5 zf#KXw#ie&R6}K9)?2Dk-b#dEHaVHM$1rKrsiq4tqJjgwEV13LX z)jzHYJ$nruG;3=`EuOQBwFsQw$KN09^_GF*-tUKtY>Sz;H5vVy?4_#*bRL7JtDnm{ Hr-UW|jkjX8 diff --git a/pyscope/bin/gui/theme/light/scroll-vert-trough.png b/pyscope/bin/gui/theme/light/scroll-vert-trough.png deleted file mode 100755 index 175bb6e33c4983d9ee4db85da19dcd7ac3411eea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eM!3HFEez+qDq&N#aB8wRq_zr_GYVZd&>2p9~Aauj(x=VPjxAV9{s(*=9de#ews;t)JgowW=>BuI{($KetuC zRPOzrE^5e-J$NNaX5GW<`_{h{;1-^ob4K_~57979#$ zRlWmtu6w!-%O(X`0SΞO7!GNmze+qQp8U4&DZ-)Bg=N^G~Yn*{E#EctG6szXP9i T^s73c=NLR){an^LB{Ts5d3}P9 diff --git a/pyscope/bin/gui/theme/light/separator.png b/pyscope/bin/gui/theme/light/separator.png deleted file mode 100755 index 1e7b972cdf57c1772f0f4a882617757da68229fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}bl&H|6fVj#T-gc;Avs>lHuk|nMY zCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+>Bu@p`&AhH%VGuK4%oXFUU_GV|8= SxmoLh5)7WMelF{r5}E+?WFjg6 diff --git a/pyscope/bin/gui/theme/light/sizegrip.png b/pyscope/bin/gui/theme/light/sizegrip.png deleted file mode 100755 index bbcdc5f66a89a86fb391cc3f3d4f66b7f153f331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0Ts>lba4!km|ENKn0MGffLVR& zwM}~e{#(~h*s0BZ+qqFGbK(b^2NU``Jnp#&CG$jO*X(U(xHI?6k-IDBAGl{d>BhFV z_7=}s4xBMCHWD_jW_>pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10%J); zK~zYI&6eFt<4_bvSMw)on$t9hdSQ(a1;vNxqxT_%qEu`xsJ+O~i7CEo7^E?`j2K9P< zZg1^R!J;S_4u@#134#D21UoxBn5MZFxfxn(CX)$LO5!-iG)-Eq7W@1AKMRgVBc7k1 zIXyka_x-gB{f#tD84Ly-A0Kmgc=%oL<>iGqj=8wFz_#tJ%FR$pad&q|5Cj|@9RXkg zNGTbOMqFK8RdQNudc7X~ejh0%073}DFeHkiwZC!z+qOABKd0O6vbVQ~=Xum7lgaev z=7v_Qg%AROx3@PsoepW5&K>0YKJ9j!-QC@_O9JG1&R{U$`uZ9p%Q76tspPcQ3+3SR zd+BsKXstIQ=y@KF;~=HPNRosg2&!VQ*IOv}Rr3D+ej|baK@gB63C4Il#`CS%y`5 zS4Am>Wmy=m>yqbrRS3gy=^?B1u8Ncr*L5+P%_eaiSA{5wmO693?-NDQ#suRyrrB&_ z2q7rTvRV$)G-`1ts!PTg!< z8;wSF>NX`;mL=V8m$S38&r|omg@xPOTMiBmszmcGZaqCc5r*N`<5rR+JU%}D9JgkS z$76=WA@A?+TMew1WpQ$HvN*7Qr6>y0G~G(%_kADNb?5f}0&Rjs(ir0izyJUM07*qo IM6N<$g7=+K0ssI2 diff --git a/pyscope/bin/gui/theme/light/switch-off-hover.png b/pyscope/bin/gui/theme/light/switch-off-hover.png deleted file mode 100755 index 2af2b43301d63bbf75a61efe615bbe2e4ecb87b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 867 zcmV-p1DyPcP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10`N&h zK~zYIwU$pyGF=#luNe?K|NSa-j<$ zm0}dBR?Rq?;5>`S=*gZpU#8#so*x&7bDz1S*Vk8<5iw1ZySqCc9v-l5n|{BKEX$)t z$If6dz~}Q32m}a+!z?T;FgG_ha#s2qu+?gDetu4`*CQ5-5ekJ+RTZDl=Ly_<`u#qZ zWnr2owOS3o-;b{AXqxst;O*@#S65f8udfq{L_7uhuQVDBip3&pYiq2mtb7H$zP_eX zsj#!NgQ6%{mW5#$R4Nr7A0GjjnVBJxNTBOFs;YVp+39pRIXNMjOtQMV`T=-$;qsow2d8F@($I za^J!M^m;vVxg4cZ$#Xy<1e=?ioSvRC7z~hWwHiVQLZJ|rWs%S4aov%N{VA^Na&&Zr zZQGth27^I_5HuPM5=UG((ysfPmf(RH8tfm8}RnrD2jrtX&S9o3xGr-F?P}7;-c4Vz}qiZ(=_CGJWjn{ z2SC?#{C@wBON0<)GMQfitk>(r<8kC@G)lMI#WYP+RVACv{^(vwlEnW0K7l~MbGT-+ ziQ_m#B9XTx+1lFT^70bLaY&_792^`lF){INfe?bj!$X#rmpzAb9EXdG3%0knk!ATq z7H)2CD3{AaS+H$;q%0(pNivztubf<`)1go(u(Y%^G@4JkRVtMz7K^0QY3AqWJqP$# z-gPUTPJfb+FT&bxw=oQZZnsM`8YL78F*!N;E5iEn@`7oa)a!MG5a_zj?Ck8Pxi5h4 tX*Qcf<+m*BSCn5>)uHkSgTaxre*k`cUXudB&`SUS002ovPDHLkV1nONj%NS> diff --git a/pyscope/bin/gui/theme/light/switch-off-pressed.png b/pyscope/bin/gui/theme/light/switch-off-pressed.png deleted file mode 100755 index d5fef562e0d53c0a274c50180817f4fd543f979c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 880 zcmV-$1CRWPP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10{uxu zK~zYIy_Qc)GF=>ouQR&f(0t#c()u_#(YtkNf+3y4^08Wic2GaGWnk zza1e29*+k_Q3!=X#N%-Sfxy?ZzY6NQ&gJDLmSqu%M9?%1MNyDt86m_}1Rcl0wrxz) z#4rpxoeqkku(Pv+rfI(ytkr5h(I?+uLkzZjRsm^Zrn+ zR;kr$92^{=C<>-&QZAQiwOTwsKQkN-KY{1w=J5G^tgWq)$z)Jfbs|9_1kq>|pU+3B zR00@Fis5kRbUGc*&d%81-)CWAfqK18p-{lK?LYpAEX(9_Ig-ibM3V2ZEQ^ba3l0wt z2?m2mj^j`)7TMa`8q1xWoP0|Tz_x9UkB@0I8m#pQpUtg(KtL*LVA%ys^&D3f& zs?{plY<650DwPV&W|Mxu|K$%{mSwzNFNs8gbUN*NloMv!4Xt^YrvIuKcEH{z&ClRdrnX{eJ(Kv;P9C;bZ9Vh#W!y0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10=r2> zK~zYI#g;iw<4_bvk1ZpR2}^|>u!UHX0wDoX(!@Z8DCp?<0r^Mp1E`^b3M!j4wpbdF zuo59`CvwN;1cIeZfgC17*hYBflzvxNNALN)`w7ivvqcwt-=|zI<9Qwr4-Z69gdhmr zO2193)k2aaQmGWGsxm!2&FJW8*IMCE!CI|`VHh-5k zcXx;Dx?Emf(%ak1($W%&qP#D7dU}d&+bk|FGC4WQzyF`g#e`wV{{B8QGcznKEW8R{Ute>0c*y$tI1$>R4OGR2$0ESa2y9Q2!f6`$hPe-H?b@$2{+(n7lR-`48ySF)>f<4 zFE{zhPX8^bREjVR5d}e@)#^G-K1CG8AJ%#$b`oV-re3eN7v38=-AJd?KQ0y%pRA}T zO2<*<^Z73~`A+S3@qHgfQ4r(X5~J%ns;Yk4Kvh)=g+gM2j^mKYWDqANCTKJoxULI; zBuNyD#SbOphdQZg;&~p8MuYM3@t0BOx-JI?2X97QsZ_$UtoARUsw%l$jzXc3=&0M- z*r;JKIh)9dYaI>+S6=uIu7B4t;%nEH5uJGBVOp_eLqqhyVZp07*qoM6N<$f+tID@Bjb+ diff --git a/pyscope/bin/gui/theme/light/switch-on-disabled.png b/pyscope/bin/gui/theme/light/switch-on-disabled.png deleted file mode 100755 index 3d03bc91557b3933b80bf339a799720a246a76c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 590 zcmV-U0pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10ozGL zK~zYI&6Z8CsxTBq7xf|0InN zTCGUa^hbgICC70XjYcS?o&%O;$!fJC48vaqyVqbaAP9oXX^Lv&6W{>CFl4=6U+O<_ z{;hSx2_f)257%{Rx7z?DNy2WoBZ?x5qPTcBn+=^#=iHZRnwGQKtk#}uHk(W)6Qq<+ z_l+^k=X0_wyR12m!*n`DXsv676GFU%1CUZOnM@Gk(s86|%6`8`#Bp339DwI}FX1Xl zDe*k-W=(62Fviry20*{xf7|Q2?oDlsK^%|ApJuvItJQkjTV0+?d7fXNnNO9lZM)VF z91e%Ky-AYX)V6IS-f7_PXuI9M?cMEmH?@=!aXgU!?=u_@FZDxU)mpP$EpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10~bj| zK~zYIwU*IOQ)d{5pYJ<8hHV$~ zZp)bBh5v$W6d^IX8}Do=F2-b9qcy-N2rITdr{{dTI5kEpLbuaj@+BuH@0&d5{oeDw zKVh1tVw)`6VRmtiJLw#cvs)}}n7lRZ?P32*O@57zRzK0MX5!Hh@n{fTlT~qIJ797l z#}A3;yj&}J3bdOJ1`Q_8L^$0S`Z%END5eq_u0MEPCj_i#24e^4X>SJL*-D=I#T9Pc%dom-f!4sa`&k4qITCTd z!8A>kT*&dw%_sGNvkHv#hxz&3Q38hVqapcXnaej*Bp$7R-UI@szdFLHSO{s^4nHL` zbzxKGB4_&B`EBg;9pL~1hL7p-FBt9*gIxsR%B|;^Wd}LCxWjx zUHFt^G1%8pbKX#_0~`~8^s78_HE(&%=E|{&RQ}dl6bT2wahJmKrb+ea_DBi`Rkfq` zkf#)?GAE@F$Z(6_W3+b?WL9c#pUmHRsMIaoq9bEnwO_+tBs4#B3$+jayQ$X*O$VT_ zD}angL%XE>HwnFwnfa&Wi{(A%Y!)nL=AR<;CIE(_As4VaWOz>~eXJG)7iS*ssTY-k zOEV8yFG-LZ2ZIJ*_O>B)P4L}t#Ct$MHnfsREiwM4g5T=_31C||<`$}41gFxM(ueRFNPhq{cjH_*(aE=`-`lM!!+Iy3 zV=9rU&wl=dx=5pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H110hL7 zK~zYIt(MJG6K5E}fA71StO-)Hmb8FCJya7Yo%UF#H28u32Xsd4e;{KI_SA#JvF*{1 zt6nNz+P|QYNkscGv8eI-`G({1WU0>VT!mZrp0LsYX-1g-+jqDy z)(^a@;LM{cKmI-+nw;mHf(wngO=4b+G8iMnu`a=-}Fk%PD7ApL3)WOlX^))_-IGoEIVRX2MPm-N)9pLq*%YT)1@(&id znV)A(S(vd-K=9qM9wtxq0o%r{t1}BBO8V!b*Q}x4CfaMSrjRBgx(_27M@YYKpF7sg z`4d~?R@=auD^|IZErr*g_sAjJ;=tGNCI<*k^d!N{Yd*d5Bn|v0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10_I6X zK~zYIy_QQzR8bhmfA?NzM)MiW1Y$bAA_&bH-9$de6On9^$jUHbx295SvruO@V@wd^azd=&!1xMJ`#mi9 zw@+2*DDupSbTnR|q0F+sU@)Z7*6-nsbLqc|#q#iG4js1&kVFB1gnvKum(w7mhCgk$ zXo3KO1d;&=3w4cn5rEFyh2e;zDB8gI3U|Aw^>YiUm;{C9>-pTfnoUu58UWKv8}yEO z>G?E|tQkR!+!ya#imA6-5VrnoYnPvYVa^xOM=q$6l?KDD6_i-h_l>w0*QkCq#`2~H zq5*(hb0SkOt{^&Ge*NUoFbM&MTgwlW1F%^$7L(afXsXCLcD$*=3aS!WPb_XC{@hRwIdVdR zDwob5T-sQQveQ9RBkLdQGV!hZG!fz+sY3~ATC@cLq97n^KC z_r_f^(~XFw)>J*r!bk?*ex5saymxdCBx7VQn)IGmS zk8>6b$&tMpn;Egb)Iz=`NgqK$M3$1NZg(Ck>7HMuy4{H)r9?`cXHKN1#2kL=K8*Xg z(>g?yJv6(SQLR{`I<~ksbY!9m400000NkvXXu0mjfBOZq! diff --git a/pyscope/bin/gui/theme/light/tab-hover.png b/pyscope/bin/gui/theme/light/tab-hover.png deleted file mode 100755 index 0c6df3e27ff33d1fdb505b41866ad67fe2d25def..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprS3FE{-7)t#7X#R>Vh4SF!oFPC={3jw7IVz?s~-}ksh^hpz|}CPR-scu*@Z>L@zbUL zvpi{Rp{=V91>U3{10U!a}WIZ36zwbBeHqfhM@ zJRWG)AaE%6X}auX#^Z)1$~&x2Us`v!}gA)@k!TV&l2pJ>trx}U+*)z4*}Q$iB}W%X{x diff --git a/pyscope/bin/gui/theme/light/tab-rest.png b/pyscope/bin/gui/theme/light/tab-rest.png deleted file mode 100755 index 725beb9abf7018fe96dabd6fcb03fb0dd2aec914..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdv$07srr@*0<*j85sF_}IJxB(8DKVw7 z3BI+ekYIZ<=i$48@+WiZ#RUXs+{}AX-oc^BV&dB@p}cFI^ZoCeYd4E^a0GePADo^N z9^Vt<8 diff --git a/pyscope/bin/gui/theme/light/treeheading-hover.png b/pyscope/bin/gui/theme/light/treeheading-hover.png deleted file mode 100755 index 47bf56f4a5e8cc6b7db6816c07230ecf7d668804..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=aGT^vI!df#4k%s!$Z(DpD~ zSXHIs%>Vz5J6k*!X;|6jOkV2Ez4i8`({;-oKNPx1gnD`YX#SEOn5}xuLz8vlw#`$5 zN@9;2@MNsb&(>63xpmWf>uXUxh8jy}B?u}$_wnk?oOK~;F=J9sME1sOW{cC0KmRA8 ze1KPPX+lpXn|yN5!YM`Vu20#Z&00sP zMPOcA|KlB5ybtCi$A<6ue*H$arzY!r+uPe7&SAb+FVHZ@`1B?Lryc7)-_2Mnd3<8! iyqCZKDgU`$$vpLIzJK@D%w<5YGI+ZBxvXanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-oLT^vI!df#3>n{~v1$L(VF zPkzUH|9zV`_B_>FHo39U>9Rq|Gvo z7dcbo=y&J#y`K_v!cu(wbiPNDtFIp#+#ix-tidtrV{;XKe= N44$rjF6*2UngEe6hY$b& diff --git a/pyscope/bin/gui/theme/light/treeheading-rest.png b/pyscope/bin/gui/theme/light/treeheading-rest.png deleted file mode 100755 index d4aa09591d8d749f0a36640afa74788ea3e987c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt<= zSI5X{74HQnR_h*{akeLO)`xqK=iD~mFKw)RtYg(B6JDhzh80@(elPEHS5o?28M^m6 zLy(unbH3*Fh36&){c0&bXFuzUsnVwX=PTc?Ruiu|DE=pY!_36Aoe)+>< zMTXqlMt#bPEZcIedGc<%aWvk3>vp~Lz;Ax04ePWI8*I=y{V-cl@YSwm@2gj>s%opZ Z&W|g~@xSWc@(Acf22WQ%mvv4FO#tQyiD3W$ diff --git a/pyscope/bin/gui/themeSetup.tcl b/pyscope/bin/gui/themeSetup.tcl deleted file mode 100755 index 750bb9f5..00000000 --- a/pyscope/bin/gui/themeSetup.tcl +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright © 2021 rdbende - -source bin/theme/light.tcl -source bin/theme/dark.tcl - -option add *tearOff 0 - -proc set_theme {mode} { - if {$mode == "dark"} { - ttk::style theme use "sun-valley-dark" - - array set colors { - -fg "#ffffff" - -bg "#1c1c1c" - -disabledfg "#595959" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style configure . \ - -background $colors(-bg) \ - -foreground $colors(-fg) \ - -troughcolor $colors(-bg) \ - -focuscolor $colors(-selectbg) \ - -selectbackground $colors(-selectbg) \ - -selectforeground $colors(-selectfg) \ - -insertwidth 1 \ - -insertcolor $colors(-fg) \ - -fieldbackground $colors(-selectbg) \ - -font {"Segoe Ui" 10} \ - -borderwidth 1 \ - -relief flat - - tk_setPalette \ - background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] - - ttk::style map . -foreground [list disabled $colors(-disabledfg)] - - option add *font [ttk::style lookup . -font] - option add *Treeview.show tree - option add *Menu.selectcolor $colors(-fg) - - } elseif {$mode == "light"} { - ttk::style theme use "sun-valley-light" - - array set colors { - -fg "#202020" - -bg "#fafafa" - -disabledfg "#a0a0a0" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style configure . \ - -background $colors(-bg) \ - -foreground $colors(-fg) \ - -troughcolor $colors(-bg) \ - -focuscolor $colors(-selectbg) \ - -selectbackground $colors(-selectbg) \ - -selectforeground $colors(-selectfg) \ - -insertwidth 1 \ - -insertcolor $colors(-fg) \ - -fieldbackground $colors(-selectbg) \ - -font {"Segoe Ui" 10} \ - -borderwidth 0 \ - -relief flat - - tk_setPalette background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] - - ttk::style map . -foreground [list disabled $colors(-disabledfg)] - - option add *font [ttk::style lookup . -font] - option add *Treeview.show tree - option add *Menu.selectcolor $colors(-fg) - } -} diff --git a/pyscope/bin/scripts/start_sync_manager b/pyscope/bin/scripts/start_sync_manager deleted file mode 100644 index cb503cb6..00000000 --- a/pyscope/bin/scripts/start_sync_manager +++ /dev/null @@ -1,3 +0,0 @@ -#!/home/$USER/anaconda3/python - -start_syncfiles diff --git a/pyscope/bin/scripts/start_sync_manager.bat b/pyscope/bin/scripts/start_sync_manager.bat deleted file mode 100644 index 4ca3a92a..00000000 --- a/pyscope/bin/scripts/start_sync_manager.bat +++ /dev/null @@ -1,33 +0,0 @@ -@echo OFF -rem How to run a Python script in a given conda environment from a batch file. - -rem It doesn't require: -rem - conda to be in the PATH -rem - cmd.exe to be initialized with conda init - -rem Define here the path to your conda installation -set CONDAPATH=C:\Users\%USERNAME%\Anaconda3\ -rem Define here the name of the environment -set ENVNAME=base - -rem The following command activates the base environment. -rem call C:\ProgramData\Miniconda3\Scripts\activate.bat C:\ProgramData\Miniconda3 -if %ENVNAME%==base (set ENVPATH=%CONDAPATH%) else (set ENVPATH=%CONDAPATH%\envs\%ENVNAME%) - -rem Activate the conda environment -rem Using call is required here, see: https://stackoverflow.com/questions/24678144/conda-environments-and-bat-files -call %CONDAPATH%\Scripts\activate.bat %ENVPATH% - -rem Run a python script in that environment -python start_syncfiles - -rem Deactivate the environment -call conda deactivate - -rem If conda is directly available from the command line then the following code works. -rem call activate someenv -rem python script.py -rem conda deactivate - -rem One could also use the conda run command -rem conda run -n someenv python script.py diff --git a/pyscope/bin/scripts/start_telrun b/pyscope/bin/scripts/start_telrun deleted file mode 100644 index 5e52deee..00000000 --- a/pyscope/bin/scripts/start_telrun +++ /dev/null @@ -1,3 +0,0 @@ -#!/home/$USER/anaconda3/python - -start_telrun diff --git a/pyscope/bin/scripts/start_telrun.bat b/pyscope/bin/scripts/start_telrun.bat deleted file mode 100644 index 1cc05a73..00000000 --- a/pyscope/bin/scripts/start_telrun.bat +++ /dev/null @@ -1,33 +0,0 @@ -@echo OFF -rem How to run a Python script in a given conda environment from a batch file. - -rem It doesn't require: -rem - conda to be in the PATH -rem - cmd.exe to be initialized with conda init - -rem Define here the path to your conda installation -set CONDAPATH=C:\Users\%USERNAME%\Anaconda3\ -rem Define here the name of the environment -set ENVNAME=base - -rem The following command activates the base environment. -rem call C:\ProgramData\Miniconda3\Scripts\activate.bat C:\ProgramData\Miniconda3 -if %ENVNAME%==base (set ENVPATH=%CONDAPATH%) else (set ENVPATH=%CONDAPATH%\envs\%ENVNAME%) - -rem Activate the conda environment -rem Using call is required here, see: https://stackoverflow.com/questions/24678144/conda-environments-and-bat-files -call %CONDAPATH%\Scripts\activate.bat %ENVPATH% - -rem Run a python script in that environment -python start_telrun - -rem Deactivate the environment -call conda deactivate - -rem If conda is directly available from the command line then the following code works. -rem call activate someenv -rem python script.py -rem conda deactivate - -rem One could also use the conda run command -rem conda run -n someenv python script.py diff --git a/pyscope/gui/theme/dark.tcl b/pyscope/gui/theme/dark.tcl deleted file mode 100644 index 4a619981..00000000 --- a/pyscope/gui/theme/dark.tcl +++ /dev/null @@ -1,484 +0,0 @@ -# Copyright © 2021 rdbende - -# A stunning dark theme for ttk based on Microsoft's Sun Valley visual style - -package require Tk 8.6 - -namespace eval ttk::theme::sun-valley-dark { - variable version 1.0 - package provide ttk::theme::sun-valley-dark $version - - ttk::style theme create sun-valley-dark -parent clam -settings { - proc load_images {imgdir} { - variable images - foreach file [glob -directory $imgdir *.png] { - set images([file tail [file rootname $file]]) \ - [image create photo -file $file -format png] - } - } - - load_images [file join [file dirname [info script]] dark] - - array set colors { - -fg "#ffffff" - -bg "#1c1c1c" - -disabledfg "#595959" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style layout TButton { - Button.button -children { - Button.padding -children { - Button.label -side left -expand 1 - } - } - } - - ttk::style layout Toolbutton { - Toolbutton.button -children { - Toolbutton.padding -children { - Toolbutton.label -side left -expand 1 - } - } - } - - ttk::style layout TMenubutton { - Menubutton.button -children { - Menubutton.padding -children { - Menubutton.label -side left -expand 1 - Menubutton.indicator -side right -sticky nsew - } - } - } - - ttk::style layout TOptionMenu { - OptionMenu.button -children { - OptionMenu.padding -children { - OptionMenu.label -side left -expand 1 - OptionMenu.indicator -side right -sticky nsew - } - } - } - - ttk::style layout Accent.TButton { - AccentButton.button -children { - AccentButton.padding -children { - AccentButton.label -side left -expand 1 - } - } - } - - ttk::style layout TCheckbutton { - Checkbutton.button -children { - Checkbutton.padding -children { - Checkbutton.indicator -side left - Checkbutton.label -side right -expand 1 - } - } - } - - ttk::style layout Switch.TCheckbutton { - Switch.button -children { - Switch.padding -children { - Switch.indicator -side left - Switch.label -side right -expand 1 - } - } - } - - ttk::style layout Toggle.TButton { - ToggleButton.button -children { - ToggleButton.padding -children { - ToggleButton.label -side left -expand 1 - } - } - } - - ttk::style layout TRadiobutton { - Radiobutton.button -children { - Radiobutton.padding -children { - Radiobutton.indicator -side left - Radiobutton.label -side right -expand 1 - } - } - } - - ttk::style layout Vertical.TScrollbar { - Vertical.Scrollbar.trough -sticky ns -children { - Vertical.Scrollbar.uparrow -side top - Vertical.Scrollbar.downarrow -side bottom - Vertical.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout Horizontal.TScrollbar { - Horizontal.Scrollbar.trough -sticky ew -children { - Horizontal.Scrollbar.leftarrow -side left - Horizontal.Scrollbar.rightarrow -side right - Horizontal.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout TSeparator { - TSeparator.separator -sticky nsew - } - - ttk::style layout TCombobox { - Combobox.field -sticky nsew -children { - Combobox.padding -expand 1 -sticky nsew -children { - Combobox.textarea -sticky nsew - } - } - null -side right -sticky ns -children { - Combobox.arrow -sticky nsew - } - } - - ttk::style layout TSpinbox { - Spinbox.field -sticky nsew -children { - Spinbox.padding -expand 1 -sticky nsew -children { - Spinbox.textarea -sticky nsew - } - - } - null -side right -sticky nsew -children { - Spinbox.uparrow -side left -sticky nsew - Spinbox.downarrow -side right -sticky nsew - } - } - - ttk::style layout Card.TFrame { - Card.field { - Card.padding -expand 1 - } - } - - ttk::style layout TLabelframe { - Labelframe.border { - Labelframe.padding -expand 1 -children { - Labelframe.label -side left - } - } - } - - ttk::style layout TNotebook { - Notebook.border -children { - TNotebook.Tab -expand 1 - Notebook.client -sticky nsew - } - } - - ttk::style layout TNotebook.Tab { - Notebook.tab -expand 1 -children { - Notebook.padding -expand 1 -sticky nsew -children { - Notebook.image -side left -sticky w - Notebook.text -side right -expand 1 - } - } - } - - ttk::style layout Treeview.Item { - Treeitem.padding -sticky nsew -children { - Treeitem.image -side left -sticky {} - Treeitem.indicator -side left -sticky {} - Treeitem.text -side left -sticky {} - } - } - - # Button - ttk::style configure TButton -padding {8 4} -anchor center -foreground $colors(-fg) - - ttk::style map TButton -foreground \ - [list disabled #7a7a7a \ - pressed #d0d0d0] - - ttk::style element create Button.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-disabled) \ - disabled $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Toolbutton - ttk::style configure Toolbutton -padding {8 4} -anchor center - - ttk::style element create Toolbutton.button image \ - [list $images(empty) \ - {selected disabled} $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Menubutton - ttk::style configure TMenubutton -padding {8 4 0 4} - - ttk::style element create Menubutton.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create Menubutton.indicator image $images(arrow-down) -width 28 -sticky {} - - # OptionMenu - ttk::style configure TOptionMenu -padding {8 4 0 4} - - ttk::style element create OptionMenu.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create OptionMenu.indicator image $images(arrow-down) -width 28 -sticky {} - - # Accent.TButton - ttk::style configure Accent.TButton -padding {8 4} -anchor center -foreground #000000 - - ttk::style map Accent.TButton -foreground \ - [list pressed #25536a \ - disabled #a5a5a5] - - ttk::style element create AccentButton.button image \ - [list $images(button-accent-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-accent-disabled) \ - selected $images(button-accent-rest) \ - pressed $images(button-accent-pressed) \ - active $images(button-accent-hover) \ - ] -border 4 -sticky nsew - - # Checkbutton - ttk::style configure TCheckbutton -padding 4 - - ttk::style element create Checkbutton.indicator image \ - [list $images(check-unsel-rest) \ - {alternate disabled} $images(check-tri-disabled) \ - {selected disabled} $images(check-disabled) \ - disabled $images(check-unsel-disabled) \ - {pressed alternate} $images(check-tri-hover) \ - {active alternate} $images(check-tri-hover) \ - alternate $images(check-tri-rest) \ - {pressed selected} $images(check-hover) \ - {active selected} $images(check-hover) \ - selected $images(check-rest) \ - {pressed !selected} $images(check-unsel-pressed) \ - active $images(check-unsel-hover) \ - ] -width 26 -sticky w - - # Switch.TCheckbutton - ttk::style element create Switch.indicator image \ - [list $images(switch-off-rest) \ - {selected disabled} $images(switch-on-disabled) \ - disabled $images(switch-off-disabled) \ - {pressed selected} $images(switch-on-pressed) \ - {active selected} $images(switch-on-hover) \ - selected $images(switch-on-rest) \ - {pressed !selected} $images(switch-off-pressed) \ - active $images(switch-off-hover) \ - ] -width 46 -sticky w - - # Toggle.TButton - ttk::style configure Toggle.TButton -padding {8 4 8 4} -anchor center -foreground $colors(-fg) - - ttk::style map Toggle.TButton -foreground \ - [list {selected disabled} #a5a5a5 \ - {selected pressed} #d0d0d0 \ - selected #000000 \ - pressed #25536a \ - disabled #7a7a7a - ] - - ttk::style element create ToggleButton.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-disabled) \ - {pressed selected} $images(button-rest) \ - {active selected} $images(button-accent-hover) \ - selected $images(button-accent-rest) \ - {pressed !selected} $images(button-accent-rest) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Radiobutton - ttk::style configure TRadiobutton -padding 4 - - ttk::style element create Radiobutton.indicator image \ - [list $images(radio-unsel-rest) \ - {selected disabled} $images(radio-disabled) \ - disabled $images(radio-unsel-disabled) \ - {pressed selected} $images(radio-pressed) \ - {active selected} $images(radio-hover) \ - selected $images(radio-rest) \ - {pressed !selected} $images(radio-unsel-pressed) \ - active $images(radio-unsel-hover) \ - ] -width 26 -sticky w - - # Scrollbar - ttk::style element create Horizontal.Scrollbar.trough image $images(scroll-hor-trough) -sticky ew -border 6 - ttk::style element create Horizontal.Scrollbar.thumb image $images(scroll-hor-thumb) -sticky ew -border 3 - - ttk::style element create Horizontal.Scrollbar.rightarrow image $images(scroll-right) -sticky {} -width 12 - ttk::style element create Horizontal.Scrollbar.leftarrow image $images(scroll-left) -sticky {} -width 12 - - ttk::style element create Vertical.Scrollbar.trough image $images(scroll-vert-trough) -sticky ns -border 6 - ttk::style element create Vertical.Scrollbar.thumb image $images(scroll-vert-thumb) -sticky ns -border 3 - - ttk::style element create Vertical.Scrollbar.uparrow image $images(scroll-up) -sticky {} -height 12 - ttk::style element create Vertical.Scrollbar.downarrow image $images(scroll-down) -sticky {} -height 12 - - # Scale - ttk::style element create Horizontal.Scale.trough image $images(scale-trough-hor) \ - -border 5 -padding 0 - - ttk::style element create Vertical.Scale.trough image $images(scale-trough-vert) \ - -border 5 -padding 0 - - ttk::style element create Scale.slider \ - image [list $images(scale-thumb-rest) \ - disabled $images(scale-thumb-disabled) \ - pressed $images(scale-thumb-pressed) \ - active $images(scale-thumb-hover) \ - ] -sticky {} - - # Progressbar - ttk::style element create Horizontal.Progressbar.trough image $images(progress-trough-hor) \ - -border 1 -sticky ew - - ttk::style element create Horizontal.Progressbar.pbar image $images(progress-pbar-hor) \ - -border 2 -sticky ew - - ttk::style element create Vertical.Progressbar.trough image $images(progress-trough-vert) \ - -border 1 -sticky ns - - ttk::style element create Vertical.Progressbar.pbar image $images(progress-pbar-vert) \ - -border 2 -sticky ns - - # Entry - ttk::style configure TEntry -foreground $colors(-fg) - - ttk::style map TEntry -foreground \ - [list disabled #757575 \ - pressed #cfcfcf - ] - - ttk::style element create Entry.field \ - image [list $images(entry-rest) \ - {focus hover !invalid} $images(entry-focus) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - {focus !invalid} $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding 8 -sticky nsew - - # Combobox - ttk::style configure TCombobox -foreground $colors(-fg) - - ttk::style map TCombobox -foreground \ - [list disabled #757575 \ - pressed #cfcfcf - ] - - ttk::style configure ComboboxPopdownFrame -borderwidth 1 -relief solid - - ttk::style map TCombobox -selectbackground [list \ - {readonly hover} $colors(-selectbg) \ - {readonly focus} $colors(-selectbg) \ - ] -selectforeground [list \ - {readonly hover} $colors(-selectfg) \ - {readonly focus} $colors(-selectfg) \ - ] - - ttk::style element create Combobox.field \ - image [list $images(entry-rest) \ - {readonly disabled} $images(button-disabled) \ - {readonly pressed} $images(button-pressed) \ - {readonly hover} $images(button-hover) \ - readonly $images(button-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 28 8} - - ttk::style element create Combobox.arrow image $images(arrow-down) -width 35 -sticky {} - - # Spinbox - ttk::style configure TSpinbox -foreground $colors(-fg) - - ttk::style map TSpinbox -foreground \ - [list disabled #757575 \ - pressed #cfcfcf - ] - - ttk::style element create Spinbox.field \ - image [list $images(entry-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 54 8} -sticky nsew - - ttk::style element create Spinbox.uparrow image $images(arrow-up) -width 35 -sticky {} - ttk::style element create Spinbox.downarrow image $images(arrow-down) -width 35 -sticky {} - - # Sizegrip - ttk::style element create Sizegrip.sizegrip image $images(sizegrip) \ - -sticky nsew - - # Separator - ttk::style element create TSeparator.separator image $images(separator) - - # Card - ttk::style element create Card.field image $images(card) \ - -border 10 -padding 4 -sticky nsew - - # Labelframe - ttk::style element create Labelframe.border image $images(card) \ - -border 5 -padding 4 -sticky nsew - - # Notebook - ttk::style configure TNotebook -padding 1 - - ttk::style element create Notebook.border \ - image $images(notebook-border) -border 5 -padding 5 - - ttk::style element create Notebook.client image $images(notebook) - - ttk::style element create Notebook.tab \ - image [list $images(tab-rest) \ - selected $images(tab-selected) \ - active $images(tab-hover) \ - ] -border 13 -padding {16 14 16 6} -height 32 - - # Treeview - ttk::style element create Treeview.field image $images(card) \ - -border 5 - - ttk::style element create Treeheading.cell \ - image [list $images(treeheading-rest) \ - pressed $images(treeheading-pressed) \ - active $images(treeheading-hover) - ] -border 5 -padding 15 -sticky nsew - - ttk::style element create Treeitem.indicator \ - image [list $images(arrow-right) \ - user2 $images(empty) \ - user1 $images(arrow-down) \ - ] -width 26 -sticky {} - - ttk::style configure Treeview -background $colors(-bg) -rowheight [expr {[font metrics font -linespace] + 2}] - ttk::style map Treeview \ - -background [list selected #292929] \ - -foreground [list selected $colors(-selectfg)] - - # Panedwindow - # Insane hack to remove clam's ugly sash - ttk::style configure Sash -gripcount 0 - } -} diff --git a/pyscope/gui/theme/dark/arrow-down.png b/pyscope/gui/theme/dark/arrow-down.png deleted file mode 100644 index 122ee4599d9d9eee5a4c8fb73d0f428ae7076c1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt;1XT^vI=X6E)9avgFIad|#F zS8UrSy>DSo4GEO|ZY$F%aCih3j5Ro_y@0xmC4t-Ks1@rL&%K3~yK%_I$sr;+d!7Upepi{4FY#0bXl2=~SQk zpm+LG##-eD4W;?}-kZl>U)#N?g0bPx@>$C+=H8BDJRmVSD*OK7Z>*L))>mKT@N5G* Olfl!~&t;ucLK6U9iewxB diff --git a/pyscope/gui/theme/dark/arrow-right.png b/pyscope/gui/theme/dark/arrow-right.png deleted file mode 100644 index 2638d885eb9be2833b009ef5b88081a4b22b34b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1g!3HF2ETDGVhgXJ?3QcUSGUi&$t1y1Z}2x(bF22!HCMuQ zI$p3OWUj4!E%8|9z|yT|74PF25?r3-^xU+1J}>zd^VQSFKbf9auLQb|!PC{xWt~$( F697n>UrPW0 diff --git a/pyscope/gui/theme/dark/arrow-up.png b/pyscope/gui/theme/dark/arrow-up.png deleted file mode 100644 index f935a0d32a699308758f10b1ccee21c92e7c6b64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=OCT^vI=X2$jzavgFIVSTQi z*}{5|xk5}oLD5M{aRSQ`Y(kAvd{KEfjKk-)x RB?6tw;OXk;vd$@?2>=XyVnF}^ diff --git a/pyscope/gui/theme/dark/button-accent-disabled.png b/pyscope/gui/theme/dark/button-accent-disabled.png deleted file mode 100644 index bf7bd9ba70f9371a9c3d98b8f97039e611018479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|;*h7sn8b-nZur`3@@xxLiDW z!YS&7&*{JVX}8u2I5|~J?otbV6Yxv^!OQ(YUir^ul6nLLCmfl2hG(tm3azU5H|7+3 zXvVgssR%kXbSOOMIVO>xeE-9~_X|4y^q$@G{Xy4mNzXNx)qXYb|7h6XkhYD-*m%R% zZ9HqXHm&Lso&LPiDfHky>!Ncjw%%(x%d=EO=6Itao1J61{YF2TkV2sI7(8A5T-G@y GGywpYLS~o% diff --git a/pyscope/gui/theme/dark/button-accent-hover.png b/pyscope/gui/theme/dark/button-accent-hover.png deleted file mode 100644 index 8aea9dd682773a2a5ed32f6a9c14a4db8f2c00ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 373 zcmV-*0gC>KP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Rl-x zK~y-6?bN+W!%!TC@#mZrlbRw0K~Ro^QncWv1YEoUXX)bTF1-OKZ$JkJXGg`wJ8*C) z4h}*mv5QFkz*dZslX(8l-Avjs&jtK=r&k$cB%EKO(P~p~MpUXHdCGobKDr$q&X0(n zd#KfDOEwP9aDENz1j)sL0;aUWOJZ&hchQYj8|T+Z3Sa;NBu>bJkma3ybiEm2r3f`z z>V(;~O?0IiG64?}@PZIMJ~dhimS73~3$*bkY66f_kbVu(Z}&HN?sxoUE~9^UjqY8Z zkPco@nWfO}h@dh{`rha9;uvL&kzj6-h3!3NR@Tw}bS}>&-weAQo~}-@gIE3l45nSi T5+#gb00000NkvXXu0mjf@z|C; diff --git a/pyscope/gui/theme/dark/button-accent-pressed.png b/pyscope/gui/theme/dark/button-accent-pressed.png deleted file mode 100644 index edc1114e5e03094ebe8796dd528ca5fb932ec9eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|*)h7sn8b-nUn6{g@nO+CDzF zx9*;udEr#r)!YL?N^{c#^cI$c$sIY?*5W;(fLphNmz7r~>{yG(qSJL}`&z|jMeIn} zd7iQEWb|I4wuQ#O+g`s687wYMU0!lt z4%JQPuG`#;Tf8_<{9(Jro53Y5417}iTRn$ z_kpST7yD~>(}-r%AL<)ClBVSFb0$nKzmjF$cdf6(yaQPpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10R~A# zK~y-6?bN+W13?sq;df?N$>0*f%3{PuB$bt@SXo&HyaDk7(pq>0iU@Z0f>_yDiC`o6 zgBBKIBg7`4kOXx#8{C~uW-P1}3)iIF_W~Y1PH}|R+68)o$>lXhrsnZgK$bcm5ihsr z-0!Z_etbZb<`&%S<{`FFB(>l&aUg&J310lpGEAgY~T(%)uP|6KJA0L?0jlm{y2v-kc)4Y0sdvDfR0U zWOaWF^IS)m1l&KKqO*`DB+VzPdm9L?wNsmmtT}V`<;yxP|+?=7sn8b-nUn**Bvq7alWYC zX%>^hdGkM4tVE}Mu^?#5Q&%xNvF6_YE-E-BXhO@(LiKFjs`p#2NhB}mNVsiQ zbk5~Sa)8j*un;fB=RGP_@6TA~H@@@p;xt}7rO8`KNiLD0*ston3yY@eoihfT-EYKy z`K?vI=FepDnuFqhIAgLmUOV)6=i7HNe((R@+N{1}w{oW;+w;mr*Q6#fOend#|C#lM ummtT}V`<;yxP|-q97sn8b-nUl{u4*O1 zsC*))jVhBP;;;%B={W8H-&&gXs@sj0Mt`_R8PZ22G9 z?3wQVH9B+GbL+L!2WszoE)}`+ce%W}QbDF^6Uc VrPTJsX8;|`;OXk;vd$@?2>?~eZKMDI diff --git a/pyscope/gui/theme/dark/button-pressed.png b/pyscope/gui/theme/dark/button-pressed.png deleted file mode 100644 index a1c525723e796c3070b3c319e7e9e70a06435650..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|;dX7sn8b-nUmAyILH0+#ZTg zZsL$T_Zj!g-yInS;MwD{tse`FwiUx|OSTSf{mh7zCvW32wReVsG-QQ=4>L zSbp!DJkxnW#|N{lwKH}L3%VQ;_)@vBIyZMN?9Bi75GgYw&Fx8DakoWax8&t;ucLK6VlYj_|4 diff --git a/pyscope/gui/theme/dark/button-rest.png b/pyscope/gui/theme/dark/button-rest.png deleted file mode 100644 index ec427edba67de887bffcb0673518c072742c194d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+?=7sn8b-nUaX_O%%Bu--2g zYMH`T_3!_84F^G$8~fgEiF)fPzAd$?|5)$Y)JU6S9dnF>yc9a+?#Qk+ee;_y)V0Sj zX_ndwt*GpR?|O=!EdmDTjjxrup0RWeUAQWC(e*>GcL_K-oRgds)HL(%q`gUL5^ao5 zo2t+5_wsbwXSVkvhx|k3KhrOq*_Ho5-Q&<1g{@&7HS%6U&nun0Gz-c*POJ@@w7+<% uh)d|vZMhQ1KVIWzXo%A8d)zSdAv-rqX!7?9^YVbcVDNPHb6Mw<&;$TnqIMJj diff --git a/pyscope/gui/theme/dark/card.png b/pyscope/gui/theme/dark/card.png deleted file mode 100644 index d87fefc3bf6327821608d7200f19bb10ecd9f348..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nlcPZ!6KjK;UO4t8lP3b;L7 z9Fe5dSl6)U|7X?ikoKSgG4XdvVa{m_RxjMQcJ_aTcRt^L*D_v-%3K@hrMR>uJzMwL zoHxJENia7s73_@@6b$+Lzp-4&>AkG6bLh#fVJTn>W<9 z>iY4x_2O$yi!`5xn18%0(Hz8e^Y`I@ol}HQZu?(LO%ZZ0>r@fsgK^Y8EV spwMS?V)VrGw%^XQo3~OcjqQN=osEgJ*1Y451co1jr>mdKI;Vst06Ai#e*gdg diff --git a/pyscope/gui/theme/dark/check-disabled.png b/pyscope/gui/theme/dark/check-disabled.png deleted file mode 100644 index f766ebafa3c9fa5b7dcf944e0a8c14961cb9c35a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 383 zcmV-_0f7FAP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Srk* zK~y-6t!pis{hQtTpX$cm-iIH|HLr4a>Y{eFvb$3Kgdvm}1 ziF0nsvW%iAu+}0XypyWZ_dVWwLI|W;mf@Ux8=4alw%aY%TH3ZnilSJI{e*YB9b&Eh z7@N!U99d)CoQSZVHls_Pj4>Pzhjl<>3}soe-|s&MjWL`~C#GqlX_|QOcs!D(>91Il zB-C}yIF9k4bB?ksztLFiC3v}97>0rK`OIdsiN;=ns!G!|gb=8zDjItU&S+iN+-|pM z3?TjMsH$AA*C^=Ug-a7vT@6!Jr0@IHz^>~M?>%d*9LEvwJrY8oZCkpoTLh~rkH>@i d{m%3GpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10cS}> zK~y-6tm>%sQxjsVJvc0EU6MnKcjAF7lwN(ubAy zmZ8%w!pUQ>oXb4ec(}%WYT*|(iy4CA#vxd@7sbRpR9P~O8G^*&L})t;=co@?t22P6 zA+nPnh#7)ED2Gtu2xH+ngp)ZCCpQoS0493=uA7=Pnf*V7DlOYE+HQi#)v|5i=VKA1 zUdVv9Wy@H%`GNZBKG+eWrKHLYIDY^xe*lcb+%BS6Dv Q3;+NC07*qoM6N<$f;?EuF#rGn diff --git a/pyscope/gui/theme/dark/check-pressed.png b/pyscope/gui/theme/dark/check-pressed.png deleted file mode 100644 index 4f9d1fc42c780c8e259064075cbd9860a8364fea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 460 zcmV;-0WpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10a-~z zK~y-6t*@gx0wzLfh4$rF{$OD)z^;@p5ti${WpvF#upG3B0}sD&>m{W78~cZh=O<*?*y8 z8Z z2=(vc3+f+zsEYiHhQ(%r*-{NuNMLY#0oO7^V>3Yz3SO#$B}MdZ&L>#85s1wO0T9Z- zOEpM*V}dajhyej#CX``qXEziS*l^xZ$1-BWTvG=fciXXnqvw0jq1=LF>I-AGF@kz| z3ML6S)E*QhGRsJ2RzQSAB*?W4Xpais&W`Y{ec=a+8+x@xd5_ru0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10cc4? zK~y-6t21Mxu(0 zQZ|KLECMC}3TAA2PMr)V!1neTg4*C}8P5SPnE8ECbxbXEh5aTyUW4)7R8*rgwW5j) zVipDftW5whP5bFzAZ^s}etBpPNENXs$fjZRvjS4erc48i zPq!esSOm0M?iy=WipU)W!P*(RBV|PJ4kxUGP7sba1W`rCM@Ga;bOUv%fNy)~je;~? RX`%oC002ovPDHLkV1i|U#n%7; diff --git a/pyscope/gui/theme/dark/check-tri-disabled.png b/pyscope/gui/theme/dark/check-tri-disabled.png deleted file mode 100644 index a9d31c760240a0deceaf99ab0f3a622de75aab08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|;>j7sn8b-lrE0xegofv_1?M z1bVY|fRyO!+&Ofw{1T8w8(7M&9i zoDy`w^0`2=4e!+sj(gPx#z9^S1P-OXk(6s=JaQ&KdF#ISj=I-?a&NbE{gb~TD|@W4 z<$K-tWvgZ^%l!UrXRAAR+}FSUKN&BanU$EvcFNM6IaE|dDPv>NZLmmtT}V`<;yxP|rq34lPpgIb0nC7z*t?s*(ZUIS$KV$=Fzr;4Ev|CG7GOvY~wd5EHE|*JZmyz zq4IeLu^1uqX=TPW^}m@`%Pil(((G2E8D{w@=ERAPXsv?Zum7h=b}{q(^N?g>`}gmmtT}V`<;yxP|;^k7sn8b-sCA0C;mLIsCHW6 z<8Y+ z<0bR_&!s0!`r@_hh5;K8r19mkd7gJ+Q_D-O?c!}_j0<&UQwuuGBcG7;JSkz(tV{!E z_j!#vJ5=t)S)cvz{CN1ASr$7aTekXaJT>vTliQsji`b6StStu~^iNStcyNFI{S|39 z_5-b%vG2}K2UBMIyEcape5tu@)3)D2$UyVK`R)EZ=eM0nN_du*nDL|HsRXn3egkId z{T$57+}fUOe}Dh%efaolG7rC)so|f0KiU`>bV3~Ue19Dl26~^t)78&qol`;+0A4kZ AaR2}S diff --git a/pyscope/gui/theme/dark/check-tri-rest.png b/pyscope/gui/theme/dark/check-tri-rest.png deleted file mode 100644 index 26edcdb13c66fa429b933f7af6a8ae04a2cc17a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmV-x0hIoUP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10QgBn zK~y-6W0aAR`OhV-!XO`0$-u8;#lXzT!$1o7`~3^U$IC|<9xQEV`2O@R1EZR`!+*WD zjSS4(d?Xo!Ver>a3>T~X7#LkA@BA-h>PC@aF!1)|b_T|f{h$An>P=kW@AoeZOw=-r zfq{X6nUjZsiE5@n!3Z>v1((FtIgJe8-#o)A$0;Jkpq)~WRgOFtGBVmmtT}V`<;yxP|;CO7sn8b-nUmbcC{D?G(4;@Y%WV|Kvzc#YHU*Gwy`4hsSJu7x(f%gS@eiVSnbVeY@5vw=@{C2|n~%YwD$W zM8dL<`EIG~`R9|T2%X*(kiC~hYw3n-M!v$IZ5BIoESVB<{j1QI;I&hy2r)diP&y{@ zt+t-~_uXx|Uuye1PCPaJzv^07N5NYr{gP($ie~-BwWd>osw!q^?+1E_!PC{xWt~$( F69Ag1dV&A| diff --git a/pyscope/gui/theme/dark/check-unsel-hover.png b/pyscope/gui/theme/dark/check-unsel-hover.png deleted file mode 100644 index 6d00402ad42910895fa8ec5617d5e50f9244e6b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-V27sn8b-nVBA``jED*e>Mf zBz0X}IN9fq>{XW?4d)bN4sm|d;(NgMh)GCR^yC&UR!$?4ZU0w`t}SnHmpQ&u=d_c| z@dtC7gSxDz1WgE9v`Wk8GK*5nj!P!4mrY*m;(NX89+zbD2OIuG38nMJ6Le0mE0hVm z{&nq(Xc5;9xzbY4WpZ!Zonly!8(m{3pD`sU=k_;?JKK17t#hunsjWP0UURs<)l1Xh zn4EiCqJt)@uNUX}&vNbi-X}}2{dx2I-)t354#gSIC7)N`>yk(oa8lr45^-gGWN|Ka wmf9nWw%5BZl>QB?kYWF|&pmQl#5C6WJ;|Y;8LQ-;1AWfm>FVdQ&MBb@0B_=m>i_@% diff --git a/pyscope/gui/theme/dark/check-unsel-pressed.png b/pyscope/gui/theme/dark/check-unsel-pressed.png deleted file mode 100644 index 67d6bb246ff2d2e9d072f90480c5ebb3279d6faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|{9^wwTk8g#epjQs&&4#vZqbaG|fmrN0GVh~)YzBO!xmJ7@A z!i0$HThD&(3F11#v-O(ZgpLDe64G*Rw?%t#E)`jkCH^Jq;lB47YZGUw1#mE?eG~mA zW$fG|=;CrWci-wM9T&_S`HLFGD;nh+&&&Rith&Pz6l(DK{DSXtmgzvFs{19B=M>+a tP|T;uVqomTa@ao3;hdyW(`TDPb|;olzx`f~a902T diff --git a/pyscope/gui/theme/dark/check-unsel-rest.png b/pyscope/gui/theme/dark/check-unsel-rest.png deleted file mode 100644 index 10cd31b102682ca0b2fcef0f6057191833ce7194..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-V27sn8b-nVBA``jED*e>Mf z7|jX{>~#6_x5G(kN>q9Qr}~Z677fxJ)5LT;U33KvCtkeqZ++O7dyH}$bzU3sFc-=k z-j=&!RhRXWDKnOFhK43aiV2+myw1e;q6zP>eaH8`-|low!s0o{VS&nd%_)&(XFvDs zT9@y7f00I0=B$96+iurO`;8bfZi~%7&)&6am4K5CU#q}^DNdC(wUvj>YYx}926-7A zlXIW7%+ZT8b(UJeIr;W=uajil|HvHwI9r92LowpI=<~{ZT@uM3YULdT7HDt@oZh4t vdtLCjq0Di{rC-7-=Bc+OIz&#pv5mLh-5^ZFF6mDy(B}-Eu6{1-oD!M diff --git a/pyscope/gui/theme/dark/empty.png b/pyscope/gui/theme/dark/empty.png deleted file mode 100644 index 22183634d5e36298e12ed067750da6c7d2fcdea9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0TuCix;TbNOifOZU~Lv=U|^iZ Vz$kX=awbrQ!PC{xWt~$(699LkAlU!_ diff --git a/pyscope/gui/theme/dark/entry-disabled.png b/pyscope/gui/theme/dark/entry-disabled.png deleted file mode 100644 index 9d25dc8b95a58511cf0912ec600abf356eeaee56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-Y37sn8b-nSPF`C1HkSTA-8 zPw19u{5{{mL$LGh+7;!Ex-;+j$NSyh=6X&tbm|-;b-0VZrr)2mKTH2dll~8F3Bmft zATJjdqvtM9eg+68_B4LEchb_{>HP0|)<@4|tX;Tj&Fd;|o4)4S`-SCp?;YJTqj(mB PT*~0->gTe~DWM4fa`k4= diff --git a/pyscope/gui/theme/dark/entry-focus.png b/pyscope/gui/theme/dark/entry-focus.png deleted file mode 100644 index 30310fb37156559369b5e99bdfc34f7eb568be5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+Pv7sn8b-nUl_y;uxI+CRQm zohhIy)}^3R+-2J;{*mLGnc2N#VISCr@zsC%IyB-?1z4~z5Bv+dF$G>zZ>s#PX5BpY diff --git a/pyscope/gui/theme/dark/entry-hover.png b/pyscope/gui/theme/dark/entry-hover.png deleted file mode 100644 index 6b93830ab5908d7af96c755acd111761524e0f4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|++;7sn8b-nUmaiZ&bYv^<>L zs&QolLx1LsTc<17q(oob^b(#GQuyf7e}?{99(~(L!!-< zWzueb=J&tdjEs9CZbmiFOkBCOX=dR(^}fdr@9lS4dug7qT)eeSyndef6wjY#F+f)` Nc)I$ztaD0e0suTIW;6f* diff --git a/pyscope/gui/theme/dark/entry-invalid.png b/pyscope/gui/theme/dark/entry-invalid.png deleted file mode 100644 index 7304b247cd136a2042d9ef347406655172be6537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-zC7sn8b-nUl{`W`V5aC>OJ zEx9tX`*fT|M2Lg`76;iY4#86x+4%%El_jO`SSR2yBjz*Lk1yv_RhBNf7UjfY@mxT# zDkwB-?UOl~Yag!JdPL%+rLnK@s#~mG9QUe|dU_sra4SwIUN+?tYpkBmQU#?J0fTdv z&no|kmqlu}CQ2A6U8;SS@6C87?Pav}j%Mp0>H(p<>mT$_FkGRv>{{3NI^!36(<8<1 zyi9)b@oDesYi5m$x7ys9Ghxc4d+Sd9{jFXe{A|Oz*b6r$=Jm7BTi3sbtA_D~anSBl Ths@^zJ;&hb>gTe~DWM4f{>_9> diff --git a/pyscope/gui/theme/dark/entry-rest.png b/pyscope/gui/theme/dark/entry-rest.png deleted file mode 100644 index e8767526ae057387f873d60cc1ec1ae578b77cec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-F|7sn8b-nUma_O>|iv_4Fi zR`<(bxX5RFna6a3#es^NlZt|K{H%9Ne|rA;WhT>Q6JJ#&1LGB19UQiyqDoBf`pz6t zP?9(a0>V{Rj@*l(t3a;Xk5lr}3qhb8mf^Wm_=9!90`MP`gbOaCHHB3=+VL81i pVP5)*EO80J8{2qa?^?9AP2QBpXKBq|V~{r)JYD@<);T3K0RWGPZ215H diff --git a/pyscope/gui/theme/dark/notebook-border.png b/pyscope/gui/theme/dark/notebook-border.png deleted file mode 100644 index 0827a074e2330c873964133f877788a1fb4cbeb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?4G5)fv*mnL5U6qGD+ zjVKAuPb(=;EJ|f4FE7{2%*!rLPAo{(%P&fw{mw=TsOX-ji(^PepF;&n47$?_anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=AJE{-7)t#8j6G9FM6U@&wz z{(F0*O38@}eB!J(jX$rEw~lAOClbOM>KGazijyj340+DDcylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=hPZ!4!iK)p`CQkf$-of?2 zBB|(e3h$>MI&k8U*F?wv|No>_Y$z0CPJXkn|9_;#&XtdL+27x9U|@T1Tf@Wu^Y4pD mfBSzvzwLOmxPd{CH4kI2rj)?bY!MZp$qb&ZelF{r5}E)bNJUcs diff --git a/pyscope/gui/theme/dark/progress-pbar-vert.png b/pyscope/gui/theme/dark/progress-pbar-vert.png deleted file mode 100644 index 3d0cb29758f0c6b066031b946818eeff9731dba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTn!3HEs#ylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=PPZ!4!iK)p`CQkf$-of>N yi|=6xAc)wsX_G`tOUsI+8@tQjOGvQqoz5s_&i0<^Jj+g?E(T9mKbLh*2~7YrkuMhj diff --git a/pyscope/gui/theme/dark/progress-trough-vert.png b/pyscope/gui/theme/dark/progress-trough-vert.png deleted file mode 100644 index 22a8c1c640586e6d8af59d584ff97089b570fec8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTn!3HEbP0l+XkK Dj5ILM diff --git a/pyscope/gui/theme/dark/radio-disabled.png b/pyscope/gui/theme/dark/radio-disabled.png deleted file mode 100644 index 965136dc72ed7492600cd63e83e1a2b218717b3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmV+^0@nSBP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10k%m* zK~y-6rIp>TsxT0R7Y#p&Qxgm|K;kR#{tv=C&_Ey>(yA3w_k~-fVAOrix|`MEYi4Mt z-fp+MVkXb?Kt$l2BMd|Mz7GHx#}Svy1zKw;rEtA2K7}_2a?X(?34$P4Bu_OA1G=t* zQp)k!G!Q}{%d(F!00@ErS(ZTv@fs+EKpe*(L371%j3h}Ou@BtJoWrcOsHzH0(?Dwt z03afyX^K40!Pv97&1Qr1`3$Y~4LIEa0E{sx%W~15zVFfZJ*1QOtG#_Di)phN} zQ51pQ5A9v|AMlOlxxVka=kTxcJa6H#aU30E-qUwltyWGvjw6^c#xc`0{TevWHO7F= z`;g~3IOiW>oO9%P?s!^jFr^fRVR)E~F%(7d7RotCQ54Q?8HNE`>)W&;qQ!33T1Y8T z*Y(G%2LMvaClL7e;Q#>p{T^M{-737BZQJ5-IQ$n3_?pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10^&(T zK~y-6m6cCy3{f1%Kkv=XZfxz^k*cPhwrNt)y4Z3+Rdfjs{)DJFAt59#6~e&<3C9xB zIH*G#H&Gf+Qju*#8ZPXqZ9<6dpo;9Kt<^O<^BiUu{iF1IdNXO~yo*wc?9rUQ-Do2h6%NuaoV#^yhj{cMr! zx=QNBl#XVZ+r!E0VWXm zHE_qiGSJDt2R?}ycK|;Cb6E} zr~h&bc4lJMaP{Cq8cw%iHtr(hc+Os3KSeqbM+(hH#oiMD1T=y5r0q1fXjFX@sUDja8cnlvE(`!tB4?;w9@}Qi}f87TobKNCE!hGK~5rq!EpI zVQ7M(?)EvI&2&1>Q1?xA=~XQfZ9)dC>Hx2r*0Y}fh81Kz>%o@?7FDf4>Wh{DAO(zl zcr$O{_=h)$e1`N$2@<>~h1ZMomjF>%DWskL>;V;o%Bts$3zbz_g;BMf<=YRS1r}g`pCC8+4e8T~RM)X= z`$0CGy+NpEC6jsB$A=w^#&3JZr;M+?EtGCM1bBU^OMy=c3eDsKIagu3ihSA_dDw|{ zukF|PnlWG_(HNEcP7)}om@|dh(Imrnu8>ajPEGCXSAY+rK1L+kM6j}M+7&QJ`emF{ fq8H`lW_;);9`znQ3JgsY00000NkvXXu0mjf>yCx# diff --git a/pyscope/gui/theme/dark/radio-pressed.png b/pyscope/gui/theme/dark/radio-pressed.png deleted file mode 100644 index 87bf8718e30763e2aabe9ba28f5ca3c5f735df48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 786 zcmV+t1MU2YP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10-s4l zK~y-6os`Q@6Hye0zk8=sQ&0wZ(~=kh1hpkZSSXJKT)0rsjYM}wf<*lvj3NF5u3U%@ zSh+x>COljMiJ&p+#)9}D8bc}aXqZMUw4E6jKxL*>5`W1o?wNDG`JH>`9#K|SX5o-f zTxfwNG=C!=Uj=Th5P-P8L@YE*bZU~--~^VDbe1BVz}%W6vD))GJqH0=sgrNE;~ zvfT>VZUv7jaVsJn+Ij)a-w1#Uso-t9VoMN!N0o%%j`C@!pUBq_0OVEHP<^(OlKNKG z6DB)1-*@gR(U~_Whnm}a@{gaf0kc%ehex-0cmEb^OCd}niD@KRTMF^xthySHO;G6CQcED|9a{jtvlg(U)4M4Oq?Jh_}6)efi#h zf;Lv>nIG&;Im-y#^sSH^XhQQh;PF+VY6So!^krh9FX&T~tOh5+G}21{0G-nL`_GUO Q^#A|>07*qoM6N<$g44ri2LJ#7 diff --git a/pyscope/gui/theme/dark/radio-rest.png b/pyscope/gui/theme/dark/radio-rest.png deleted file mode 100644 index a86b523f78ed0497d3d081a5cc9bf9b5c2c41f78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 830 zcmV-E1Ht@>P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10?SE6 zK~y-6jg?Jo3}G0@fA2fHvt`+^gOZl6MOsqQP+JL!6YU~2y;eyaiVNZ*Q3r`2ZXA3x zPJ|v2k+jRfK^$x~p(<(hLra^sl9?iHbhf)Q4%?lbZfW}@Gnx0FXMXQ9PoDQbqOh>g zCMSf)ix#Oui&WxYUy7=&0U)6dGx4P*k?EoOSp_Gl$T1sl_Nhm(1 zrpX1>r}_P*4I>^ytSt)J+i$%lZ*|Cl*~SnpXAh!(`S{Pz)QZcfy7>a36*y4y$9LM! z>_uu7Q3ocbE1L@jpm%+s<=i3El!*{&&9`bTTBH)GMJk;{RY2#H#yMebXxG#G_+(ZK zmhVIQ!&|exlhD)LG%qX@-OZ2ji0pbvcpFj;<~xD}#`-=l7&zA3fph^?Z8Z|xHP^zq zEhvPLGX>j5CX7*MZP~Cqyk)__W#KJYu6j&k1UcUK$q|w^Y>d_}7`Q1~OVS0L=xs+D zt+DJ57L?S~qZOCU3)70rD5pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10kug) zK~y-6t(9G?;xG_EPrq!N#21SNq5uDnDF~YSU=yP;w)-IMu3neBUiUnOFgcllVJ2#t zX5fh^ijY#GlwvlUk)|mCAq2elSZguHFbrRwl0IK3rKqZk5CUD-aX1_ZA>4!0l%gn@ z&*v-_3);5D81q^fMG;k1A%tMJ+u@x1^1JsQ?>)!kk+LkQ>zbk{&{|J1@p!1JiagKR zY&I`t&p78;uh$esK~+_cLZuW!2)5fTecyj88|nL=?RJY&3Mu7XIF`EK@86P*+-^5^ zyB*8rk|>IZrIdsa{;ABr;he*JkCc*FDMi61qWD-Sgy3?y5L;_eN_{MpQev$o?z)aF%Me2RSXP!L zS(e>}0Bze+*ELC!ye&(Tgu1TLS~Cp8U1*HK7{h9{dP|xl39Ho#V+__>;E!pewdQ<2 z(=-ibS$-=k%aW#PaL&=T?R5XpFIsDybJTUs^?Jox%jtCDcDqd(2_cYDl4TiM>nXF5 q=+z1UrjeBA`NImhTrSfJc*QS>7}QeWq3D4C0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10q03X zK~y-6t<}A5;y@IJ;TO9AB-;g*BgUbhpX-vi*fF2muFFbwfLk37#QisJX6Wm&{=jEFFs&8X}8%jv%+2m;bH zr6>yWJm+*eeFT#vK`F&#GNEajzhdWfyIry@8%spcTJ!Sqay52NUDs%>5fQ8?iYUwS+t@i( zRS||Emhbz#zP{cJ)^*L@-5r+WIG1;JON=qNuG@C`f27mt0ALwoa2)4m&~Y5J)>xa( z2Bp-^pi+vrw>PY^ED6K#W>7?^stT(p3Ovsv2!d~8K@i}&ZVMidNAf&pJRZ~U_pio! zy&h?rGM!F291d-li=tq)TJiAkK(E)k3VVEfWVu{YRTTiX=XpN>o}Qi<3yf5{xmI0c(r_+5xMSLPT&JhuLg)sqBv+w8J)vBEm4l o_kBFi13+v2;R`q(k6$YP2{rQD5mPw%UH||907*qoM6N<$f;>wMqyPW_ diff --git a/pyscope/gui/theme/dark/radio-unsel-pressed.png b/pyscope/gui/theme/dark/radio-unsel-pressed.png deleted file mode 100644 index 1534a969184e56e4f039e4fe143eb2897008bed7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmV-u0+;=XP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10rg2l zK~y-6rIlNA>L3(G7c`eDpfio=i~s+>dC5#BLR*rU^uZy=L~S3=_mm-<#lEo-`~7}k zCLsh$DYVvPSw@m10CZhP(=>SRan3Oe4_gs)pw^nAC;$i{(6%jo-vc0}Bu!KDJO`jG zOPq5PU~=zOQ4|y_*E%J=s-X_{iK<^BDg^Z7j5$oNqd1xhKZs(K2W zt8H7Vs+vSJ0=3o{W2o!;X2#W`dGGPwW36Sk+fBkBh5@A%Qfo~J;lZ;x>|UqS ziOc0OSqmZDfO(!jcvqI?OIXx>WuupQo+IDh-o`$QiaGmDbDO^JNs{DW+x)MD5CF)o z>yUEFT`U%V0_HYC2)eFAHcdmCrjwtW&E`+w+$K#^LI_Cj{dhN{j4@biU&5@llU@YK zvJCG%$>nn4av4XD# zY?fsRAvhk7qi0bM6FW+4jZ%s{&nHhn2w{8zp6LhE$PY75OJAA*0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10s2Wq zK~y-6t<}Mbs!$w&@h{zsk&-YAgCT2CNAN8gYggN*x4DngHexn2NH3A3Zp0vp78Sb4 zO?4Mi%&mI|n(nt0{CVVf{uW+dUQS#k%Q8X;gb)~pfvTziq-n}(wIT=tf*{~KYNS0;aV&|Too)`=UEEWq~*9Aa2zhpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10%1u+ zK~y-6ot4i|)Ib!+Kb@KFPg)y7Jdjmm!c9YrCr^+VkN&+}YYO-a*~FbvmTZ&gxC(lli>8X=`5%QD)w{R?&+ha^cJ z1zl7WMZkTUCX)$W*C7CcARvmOCqWnCTU%QML2!R!$8iY5kR(ag26(UE66_zMc3uE5 zzxhFVeoAw7Iq;3+n5wGKTBAJABM5^2)fct(;WNRzk4v`E?jFx}_bAVfnSDK4`X;5s z_kCQ~Mc7(hd(su?qQdu|@Ls(c#(5r|=OLt&j7Fn@L$H6i5o+b{9}H}^LWB^a@2W+q zo$-U<(esxBo9)se@iE%a&H%#p{;K9T*AH^%H$Mk9%M-TuS5=;$J_s%^P6oE7X=vLP zQP(w9RSg}kE-BBBH$%UDp}G1#u-mDpnx;W(O&rIATAqD9V*cw2fB&F=sQK?}%8Qe= zP&@VNy2crg$6XkPY;SLqrs+es|EIDnN6E(9$1n90000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10<}p* zK~y-6m6f}a(?}GCziwT+CHbOpuq2LPYc~W6_6>$A2;e0qTb_rE+J_*4DtH8-*pwln*aJ%`nNFu=iDJ!O3BH|3DfD6G)<4b-uDWEfIQEMqKF^}SS%KF zUAF}bA(%`i2T++#NbfyB-Mme3a${49<-gcZe`DUip))H0vMfUgfpdcI(_`-51{}e z*5pr~qJF#Y*G8j}&t58}&|3QsCDRF$hi4$f{})*ZrjO2$>Dbp?YfT)-8?aK!?@jML zxH8kj%oCEkr~W>UW0X?JAP9(}X#3#i?VpAY(!bvE*HIJ^h9Oc)>AMOb%zwEyILL2o zufKJvykh?~blBE%=>0*i_;PLRZp^@wL+=l=pFUh0yc=_Iaj^kgYiXLsKQQm#(6`O8 z_@nBqp?ddvdx*8PZMy{z<><_c^5t{*fgSFt?^(b4i@yF3*f`(ddb00(j4`a&>+Pu@ z-%`AIM&H&~W?H^@Mzhn>>-Czds!0pjeb|o zIsf{r)yhNnzyrJ}3aqu%bxo2a#Bsbam7ST_wk>VjQr9)c7>qFohaSwl;bYcyO&rH) mt&vjh%{=EEZQEWsOuqv99$IH#>fXfw0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10&Gb{ zK~y-6ot4jT(=ZgrKe4^UX_{4;;DQQ5;)Ia+apE?CIPwSYfOdc*5}LG0oI!9vpjAm% z$4*-(97aOBq*UyCbl&^;Iezc!U^<;P3@8W!q9`JYBBYcEApofB8t*+-RZ&$HO*5E| z8Un^~Op+vMt9v=P| zbW?4P_nth@X_^KB5XUiTn%)Y!2_KKg#BqFGaS#L~NkW>Yg9+ioCybsRqwd@XVD;-8 z_WV7|ub+FdX`14k!&-}qq6n?^;6)`b-!gjsdV8aLj|lHQqC7jrd_39uCZ$AcO&ErV z7Ccz#4d|vuFWwM7c-&iWdtXQ?QL5L@jGi9vgmy8{UiH=v4h~RCA%qaa{d#A*7p(5~ zW7;Vdd$a$g9pelj+S$LU)vxb+x$A+LmM7ZTzbSkEelOTweCQXnT3v&ys=_(f3s`>r zOnG*?6KX!aqZ;UC=N!xB5>-_d*4jbSF&|G@{rJM@*{klN)<3^tFFp)Hz4tihsH%!! zI-NG7(TJm?BeE>pd;EVX&vPy>FDc9Nx+|=;7-KNT+!|_(VX;_Xtp)zLThlZYMbZ6= z|1~3H3`J2;6h&wF-N@B-&1^Qqd(UdM+G+Hg^4@cGb;V+_U_PIBUc3cv;d!3ZUT7T0 zL{W5YDjRR!a=Gkc%Ce*^%ig9vlW3ZTvMjOI5{4mKYc|K;dyn_NgYSAwe*=@2Fh43d RT<-t?002ovPDHLkV1jvZPDlU% diff --git a/pyscope/gui/theme/dark/scale-thumb-rest.png b/pyscope/gui/theme/dark/scale-thumb-rest.png deleted file mode 100644 index f6571b9336e9fde5c8b92387bca71c4b0ede340b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 771 zcmV+e1N{7nP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10+2~W zK~y-6m6f}0)Ib!5zq$CfW*vvHh+>1p3s9w}gQwtC5aK0xfbmu4{7e=u+r9JUn+sp7@V-DWe^1-HUR717D2kp6A%@32!0Uq5r#ILanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-vZE{-7G896g;3H;Bs-V z3Rf``r|}i%@QEfR#lDRWik)3O%`1Mh+H6eHJZ7zopr08NrgdH?_b diff --git a/pyscope/gui/theme/dark/scale-trough-vert.png b/pyscope/gui/theme/dark/scale-trough-vert.png deleted file mode 100644 index 205fed89725e9c6399776707dff139f02916abc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(B@E{-7HKKbo$Pw?#jYb+z^;UtvYqx^#{;HXxUHx3v IIVCg!0NgxI_W%F@ diff --git a/pyscope/gui/theme/dark/scroll-down.png b/pyscope/gui/theme/dark/scroll-down.png deleted file mode 100644 index 4c0e24fa06c2478235e21ac90290d52cbe893c0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_G1(%kFERZ*H}=vaekL7T zsTuPK4U5vV2y0-MR<@)j{#s;mWYtHOBA<(qDxmJDYlVy1)&gck!oWZK} zC|dC1;VA7r-;YTYuQU{#o7@xrzE=Ey`5S%)T|KpL{*DuX&SLO%^>bP0l+XkKH`8Hy diff --git a/pyscope/gui/theme/dark/scroll-hor-trough.png b/pyscope/gui/theme/dark/scroll-hor-trough.png deleted file mode 100644 index 89d04035b472f791ab57dc0443a8b7f70b39d3e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W;!3HFgc;@~FQk(@Ik;M!Qe1}1p@p%4<6riAF ziEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0xbJzX3_G$yV+W9Y@~DAMro z|K6FZ8=@mlCfI9Lv+@fb^jcfYtFIK1uiRnTP%crxGlQF>*>{3S)9;+1j*iBZx*xaw zZ-^w%sd&HMB_%8ExWnoHYa7E8-p!FNIyawz>B9HFf(7Ym%cCdQ3r}oab*|`KzEJWU z6W`Apw7+rh4S0U%>pqEwnO`4U#I0K8_x)(yg6COj8^t*0WVrGqga}54u}sfO>)&c3 zICFJzp1?#`*7r+uZy%f*w9kpd|A1t{4((@q=P#WS#$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt*MqE{-7_Gjsbk@*Xk}VSRs7 zFnfpNDFzRdH3uZ_HK;8;D%1C*jdw{vzN_rIIuoH)YEsP b2Ojgjc&2X4#`4$+=mG{$S3j3^P6$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt%nyqq^H$w5`ut?h?T-$MEVZ9F z4@76D3tH}b6eqNF&gT<97Ot{2)}LCjl0|T7(5VYGyM!CekDXB}GGPc~xcmPdYv^6o V(3_U06oGa#c)I$ztaD0e0su$+QKSF> diff --git a/pyscope/gui/theme/dark/scroll-up.png b/pyscope/gui/theme/dark/scroll-up.png deleted file mode 100644 index 7ddba7fff7796e7f279467b5fc1acbfbe765ad53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_GX%tdV$$gfy*IINX}`O{#?QQcXpFYCW`F+o~!wk zErDU{HN~YRlT6sf(#rDIK7DV{_t;M9+MhK==d2la{=Rf(ox`*H!IzGmd1E2S*w$eo f@nJ@X{CDw5#j>p3*0Usmj$rU~^>bP0l+XkKK4()N diff --git a/pyscope/gui/theme/dark/scroll-vert-thumb.png b/pyscope/gui/theme/dark/scroll-vert-thumb.png deleted file mode 100644 index 572f33d8df6df17dd658503f9e26acc2760b2d42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^Ahrkx8<5=cZcP}F;wEaloG4buBjl9hU0$TW>Td&>S_rB+G1@j8y6VuNHddc2ZH1jRiJX#z+{X_J>NBmv4 zx7m70B>TuOD%P8_sQBHy=Z_CB&-}y?bV@8Ca+<;U&FBBH-o9@jy4KwA7|?+Xp00i_ I>zopr0OB2G#sB~S diff --git a/pyscope/gui/theme/dark/scroll-vert-trough.png b/pyscope/gui/theme/dark/scroll-vert-trough.png deleted file mode 100644 index c947ed1e9bc6585713c978ed6c50ba8c90921163..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343 zcmV-d0jU0oP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10OUzT zK~yM_?aMJr13?sr@$bEvC9qpq$zVS!dj$^>!Ah-j4KE^CrxAiVfOi;h444ZDgG~q) ziu-J~5HV!8_Ybdm{63*yjB(<wDnH~4<7hvg}o9CQA00f|I+auu4%ue}ZBC=A| zS%SaJUP5?`J8cjRPp9Y(oh5 pz#DMT-zV@SBDYP`+@zE?0N+L>O0OTY#%}-s002ovPDHLkV1jbciuM2i diff --git a/pyscope/gui/theme/dark/separator.png b/pyscope/gui/theme/dark/separator.png deleted file mode 100644 index 6e01f551a104e787194301b25e8d12128bb520f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}bl&H|6fVj#T-gc;Avs>lHuk|nMY zCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+>Bu@p`&AhH%VGzHt5ee|`q0)r`GU TeO=c8B^W$i{an^LB{Ts5>Ixx& diff --git a/pyscope/gui/theme/dark/sizegrip.png b/pyscope/gui/theme/dark/sizegrip.png deleted file mode 100644 index 7080c04c67807e08452264809cbacf35b89a0503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0TnIuba4!km|A<@H|v-IPs_vW zM}Hmco}=*Het~&k%Ix*smmD-!UOQ~OxM<$<_UXmtQQ0mm@%f4jp-ZPU%?$j{-Dk*f z;>`X<9Sc@5tl8SZVf0zEi{p49!-eYOcLPJe-Sgi#<&)K8#Wc0OpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10%}P_ zK~zYI#g<)fqCgaeA4ZHY4wVa>-=DddQ`#X-vvMiKRSeC_bIK(iFV`+b_-EK$IG*neZp68Thc{I;F2IhGlhGCGV zDNz*BG!1RrUKLOX!EiXlwrxDmW4GI}*=(q)>LAbf+(FlMab1@vipcZ)sz85|QVP%W zcz=IqyWRHR`wqIUi{m(~*K4%a-wFqiWf@gf@$m3~VHm`5{1Z@0$z(ENy0r?%Cf`=f?yW}0Z|n7gKFD0&(F_H zCX=Iu;y7lpSp0Ud|5Z{-JkMjbS|OyAXs!F72_a6x!G0W`o}Lgw+yt;F3bfWpDG^F3 zvMlR^^ZEQF+-JzyY<3esfHX}RkH?77XmoH?K@gl>WICPRf(?MOEU_#LVVWjQ)AWJv zLY$*ZaxazE8q+ipz}2_JHPkVQjz@T#|dK zq?FWkjmWYL$8q{V7>4~TeoAp16Ncf<0K3;k6h*;cFhEKPK-;z~7K@XR-9vqgZ36r5 znWjlm6nnEErR4tp{>!SH&1Oue)Bd(@w_8@L)y-BN>}M|s0^Z)<_N%UQGp_5Rl;ZXE z^}m<}0Mj&ie0(HHlHSquyDE-j(lo{QeUwt)4yTmD_x%Gn_{9$bj7B3o&qD}7nx>Rx zd2_?+I1X*wve|5?>-r$?3t%VPwi%Dd*tU&nnm4ihT5F1;AkXtJPP)?%6C*4`N&^#H P00000NkvXXu0mjf=LlB> diff --git a/pyscope/gui/theme/dark/switch-off-hover.png b/pyscope/gui/theme/dark/switch-off-hover.png deleted file mode 100644 index 5a136bd3f4c6fd280134f300e5cf50136e217b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 945 zcmV;i15W&jP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H113pPa zK~zYIy_QW&a$6XNuf023+x%!)<`lSP_n@SJa?RGpK4=$Gr zlgWf$ujl3ErB~^<)z{ZYr_-U)XizSf$>;M_tJR*h!biZ-(NR`bR_O2V=j!T;N~J=* zUg!DwSrfQ7>2x|Q77I?N6Q9pVyWJ+4Od`wj?*ZrL<_H7=93LNZb8~~L_H3@^5(I&X zi3x(iAZKT1TwY$j@BQ_*n4h0#YHEu8{e2Wg!D6ux4u|pk{fvx^03gdUnM{U6B0;@g z*9=fqRj#kEk!6|n^>sv14n1@7T4j2A8l6tp)1cXGvbVSQ{$FY-gTcVs+8XhAoLa3$pV@5wS*z6m2nK_c zN+pWLBB@l0Znw+8zyOjY(d~A*y}c!sO0m1UOR-qg9L{Jovazv2I-Mq)%>p0d*6i#o zi;Ih#oSaZ76#i)3a=YCuEiG|+diu-5`xIEMRx8WP%Se($KA)#tE>o}9>2x}pLh1E- zhK7c4I-Pht9$Kvy$z<}=*!l$cCcE9vj~_p9xm=jdX3ZmiyWRdJ^55Ov^{o9H?WJ3w Tsq6+i00000NkvXXu0mjfS6H?B diff --git a/pyscope/gui/theme/dark/switch-off-pressed.png b/pyscope/gui/theme/dark/switch-off-pressed.png deleted file mode 100644 index 040e2ea30c96c8fcf103abdb749a706ec9b01975..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 963 zcmV;!13dhRP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H115ims zK~zYIy_QW&GHn=!562|6Mq7oJMv;@!rcWw2(MEI;En4&sY}JqGPecoULIlw&anT1A zMr5sGWOA}n3n9p68q2_W7cV`~lkdw@%5$y;=Hi%RxVgDbi_hmXX%N5PPcRsyzrP<* z6mdG8SS*%Cqu-8cniz(GuIpT1Uz5#dxx2e-IBWS`u&1YoNF+i>M+b_caCdjd^Yb&+ zY8BIL_%Gj%#bUwXa3G2zeSLjMlEll)3#n9!QmORcf|HYz%+AhoczB4SC@mBE6X9Sm zNH`qk;NXC>v$J3C+CCnpr>7YoA7^7@15ML#yWPZMF-Av6@pwG6x3_-*zrDS2e}B)# z#RbV^l1ioWBSF(Nk!6`usl@#JJc1x_dV2b=ywB$|2L=XMSXf|la}!Fu)up8+j*gBfm&=61VOCaFu-R%#UBgSYBe-XV{UGaOeTX62n49rYUJ~I+-^7Vc>Kq=)nc)*yu6Id z<@&K8fU2t0YBd6Z0K)L_Fp8o85R1jWb*zoza5#uYqfIdYuCA^Kg+d5kulLuHjE#*o z4Q>^uP$z6k^_Fl<(?rlTjgF2E z0LtZZOTew*bar;4>pDWIRKn-;0g%aLng#=$pPx6)@cDcc3I&AA%S$9l0w9@8Qms~- z0vm?G&dyHLf|4X5%Q8YPm&0bWF)%PdrBWf0Nc?yQo2E%3kwDY5rsM_(2eDeM)~!$|#LUbL`}_N!F6)=V>h*ewL?Vcyh@vRm z+}xn+I)-7iw6HpzPFyY*e!m|{l6Zc8CY4I@@bK_?_WvsX(9qDQ$}b2)O9bonI)-7O lX&U)_o@_Qtp-^Zz`wvZHY;hpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10}M$- zK~zYIz1Gi5Dr+3a@welhTr?v^t1Pq0q@<=*SWBZIqeZJATn7Ce=^xQbXjz-qB^cx` z`mqTMG0Y#CQjj3ygycxx#ktz);PH03&iSl{`96H-#RJbXU(4v|Xp;_cI-N{SP2qC6 zu-om3qR7X`N2jI#If5Wy7zUcAk$2i3I6%no6ZY zyJDwTc@^!a=&E-rF%azZMVqS@@Yxjm1?VqtoE znm{1H`T03lS6A)YU#*9Qg#~73XNg22=(_$#V68NpO_IqZg+hT~Fo+-sTwGjy0w*RW zn4h0#e}BJ?6Gf3gAb{WR$K`SXkk99d$Kyn!Q4GWAIaJqm4h|03-rnZr<%L`>hh=PR zthuwZLo^yClgR)uG&Dpg6hcvyj*T*z4B>E?a=F}dB!HqQtgWpPi9`@Q9uJzPwQ-^- zcE!Pu9pByEMHEF-fZ1%8YPE{n?MCo=y(AKeb}O zpzAu4B(-1u?w8XkkB^ULxB!r48C};AN~O}5LveO?_U$I|c-#cqkBpCxQ!Ew{lF1~h zs&0uh*$ot7NlT?(gq;dV1=(_HUD! VO;&6PK$8Ff002ovPDHLkV1k-`qrU(E diff --git a/pyscope/gui/theme/dark/switch-on-disabled.png b/pyscope/gui/theme/dark/switch-on-disabled.png deleted file mode 100644 index c0d67c567752d96e322b3f3dcea9cd95bbf8efec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 623 zcmV-#0+9WQP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10sKis zK~zYI&6cr}qCgZy4^0bI(;cV~5I9Cyg@2-d?4OWmFp&xE7|>HHC>vO^qEqh7j=i-v z;$2=9r=TD2dZW>(;YOb4p_D>vO%Mb~DG@^4w*5$_(}}vSIUEj@Wr?+xrny5w-WpnK z;y6aiyIW6{5P~2G2!a5uHC0t%jG-*cZr|m?^E~1>CP@yac0aUA1$-aTLx zMfCgqe-3u5e!ou?MQttN{8?SY`MytN}R$|;=h`w!vZe7t6}8NTng zo|F=$6hdq53=Sa6vWIXNMNveSWgSl`g$TpY6&nDP$>htuX_|KGFbombuhoUVGkk-= zpi@gJdG2~}AW_$~(+6y~+b{RNzrT0tx~>t2!{OOX9W9s3zwX`d_lVow_iW{Pjxpws zea0B_Jnwkc+B0BPRj&9r9*-;*i-(YxANA>UYQ3teu-4*@Mx#b+O_C(eAmN-|rqd~D znzqw=z3Q&v0M_gEZ$N-Jj{l3W0O!FfiUPPwGsc*2O`I!jx7*HfXoFwVG!#X_ZnqpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H111w2I zK~zYIwU$k66lEC4fA2dVrQ7agw-gIyT5W*1y=eJJ)FQE!EFLff1aFlCM!eO=)B^`2 z37m)^+5>4w2pSU-KGNXLqAew+=>=8~S`LcAPJvo*pl!BYXJ=jxv)jZiUmIutr^%cD zfAX6r&-=_HY};0pNN5Jh^_?U)yh*I-Ma*OavZ}h&_P-S5=K0s1BKy~M+{`d;W*Fsr zOMIdfux?YDrrkZ5^^K7NJx#g0lQ`#(^Uu{E9t|j@hP|hUmFcdE0BAy>OVEWti117) z$SZ~CD}sV=>e9DNoIj2Vd;s*~3n~RC5SSVSGxzv)pr6UHOUz9E0f1$%#eOACYS(^D ztDXfvpdsCbQi_Rl$HKgA+p2C;nl&FDsR&%PE^w|~Wb|k^UUu%0J|k93dv721>pRH$ zVJ&|@(TjWSN2JgU?032&hXbh6z`1gfk;Csl77oD6&M|UmAI?wTQKd-$n&15xp&Q8L z`cA4=*pb6&Lg3HbWAtcu*lbUeQjC7yO)z^GDZs36B-z%1Om5s7IUIm4VSJ$fiIppr zm!0MMz)1`l-l9ZXC$grwwK9N0;EWBHopZ(p(F=ez&$l9VYkAac07wC|xBe_UH+$;_ zqR|%Nq>$c&=}5tfQb2rFOWC=2V++MAm2>|_W^ay13RYBJ zDePCbm7TL+dy9M!B4%&?hIBK-QL_Qy`HIx8{TQ*@XU>>47Ogu!z$*aW|78T}W`@X3 zJCOqhfM(Ux-rEPy(u+dS{z)GqUPqw9<-yHdMk+s#bK%R#0Ra~LfYn=e@W!X7FkAn3UGJ6|C*_xGeGIOwfiGhZhJ3sNC@YmAqBb=i?b~# ze1I1y3T?-o!EbObe6_f4Em>HT8@AH4r>7$S=O-yQ?J#lf3*5}mf6kWxLUHBqAko%I ztT~05Y(QHUixkLT$QD-qaqee^aeuyy^7Bi44*@HzBP+<;3C;ij002ovPDHLkV1m(s Bw9^0p diff --git a/pyscope/gui/theme/dark/switch-on-pressed.png b/pyscope/gui/theme/dark/switch-on-pressed.png deleted file mode 100644 index 00e87c68a1b37cde51142d964e4ac1374a69de4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 936 zcmV;Z16TZsP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H112suR zK~zYIt(HG*6K5F4f8Td!J6Aio1W3V-qk?h^I*HjWdzO86QJ=?vgrD z3)sA|kHpSF!YymG0=-Y=XOGB_oMQUsrMCkLX%HXyh7bF8YkQ~=U74|JiwhVE?zey7LD<>#6SI< z&i=g^jVq{lKCN4JgHq(qpA23ynM|tYjeV@$vtJvWWlC~aGF(4#0Jk*v-g?uD(!K8x ziJl#lTpxh@!v}H3$B@D>N%ViM4K88|a#u2BkNvnTH~_aeOZMmxg=@bPF(m+PyS_u1 zA*7v36J8nD0w;vPd-0s>Ck}vGG92D5rMPx{Kih`*N#8$U2{$FKh3OSeiO9NV< ziVulRTkGnP*t8Y5x~&u+-bFeSSsiQuxW1yZe=nvL{a^QotQegGd#L!quy$@=MNZ$k zOnIuH1<+Rt*|>u4eTRaL_`gU9y1ze+h_;|q5FVU~EE3-%pE<1s5TNY&Bzks`9y+>g zCqq_@^w2Tlo3~T;0{3#~Pog{*qp7Lsr_$qpFdCvnKl)e;r0Xl}_6|C>eTfl{;1=g_ zOSAZHWwFL=_yD`TgZ10KqUVPrG_LDn-V4^=lhI$896JZp#I3}@Agz60Xn}+vT$zTz z5Tg3U-;7&If#(NtYhkoJ8U2+)=FDQ;S`t|8RGPM(-)h^Rm#gwrf!u|kamL5qIKKoC zs*%5|7rUz$t8D`zdlg2+(hB6w7pXXt6m$1*CbCSAXQAra>v{!PR4Lbkib03~0000< KMNUMnLSTZP*s$RM diff --git a/pyscope/gui/theme/dark/switch-on-rest.png b/pyscope/gui/theme/dark/switch-on-rest.png deleted file mode 100644 index 52a19ea658446caede4fb410192b05bdc78353ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 859 zcmV-h1ElpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10_aIZ zK~zYIz1B@g6k#04@$d7@zUAtYPFj}YYA>1@NRowg=#*<2gjel_Adl4{2$aG@MMpc8 zc&qGK6qYU>f(XK;pce@(Enl$OXjYR}cGlgQ4(rRhnhH7nU>N4%cla=G&tK%_<(Z^J z*zBm4KGc0a+}>R{)Lax-T59c|5|2y~)xHt_(u)@GqXoJ#W7CUzA{DrysupiU6V9Cc zH3hAdNN|MVyVv;n^6}C@Ar%UaH?!sFng0q)&DYL$hHhWQG~xg#9*?I5z;@YR^sde9EMKc{Wb==?hzX71t4o{8QW^= zDXD8DsyLaRk#0WP$YkFuw4N?RzPHfabK@DVjRltB%+MI!=Z>I#e7j^vEiT8`+KEuN z%pvt3qr5(Q5UE!BEW?SJJGUGTKn)8c~bOSL~|A<&@MlqQ~dgLa&}8wVo~_ z!C^~)rU_BSiLbSDS>)`GT9*!HZ(t-if)?3h|7Xe{*!B{s45B2apG5)R_PP002ovPDHLkV1jPWmj3_% diff --git a/pyscope/gui/theme/dark/tab-hover.png b/pyscope/gui/theme/dark/tab-hover.png deleted file mode 100644 index 43a113b35c4019897390acd452cda5aab135512a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprUD>E{-7)t#7Y+3msPAVGc;# z;s37Q{Pe47lYDL&d7eBJb;zmzyup;*+byjdr#|JZU|BHNcbBz_Ko>H2y85}S Ib4q9e0C6#5TmS$7 diff --git a/pyscope/gui/theme/dark/tab-rest.png b/pyscope/gui/theme/dark/tab-rest.png deleted file mode 100644 index 9753e067c6ba2e08b90d28da491836864a47eedb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdv$07srr@*0<*j85siF&&FxvXEnW$OKlEFe7>G-FC};|YBqbt`|hK~z4{J48!Zy<-k8&PDcU^mzkp!_ zBToVYv&4ah=M3w9NZ!n@Wq3XR=%navucin+t2}1nJDu^K?oyFBFaILBy_Of5fc|3e MboFyt=akR{0ByQ@3jhEB diff --git a/pyscope/gui/theme/dark/treeheading-hover.png b/pyscope/gui/theme/dark/treeheading-hover.png deleted file mode 100644 index beaaf1353fe62c3607118cbabce26b78ec5ceea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt)?TT^vI!df#5%$kl8h;`Xpu zHGG23uHXOGt2;GG>|ECD+v?Q5YH7DU%N#pDG3DC(>2(I*e--e(w_VlMQPcWlPUN&! z0j9KVhtFBw+NS#akJWv@(3iXV5+w|bwHV(2ReZB|SHuA}`JMlkUoG8xSgB~2#I{#L z5p%EEy=lw;(e|G${^NWN?J(8}LAzK)1)o*kdnoZ(^=g`1k6^_anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-oKT^vI!df#4g>}xjQakwb| z#>4Ru$DRKzj7t+HM=lTxIUB+ha<*;%DK-6b#}>Z-eM5jw!N`qgXha3kZS z9%&gT3)7d?)r)to`FoM~{s-Rw(lOb#dz$OB_ZLe=Y@gH-(-u0la-R8)DNU;ut&-QT z(>m7C5#SXm&sb1ibZ*I%jN9KtYanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt<;~T^vI!df!ex*wtbnz`8nF zL{WV2@BjPNJDQ~C-#lzkC^4h?v0n8?zs3otvYmFVeZA|5{DaSjs+~Bdq;}4nJ88+3 z4nw0pgcsqI&hao}L|^sv&cRlB#1*Rw@{LGIBe zotxkPZuu~`_WsIMuLNe_4EMb6srmGK;q+Yj9Y?qS5c@y-$JhS&hg+XpG?c~`zN@Nu zE^~U5%%jX%pN^TIJgYOmdY4UuN?YQBb+6aE3EQli_uTUPSDVi&PjA;X%g1a>RN*`` R;|9=Y44$rjF6*2UngA+#iNF8= diff --git a/pyscope/gui/theme/light.tcl b/pyscope/gui/theme/light.tcl deleted file mode 100644 index 8e2999f5..00000000 --- a/pyscope/gui/theme/light.tcl +++ /dev/null @@ -1,489 +0,0 @@ -# Copyright © 2021 rdbende - -# A stunning light theme for ttk based on Microsoft's Sun Valley visual style - -package require Tk 8.6 - -namespace eval ttk::theme::sun-valley-light { - variable version 1.0 - package provide ttk::theme::sun-valley-light $version - - ttk::style theme create sun-valley-light -parent clam -settings { - proc load_images {imgdir} { - variable images - foreach file [glob -directory $imgdir *.png] { - set images([file tail [file rootname $file]]) \ - [image create photo -file $file -format png] - } - } - - load_images [file join [file dirname [info script]] light] - - array set colors { - -fg "#202020" - -bg "#fafafa" - -disabledfg "#a0a0a0" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style layout TButton { - Button.button -children { - Button.padding -children { - Button.label -side left -expand 1 - } - } - } - - ttk::style layout Toolbutton { - Toolbutton.button -children { - Toolbutton.padding -children { - Toolbutton.label -side left -expand 1 - } - } - } - - ttk::style layout TMenubutton { - Menubutton.button -children { - Menubutton.padding -children { - Menubutton.label -side left -expand 1 - Menubutton.indicator -side right -sticky nsew - } - } - } - - ttk::style layout TOptionMenu { - OptionMenu.button -children { - OptionMenu.padding -children { - OptionMenu.label -side left -expand 1 - OptionMenu.indicator -side right -sticky nsew - } - } - } - - ttk::style layout Accent.TButton { - AccentButton.button -children { - AccentButton.padding -children { - AccentButton.label -side left -expand 1 - } - } - } - - ttk::style layout TCheckbutton { - Checkbutton.button -children { - Checkbutton.padding -children { - Checkbutton.indicator -side left - Checkbutton.label -side right -expand 1 - } - } - } - - ttk::style layout Switch.TCheckbutton { - Switch.button -children { - Switch.padding -children { - Switch.indicator -side left - Switch.label -side right -expand 1 - } - } - } - - ttk::style layout Toggle.TButton { - ToggleButton.button -children { - ToggleButton.padding -children { - ToggleButton.label -side left -expand 1 - } - } - } - - ttk::style layout TRadiobutton { - Radiobutton.button -children { - Radiobutton.padding -children { - Radiobutton.indicator -side left - Radiobutton.label -side right -expand 1 - } - } - } - - ttk::style layout Vertical.TScrollbar { - Vertical.Scrollbar.trough -sticky ns -children { - Vertical.Scrollbar.uparrow -side top - Vertical.Scrollbar.downarrow -side bottom - Vertical.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout Horizontal.TScrollbar { - Horizontal.Scrollbar.trough -sticky ew -children { - Horizontal.Scrollbar.leftarrow -side left - Horizontal.Scrollbar.rightarrow -side right - Horizontal.Scrollbar.thumb -expand 1 - } - } - - ttk::style layout TSeparator { - TSeparator.separator -sticky nsew - } - - ttk::style layout TCombobox { - Combobox.field -sticky nsew -children { - Combobox.padding -expand 1 -sticky nsew -children { - Combobox.textarea -sticky nsew - } - } - null -side right -sticky ns -children { - Combobox.arrow -sticky nsew - } - } - - ttk::style layout TSpinbox { - Spinbox.field -sticky nsew -children { - Spinbox.padding -expand 1 -sticky nsew -children { - Spinbox.textarea -sticky nsew - } - - } - null -side right -sticky nsew -children { - Spinbox.uparrow -side left -sticky nsew - Spinbox.downarrow -side right -sticky nsew - } - } - - ttk::style layout Card.TFrame { - Card.field { - Card.padding -expand 1 - } - } - - ttk::style layout TLabelframe { - Labelframe.border { - Labelframe.padding -expand 1 -children { - Labelframe.label -side left - } - } - } - - ttk::style layout TNotebook { - Notebook.border -children { - TNotebook.Tab -expand 1 - Notebook.client -sticky nsew - } - } - - ttk::style layout TNotebook.Tab { - Notebook.tab -expand 1 -children { - Notebook.padding -expand 1 -sticky nsew -children { - Notebook.image -side left -sticky w - Notebook.text -side right -expand 1 - } - } - } - - ttk::style layout Treeview.Item { - Treeitem.padding -sticky nsew -children { - Treeitem.image -side left -sticky {} - Treeitem.indicator -side left -sticky {} - Treeitem.text -side left -sticky {} - } - } - - # Button - ttk::style configure TButton -padding {8 4} -anchor center -foreground $colors(-fg) - - ttk::style map TButton -foreground \ - [list disabled #a2a2a2 \ - pressed #636363 \ - active #1a1a1a] - - ttk::style element create Button.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-disabled) \ - disabled $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Toolbutton - ttk::style configure Toolbutton -padding {8 4} -anchor center - - ttk::style element create Toolbutton.button image \ - [list $images(empty) \ - {selected disabled} $images(button-disabled) \ - selected $images(button-rest) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Menubutton - ttk::style configure TMenubutton -padding {8 4 0 4} - - ttk::style element create Menubutton.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create Menubutton.indicator image $images(arrow-down) -width 28 -sticky {} - - # OptionMenu - ttk::style configure TOptionMenu -padding {8 4 0 4} - - ttk::style element create OptionMenu.button \ - image [list $images(button-rest) \ - disabled $images(button-disabled) \ - pressed $images(button-pressed) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - ttk::style element create OptionMenu.indicator image $images(arrow-down) -width 28 -sticky {} - - # Accent.TButton - ttk::style configure Accent.TButton -padding {8 4} -anchor center -foreground #ffffff - - ttk::style map Accent.TButton -foreground \ - [list disabled #ffffff \ - pressed #c1d8ee] - - ttk::style element create AccentButton.button image \ - [list $images(button-accent-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-accent-disabled) \ - selected $images(button-accent-rest) \ - pressed $images(button-accent-pressed) \ - active $images(button-accent-hover) \ - ] -border 4 -sticky nsew - - # Checkbutton - ttk::style configure TCheckbutton -padding 4 - - ttk::style element create Checkbutton.indicator image \ - [list $images(check-unsel-rest) \ - {alternate disabled} $images(check-tri-disabled) \ - {selected disabled} $images(check-disabled) \ - disabled $images(check-unsel-disabled) \ - {pressed alternate} $images(check-tri-hover) \ - {active alternate} $images(check-tri-hover) \ - alternate $images(check-tri-rest) \ - {pressed selected} $images(check-hover) \ - {active selected} $images(check-hover) \ - selected $images(check-rest) \ - {pressed !selected} $images(check-unsel-pressed) \ - active $images(check-unsel-hover) \ - ] -width 26 -sticky w - - # Switch.TCheckbutton - ttk::style element create Switch.indicator image \ - [list $images(switch-off-rest) \ - {selected disabled} $images(switch-on-disabled) \ - disabled $images(switch-off-disabled) \ - {pressed selected} $images(switch-on-pressed) \ - {active selected} $images(switch-on-hover) \ - selected $images(switch-on-rest) \ - {pressed !selected} $images(switch-off-pressed) \ - active $images(switch-off-hover) \ - ] -width 46 -sticky w - - # Toggle.TButton - ttk::style configure Toggle.TButton -padding {8 4 8 4} -anchor center -foreground $colors(-fg) - - ttk::style map Toggle.TButton -foreground \ - [list {selected disabled} #ffffff \ - {selected pressed} #636363 \ - selected #ffffff \ - pressed #c1d8ee \ - disabled #a2a2a2 \ - active #1a1a1a - ] - - ttk::style element create ToggleButton.button image \ - [list $images(button-rest) \ - {selected disabled} $images(button-accent-disabled) \ - disabled $images(button-disabled) \ - {pressed selected} $images(button-rest) \ - {active selected} $images(button-accent-hover) \ - selected $images(button-accent-rest) \ - {pressed !selected} $images(button-accent-rest) \ - active $images(button-hover) \ - ] -border 4 -sticky nsew - - # Radiobutton - ttk::style configure TRadiobutton -padding 4 - - ttk::style element create Radiobutton.indicator image \ - [list $images(radio-unsel-rest) \ - {selected disabled} $images(radio-disabled) \ - disabled $images(radio-unsel-disabled) \ - {pressed selected} $images(radio-pressed) \ - {active selected} $images(radio-hover) \ - selected $images(radio-rest) \ - {pressed !selected} $images(radio-unsel-pressed) \ - active $images(radio-unsel-hover) \ - ] -width 26 -sticky w - - # Scrollbar - ttk::style element create Horizontal.Scrollbar.trough image $images(scroll-hor-trough) -sticky ew -border 6 - ttk::style element create Horizontal.Scrollbar.thumb image $images(scroll-hor-thumb) -sticky ew -border 3 - - ttk::style element create Horizontal.Scrollbar.rightarrow image $images(scroll-right) -sticky {} -width 12 - ttk::style element create Horizontal.Scrollbar.leftarrow image $images(scroll-left) -sticky {} -width 12 - - ttk::style element create Vertical.Scrollbar.trough image $images(scroll-vert-trough) -sticky ns -border 6 - ttk::style element create Vertical.Scrollbar.thumb image $images(scroll-vert-thumb) -sticky ns -border 3 - - ttk::style element create Vertical.Scrollbar.uparrow image $images(scroll-up) -sticky {} -height 12 - ttk::style element create Vertical.Scrollbar.downarrow image $images(scroll-down) -sticky {} -height 12 - - # Scale - ttk::style element create Horizontal.Scale.trough image $images(scale-trough-hor) \ - -border 5 -padding 0 - - ttk::style element create Vertical.Scale.trough image $images(scale-trough-vert) \ - -border 5 -padding 0 - - ttk::style element create Scale.slider \ - image [list $images(scale-thumb-rest) \ - disabled $images(scale-thumb-disabled) \ - pressed $images(scale-thumb-pressed) \ - active $images(scale-thumb-hover) \ - ] -sticky {} - - # Progressbar - ttk::style element create Horizontal.Progressbar.trough image $images(progress-trough-hor) \ - -border 1 -sticky ew - - ttk::style element create Horizontal.Progressbar.pbar image $images(progress-pbar-hor) \ - -border 2 -sticky ew - - ttk::style element create Vertical.Progressbar.trough image $images(progress-trough-vert) \ - -border 1 -sticky ns - - ttk::style element create Vertical.Progressbar.pbar image $images(progress-pbar-vert) \ - -border 2 -sticky ns - - # Entry - ttk::style configure TEntry -foreground $colors(-fg) - - ttk::style map TEntry -foreground \ - [list disabled #0a0a0a \ - pressed #636363 \ - active #626262 - ] - - ttk::style element create Entry.field \ - image [list $images(entry-rest) \ - {focus hover !invalid} $images(entry-focus) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - {focus !invalid} $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding 8 -sticky nsew - - # Combobox - ttk::style configure TCombobox -foreground $colors(-fg) - - ttk::style configure ComboboxPopdownFrame -borderwidth 1 -relief solid - - ttk::style map TCombobox -foreground \ - [list disabled #0a0a0a \ - pressed #636363 \ - active #626262 - ] - - ttk::style map TCombobox -selectbackground [list \ - {readonly hover} $colors(-selectbg) \ - {readonly focus} $colors(-selectbg) \ - ] -selectforeground [list \ - {readonly hover} $colors(-selectfg) \ - {readonly focus} $colors(-selectfg) \ - ] - - ttk::style element create Combobox.field \ - image [list $images(entry-rest) \ - {readonly disabled} $images(button-disabled) \ - {readonly pressed} $images(button-pressed) \ - {readonly hover} $images(button-hover) \ - readonly $images(button-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 28 8} - - ttk::style element create Combobox.arrow image $images(arrow-down) -width 35 -sticky {} - - # Spinbox - ttk::style configure TSpinbox -foreground $colors(-fg) - - ttk::style map TSpinbox -foreground \ - [list disabled #0a0a0a \ - pressed #636363 \ - active #626262 - ] - - ttk::style element create Spinbox.field \ - image [list $images(entry-rest) \ - invalid $images(entry-invalid) \ - disabled $images(entry-disabled) \ - focus $images(entry-focus) \ - hover $images(entry-hover) \ - ] -border 5 -padding {8 8 54 8} -sticky nsew - - ttk::style element create Spinbox.uparrow image $images(arrow-up) -width 35 -sticky {} - ttk::style element create Spinbox.downarrow image $images(arrow-down) -width 35 -sticky {} - - # Sizegrip - ttk::style element create Sizegrip.sizegrip image $images(sizegrip) \ - -sticky nsew - - # Separator - ttk::style element create TSeparator.separator image $images(separator) - - # Card - ttk::style element create Card.field image $images(card) \ - -border 10 -padding 4 -sticky nsew - - # Labelframe - ttk::style element create Labelframe.border image $images(card) \ - -border 5 -padding 4 -sticky nsew - - # Notebook - ttk::style configure TNotebook -padding 1 - - ttk::style element create Notebook.border \ - image $images(notebook-border) -border 5 -padding 5 - - ttk::style element create Notebook.client image $images(notebook) - - ttk::style element create Notebook.tab \ - image [list $images(tab-rest) \ - selected $images(tab-selected) \ - active $images(tab-hover) \ - ] -border 13 -padding {16 14 16 6} -height 32 - - # Treeview - ttk::style element create Treeview.field image $images(card) \ - -border 5 - - ttk::style element create Treeheading.cell \ - image [list $images(treeheading-rest) \ - pressed $images(treeheading-pressed) \ - active $images(treeheading-hover) - ] -border 5 -padding 15 -sticky nsew - - ttk::style element create Treeitem.indicator \ - image [list $images(arrow-right) \ - user2 $images(empty) \ - user1 $images(arrow-down) \ - ] -width 26 -sticky {} - - ttk::style configure Treeview -foregound #1a1a1a -background $colors(-bg) -rowheight [expr {[font metrics font -linespace] + 2}] - ttk::style map Treeview \ - -background [list selected #f0f0f0] \ - -foreground [list selected #191919] - - # Panedwindow - # Insane hack to remove clam's ugly sash - ttk::style configure Sash -gripcount 0 - } -} diff --git a/pyscope/gui/theme/light/arrow-down.png b/pyscope/gui/theme/light/arrow-down.png deleted file mode 100644 index 45fc33bd33a3d6408eefb367ff285967dec941dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt+o^T^vI=X6E)9avgFIaQ*+r z!C}H3RhDXP?X@a%7o?c`>Lp~pWn{UT`*xCX1*4~z{{z-d9P7^Zg*qI$zwi5xC0qAt zE&X!;{#dF%Zo$S4(vDYsPIC;D1Hkdc)8n0iwm6_qf_s~F*$tJ$~3=j6+H}U-}b(4MN XIqN;$bHdI5oy*|q>gTe~DWM4f<}hm{ diff --git a/pyscope/gui/theme/light/arrow-right.png b/pyscope/gui/theme/light/arrow-right.png deleted file mode 100644 index 6461ffc94e88836ac40661d9ac943ce63592464e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^tU%1g!3HF2ETp?{#H&-P9X!wXP=*+-)~}Kvgh!TBPQ1m9ZGsT(>T4(;84osNt6Ca zNJ@6s|NZs!D37pB{l7oH$;ruXX=%@j)Yx|;L~N zjo*-Pkp0<}DanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-!OT^vI=X2$jzavgFIaQ%NK ztV!SlgPf9Y)O&%T$OsjkNzxTu-l{oGtt*8Dj+{Eh%)%7svP~d)_+WY2W|= diff --git a/pyscope/gui/theme/light/button-accent-disabled.png b/pyscope/gui/theme/light/button-accent-disabled.png deleted file mode 100644 index c3845a54e8991cd4b466606b5c50396360cffc5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+Mu7sn8b-nUmb@-`cYxL)l3 z@4jf0`i483|LgE1?zDAsoU5*vmT)?7R3h-|=^3jg1tnSNEtt|Y^KU7mB1@20&*Kx^7S4|MmIhb< Q2D+2M)78&qol`;+0E^6Rj{pDw diff --git a/pyscope/gui/theme/light/button-accent-hover.png b/pyscope/gui/theme/light/button-accent-hover.png deleted file mode 100644 index 054d56c0d2e22cc654f0dd1586e1dd8842f18df0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 374 zcmV-+0g3*JP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Ru@y zK~y-6?a!@C17QGw@#lSyefY5%78DUCR#6w$O^XJzMZsvX*aV~9DklF7!DbK^L`;It zg`u*AyK#4W-@P9OlVI>T<2`@C4<4bl_HV^(UPP=cS(>W^v{&mKb(%CARmAfk z^aqzud`1dw9M1bppcH~~NwQckb9J~uAh!0-9{OV@Q%g4LI}aG^QBzNt7-Y8>p>4L! z-_q7{{~97=WpOUIhjS1si_A~`tN;tJ0RIG5(TM+m@XQE}Y%K=_HqH;4l=eyqdAzgE z{Nw|Xj=-7hk~4^8$bxz2WOoyxwe~NA5$D&p+&%UfCn}Ryo2{_jTH|!T!&0Nh7iiII U9<_Fs3jhEB07*qoM6N<$f?4UA%K!iX diff --git a/pyscope/gui/theme/light/button-accent-pressed.png b/pyscope/gui/theme/light/button-accent-pressed.png deleted file mode 100644 index 9da8b53613ea07818317a7db21a0c144e0ed5e5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367 zcmV-#0g(QQP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10Q^Zr zK~y-6?bR_#13?sr@$b#yBq_a(-U>9-@8;d|{kKh%&KuRx?1852h3#-*& zA&R(JnQ>>wN>H$Hv+aHd`0;<$6KkzcUkr=oBdc^nZXH$Xv;xM%nAxPy!LCN`pWglX z)g8}SfpsO``%0jQphKY-x41k#BuGxqE^gO3rl_{;XGQ7xEK1%!3e+myVBPlEcgB@m zrx_}@j*3_QnfH(zhpJEgt^pgc0sjSdS^*+b9}?*505u-Q=&(K*g^Ht5f|^bG^tuu4 zNTIe^szhjqiv1nUd^$j^wf^DNuvo6SNi%X&@dfCB<59wVI$$r3_yWgoS4rv;lwtq? N002ovPDHLkV1nu-o45b~ diff --git a/pyscope/gui/theme/light/button-accent-rest.png b/pyscope/gui/theme/light/button-accent-rest.png deleted file mode 100644 index 3b7959a323100057dddfa4edd26e18399d1c8c03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmV-`0e}99P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10S!q+ zK~y-6?bN?Y!%!H8@h7K=fl@+21#u88>MH6W=;-1iI4F1p?rwrhCvo)(yavI|O^_}k zZVsX1(7}JjriPGna+1&4PNrG=T)>Za`98-OBi)aLi<>T;Fe28LGBtdct>q@Cdo5bC zb)4tkK=v;md5bjK3VBh=qo%-j6?4H9*C*@L;+4bp!|Ra52qNV$01?P?L1zWuu|ajh zh%_q?jh0&BJ{+K8Z7If?Hc}zNPij%sxBYDbPQVHHFR-pMlvDkdl>?2MMQtv4czIG8 z^lZ%bqK`T|Sf!c2})J? ee!R>4Ou#Qx(r!GF=i%T00000mmtT}V`<;yxP|*QT7sn8b-nZuuX0;lKxL%yQ zxQxh>Y|Br@x#!%7FzYmmtT}V`<;yxP|bP0l+XkK&S7+& diff --git a/pyscope/gui/theme/light/button-pressed.png b/pyscope/gui/theme/light/button-pressed.png deleted file mode 100644 index 920bf70fb9f7aabc4a04b70935e97bc69246d1b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-S17sn8b-nUl`^V$q}93G~t zGrB(dcRj@^q5X*LD!u(yCX1BcGt7T;RII$;knMTprD}Dh8J;d4nt|D_p%Y*4dSt;j zEp_3Prd588$vjcnGbC?sQ~bTp{oKsb*yRh(pXvF%@8Uh{rBmD`95h?+SxX6C&i*%T zm5`v#bi0S*H3!B2aBC>_7|J;dHg+t~RE^5+cPv+b-g7Ogu>4HUuc-e%X=yVgw}w5j hk8?QpQ(E{Tw^DiXl)8_0CxI?!@O1TaS?83{1OV`cZc_jN diff --git a/pyscope/gui/theme/light/button-rest.png b/pyscope/gui/theme/light/button-rest.png deleted file mode 100644 index 1b211884bd062672de26718e641fb1b264a3c251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|+Sw7sn8b-nZv>uWK>jX?fV` zIPvJx$A9MwGzKwD^L(>5LRL0+V`S97I=jrKRl8<7x&(MN?q0l1fN9n;M&srV0hc4& zbD52mk8voiVmiySic8S({`U-5mNPtQ>zdYeyx1!$7@)~&cx?WL{YNDNy%={^e?99j zD0oNq`_lg(nC%`g*D=qMJoCn^#FB;K_rCjV`ps>L2R7+=XevIB;e9%1V$h*o>m-f~ wI)?tW@jvsq=ds1A_rij^)*UwS&A2V5AJdy^Ep$ND0q73~Pgg&ebxsLQ0RK*M>i_@% diff --git a/pyscope/gui/theme/light/card.png b/pyscope/gui/theme/light/card.png deleted file mode 100644 index 78ac82ebe41fcbc4b444f7c927d2eadfd1bd0338..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_nlPPZ!6KjK;UO9J7uX@VGrx zPp+O+G4Z#&F}HtHPJp=T>^!ce23#+!8|9;ZiHDjpgg)J+_iWA#$$6a|mg(0@mrh}F zV5``Fe}&ds)1q@7C*0+KNgF4h^L#%yO8fSaqGpmhCPv+*K25T{&V2r&8d+qwlaNSeYU9FNz2`c4;YRNp00i_>zopr09-Ml Au>b%7 diff --git a/pyscope/gui/theme/light/check-disabled.png b/pyscope/gui/theme/light/check-disabled.png deleted file mode 100644 index 2c59e08fde6b9d9b35b23a844da6753026662ed5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381 zcmV-@0fPRCP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10SZY( zK~y-6t<}AXg)k6?;V+X2r!t73l~_u&^8W8b6g#~DAvPk3*=AkX1>N}bwgV5tG{e?) zb<46~7zWJq3~Mc}#5qS8hRCuEpU($OYwen*fidP1^fw^{%CZCv!|)gb0AmcsaRklt z{21HQT7$0M>?~_7=r!%TTObi3Ns@O!B7#y1aU4Gfi3mkez&VGm>-@nqO+oZ~v}24h zv~7!R+x)>OilCJGL4C19u-V6G7!@CjZ{56bo4hqBYHBd?k%CZbXoSxDvAq4U~2isr3IF8U--+BR3O5}Nt bAPDdUnj(YdxTMpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10clA@ zK~y-6t<*nA0$~`(@!!4Pc^;a7qChtdNy2n2a;U}zk+z5?E$PqTZXmSP-d35ov^O|4 zG(@>ao+|v%9(Z1cQmWiNOV}+`QLNPP*6u<}Hr|AY z-+^@0i?LAwO!=XvEbd>UF3Fmp-`NEgr>QVZjll9w`LU@B1Aw;N!&9At6e~5|*jJag zH4v>k`>RGv1~E+g>|Y?y5iCu+jRAR%Ah$M&h0ut8(99AX#Dh>2U?VT82Uli1uy8*| zW|rVE5d=#ZB(gVXcY5l<`H5lVlc8@^E!G#@%8O{yE>1SXa9KIkSYNQyRj{5DQI}*~ zY)^qOK>TPh)))Lhdq^IOI4iwkHCqCe2V(%hcv9zzs+TG={!(nb$uP_=upkk?!!U4m z+zFD7dSMq>-I&YDArtd~p_D53FB0|&Ra{l-XtcEb+(Q-?a{(tZF(2FxGu{ExU4WbI SxXB#=0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10bof) zK~y-6t<*hB!$2H>@h7>IhS<=8Ac!_Xbx?2-igvLG(#?dUC*a_Ve~h3PcAC4(_N!3Q4mfHUia7)k&D002ov JPDHLkV1f>4&!PYT diff --git a/pyscope/gui/theme/light/check-rest.png b/pyscope/gui/theme/light/check-rest.png deleted file mode 100644 index 4f8d140379ca90dcee2d82e8b17da7be5018750e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmV;~0Ve*5P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10cJ@= zK~y-6t<<|OLtz+z@#lO!r7fjO7%HZcCXJ-UNTdVSlDbVSVjv8*T1+MBM1ntpCc9TmJ$|3h(S|LFQ=Dd5d&$Q>Urijzr648eUhRm>di}!|>D*Si5>mRfPd$^Ch0&b4b?t6SdMa%hh0( z5qe<&$xH#EoA%YeK$B50+waf^G#Ld4OJ4jvt<{6IvS4q)g9=RT#FPRZWu`)BmMdO|ouSqgh7yTsGanA-5e7 z3OKP@s^7A?-bi?&6G>4N_4YN*YUG~N_&do=W#3tYS!UR6Cp^)Kqp6lpn(BYD<0X+p P00000NkvXXu0mjfAj!+q diff --git a/pyscope/gui/theme/light/check-tri-disabled.png b/pyscope/gui/theme/light/check-tri-disabled.png deleted file mode 100644 index 5c796c07d129e7a97cea9575c7b2112f0ed4b78f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|*%g7sn8b-ltOyxegofxYp~a zrfl+8*z^2f`bow$(kHwP%-&woTbemrZOXKLUk^W4=DadB$nW$fg{3WXjH9w|FbN*4 z<`+C@(f8ua8F!QSD~L}2)^SbhPU1H1 zdW|Pr9b61}4&Plfg(-CJ?Amn?qngue{s+}Lc(}Bm?BF<8T|dL1g+W~@A#U%y>4|Aa rBre9rH>^5z=3W(}A`6iB-S(WZx>q;DiF-g_Gcb6%`njxgN@xNA=3R6R diff --git a/pyscope/gui/theme/light/check-tri-hover.png b/pyscope/gui/theme/light/check-tri-hover.png deleted file mode 100644 index a11cd661cdb79afbf83bb2415f2cac3f21a94f14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 365 zcmV-z0h0cSP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10QyNp zK~y-6WBm2&*Z;e(zB9CMeZX-1&PRqXKmRh20(jV&8LU+J87h4g7?eag85plW`S?Fz z@+F2(-~W(g5IW%FWMSBtr^mqjSv|7h`m?VT8OFfC!0_|WKZci|elai|zw?n|!(ia( z?T-vhq=p$j@cG9d1}3VR1_dL~Ko(pQ#U2U_;{0q_#>dIRz+|PuPc;MW)C3rqDt#0f_&9NpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10QX5m zK~y-6WBm2&*Z+rae=tnl`%QImn0*)XQz_RDV+8OFfC!0_wOKZe(ze={(hdH9)P!(ia_ zgHH@hq=p$j@b%|k1}3VR1_dL~Ko(pQ?z*B3{G8aOKYstiu=Db3ta4mmtT}V`<;yxP|+Vx7sn8b-sC@DUtj+}TYsNn z$lAS!3mzO}Z#!@Q&tL*mg=K_s5(_SiCB5JaPMsi@lFq=u=I4|1S^qc>FdP^>UHx3v IIVCg!0K31G#Q*>R diff --git a/pyscope/gui/theme/light/check-unsel-disabled.png b/pyscope/gui/theme/light/check-unsel-disabled.png deleted file mode 100644 index a0f31320c9ffa70bc3a3aef1bd4b890ba3a5023e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-zC7sn8b-nUl`xmy$@j(t>D z&Gck%&JcLM?|*n^hTaC1m?;V(FPBC4Nw3R(`=7CH&-*VThQ~fz@cCSB5nw7hw?k;D z$ed!0r2(O$T<`x{_9@@m);dL~XQOvQkKj4WHLtt&y$|F$TyU+lcin3}$N7@U8{S)c za4-g)ik4t7@nwE*>v8Vqr`h+DdK91Y6h;4f!6^TMv7SkDX@}vxwji$sQ<@6SJ)EmmtT}V`<;yxP|(t_inA9gUj_onHRhG zu0;t5zB?k(EWqTYsp8p`aR2-2OsOfUoSLc@&$m0gx9u^U@m#VrRypJLH?PYgp`mx1 z?4;WmpI0hvx#r~clY_Zok;aB~+D=Nl{yvK@Jm=`bA@41}_W}352m2k4N$mOFulQVM zk%p5-OM~Gv!;BusX{j7jgJvw_d~9*Z^83})!v+bFVi{`%|M)rF|9)fJ+tr*WKFdU9 dA1UmS-!7NmmtT}V`<;yxP|mmtT}V`<;yxP|+<<7sn8b-nUm3`&tb|S|2hm zQsHH5)jIKrW1naEL9Imb1>Um~vKXD6nzD|X_DFp;N~(zX$6B*9d~Qd_y3aO-Jj~m2 znFSyE^%!y}E|}spEw$rY?fzZsoMW#~2x=;>v)9k7aiq{;hZ|pH_5s-7fSFPgN z6v4sx{7&)(%l1VY0@_Ox)*dzS{cx|obfzKOXB+42-WTOe#|n?^S{Lu@5IOBal(KK| a9|nQS#J#~6B87myWbkzLb6Mw<&;$VY3x65_ diff --git a/pyscope/gui/theme/light/empty.png b/pyscope/gui/theme/light/empty.png deleted file mode 100644 index 22183634d5e36298e12ed067750da6c7d2fcdea9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0TuCix;TbNOifOZU~Lv=U|^iZ Vz$kX=awbrQ!PC{xWt~$(699LkAlU!_ diff --git a/pyscope/gui/theme/light/entry-disabled.png b/pyscope/gui/theme/light/entry-disabled.png deleted file mode 100644 index 920bf70fb9f7aabc4a04b70935e97bc69246d1b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-S17sn8b-nUl`^V$q}93G~t zGrB(dcRj@^q5X*LD!u(yCX1BcGt7T;RII$;knMTprD}Dh8J;d4nt|D_p%Y*4dSt;j zEp_3Prd588$vjcnGbC?sQ~bTp{oKsb*yRh(pXvF%@8Uh{rBmD`95h?+SxX6C&i*%T zm5`v#bi0S*H3!B2aBC>_7|J;dHg+t~RE^5+cPv+b-g7Ogu>4HUuc-e%X=yVgw}w5j hk8?QpQ(E{Tw^DiXl)8_0CxI?!@O1TaS?83{1OV`cZc_jN diff --git a/pyscope/gui/theme/light/entry-focus.png b/pyscope/gui/theme/light/entry-focus.png deleted file mode 100644 index 56309029ffceb7d1799eecde1146f748356de8d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|*!f7sn8b-nZusy;u!JS|8e* zH}2_HKC$}kQQjLjcXC`$O3Z%hcc-OwF1NtO%^T{hoP89&M`T!6{YEksmMai-*DlZC6E%n_UzxnO2scB+hfTcX0$pn%YjQn4>HJ8E*h zH9g+Xo%4DBhWAhByxgn&&hGcK{7#N{=Zapc?`hKB(Nxdq%rWC;(up%1iVB8qS!=rw zMrmuRUi!ScxT^H{x-YH|%Jd8CE1fdd{ycp8#L=_D+xxBW-FtrU_v{llKQdl_?kmmtT}V`<;yxP|^y?ZlqORdubk%U1Oma=fei zJ*!?-X@~X0jbB!a*Bli8BVMuh;PTjdrw+qQ)%85z?s;kodTGv(O#5~S3j3^P6mmtT}V`<;yxP|-zC7sn8b-nZusy;vLtS|9FT z9ewN2%#`5X6i5AyoeMc0wfgdy-4vd&OFUx6!k3NfZg~hA7R)L6_ow#5Ars%cv?)Ou zYahNlquJunBN*!H;*oaDqHl$kWx7j>SVr2FD9N_Qb+0{^iiC)U&SkAtQ;NSG#C0i3 z`S~CHH`SKuQP~~4&+q#gt<Ia5%=TzXr(ZBb?Y@wd0N!~Od`?w@a4^8RJqyqj|0V(!EgvweN$)y5|D RjT`7W22WQ%mvv4FO#ouqfad@J diff --git a/pyscope/gui/theme/light/entry-rest.png b/pyscope/gui/theme/light/entry-rest.png deleted file mode 100644 index d347a65a9c36ef61e592f66bfa2e34660d47f5c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VXMsm#F#`kNVGw3Kp1&dmC@5Lt z8c`CQpH@mmtT}V`<;yxP|-n87sn8b-nZu+^I8me+#aT@ zyL28AwcufYZgD3eKxwhX=B&`r*eQvpZ~wlWZ@Bs4yDL#I_ZlC2;HBim(HO+F%B!Tj z@3F#jA1}_(y)3(|(~iCVnlvvxY36uU!p(}EU#@9Q(v z+y66s)hUVQ-m{LkFQ@vcs~sN98xS1}=NyQ8u4v5sj$8EY>_{mxjq`*PIc zYhIeID^|^ryuEGXd+XanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&KtA z7E`k)|DN;QXuPp;Th-a-H)2{-obGehP1rT@^{zMj^0!a@IU3w+WQBu-Fp3qAz^LnJ!^~SJ$aQT#~2pZzmqm*UaVYq(qTQ%ms11? z{VtD^!^(n;p{BbYPh8csRAj>Y-6oz_q7>_mUEa$k_gp_Cur+ML5q7?EC;k=Xi+zD! OVDNPHb6Mw<&;$S}eSJOv diff --git a/pyscope/gui/theme/light/notebook.png b/pyscope/gui/theme/light/notebook.png deleted file mode 100644 index 255dee8e2309dba442cbcd78a38328d69de628ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt*nzE{-7)t#8jQWNa`HVA#0r zxBs8EzJ?Bl!kG%yY4g4&KYx8-cGIc_S}V9#;imMCPGYgm%{I#bn#kbk>gTe~DWM4f DG!H=O diff --git a/pyscope/gui/theme/light/progress-pbar-hor.png b/pyscope/gui/theme/light/progress-pbar-hor.png deleted file mode 100644 index 9806e3d5fc81faf66368913c69487ab94563419c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W_!3HEylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD=>PZ!4!iK)qdzP`R*&!)vR z?bp|GiDhppk9>H2-oA?`DLFCeMDf)01d($$e*Qmwet$@ztUfz8b8y>a8D?p2;iGLE kHyRkGSe-O72(sp3JYgnw;To%SA<$q3Pgg&ebxsLQ00#v>bweIk=>!utY)$y{>b|Sh5TFw(>>EBq`cj5WBZ&n(&3X+8dbbk zNxo&bE6J&CW4=(w@L~7cABUyok8A9ixBo4}D;w2s^0Sm!AF$-lR6Lub$-fb3A%mx@ KpUXO@geCwXjZ9wv diff --git a/pyscope/gui/theme/light/progress-trough-hor.png b/pyscope/gui/theme/light/progress-trough-hor.png deleted file mode 100644 index 6999a37814de71d6b09fe3ffc3e924021ebd110d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W_!3HEylDb50q$YKTtzQZ8Qcszea3Q$n8 z#5JNMI6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD>4PZ!4!iK)qdzP`R*&!)ws zmL+xU01#|SILOq-FK-uc)PRwZk?ok{`&0&X1)ifTTXS~lN zf9IQKU#pjx-O0#hSM!4*+2{c4#DgmodRbUmSROPnFi#R)5&3YZA5bTQr>mdKI;Vst E06Q)-zW@LL diff --git a/pyscope/gui/theme/light/radio-disabled.png b/pyscope/gui/theme/light/radio-disabled.png deleted file mode 100644 index d44a9bf6ad73588f1421a14ba588c8ca861bdcc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 523 zcmV+m0`&cfP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hmcd zK~y-6t(CEkt1u8n57rV(aSA{}geW9k3X1&ycch?6w-kUVTp0ZI}>(D2fQfko9^EK+`nT zbxl=O48yR1ZZ?~#u5H_nm&;{Qv(;)vnx-hFo_>`vhAhkIx^DKQl$_6Jgw}ci6GFU% z0Vt(N(-a}*iqW<$TI&h;s3?kF!XBYf6fHchHNqIPFvBqX5%yiIF$QrP+M8|-ct>_@ z-}m>i`Ck&Q>n@C@Y2Hq@V_nxp*bS)b`fZ>ctCT|6cd@D}>_$D4F@~}%7hVtq6HrPy zo5L`WW!X#UZLr7jzE`A_c%C;c2dy>N>vfUjI1bzG_UY9t%W?q&IGs*+ApHAs0NCwz z?Du=%VcR50LLA3`4U6M=hQTiv?D2TS^Sp)s1qeSs0YMN<^?LFI`~e-LKFh3 diff --git a/pyscope/gui/theme/light/radio-hover.png b/pyscope/gui/theme/light/radio-hover.png deleted file mode 100644 index af45ede55aba74100cca821146c846c44f7f6dad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 837 zcmV-L1G@Z)P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10@6uD zK~y-6m6gj+6Hy$6zk7$lL8``10wp%o1~;Y!qbzjktJH|-zks3vce?TwT^QqI#nJ~M z=%1j1N|nfxg+_&vxDYK0;h`-N+fX{4xh|&isv?}-%-s9UnP2WXBXYT%VudW*p*NnQ zH=ZOumSOT|4glYlQo{anqG6M0*o&^om0J-jfzOAM+ z9q1^wMHydH@)iO7m)PlFq(Q{FoES(sJ(ik0XTOQe$Az;ftXpt&cu}0w?>EraeB| zo9poy3-4Q|bT-eqt~YeQn?f2}KtRXIoiv2JNXu5->`u7Oav7TT_;}E|_n$BTk5NKL z+dhu%u0&ZG0Iom&jAc8>-guJHWELD7vrN(6Tu)&ft4auHZ?4B{C~$1PrRIqZ&$vJU zmCw?0XveC~R%%n3PRpShRDK?SSUiaw8gnfx+aj{dw`SNMMe3?hwtG$ozon7Wv+f0s zL$JcLZeXy&vv91YW^>4eaS3poqR{+TN-ge@LLe(PmllAM#P`g)fr;-K5N-r3HtWb* zzx$a&(|I{Kv2I}O!vsRp0jTqPkkLXGMTtRY-w40vtTn@C=PWw=M%*X@4Pg`65b_c* z-E5|^g7XjGqSi13jiCN?-hXWs}j zSy6xq&K~r0@#vCr0JpyGJR5bP0FGU#uN*jq`jW-<)po$og`?FA^;J}`vEdXqo_ulh zy@~>+!PVnIA|dax`$a$i%T~mOQ^ev)hQ`v%egU<957DqmB;-Zcid*D2$)q_OpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10*OgP zK~y-6os~^b6Hyd~pSkT&KBXg&P%NMjXhD~3Af*MO@gKOMniw}mBU}H2E29t>>dLb6 z9|!^48uqDPZ|1bjC$LV5Ti3zR4`+z2`nT_ug~vm9p8afkme|gRrU* zQ8i+lhin~W0cdNg!P{O>z@y;zD9AR+Iu*$ZT#RYFolmflJ}C>ns2hrd7k4`u^C*>p zx&bo_31*izF9lgOIo!_kiB3czSy3GL9~i*w(kAZ~lcj$F@Kx0+!i?N0M)nB(B!hmE z!N?v#?yM~C)AtmScp3ovSXAAp`cX6k+~kH>DB{GlF&p&o^gi>pk}tsvyO(%44&glIgC2&-Dz zG6MrGWw48SGJXrcrv<~Z{D`U{VyQ!Obnc9xue&O&sGzS4Lq9cnzf&2+_FuDU7&?Q6 zJ-bYU?rWgyCU5)T6oIAKB_s@^ta+-)JXw-Jv^L6E-CxQ5D^bhIeM;3_P-~-%@Ve^F zJey4D+xnHjP;?DRn3EakXh4j4uA3umb|UHy9~M@t!rm{e5RLC5*=#0%)T3bc_d9Vb z4pS`(xt5vv6@Dc4nDljVr?VM=pNTy}(KVv+-Lk^&a5@<6bLKCHu$t!O=RcM_42)bZ zUmC^JlmwDZE@WojzgG|X@b^3Oy&Uj32@bm~KnclaN62*uM??OrsY9~a%V3X2T=@H) z`PCK8XA|xU(5v-u#KqHb_jwxD3pNr@^JZ?ntiP>tD-Ndab(J!!3IsUI8ARe~!m36r um0{~3-%V{zGG12$0k49;&xyUl6Yvjbu>X#2yHO7S0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10+LBY zK~y-6jg>J>6Hyq)fA6)lWvGzYk`R;ws*?sb`vQC zBi#Y}4d2F7+)HtxDB_`L78{Dk=|qGOWFil%IWmC2abgmL+`aWLSDDlX5uO2VsgjMGII2W>raFo zs4uKRK4-=86#Lxak<3KNfn>*ja1~Hu^ktw461qHyuyWQ(`@r z(z$=Fk-o5IS#N1G!|RVpqI2se%wbz^YM_}&&}UDZr;}^EoK4zLrOc)+9u881JUko# z9^KR!zGDst>qLjUn*eO65a9Hjc$N=uXbjzMvf{D<8%v}aoLDXL_t6T}R5IFgkx0;Y zXuS*wu%B1N5-DQw6pJOlfL6^*SEzpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10hUQb zK~y-6t(9G`qA(CZ4+Me&z+=x1T0DS!C>xs-F3b0dGF~=XF3yH zuUF3l-g`s@5#fA3CIF12u&T%{* zF~-pMJ*(9UrPOnw_nyPyfQYc!Y{;_g+wEVm*0SI4QA)8~F24)AuH$q%QB@UD6#Z2; zF^(g3T{EB0@6vEeQ$*g90Yp(mRaF>c5RseEdr#Z8lx6vr?5lLU-O{!#-g`n3VK$qQ z=lR>Ri7d-Vk^~VUG{&H{{;_OAYkd=5E|-@T{TK2)=X^dBI_K_v_6czu*C8U5W%=?eT5D;V2Cen; z@ij3G*d$5NT9fDbZv)m^i!p{_7+9~@_sISzgx4!TYfVuUPglS%+OO)M8pId`00000 LNkvXXu0mjfSkvT7 diff --git a/pyscope/gui/theme/light/radio-unsel-hover.png b/pyscope/gui/theme/light/radio-unsel-hover.png deleted file mode 100644 index 7abe53eba6e8608a5f2029f18ccf35e5e120e807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 573 zcmV-D0>b@?P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10m?~4 zK~y-6t<}Mb>M$6F;n%nj+O8X22u@q|9yIF0>vkcvUPLu^Bh&tE;k-hRTWxmwALJtM^2{`0G{XJ`#w<=5d;CvX7h0=jYn|1-4e$!p6Ai) z^^j8Hx-J0LT8uGdS%$Ti$z(zlMgInGw;QX~ip^$&QVJ>M)9ycWI2=flgnqw|Qi?{S z@i!R9G5h_V`Fu{R)q085Da(?@VnMgtMJWYPt?MM0kDzs3%S14U5~1OY+_fl`XqYDHO=Z)0Uyk|YVU*^Fkhd7p9= zMGS{S7K_DOXk8A&;gBE*fQR}@l7uu(8IQ-b+wGTFp69IBYX*Y>pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10trb( zK~y-6m6cs@sz4BiAGSqMqGeI+jkZbu|9@(NUf8g4BMPn%*>hna1!_Hg@0b}LW|@z5 z%CfZdWUWPOjnA#B82WqWJk_5+bm`o;kp2ySE699Ex zV~n9F3aqufy}co&{26GiWxL&?lp=~EJkJ|^{kx1YXsuZ+7OdCnA#k(Vu;1^A;}{{t zudqgSU6ZCMQ53aM(Hv7L^(PpB5P~?4Nz)XqH2}}nT9PC|N;!ay$780`DWlN{fU+z( z9*>;Q=MHo}pOYjBVHonPwZ?HAeBbZDUS3|9&1M~&=XrRZN0wzA4u_7tIjPw1_e>^} zL1ul}cg^6@B8F=j;N}t?XyPBKl@Gn7$F2zRq_8N z?aA8ifHhMiT-W7#y>^^sSv~~zW3JaLuInO#AmDPjbezZI@geYZI(43nF$6(?kWx|< zMaOwQpUJZ9E-cHkPA>xFc}^5XH=wn4kmccUKq#YpS-@l4l4dgbzQUD?fCfkAPmD>m8F!#ag0)mx~~5OOVgC)a!DA506aI&W?OuH zeIcbBjACELn07m@R;$*2cfrDKoP6JJmqxQ-^E?OO{r$bo>@E-AxpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10nJH7 zK~y-6t(Cow;xHIR52ipOQA#367aLxJ@RyPYhBBbQQ^cmj60{_UAO*HSpjlYWEX$tW z$F}b0>)QEzzVbv>RTyJ1#<1V-$@3fl*L7*NS}3Im!;pHt{=AghvrubInx=T3hm?{| zr-S1-02D=mwHB>4hr@xvV1QEU&%*2V%4)SjYfT)-wA<~M-M?b3B}o#LQbbWitycS8 zn5HS#TBg$}jYi{J*`2a1naySdK|mBm0EqiFwbpOR02+-3)9I8f%P_{=gjH3MrYYm` z_$}F2X&lEamrJUuLKtK4JdYp<-j>~Ix7&E0$7ZuZXsut8`WK{>7-JCo{r<;9|Art4 z*zI1|nAmLy5SXf&c;uisKuO403hna}5x zW%-tDHk;Aw^$5cdc&e}UdQFyPOePaLozAzi?RLvzv7p=S((m_Q>+6m&hBQraUH4J2 zuImD~(YP0EQ4|b^!^g?)ya)jpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10v}04 zK~y-6t(9SKsxTCWAE02N42R1Wb@BiIuq8^iFlS|?LMivdqSrgnCGNf-Pv7&T?P<^P znx^R)k#i1X48|C&wRBwvKoA57A&^ocrNsCBv2?ue;J)vvs*1X$AW0I^G{y70%V6gmWmyu2A$gvkV3%aAWxL(dwk@;Sj35YxVCNjKudhs} zQ?e|(3j8juHM`x8r>CdW)%eHbmt}bq6d=norqd~9Sc5}2t8`E z*$iS)6k)B!cg~Go!{_JckKi$n5Q47j82^vIP~Z1>o`)X<0oK}qK`HekWX#ib9lr15 z3n6HlW?;DV{LZylEC#WrX%Ir-ODU=AdSJ-&oaJ(P5&Hc6%>DiSAYRusaU3J0le!r*dIvyV%e?0y*y}i95gdk1R zuUC<#DQ(-LwZ0juwZ>X|DyR?Oc^(fB5A1fko8ZT9KAX)>4S&$+-{0R6Lj1o*f7JPC snoK4nNpf{GDWyHaK>z>% diff --git a/pyscope/gui/theme/light/scale-thumb-hover.png b/pyscope/gui/theme/light/scale-thumb-hover.png deleted file mode 100644 index 34664b4365a089799f52819841eca8e0526e8528..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 749 zcmVpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10(wbA zK~y-6wUxV08$lF?zuDWYy~ag=G!&6&f?_GW0Zm8~-he8hNqXLcK9N94#S0`|C=f4@ z6d=;%B0*)G!r8SwGrKz}Rvuqi5GcxD&Fy@1G?)Jft+k~_)>@1)7-J}k0&A;fLI`}{ zCk#WvFhmGZ7fw(IR7#3bHIC&vRO>)(qT%t+n*~eUc=h(P-QXoY!D5U@#caZnqsgn9g(_ zdX;MSk1sho>+z#I1Yq@HgJ(|`+1XfOsTBdxY&HQ%)6``ywAOYs8ey%a-EKSR$1h!W z56?+eQJZB;`s{75v9-SJ#`^s}LI~nG#v5N&k|ZTtaoRqi#eUd1VKPi6xA7zA3wmatKq%DKf99{&-1D?o?le| zL2p;xxAue(lx67#9xdJPb5t?0YnNq-5CYHleeyha6PumIdxM{MmR#EygYWxzVHl#6 zauYinE41Y89x<(jIACXE#r3t;gke~Lwbm3x^~9E15qsNf+~9^Y5(4(NAM+pyfy$H| zSO|fXk}S(yz}EUQZ(nv;h_AWoOs4N%b$Ib~@};vZa~3MJ)^=<{aU45C0qCWMgOdx6 zPJi>gI{;vHxyfdyGNEy%P&wRaG@3ObnAm2s*_?sjMRm6M|BZecK3$rmlsA?pt@YhX f)BFbiQcV8>eYs-Ep8zIn00000NkvXXu0mjf2U1Af diff --git a/pyscope/gui/theme/light/scale-thumb-pressed.png b/pyscope/gui/theme/light/scale-thumb-pressed.png deleted file mode 100644 index b0de0d07965dc7eacaf15119b05001beacb29d7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 675 zcmV;U0$lxxP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10x(HL zK~y-6t(Cz}+dvdWkH_P&ViOdWgsRJ;(l3Zq*z_~9qr3i2_m#TfFS@Cy1YbZA%Synm zL>R|-?3vd^h!wDtv{CPJeCNEe=lQ|Vy(p(gE0oJHA*P}y!X^~jkUJ%#Btmz@C+;> zR8@uY-lMf{8BWe`mqHuQS=*zv#(R$vk*4_$aBnH} z-tBEM9_@BQopVeklUZh1Yvu9r z5fNc~dwb=puhaegJxVF!IG%My9LG54D2ifZXi*e6=bE2Q!E7p|Y07ju-3UI9&BaXa z4Eit(Nz?TI1pOl1T1#1$e>IxoIG%OEs?qeaz^Y+7FSu-&{sENzIE*?h&~g9(002ov JPDHLkV1msrJEZ^s diff --git a/pyscope/gui/theme/light/scale-thumb-rest.png b/pyscope/gui/theme/light/scale-thumb-rest.png deleted file mode 100644 index 46bd9ed0075b48ed4c008adb569292a5e01da686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 701 zcmV;u0z&pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10!m3l zK~y-6t(CD-+dvS8Kb=k|TS5#eAs!welguzwA(i{&2|{Z3yhu`$)Z_{1p(7QO%rJR? z0HF+nkZhfFcPTtpe8x5&CfjLu_3i3T`){DNcFZy79Cck&*EQB!oExVFL4Xi~D2j-p zh#&~&y-qL-cFs{%6l&@~_{^)Sa(SM!v9aNu z>GhuxZ$F;%<1(My<<54-@xd<7pLJTFF^1dQTe2)e)OC$g%7a#oT?z&G{wwF*r*o>t zw!Sb7QA$zQ^*`{m9iCh+g-*l2oL+gp7Hq9WO6diU&TbxsF3OcsVy#6u=h~ltVn2W1 zuMEDp_P`hy-n}A zX&QtOh$xECT6@7kzsJsY_9%2`J7duAdA^A-wP0gR8;qsk_+WP_d@>+vYWj>ZXswY_ zjzwrR8sVH{eSO`p-dH}LUU77G)8788UdH>^n+*Cr4+?O1cZU#SJknZgHxZ#ur}HEc zg0aGlMx$0f1&p^snx>Rx$#6J)60|H!Pm|{geHezMY5ISJej2W|rmCuyR#P0uD5aLK jrr83^mg%J6qGkFQU&&dP858c)00000NkvXXu0mjf7>+yo diff --git a/pyscope/gui/theme/light/scale-trough-hor.png b/pyscope/gui/theme/light/scale-trough-hor.png deleted file mode 100644 index 7adbe2d02f6b77d3d5d9e278a0038d51227a3253..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-{hE{-7G89K2hJm&Nt`HUVf%sN*7v$|4HsU%ahD=#xmAn(u%E4 zRqxZ+K5g1JHHgVPEObH%v*)6%Y$t`iEFQj>i!GCATX8f{4`>mCr>mdKI;Vst06@M+ AasU7T diff --git a/pyscope/gui/theme/light/scale-trough-vert.png b/pyscope/gui/theme/light/scale-trough-vert.png deleted file mode 100644 index 924dfa9c60ae6cf6fdbecda91b5d7db42401e4d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt;)(E{-7HKKKxDVW&Su7;D zgTe~ HDWM4f5vffb diff --git a/pyscope/gui/theme/light/scroll-down.png b/pyscope/gui/theme/light/scroll-down.png deleted file mode 100644 index f4dd741ad41fef3f49d476ac11c38f91bb2dcf58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_G@Mt{BJut(gJ>Hjh*KX-8{lZYe Yd~mL8f}kTaGthPhPgg&ebxsLQ0L7G89{>OV diff --git a/pyscope/gui/theme/light/scroll-hor-thumb.png b/pyscope/gui/theme/light/scroll-hor-thumb.png deleted file mode 100644 index 989bc941ed811786635262e2fe62500b63b1da64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^B0$W;!3HFgc;@~FQk(@Ik;M!Qe1}1p@p%4<6riAF ziEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0xJo-U3d8WW#hQsg^qAi&~q z+t6U+l|Kpl6Mp{px5x|1oVn1EtM3SBhnA>$_?6lN8C%Queg9X|#yRbV{{f5VUz9dY zIPK9S+nB1Z>b(CFI4)#IzcDbb7BM!E2r#|6Z@Wu^e>ws_FM9Cc)S#+_V8-a> zQ>vyH->Z7RTlnJE;&qd^TuwCVdmPzQ$hzgvou?PC8*euEboN>~eGBssw&-sT|8fmy RT>|=y!PC{xWt~$(695mnf;0dC diff --git a/pyscope/gui/theme/light/scroll-left.png b/pyscope/gui/theme/light/scroll-left.png deleted file mode 100644 index 498d3caf57a8f0ed8c16c7b494eaf452ad955cce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJx0U~c5>$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-jVE{-7_Gh_QUayl7`xSs#Y zs#wjzr*MO1&Vk$*g&Rvwa&VdDCCp}(d3O7z>Ulpq%cr(7JCa=2etqz+y!=0h!gHCV z9&yj`1nvbvr_P<3cc3tq-ME^uTFvb92l2h%jX%t5-Fj`>luODjAEuTt@i=_GYj}(| aqON1M{CfGR=U)OHz~JfX=d#Wzp$Pzrj#Op< diff --git a/pyscope/gui/theme/light/scroll-right.png b/pyscope/gui/theme/light/scroll-right.png deleted file mode 100644 index 7f771bf82807b5456cdd6d4710c76e1f9062faa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^Y(UJx0U~c5>$3n-oCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt(y8E{-7_Gn3!Ed1G?|2#y~) zG9@S|sAw7^m&EQ}yZ$-fte23KtlnSuH|qL@3m>>6nb`dP{{3w}tCLZ9hM}=>{xP+K z2Y{f#_tBO6`|E2bs{S)DGAgV4{q5|wO`AUPaWgX?u47|kb1Zqz#BfznRVv)A$qr~Q NgQu&X%Q~loCIBP>Q%(Q? diff --git a/pyscope/gui/theme/light/scroll-up.png b/pyscope/gui/theme/light/scroll-up.png deleted file mode 100644 index 09ef917ab4e4bcf12c3bca7a1333ce34842a4b6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237 zcmeAS@N?(olHy`uVBq!ia0vp^96-#*!3HG%vEKg;q&N#aB8wRq_zr_GI(6x;{(%$w+gIgXxyvssnDAon gd(j>KNBVDbzvxi%I2xj92y_L5r>mdKI;Vst09poCGXMYp diff --git a/pyscope/gui/theme/light/scroll-vert-thumb.png b/pyscope/gui/theme/light/scroll-vert-thumb.png deleted file mode 100644 index 6f84abf0c516279f8b6b3bdd59a6b327ddffeb04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eM!3HFEez+qDq&N#aB8wRq_zr_GE(z-`$&|{C-N%uEXj}r<|17d{jc5 zf#KXw#ie&R6}K9)?2Dk-b#dEHaVHM$1rKrsiq4tqJjgwEV13LX z)jzHYJ$nruG;3=`EuOQBwFsQw$KN09^_GF*-tUKtY>Sz;H5vVy?4_#*bRL7JtDnm{ Hr-UW|jkjX8 diff --git a/pyscope/gui/theme/light/scroll-vert-trough.png b/pyscope/gui/theme/light/scroll-vert-trough.png deleted file mode 100644 index 175bb6e33c4983d9ee4db85da19dcd7ac3411eea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eM!3HFEez+qDq&N#aB8wRq_zr_GYVZd&>2p9~Aauj(x=VPjxAV9{s(*=9de#ews;t)JgowW=>BuI{($KetuC zRPOzrE^5e-J$NNaX5GW<`_{h{;1-^ob4K_~57979#$ zRlWmtu6w!-%O(X`0SΞO7!GNmze+qQp8U4&DZ-)Bg=N^G~Yn*{E#EctG6szXP9i T^s73c=NLR){an^LB{Ts5d3}P9 diff --git a/pyscope/gui/theme/light/separator.png b/pyscope/gui/theme/light/separator.png deleted file mode 100644 index 1e7b972cdf57c1772f0f4a882617757da68229fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}bl&H|6fVj#T-gc;Avs>lHuk|nMY zCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+>Bu@p`&AhH%VGuK4%oXFUU_GV|8= SxmoLh5)7WMelF{r5}E+?WFjg6 diff --git a/pyscope/gui/theme/light/sizegrip.png b/pyscope/gui/theme/light/sizegrip.png deleted file mode 100644 index bbcdc5f66a89a86fb391cc3f3d4f66b7f153f331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7BevL9R^{>lpinR(g8$%zH2dih1^v)|cB0Ts>lba4!km|ENKn0MGffLVR& zwM}~e{#(~h*s0BZ+qqFGbK(b^2NU``Jnp#&CG$jO*X(U(xHI?6k-IDBAGl{d>BhFV z_7=}s4xBMCHWD_jW_>pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10%J); zK~zYI&6eFt<4_bvSMw)on$t9hdSQ(a1;vNxqxT_%qEu`xsJ+O~i7CEo7^E?`j2K9P< zZg1^R!J;S_4u@#134#D21UoxBn5MZFxfxn(CX)$LO5!-iG)-Eq7W@1AKMRgVBc7k1 zIXyka_x-gB{f#tD84Ly-A0Kmgc=%oL<>iGqj=8wFz_#tJ%FR$pad&q|5Cj|@9RXkg zNGTbOMqFK8RdQNudc7X~ejh0%073}DFeHkiwZC!z+qOABKd0O6vbVQ~=Xum7lgaev z=7v_Qg%AROx3@PsoepW5&K>0YKJ9j!-QC@_O9JG1&R{U$`uZ9p%Q76tspPcQ3+3SR zd+BsKXstIQ=y@KF;~=HPNRosg2&!VQ*IOv}Rr3D+ej|baK@gB63C4Il#`CS%y`5 zS4Am>Wmy=m>yqbrRS3gy=^?B1u8Ncr*L5+P%_eaiSA{5wmO693?-NDQ#suRyrrB&_ z2q7rTvRV$)G-`1ts!PTg!< z8;wSF>NX`;mL=V8m$S38&r|omg@xPOTMiBmszmcGZaqCc5r*N`<5rR+JU%}D9JgkS z$76=WA@A?+TMew1WpQ$HvN*7Qr6>y0G~G(%_kADNb?5f}0&Rjs(ir0izyJUM07*qo IM6N<$g7=+K0ssI2 diff --git a/pyscope/gui/theme/light/switch-off-hover.png b/pyscope/gui/theme/light/switch-off-hover.png deleted file mode 100644 index 2af2b43301d63bbf75a61efe615bbe2e4ecb87b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 867 zcmV-p1DyPcP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10`N&h zK~zYIwU$pyGF=#luNe?K|NSa-j<$ zm0}dBR?Rq?;5>`S=*gZpU#8#so*x&7bDz1S*Vk8<5iw1ZySqCc9v-l5n|{BKEX$)t z$If6dz~}Q32m}a+!z?T;FgG_ha#s2qu+?gDetu4`*CQ5-5ekJ+RTZDl=Ly_<`u#qZ zWnr2owOS3o-;b{AXqxst;O*@#S65f8udfq{L_7uhuQVDBip3&pYiq2mtb7H$zP_eX zsj#!NgQ6%{mW5#$R4Nr7A0GjjnVBJxNTBOFs;YVp+39pRIXNMjOtQMV`T=-$;qsow2d8F@($I za^J!M^m;vVxg4cZ$#Xy<1e=?ioSvRC7z~hWwHiVQLZJ|rWs%S4aov%N{VA^Na&&Zr zZQGth27^I_5HuPM5=UG((ysfPmf(RH8tfm8}RnrD2jrtX&S9o3xGr-F?P}7;-c4Vz}qiZ(=_CGJWjn{ z2SC?#{C@wBON0<)GMQfitk>(r<8kC@G)lMI#WYP+RVACv{^(vwlEnW0K7l~MbGT-+ ziQ_m#B9XTx+1lFT^70bLaY&_792^`lF){INfe?bj!$X#rmpzAb9EXdG3%0knk!ATq z7H)2CD3{AaS+H$;q%0(pNivztubf<`)1go(u(Y%^G@4JkRVtMz7K^0QY3AqWJqP$# z-gPUTPJfb+FT&bxw=oQZZnsM`8YL78F*!N;E5iEn@`7oa)a!MG5a_zj?Ck8Pxi5h4 tX*Qcf<+m*BSCn5>)uHkSgTaxre*k`cUXudB&`SUS002ovPDHLkV1nONj%NS> diff --git a/pyscope/gui/theme/light/switch-off-pressed.png b/pyscope/gui/theme/light/switch-off-pressed.png deleted file mode 100644 index d5fef562e0d53c0a274c50180817f4fd543f979c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 880 zcmV-$1CRWPP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10{uxu zK~zYIy_Qc)GF=>ouQR&f(0t#c()u_#(YtkNf+3y4^08Wic2GaGWnk zza1e29*+k_Q3!=X#N%-Sfxy?ZzY6NQ&gJDLmSqu%M9?%1MNyDt86m_}1Rcl0wrxz) z#4rpxoeqkku(Pv+rfI(ytkr5h(I?+uLkzZjRsm^Zrn+ zR;kr$92^{=C<>-&QZAQiwOTwsKQkN-KY{1w=J5G^tgWq)$z)Jfbs|9_1kq>|pU+3B zR00@Fis5kRbUGc*&d%81-)CWAfqK18p-{lK?LYpAEX(9_Ig-ibM3V2ZEQ^ba3l0wt z2?m2mj^j`)7TMa`8q1xWoP0|Tz_x9UkB@0I8m#pQpUtg(KtL*LVA%ys^&D3f& zs?{plY<650DwPV&W|Mxu|K$%{mSwzNFNs8gbUN*NloMv!4Xt^YrvIuKcEH{z&ClRdrnX{eJ(Kv;P9C;bZ9Vh#W!y0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10=r2> zK~zYI#g;iw<4_bvk1ZpR2}^|>u!UHX0wDoX(!@Z8DCp?<0r^Mp1E`^b3M!j4wpbdF zuo59`CvwN;1cIeZfgC17*hYBflzvxNNALN)`w7ivvqcwt-=|zI<9Qwr4-Z69gdhmr zO2193)k2aaQmGWGsxm!2&FJW8*IMCE!CI|`VHh-5k zcXx;Dx?Emf(%ak1($W%&qP#D7dU}d&+bk|FGC4WQzyF`g#e`wV{{B8QGcznKEW8R{Ute>0c*y$tI1$>R4OGR2$0ESa2y9Q2!f6`$hPe-H?b@$2{+(n7lR-`48ySF)>f<4 zFE{zhPX8^bREjVR5d}e@)#^G-K1CG8AJ%#$b`oV-re3eN7v38=-AJd?KQ0y%pRA}T zO2<*<^Z73~`A+S3@qHgfQ4r(X5~J%ns;Yk4Kvh)=g+gM2j^mKYWDqANCTKJoxULI; zBuNyD#SbOphdQZg;&~p8MuYM3@t0BOx-JI?2X97QsZ_$UtoARUsw%l$jzXc3=&0M- z*r;JKIh)9dYaI>+S6=uIu7B4t;%nEH5uJGBVOp_eLqqhyVZp07*qoM6N<$f+tID@Bjb+ diff --git a/pyscope/gui/theme/light/switch-on-disabled.png b/pyscope/gui/theme/light/switch-on-disabled.png deleted file mode 100644 index 3d03bc91557b3933b80bf339a799720a246a76c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 590 zcmV-U0pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10ozGL zK~zYI&6Z8CsxTBq7xf|0InN zTCGUa^hbgICC70XjYcS?o&%O;$!fJC48vaqyVqbaAP9oXX^Lv&6W{>CFl4=6U+O<_ z{;hSx2_f)257%{Rx7z?DNy2WoBZ?x5qPTcBn+=^#=iHZRnwGQKtk#}uHk(W)6Qq<+ z_l+^k=X0_wyR12m!*n`DXsv676GFU%1CUZOnM@Gk(s86|%6`8`#Bp339DwI}FX1Xl zDe*k-W=(62Fviry20*{xf7|Q2?oDlsK^%|ApJuvItJQkjTV0+?d7fXNnNO9lZM)VF z91e%Ky-AYX)V6IS-f7_PXuI9M?cMEmH?@=!aXgU!?=u_@FZDxU)mpP$EpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10~bj| zK~zYIwU*IOQ)d{5pYJ<8hHV$~ zZp)bBh5v$W6d^IX8}Do=F2-b9qcy-N2rITdr{{dTI5kEpLbuaj@+BuH@0&d5{oeDw zKVh1tVw)`6VRmtiJLw#cvs)}}n7lRZ?P32*O@57zRzK0MX5!Hh@n{fTlT~qIJ797l z#}A3;yj&}J3bdOJ1`Q_8L^$0S`Z%END5eq_u0MEPCj_i#24e^4X>SJL*-D=I#T9Pc%dom-f!4sa`&k4qITCTd z!8A>kT*&dw%_sGNvkHv#hxz&3Q38hVqapcXnaej*Bp$7R-UI@szdFLHSO{s^4nHL` zbzxKGB4_&B`EBg;9pL~1hL7p-FBt9*gIxsR%B|;^Wd}LCxWjx zUHFt^G1%8pbKX#_0~`~8^s78_HE(&%=E|{&RQ}dl6bT2wahJmKrb+ea_DBi`Rkfq` zkf#)?GAE@F$Z(6_W3+b?WL9c#pUmHRsMIaoq9bEnwO_+tBs4#B3$+jayQ$X*O$VT_ zD}angL%XE>HwnFwnfa&Wi{(A%Y!)nL=AR<;CIE(_As4VaWOz>~eXJG)7iS*ssTY-k zOEV8yFG-LZ2ZIJ*_O>B)P4L}t#Ct$MHnfsREiwM4g5T=_31C||<`$}41gFxM(ueRFNPhq{cjH_*(aE=`-`lM!!+Iy3 zV=9rU&wl=dx=5pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H110hL7 zK~zYIt(MJG6K5E}fA71StO-)Hmb8FCJya7Yo%UF#H28u32Xsd4e;{KI_SA#JvF*{1 zt6nNz+P|QYNkscGv8eI-`G({1WU0>VT!mZrp0LsYX-1g-+jqDy z)(^a@;LM{cKmI-+nw;mHf(wngO=4b+G8iMnu`a=-}Fk%PD7ApL3)WOlX^))_-IGoEIVRX2MPm-N)9pLq*%YT)1@(&id znV)A(S(vd-K=9qM9wtxq0o%r{t1}BBO8V!b*Q}x4CfaMSrjRBgx(_27M@YYKpF7sg z`4d~?R@=auD^|IZErr*g_sAjJ;=tGNCI<*k^d!N{Yd*d5Bn|v0000pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10_I6X zK~zYIy_QQzR8bhmfA?NzM)MiW1Y$bAA_&bH-9$de6On9^$jUHbx295SvruO@V@wd^azd=&!1xMJ`#mi9 zw@+2*DDupSbTnR|q0F+sU@)Z7*6-nsbLqc|#q#iG4js1&kVFB1gnvKum(w7mhCgk$ zXo3KO1d;&=3w4cn5rEFyh2e;zDB8gI3U|Aw^>YiUm;{C9>-pTfnoUu58UWKv8}yEO z>G?E|tQkR!+!ya#imA6-5VrnoYnPvYVa^xOM=q$6l?KDD6_i-h_l>w0*QkCq#`2~H zq5*(hb0SkOt{^&Ge*NUoFbM&MTgwlW1F%^$7L(afXsXCLcD$*=3aS!WPb_XC{@hRwIdVdR zDwob5T-sQQveQ9RBkLdQGV!hZG!fz+sY3~ATC@cLq97n^KC z_r_f^(~XFw)>J*r!bk?*ex5saymxdCBx7VQn)IGmS zk8>6b$&tMpn;Egb)Iz=`NgqK$M3$1NZg(Ck>7HMuy4{H)r9?`cXHKN1#2kL=K8*Xg z(>g?yJv6(SQLR{`I<~ksbY!9m400000NkvXXu0mjfBOZq! diff --git a/pyscope/gui/theme/light/tab-hover.png b/pyscope/gui/theme/light/tab-hover.png deleted file mode 100644 index 0c6df3e27ff33d1fdb505b41866ad67fe2d25def..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXprS3FE{-7)t#7X#R>Vh4SF!oFPC={3jw7IVz?s~-}ksh^hpz|}CPR-scu*@Z>L@zbUL zvpi{Rp{=V91>U3{10U!a}WIZ36zwbBeHqfhM@ zJRWG)AaE%6X}auX#^Z)1$~&x2Us`v!}gA)@k!TV&l2pJ>trx}U+*)z4*}Q$iB}W%X{x diff --git a/pyscope/gui/theme/light/tab-rest.png b/pyscope/gui/theme/light/tab-rest.png deleted file mode 100644 index 725beb9abf7018fe96dabd6fcb03fb0dd2aec914..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IXpdv$07srr@*0<*j85sF_}IJxB(8DKVw7 z3BI+ekYIZ<=i$48@+WiZ#RUXs+{}AX-oc^BV&dB@p}cFI^ZoCeYd4E^a0GePADo^N z9^Vt<8 diff --git a/pyscope/gui/theme/light/treeheading-hover.png b/pyscope/gui/theme/light/treeheading-hover.png deleted file mode 100644 index 47bf56f4a5e8cc6b7db6816c07230ecf7d668804..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 338 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt=aGT^vI!df#4k%s!$Z(DpD~ zSXHIs%>Vz5J6k*!X;|6jOkV2Ez4i8`({;-oKNPx1gnD`YX#SEOn5}xuLz8vlw#`$5 zN@9;2@MNsb&(>63xpmWf>uXUxh8jy}B?u}$_wnk?oOK~;F=J9sME1sOW{cC0KmRA8 ze1KPPX+lpXn|yN5!YM`Vu20#Z&00sP zMPOcA|KlB5ybtCi$A<6ue*H$arzY!r+uPe7&SAb+FVHZ@`1B?Lryc7)-_2Mnd3<8! iyqCZKDgU`$$vpLIzJK@D%w<5YGI+ZBxvXanMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt-oLT^vI!df#3>n{~v1$L(VF zPkzUH|9zV`_B_>FHo39U>9Rq|Gvo z7dcbo=y&J#y`K_v!cu(wbiPNDtFIp#+#ix-tidtrV{;XKe= N44$rjF6*2UngEe6hY$b& diff --git a/pyscope/gui/theme/light/treeheading-rest.png b/pyscope/gui/theme/light/treeheading-rest.png deleted file mode 100644 index d4aa09591d8d749f0a36640afa74788ea3e987c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4foCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt<= zSI5X{74HQnR_h*{akeLO)`xqK=iD~mFKw)RtYg(B6JDhzh80@(elPEHS5o?28M^m6 zLy(unbH3*Fh36&){c0&bXFuzUsnVwX=PTc?Ruiu|DE=pY!_36Aoe)+>< zMTXqlMt#bPEZcIedGc<%aWvk3>vp~Lz;Ax04ePWI8*I=y{V-cl@YSwm@2gj>s%opZ Z&W|g~@xSWc@(Acf22WQ%mvv4FO#tQyiD3W$ diff --git a/pyscope/gui/themeSetup.tcl b/pyscope/gui/themeSetup.tcl deleted file mode 100644 index 750bb9f5..00000000 --- a/pyscope/gui/themeSetup.tcl +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright © 2021 rdbende - -source bin/theme/light.tcl -source bin/theme/dark.tcl - -option add *tearOff 0 - -proc set_theme {mode} { - if {$mode == "dark"} { - ttk::style theme use "sun-valley-dark" - - array set colors { - -fg "#ffffff" - -bg "#1c1c1c" - -disabledfg "#595959" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style configure . \ - -background $colors(-bg) \ - -foreground $colors(-fg) \ - -troughcolor $colors(-bg) \ - -focuscolor $colors(-selectbg) \ - -selectbackground $colors(-selectbg) \ - -selectforeground $colors(-selectfg) \ - -insertwidth 1 \ - -insertcolor $colors(-fg) \ - -fieldbackground $colors(-selectbg) \ - -font {"Segoe Ui" 10} \ - -borderwidth 1 \ - -relief flat - - tk_setPalette \ - background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] - - ttk::style map . -foreground [list disabled $colors(-disabledfg)] - - option add *font [ttk::style lookup . -font] - option add *Treeview.show tree - option add *Menu.selectcolor $colors(-fg) - - } elseif {$mode == "light"} { - ttk::style theme use "sun-valley-light" - - array set colors { - -fg "#202020" - -bg "#fafafa" - -disabledfg "#a0a0a0" - -selectfg "#ffffff" - -selectbg "#2f60d8" - } - - ttk::style configure . \ - -background $colors(-bg) \ - -foreground $colors(-fg) \ - -troughcolor $colors(-bg) \ - -focuscolor $colors(-selectbg) \ - -selectbackground $colors(-selectbg) \ - -selectforeground $colors(-selectfg) \ - -insertwidth 1 \ - -insertcolor $colors(-fg) \ - -fieldbackground $colors(-selectbg) \ - -font {"Segoe Ui" 10} \ - -borderwidth 0 \ - -relief flat - - tk_setPalette background [ttk::style lookup . -background] \ - foreground [ttk::style lookup . -foreground] \ - highlightColor [ttk::style lookup . -focuscolor] \ - selectBackground [ttk::style lookup . -selectbackground] \ - selectForeground [ttk::style lookup . -selectforeground] \ - activeBackground [ttk::style lookup . -selectbackground] \ - activeForeground [ttk::style lookup . -selectforeground] - - ttk::style map . -foreground [list disabled $colors(-disabledfg)] - - option add *font [ttk::style lookup . -font] - option add *Treeview.show tree - option add *Menu.selectcolor $colors(-fg) - } -} diff --git a/pyscope/telrun/__init__.py b/pyscope/telrun/__init__.py index 2a3b306d..440fab9e 100644 --- a/pyscope/telrun/__init__.py +++ b/pyscope/telrun/__init__.py @@ -11,20 +11,17 @@ from .telrun_exception import TelrunException from .exoplanet_transits import exoplanet_transits from .init_telrun_dir import init_telrun_dir -from .init_queue import init_queue from .mk_mosaic_schedule import mk_mosaic_schedule from .rst import rst from . import sch, schedtab, reports from .schedtel import schedtel, plot_schedule_gantt, plot_schedule_sky from .startup import start_telrun_operator from .survey_builder import survey_builder -from .telrun_block import TelrunBlock from .telrun_operator import TelrunOperator __all__ = [ "exoplanet_transits", "init_telrun_dir", - "init_queue", "mk_mosaic_schedule", "rst", "sch", @@ -35,7 +32,6 @@ "plot_schedule_sky", "start_telrun_operator", "survey_builder", - "TelrunBlock", "TelrunOperator", "TelrunException", ] diff --git a/pyscope/telrun/block.py b/pyscope/telrun/block.py new file mode 100644 index 00000000..e69de29b diff --git a/pyscope/telrun/boundary_condition.py b/pyscope/telrun/boundary_condition.py new file mode 100644 index 00000000..5e81ae6c --- /dev/null +++ b/pyscope/telrun/boundary_condition.py @@ -0,0 +1,2 @@ +class BoundaryCondition: + pass diff --git a/pyscope/telrun/calibration_block.py b/pyscope/telrun/calibration_block.py new file mode 100644 index 00000000..c9de63e9 --- /dev/null +++ b/pyscope/telrun/calibration_block.py @@ -0,0 +1,5 @@ +from .block import Block + + +class CalibrationBlock(Block): + pass diff --git a/pyscope/telrun/dark_field.py b/pyscope/telrun/dark_field.py new file mode 100644 index 00000000..02a83024 --- /dev/null +++ b/pyscope/telrun/dark_field.py @@ -0,0 +1,5 @@ +from .field import Field + + +class DarkField(Field): + pass diff --git a/pyscope/telrun/field.py b/pyscope/telrun/field.py new file mode 100644 index 00000000..bf8ccb25 --- /dev/null +++ b/pyscope/telrun/field.py @@ -0,0 +1,2 @@ +class Field: + pass diff --git a/pyscope/telrun/flat_field.py b/pyscope/telrun/flat_field.py new file mode 100644 index 00000000..fbe973d8 --- /dev/null +++ b/pyscope/telrun/flat_field.py @@ -0,0 +1,5 @@ +from .field import Field + + +class FlatField(Field): + pass diff --git a/pyscope/telrun/init_queue.py b/pyscope/telrun/init_queue.py deleted file mode 100644 index 79e03f2b..00000000 --- a/pyscope/telrun/init_queue.py +++ /dev/null @@ -1,13 +0,0 @@ -import click - - -@click.command() -def init_queue_cli(): - """ - TBD - - """ - pass - - -init_queue = init_queue_cli.callback diff --git a/pyscope/telrun/init_telrun_dir.py b/pyscope/telrun/init_telrun_dir.py index c2ef2fc3..8b612b6d 100644 --- a/pyscope/telrun/init_telrun_dir.py +++ b/pyscope/telrun/init_telrun_dir.py @@ -6,8 +6,6 @@ import click -from . import init_queue - logger = logging.getLogger(__name__) diff --git a/pyscope/telrun/light_field.py b/pyscope/telrun/light_field.py new file mode 100644 index 00000000..21d386c7 --- /dev/null +++ b/pyscope/telrun/light_field.py @@ -0,0 +1,5 @@ +from .field import Field + + +class LightField(Field): + pass diff --git a/pyscope/telrun/optimizer.py b/pyscope/telrun/optimizer.py new file mode 100644 index 00000000..b9ded9bf --- /dev/null +++ b/pyscope/telrun/optimizer.py @@ -0,0 +1,2 @@ +class Optimizer: + pass diff --git a/pyscope/telrun/prioritizer.py b/pyscope/telrun/prioritizer.py new file mode 100644 index 00000000..f446519b --- /dev/null +++ b/pyscope/telrun/prioritizer.py @@ -0,0 +1,2 @@ +class Prioritizer: + pass diff --git a/pyscope/telrun/queue.py b/pyscope/telrun/queue.py new file mode 100644 index 00000000..89ebf117 --- /dev/null +++ b/pyscope/telrun/queue.py @@ -0,0 +1,2 @@ +class Queue: + pass diff --git a/pyscope/telrun/schedule.py b/pyscope/telrun/schedule.py new file mode 100644 index 00000000..8806186e --- /dev/null +++ b/pyscope/telrun/schedule.py @@ -0,0 +1,2 @@ +class Schedule: + pass diff --git a/pyscope/telrun/schedule_block.py b/pyscope/telrun/schedule_block.py new file mode 100644 index 00000000..46cc3625 --- /dev/null +++ b/pyscope/telrun/schedule_block.py @@ -0,0 +1,18 @@ +from .block import Block + + +class ScheduleBlock(Block): + def __init__(self): + """ + Initialize the ScheduleBlock object + + + + """ + pass + + def __str__(self): + pass + + def __repr__(self): + return str(self) diff --git a/pyscope/telrun/scheduler.py b/pyscope/telrun/scheduler.py new file mode 100644 index 00000000..eff71b90 --- /dev/null +++ b/pyscope/telrun/scheduler.py @@ -0,0 +1,2 @@ +class Scheduler: + pass diff --git a/pyscope/telrun/telrun_block.py b/pyscope/telrun/telrun_block.py deleted file mode 100644 index af22bbc6..00000000 --- a/pyscope/telrun/telrun_block.py +++ /dev/null @@ -1,7 +0,0 @@ -import astroplan - -# convenience class that wraps astroplan.ObservingBlock - - -class TelrunBlock(astroplan.ObservingBlock): - pass diff --git a/pyscope/telrun/telrun_operator.py b/pyscope/telrun/telrun_operator.py index cc5bca22..368671d2 100644 --- a/pyscope/telrun/telrun_operator.py +++ b/pyscope/telrun/telrun_operator.py @@ -15,7 +15,6 @@ import astroplan import numpy as np -import tksheet from astropy import coordinates as coord from astropy import table from astropy import time as astrotime diff --git a/pyscope/telrun/transition_field.py b/pyscope/telrun/transition_field.py new file mode 100644 index 00000000..e69de29b diff --git a/pyscope/telrun/unallocated_block.py b/pyscope/telrun/unallocated_block.py new file mode 100644 index 00000000..72ff86ea --- /dev/null +++ b/pyscope/telrun/unallocated_block.py @@ -0,0 +1,5 @@ +from .block import Block + + +class UnallocatedBlock(Block): + pass diff --git a/requirements.txt b/requirements.txt index bc971db9..2c7f777b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,15 +6,16 @@ ccdproc == 2.4.2 click == 8.1.7 cmcrameri == 1.9 markdown == 3.6 -numpy == 2.1.0 matplotlib == 3.9.1 +numpy == 2.1.0 oschmod == 0.3.12 paramiko == 3.4.1 photutils == 1.13.0 +prettytable == 3.11.0 pywin32 == 306;platform_system=='Windows' -timezonefinder == 6.5.2 scikit-image == 0.24.0 scipy == 1.14.1 smplotlib == 0.0.9 +timezonefinder == 6.5.2 tqdm == 4.66.4 -# twirl == 0.4.2 +twirl == 0.4.2 From 504300e4d21e005b4da82d65f2c94fd5f8572463 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:25:56 +0000 Subject: [PATCH 03/30] Bump twine from 5.0.0 to 5.1.1 Bumps [twine](https://github.com/pypa/twine) from 5.0.0 to 5.1.1. - [Release notes](https://github.com/pypa/twine/releases) - [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/twine/compare/5.0.0...v5.1.1) --- updated-dependencies: - dependency-name: twine dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4f1b5483..78dc18a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -87,7 +87,7 @@ dev = sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 sphinxcontrib-programoutput==0.17 - twine==5.0.0 + twine==5.1.1 [options.package_data] pyscope = *.txt From 21fd57de0a1fdaa7589bf7359e866785de53ad36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:26:02 +0000 Subject: [PATCH 04/30] Bump numpy from 2.1.0 to 2.1.1 Bumps [numpy](https://github.com/numpy/numpy) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.1.0...v2.1.1) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 68eeb74c..66311c93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ccdproc == 2.4.2 click == 8.1.7 cmcrameri == 1.9 markdown == 3.6 -numpy == 2.1.0 +numpy == 2.1.1 matplotlib == 3.9.1 oschmod == 0.3.12 paramiko == 3.4.0 From 8c6f8c8961e3eecf70ac4c3b5fc4b1459f1725d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:26:07 +0000 Subject: [PATCH 05/30] Bump sphinx from 7.2.6 to 8.0.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 7.2.6 to 8.0.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/v8.0.2/CHANGES.rst) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v7.2.6...v8.0.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4f1b5483..f2299fa9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,7 +62,7 @@ console_scripts = [options.extras_require] docs = - sphinx==7.2.6 + sphinx==8.0.2 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 sphinxcontrib-programoutput==0.17 @@ -83,7 +83,7 @@ dev = pytest-cov==5.0.0 pytest-order==1.3.0 rstcheck==6.2.1 - sphinx==7.2.6 + sphinx==8.0.2 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 sphinxcontrib-programoutput==0.17 From 4c7a1ee83735a3eda88f5c2ad92ab32e79623f77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:26:08 +0000 Subject: [PATCH 06/30] Bump docutils from 0.20.1 to 0.21.2 Bumps [docutils](https://docutils.sourceforge.io) from 0.20.1 to 0.21.2. --- updated-dependencies: - dependency-name: docutils dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4f1b5483..7234522b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,7 +75,7 @@ tests = dev = black==24.3.0 build==1.2.1 - docutils==0.20.1 + docutils==0.21.2 esbonio==0.16.4 isort==5.13.2 pre-commit==3.7.0 From 0e30b4a375f6866e08339a6ca6b6ca8ce0b3ad00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:26:11 +0000 Subject: [PATCH 07/30] Bump tqdm from 4.66.4 to 4.66.5 Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.4 to 4.66.5. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.66.4...v4.66.5) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 68eeb74c..22fd559e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,5 +19,5 @@ scipy == 1.14.1 smplotlib == 0.0.9 timezonefinder == 6.5.0 tksheet == 7.2.12 -tqdm == 4.66.4 +tqdm == 4.66.5 # twirl == 0.4.2 From e4670d11d4f6b758b3d3fc6a274c2b1c0217a5ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:29:56 +0000 Subject: [PATCH 08/30] Bump pypa/gh-action-pypi-publish from 1.10.0 to 1.10.2 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.0 to 1.10.2. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.0...v1.10.2) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/pypi-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 77cf1689..87b46f35 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -34,4 +34,4 @@ jobs: - name: Build package run: python -m build - name: pypi-publish - uses: pypa/gh-action-pypi-publish@v1.10.0 + uses: pypa/gh-action-pypi-publish@v1.10.2 From 5c29ac2f6375895bc6b89176b3c3b55f0c0b4625 Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 1 Oct 2024 08:39:21 -0400 Subject: [PATCH 09/30] Docs update --- docs/source/index.rst | 49 ++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index deb26582..8ab95862 100755 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,12 +19,42 @@ Getting Started Authors ======= -* Walter Golay +* `Walter W. Golay `__ + - Lead Developer & Maintainer + - Founding Member of the Macalester-Augustana Coe Robotic Observatory + `(MACRO) Consortium `__ - Graduate Student at Harvard University, Department of Astronomy - Former Undergraduate at University of Iowa, Department of Physics and Astronomy +* John M. Cannon + + - Director of the `MACRO Consortium `__ + - Professor at Macalester College, Department of Physics and Astronomy + +* William M. Peterson + + - Founding Institution Lead of the `MACRO Consortium `__ + - Associate Professor at Augustana College, Department of Physics and Astronomy + +* James Wetzel + + - Founding Institution Lead of the `MACRO Consortium `__ + - Adjunct Professor at Coe College, Department of Physics + +* Philip Griffin + + - Founding Member of the `MACRO Consortium `__ + - Graduate Student at University of Iowa, Department of Physics and Astronomy + +* Student Developers: + + - William St. John, Macalester College Class of 2026 + - Lila Schisgal, Macalester College Class of 2026 + - Cain Rinkoski, Macalester College Class of 2025 + - Olivia Laske, Macalester College Class of 2024 + Acknowledgements ================ @@ -35,21 +65,12 @@ Acknowledgements (VAO), and the Iowa Robotic Observatory (IRO), including the Rigel Telescope and the Gemini Telescope, now known as the *Robert L. Mutel Telescope* (RLMT). -* The Macalester-Augustana Coe Robotic Observatory - (`MACRO `__) - Consortium for providing unrestricted access to the - *Robert L. Mutel Telescope* for testing the early iterations of - this software. - - * John Cannon, Professor at Macalester College, Department of - Physics and Astronomy - * William Peterson, Professor at Augustana College, Department - of Physics and Astronomy - * James Wetzel, Professor at Coe College, Department of Physics - +* The benefactors of the `MACRO Consortium `__ + for providing unrestricted access to the *Robert L. Mutel Telescope* for + testing the early iterations of this software. * Mark and Pat Trueblood, Directors of the `Winer Observatory `__ where the *Robert L. Mutel Telescope* is located -* Kevin Ivarsen +* Kevin Ivarsen, Lead Software Developer at PlaneWave Instruments * The astronomy faculty and staff at the University of Iowa, Department of Physics and Astronomy From 6431da2756a52d18e7943b4e49416ea2c4e5a28c Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 1 Oct 2024 08:39:41 -0400 Subject: [PATCH 10/30] Add _block private class --- pyscope/telrun/_block.py | 366 ++++++++++++++++++++++++++++ pyscope/telrun/block.py | 0 pyscope/telrun/calibration_block.py | 5 - 3 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 pyscope/telrun/_block.py delete mode 100644 pyscope/telrun/block.py delete mode 100644 pyscope/telrun/calibration_block.py diff --git a/pyscope/telrun/_block.py b/pyscope/telrun/_block.py new file mode 100644 index 00000000..b7d4336c --- /dev/null +++ b/pyscope/telrun/_block.py @@ -0,0 +1,366 @@ +import ast +import logging +from uuid import UUID, uuid4 + +from astropy.time import Time + +from .configuration import Configuration + +logger = logging.getLogger(__name__) + + +class _Block: + def __init__(self, *args, config=None, name=None, description=None, **kwargs): + """ + A class to represent a time range in the schedule. + + A `~pyscope.telrun._Block` are used to represent a time range in the schedule. A `~pyscope.telrun._Block` can be + used to represent allocated time with a `~pyscope.telrun.ScheduleBlock` or unallocated time with a + `~pyscope.telrun.UnallocatedBlock`. The `~pyscope.telrun._Block` class is a base class that should not be instantiated + directly. Instead, use the `~pyscope.telrun.ScheduleBlock` or `~pyscope.telrun.UnallocatedBlock` subclasses. + + Parameters + ---------- + *args : `tuple` + If provided, the first argument should be a string representation of a `~pyscope.telrun._Block`. The `from_string` + class method will parse the string representation and create a new `~pyscope.telrun._Block` object. The remaining + arguments will override the parsed values. + + configuration : `~pyscope.telrun.Configuration`, default: `None` + The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun._Block`. This `~pyscope.telrun.Configuration` will be + used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun._Block` and + will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` s + in the `~pyscope.telrun._Block` if a `~pyscope.telrun.Configuration` has not been provided. If a `~pyscope.telrun.Field` + has a different `~pyscope.telrun.Configuration`, it will override the block `~pyscope.telrun.Configuration` for the + duration of the `~pyscope.telrun.Field`. + + name : `str`, default: `None` + A user-defined name for the `~pyscope.telrun._Block`. This parameter does not change + the behavior of the `~pyscope.telrun._Block`, but it can be useful for identifying the + `~pyscope.telrun._Block` in a schedule. + + description : `str`, default: `None` + A user-defined description for the `~pyscope.telrun._Block`. Similar to the `name` + parameter, this parameter does not change the behavior of the `~pyscope.telrun._Block`. + + kwargs : `dict` + A dictionary of keyword arguments that can be used to store additional information + about the `~pyscope.telrun._Block`. This information can be used to store any additional + information that is not covered by the `configuration`, `name`, or `description` parameters. + + See Also + -------- + pyscope.telrun.ScheduleBlock : A subclass of `~pyscope.telrun._Block` that is used to schedule `~pyscope.telrun.Field` s + in a `~pyscope.telrun.Schedule`. + pyscope.telrun.UnallocatedBlock : A subclass of `~pyscope.telrun._Block` that is used to represent unallocated time in a + `~pyscope.telrun.Schedule`. + pyscope.telrun.Configuration : A class that represents the configuration of the telescope. + pyscope.telrun.Field : A class that represents a field to observe. + """ + if len(args) > 0: + self = self.from_string( + args[0], config=config, name=name, description=description, **kwargs + ) + return + + logger.debug( + "\n\n\n----------------------------------------------------------------------" + ) + logger.debug("Creating a new Block object...") + logger.debug("configuration=%s" % config) + logger.debug("name=%s" % name) + logger.debug("description=%s" % description) + logger.debug("kwargs=%s" % kwargs) + + if type(config) is not Configuration: + logger.error( + "The configuration parameter must be a Configuration object, not a %s", + type(config), + ) + logger.error("Creating a new empty Configuration object.") + config = Configuration() + + self.config = config + self.name = name + self.description = description + self.kwargs = kwargs + + self._uuid = uuid4() + self._start_time = None + self._end_time = None + + logger.debug("Block ID: %s" % self.ID.hex) + logger.debug("Creating a new Block object... Done!") + logger.debug( + "----------------------------------------------------------------------\n\n\n" + ) + + @classmethod + def from_string(cls, string, config=None, name=None, description=None, **kwargs): + """ + Create a new `~pyscope.telrun._Block` from a string representation. Additional arguments can be provided to override + the parsed values. + + Parameters + ---------- + string : `str` + + config : `~pyscope.telrun.Configuration`, default: `None` + + name : `str`, default: `None` + + description : `str`, default: `None` + + kwargs : `dict` + + Returns + ------- + block : `~pyscope.telrun._Block` + + """ + logger.debug("_Block.from_string called") + logger.debug("Creating a new Block object from string...") + logger.debug("string=%s" % string) + + # Parse the string representation to extract the block information + block_info = string.split("\n") + block_id = uuid(hex=block_info[1].split("Block ID: ")[1]) + if name is None: + name = ( + block_info[2].split("Name: ")[1] + if block_info[2].split("Name: ")[1] != "None" + else None + ) + if description is None: + description = ( + block_info[3].split("Description: ")[1] + if block_info[3].split("Description: ")[1] != "None" + else None + ) + + if kwargs is None: + try: + kwargs = ast.literal_eval(block_info[4].split("Keyword Arguments: ")[1]) + except: + logger.error("Failed to parse the keyword arguments.") + kwargs = {} + + start_time = ( + Time(block_info[5].split("Start Time: ")[1]) + if block_info[5].split("Start Time: ")[1] != "None" + else None + ) + end_time = ( + Time(block_info[6].split("End Time: ")[1]) + if block_info[6].split("End Time: ")[1] != "None" + else None + ) + + if config is None: + config = ( + Configuration.from_string(block_info[7].split("Configuration: ")[1]) + if block_info[7].split("Configuration: ")[1] != "None" + else None + ) + + # Create a new block object + block = cls(config=config, name=name, description=description, **kwargs) + block._uuid = block_id + block._start_time = start_time + block._end_time = end_time + + logger.debug("Creating a new Block object from string... Done!") + return block + + def __str__(self): + """ + A `str` representation of the `~pyscope.telrun._Block`. + + Returns + ------- + str : `str` + """ + logger.debug("_Block.__str__ called") + s = "\nBlock ID: %s\n" % self.ID.hex + s += "Name: %s\n" % self.name + s += "Description: %s\n" % self.description + s += "Keyword Arguments: %s\n" % self.kwargs + s += "Start Time: %s\n" % self.start_time + s += "End Time: %s\n" % self.end_time + s += "Configuration: %s\n" % self.config + return s + + def __repr__(self): + """ + A `str` representation of the `~pyscope.telrun._Block`. + + Returns + ------- + repr : `str` + """ + logger.debug("_Block.__repr__ called") + return str(self) + + @property + def config(self): + """ + The default `~pyscope.telrun.Configuration` for the `~pyscope.telrun._Block`. + + Returns + ------- + config : `~pyscope.telrun.Configuration` + """ + logger.debug("_Block.config called") + return Configuration(self._config) + + @config.setter + def config(self, value): + """ + The default `~pyscope.telrun.Configuration` for the `~pyscope.telrun._Block`. + + Parameters + ---------- + value : `~pyscope.telrun.Configuration` + """ + logger.debug("_Block.config(value=%s) called" % value) + if type(value) is not Configuration: + logger.error( + "The configuration parameter must be a Configuration object, not a %s", + type(value), + ) + logger.error("Ignoring the new configuration.") + return + logger.debug("Setting the configuration to %s" % value) + self._config = value + + @property + def name(self): + """ + A user-defined `str` name for the `~pyscope.telrun._Block`. + + Returns + ------- + name : `str` + + """ + logger.debug("_Block.name called") + return str(self._name) + + @name.setter + def name(self, value): + """ + A user-defined `str` name for the `~pyscope.telrun._Block`. + + Parameters + ---------- + value : `str` + """ + logger.debug("_Block.name(value=%s) called" % value) + if type(value) is not str: + logger.error("The name parameter must be a string, not a %s", type(value)) + logger.error("Ignoring the new name.") + return + logger.debug("Setting the name to %s" % value) + self._name = value + + @property + def description(self): + """ + A user-defined `str` description for the `~pyscope.telrun._Block`. + + Returns + ------- + description : `str` + """ + logger.debug("_Block.description called") + return str(self._description) + + @description.setter + def description(self, value): + """ + A user-defined `str` description for the `~pyscope.telrun._Block`. + + Parameters + ---------- + value : `str` + + """ + logger.debug("_Block.description(value=%s) called" % value) + if type(value) is not str: + logger.error( + "The description parameter must be a string, not a %s", type(value) + ) + logger.error("Ignoring the new description.") + return + logger.debug("Setting the description to %s" % value) + self._description = value + + @property + def kwargs(self): + """ + Additional user-defined keyword arguments in a `dict` for the `~pyscope.telrun._Block`. + + Returns + ------- + kwargs : `dict` + + """ + logger.debug("_Block.kwargs called") + return dict(self._kwargs) + + @kwargs.setter + def kwargs(self, value): + """ + Additional user-defined keyword arguments for the `~pyscope.telrun._Block`. + + Parameters + ---------- + value : `dict` + + """ + logger.debug("_Block.kwargs(value=%s) called" % value) + if type(value) is not dict: + logger.error("The kwargs parameter must be a dict, not a %s", type(value)) + logger.error("Ignoring the new keyword arguments.") + return + logger.debug("Setting the keyword arguments to %s" % value) + self._kwargs = value + + @property + def ID(self): + """ + A `~uuid.UUID` that uniquely identifies the `~pyscope.telrun._Block`. + + Returns + ------- + ID : `uuid.UUID` + The unique identifier for the `~pyscope.telrun._Block`. + """ + logger.debug("_Block.ID called") + return uuid(self._uuid) + + @property + def start_time(self): + """ + The `~astropy.time.Time` that represents the start of the `~pyscope.telrun._Block`. + + Returns + ------- + start_time : `astropy.time.Time` + The start time of the `~pyscope.telrun._Block`. + """ + logger.debug("_Block.start_time called") + return Time(self._start_time) + + @property + def end_time(self): + """ + The `~astropy.time.Time` that represents the end of the `~pyscope.telrun._Block`. + + Returns + ------- + end_time : `astropy.time.Time` + The end time of the `~pyscope.telrun._Block`. + """ + logger.debug("_Block.end_time called") + return Time(self._end_time) diff --git a/pyscope/telrun/block.py b/pyscope/telrun/block.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyscope/telrun/calibration_block.py b/pyscope/telrun/calibration_block.py deleted file mode 100644 index c9de63e9..00000000 --- a/pyscope/telrun/calibration_block.py +++ /dev/null @@ -1,5 +0,0 @@ -from .block import Block - - -class CalibrationBlock(Block): - pass From 8f2a5b0fa5159ea72035b5563dc4eab135c74e73 Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 1 Oct 2024 08:40:02 -0400 Subject: [PATCH 11/30] Add _block-inheriting classes --- pyscope/telrun/__init__.py | 37 +++++++++++++++++++++++++++++ pyscope/telrun/schedule_block.py | 19 +++------------ pyscope/telrun/unallocated_block.py | 4 ++-- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/pyscope/telrun/__init__.py b/pyscope/telrun/__init__.py index 440fab9e..8df1974c 100644 --- a/pyscope/telrun/__init__.py +++ b/pyscope/telrun/__init__.py @@ -8,6 +8,28 @@ logger = logging.getLogger(__name__) +from .boundary_condition import BoundaryCondition + +from .field import Field +from .light_field import LightField +from .dark_field import DarkField +from .flat_field import FlatField +from .transition_field import TransitionField + +from .configuration import Configuration + +from ._block import _Block +from .schedule_block import ScheduleBlock +from .unallocated_block import UnallocatedBlock + +from .prioritizer import Prioritizer +from .optimizer import Optimizer + +from .queue import Queue +from .schedule import Schedule +from .scheduler import Scheduler + + from .telrun_exception import TelrunException from .exoplanet_transits import exoplanet_transits from .init_telrun_dir import init_telrun_dir @@ -20,6 +42,21 @@ from .telrun_operator import TelrunOperator __all__ = [ + "BoundaryCondition", + "Field", + "LightField", + "DarkField", + "FlatField", + "TransitionField", + "Configuration", + "_Block", + "ScheduleBlock", + "UnallocatedBlock", + "Prioritizer", + "Optimizer", + "Queue", + "Schedule", + "Scheduler", "exoplanet_transits", "init_telrun_dir", "mk_mosaic_schedule", diff --git a/pyscope/telrun/schedule_block.py b/pyscope/telrun/schedule_block.py index 46cc3625..5335ef57 100644 --- a/pyscope/telrun/schedule_block.py +++ b/pyscope/telrun/schedule_block.py @@ -1,18 +1,5 @@ -from .block import Block +from ._block import _Block -class ScheduleBlock(Block): - def __init__(self): - """ - Initialize the ScheduleBlock object - - - - """ - pass - - def __str__(self): - pass - - def __repr__(self): - return str(self) +class ScheduleBlock(_Block): + pass diff --git a/pyscope/telrun/unallocated_block.py b/pyscope/telrun/unallocated_block.py index 72ff86ea..42f72132 100644 --- a/pyscope/telrun/unallocated_block.py +++ b/pyscope/telrun/unallocated_block.py @@ -1,5 +1,5 @@ -from .block import Block +from ._block import _Block -class UnallocatedBlock(Block): +class UnallocatedBlock(_Block): pass From 9ffd6f4848dfd76726ac0acf2a3f7622cbd3ab75 Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 1 Oct 2024 08:40:19 -0400 Subject: [PATCH 12/30] Add files for configuration and transition_field --- pyscope/telrun/configuration.py | 2 ++ pyscope/telrun/transition_field.py | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 pyscope/telrun/configuration.py diff --git a/pyscope/telrun/configuration.py b/pyscope/telrun/configuration.py new file mode 100644 index 00000000..12efa2f8 --- /dev/null +++ b/pyscope/telrun/configuration.py @@ -0,0 +1,2 @@ +class Configuration: + pass diff --git a/pyscope/telrun/transition_field.py b/pyscope/telrun/transition_field.py index e69de29b..6a85acf8 100644 --- a/pyscope/telrun/transition_field.py +++ b/pyscope/telrun/transition_field.py @@ -0,0 +1,5 @@ +from .field import Field + + +class TransitionField(Field): + pass From 0bc220435d90d43df92beb7fe36dfd0aecf4899e Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 1 Oct 2024 13:41:15 -0400 Subject: [PATCH 13/30] Blocks --- pyscope/observatory/observatory.py | 7 + pyscope/telrun/_block.py | 123 ++++---- pyscope/telrun/schedule_block.py | 444 +++++++++++++++++++++++++++- pyscope/telrun/unallocated_block.py | 179 ++++++++++- 4 files changed, 680 insertions(+), 73 deletions(-) diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index 6e93310b..34dd3f96 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -1449,6 +1449,13 @@ def slew_to_coordinates( ) return False + if self.telescope.Altitude < self.min_altitude: + logger.exception( + "Telescope is below the minimum altitude of %.2f degrees" + % self.min_altitude.to(u.deg).value + ) + return False + if control_rotator and self.rotator is not None: self.stop_derotation_thread() diff --git a/pyscope/telrun/_block.py b/pyscope/telrun/_block.py index b7d4336c..c87870e7 100644 --- a/pyscope/telrun/_block.py +++ b/pyscope/telrun/_block.py @@ -29,8 +29,8 @@ class method will parse the string representation and create a new `~pyscope.tel configuration : `~pyscope.telrun.Configuration`, default: `None` The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun._Block`. This `~pyscope.telrun.Configuration` will be used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun._Block` and - will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` s - in the `~pyscope.telrun._Block` if a `~pyscope.telrun.Configuration` has not been provided. If a `~pyscope.telrun.Field` + will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` objects in the + `~pyscope.telrun._Block` if a `~pyscope.telrun.Configuration` has not been provided. If a `~pyscope.telrun.Field` has a different `~pyscope.telrun.Configuration`, it will override the block `~pyscope.telrun.Configuration` for the duration of the `~pyscope.telrun.Field`. @@ -50,7 +50,7 @@ class method will parse the string representation and create a new `~pyscope.tel See Also -------- - pyscope.telrun.ScheduleBlock : A subclass of `~pyscope.telrun._Block` that is used to schedule `~pyscope.telrun.Field` s + pyscope.telrun.ScheduleBlock : A subclass of `~pyscope.telrun._Block` that is used to schedule `~pyscope.telrun.Field` objects in a `~pyscope.telrun.Schedule`. pyscope.telrun.UnallocatedBlock : A subclass of `~pyscope.telrun._Block` that is used to represent unallocated time in a `~pyscope.telrun.Schedule`. @@ -72,14 +72,6 @@ class method will parse the string representation and create a new `~pyscope.tel logger.debug("description=%s" % description) logger.debug("kwargs=%s" % kwargs) - if type(config) is not Configuration: - logger.error( - "The configuration parameter must be a Configuration object, not a %s", - type(config), - ) - logger.error("Creating a new empty Configuration object.") - config = Configuration() - self.config = config self.name = name self.description = description @@ -123,47 +115,40 @@ def from_string(cls, string, config=None, name=None, description=None, **kwargs) logger.debug("string=%s" % string) # Parse the string representation to extract the block information - block_info = string.split("\n") - block_id = uuid(hex=block_info[1].split("Block ID: ")[1]) - if name is None: - name = ( - block_info[2].split("Name: ")[1] - if block_info[2].split("Name: ")[1] != "None" - else None - ) - if description is None: - description = ( - block_info[3].split("Description: ")[1] - if block_info[3].split("Description: ")[1] != "None" - else None + block_info = string.split( + "\n******************** Start Block Metadata ********************" + )[1].split("\n******************** End Block Metadata ********************")[0] + + block_id = UUID(block_info.split("\nBlock ID: ")[1].split("\n")[0]) + name = block_info.split("\nName: ")[1].split("\n")[0] if name is None else name + description = ( + block_info.split("\nDescription: ")[1].split("\n")[0] + if description is None + else description + ) + kwargs = ( + ast.literal_eval( + block_info.split("\nKeyword Arguments: ")[1].split("\n")[0] ) - - if kwargs is None: - try: - kwargs = ast.literal_eval(block_info[4].split("Keyword Arguments: ")[1]) - except: - logger.error("Failed to parse the keyword arguments.") - kwargs = {} - + if kwargs is None + else kwargs + ) start_time = ( - Time(block_info[5].split("Start Time: ")[1]) - if block_info[5].split("Start Time: ")[1] != "None" + Time(block_info.split("\nStart Time: ")[1].split("\n")[0]) + if block_info.split("\nStart Time: ")[1].split("\n")[0] != "None" else None ) end_time = ( - Time(block_info[6].split("End Time: ")[1]) - if block_info[6].split("End Time: ")[1] != "None" + Time(block_info.split("\nEnd Time: ")[1].split("\n")[0]) + if block_info.split("\nEnd Time: ")[1].split("\n")[0] != "None" else None ) + config = ( + Configuration.from_string(block_info.split("\nConfiguration: ")[1]) + if config is None + else config + ) - if config is None: - config = ( - Configuration.from_string(block_info[7].split("Configuration: ")[1]) - if block_info[7].split("Configuration: ")[1] != "None" - else None - ) - - # Create a new block object block = cls(config=config, name=name, description=description, **kwargs) block._uuid = block_id block._start_time = start_time @@ -181,13 +166,15 @@ def __str__(self): str : `str` """ logger.debug("_Block.__str__ called") - s = "\nBlock ID: %s\n" % self.ID.hex - s += "Name: %s\n" % self.name - s += "Description: %s\n" % self.description - s += "Keyword Arguments: %s\n" % self.kwargs - s += "Start Time: %s\n" % self.start_time - s += "End Time: %s\n" % self.end_time - s += "Configuration: %s\n" % self.config + s = "\n******************** Start Block Metadata ********************" + s += "\nBlock ID: %s" % self.ID.hex + s += "\nName: %s" % self.name + s += "\nDescription: %s" % self.description + s += "\nKeyword Arguments: %s" % self.kwargs + s += "\nStart Time: %s" % self.start_time + s += "\nEnd Time: %s" % self.end_time + s += "\nConfiguration: %s" % self.config + s += "\n******************** End Block Metadata ********************" return s def __repr__(self): @@ -223,14 +210,13 @@ def config(self, value): value : `~pyscope.telrun.Configuration` """ logger.debug("_Block.config(value=%s) called" % value) - if type(value) is not Configuration: - logger.error( - "The configuration parameter must be a Configuration object, not a %s", - type(value), + if Configuration not in (config.__class__, *config.__class__.__bases__): + raise TypeError( + "The config parameter must be a Configuration object (class=%s) or a subclass of Configuration (bases=%s), not a %s", + Configuration.__class__, + Configuration.__class__.__bases__, + type(config), ) - logger.error("Ignoring the new configuration.") - return - logger.debug("Setting the configuration to %s" % value) self._config = value @property @@ -257,10 +243,9 @@ def name(self, value): """ logger.debug("_Block.name(value=%s) called" % value) if type(value) is not str: - logger.error("The name parameter must be a string, not a %s", type(value)) - logger.error("Ignoring the new name.") - return - logger.debug("Setting the name to %s" % value) + raise TypeError( + "The name parameter must be a string, not a %s", type(value) + ) self._name = value @property @@ -287,12 +272,9 @@ def description(self, value): """ logger.debug("_Block.description(value=%s) called" % value) if type(value) is not str: - logger.error( + raise TypeError( "The description parameter must be a string, not a %s", type(value) ) - logger.error("Ignoring the new description.") - return - logger.debug("Setting the description to %s" % value) self._description = value @property @@ -320,10 +302,9 @@ def kwargs(self, value): """ logger.debug("_Block.kwargs(value=%s) called" % value) if type(value) is not dict: - logger.error("The kwargs parameter must be a dict, not a %s", type(value)) - logger.error("Ignoring the new keyword arguments.") - return - logger.debug("Setting the keyword arguments to %s" % value) + raise TypeError( + "The kwargs parameter must be a dict, not a %s", type(value) + ) self._kwargs = value @property @@ -337,7 +318,7 @@ def ID(self): The unique identifier for the `~pyscope.telrun._Block`. """ logger.debug("_Block.ID called") - return uuid(self._uuid) + return UUID(self._uuid) @property def start_time(self): diff --git a/pyscope/telrun/schedule_block.py b/pyscope/telrun/schedule_block.py index 5335ef57..1b799ff9 100644 --- a/pyscope/telrun/schedule_block.py +++ b/pyscope/telrun/schedule_block.py @@ -1,5 +1,447 @@ +import logging + from ._block import _Block +from .boundary_condition import BoundaryCondition +from .field import Field + +logger = logging.getLogger(__name__) class ScheduleBlock(_Block): - pass + def __init__( + self, + *args, + config=None, + name=None, + description=None, + conditions=[], + priority=0, + fields=[], + **kwargs + ): + """ + A class to contain a list of `~pyscope.telrun.Field` objects to be scheduled as a single time range in the + observing `~pyscope.telrun.Schedule`. + + The `~pyscope.telrun.ScheduleBlock` is the fundamental unit that users interact with when creating observing + requests. It is a container for one or more `~pyscope.telrun.Field` objects, which represent the actual + observing targets. The `~pyscope.telrun.ScheduleBlock` also contains metadata about the block used by the + `~pyscope.telrun.Scheduler` to determine the best possible schedule using the `~pyscope.telrun.BoundaryCondition` + objects and the priority level provided when instantiating the `~pyscope.telrun.ScheduleBlock`. + + The `~pyscope.telrun.Scheduler` can also take advantage of the `~pyscope.telrun.Field` objects themselves to make + scheduling decisions. This mode is optimal for a `~pyscope.telrun.ScheduleBlock` that contains only one + `~pyscope.telrun.Field` or multiple `~pyscope.telrun.Field` objects that have small angular separations on the + sky. For larger separations, it is recommended to create separate `~pyscope.telrun.ScheduleBlock` objects for + each `~pyscope.telrun.Field` if the indexing of the `~pyscope.telrun.Field` objects is *not* important. If the order + of the `~pyscope.telrun.Field` objects *is* important, then we recommend the user employs a single + `~pyscope.telrun.ScheduleBlock` with a user-defined `~pyscope.telrun.BoundaryCondition` that will restrict the valid + local sidereal time (LST) range for the start of the `~pyscope.telrun.ScheduleBlock` to ensure that the + `~pyscope.telrun.ScheduleBlock` is executed at the most optimal time when using a basic `~pyscope.telrun.Scheduler` + (more advanced scheduling algorithms may not require this). + + Parameters + ---------- + *args : `tuple` + If provided, the first argument should be a string representation of a `~pyscope.telrun.ScheduleBlock`. The `from_string` + class method will parse the string representation and create a new `~pyscope.telrun.ScheduleBlock` object. The remaining + arguments will override the parsed values. + + config : `~pyscope.telrun.Configuration`, default: `None` + The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.ScheduleBlock`. This `~pyscope.telrun.Configuration` will be + used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun.ScheduleBlock` and + will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` objects in the + `~pyscope.telrun.ScheduleBlock` if a `~pyscope.telrun.Configuration` has not been provided. If a `~pyscope.telrun.Field` + has a different `~pyscope.telrun.Configuration`, it will override the block `~pyscope.telrun.Configuration` for the + duration of the `~pyscope.telrun.Field`. + + name : `str`, default: `None` + A user-defined name for the `~pyscope.telrun.ScheduleBlock`. This parameter does not change + the behavior of the `~pyscope.telrun.ScheduleBlock`, but it can be useful for identifying the + `~pyscope.telrun.ScheduleBlock` in a schedule. + + description : `str`, default: `None` + A user-defined description for the `~pyscope.telrun.ScheduleBlock`. Similar to the `name` + parameter, this parameter does not change the behavior of the `~pyscope.telrun.ScheduleBlock`. + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default: [] + A list of `~pyscope.telrun.BoundaryCondition` objects that define the constraints for all `~pyscope.telrun.Field` + objects in the `~pyscope.telrun.ScheduleBlock`. The `~pyscope.telrun.Optimizer` inside the `~pyscope.telrun.Scheduler` + will use the `~pyscope.telrun.BoundaryCondition` objects to determine the best possible schedule. + + priority : `int`, default: 0 + The priority level of the `~pyscope.telrun.ScheduleBlock`. The `~pyscope.telrun.Prioritizer` inside the + `~pyscope.telrun.Scheduler` will use this parameter to determine the best possible schedule. The highest + priority level is 1 and decreasing priority levels are integers above 1. The lowest priority is 0, which + is the default value. Tiebreakers are usually determined by the `~pyscope.telrun.Prioritizer` inside the + `~pyscope.telrun.Scheduler`, but some more advanced scheduling algorithms may use results from the + `~pyscope.telrun.Optimizer` to break ties. + + fields : `list` of `~pyscope.telrun.Field`, default: [] + A list of `~pyscope.telrun.Field` objects to be scheduled in the `~pyscope.telrun.ScheduleBlock`. The + `~pyscope.telrun.Field` objects will be executed in the order they are provided in the list. If the + `~pyscope.telrun.Field` objects have different `~pyscope.telrun.Configuration` objects, the `~pyscope.telrun.Configuration` + object for the `~pyscope.telrun.Field` will override the block `~pyscope.telrun.Configuration` for the duration + of the `~pyscope.telrun.Field`. + + **kwargs : `dict` + A dictionary of keyword arguments that can be used to store additional information + about the `~pyscope.telrun.ScheduleBlock`. This information can be used to store any additional + information that is not covered by the `configuration`, `name`, or `description` parameters. + + """ + + if len(args) > 0: + self = self.from_string( + args[0], + config=config, + name=name, + description=description, + conditions=conditions, + priority=priority, + fields=fields, + **kwargs, + ) + return + + super().__init__( + *args, config=config, name=name, description=description, **kwargs + ) + + self.conditions = conditions + self.priority = priority + self.fields = fields + + @classmethod + def from_string( + cls, + string, + config=None, + name=None, + description=None, + conditions=[], + priority=0, + fields=[], + **kwargs + ): + """ + Create a new (list of) `~pyscope.telrun.ScheduleBlock` object(s) from a string representation. + + Parameters + ---------- + string : `str` + A string representation of `~pyscope.telrun.ScheduleBlock`. + + Returns + ------- + `~pyscope.telrun.ScheduleBlock` or `list` of `~pyscope.telrun.ScheduleBlock` + A new `~pyscope.telrun.ScheduleBlock` object or a list of `~pyscope.telrun.ScheduleBlock` objects. + + """ + logger.debug("ScheduleBlock.from_string called") + logger.debug("Creating a new ScheduleBlock object from string...") + logger.debug("string=%s" % string) + + n_blocks = string.count( + "******************** Start ScheduleBlock ********************" + ) + end_blocks = string.count( + "******************** End ScheduleBlock ********************" + ) + if end_blocks != n_blocks: + raise ValueError( + "Invalid string representation of ScheduleBlock, %s start blocks and %s end blocks" + % (n_blocks, end_blocks) + ) + logger.debug("n_blocks=%i" % n_blocks) + sbs = [] + for i in range(n_blocks): + logger.debug("Start i=%i" % i) + block_info = string.split( + "\n******************** Start ScheduleBlock ********************" + )[i + 1].split( + "\n******************** End ScheduleBlock ********************" + )[ + 0 + ] + logger.debug("block_info=%s" % block_info) + + block = super().from_string( + block_info, config=config, name=name, description=description, **kwargs + ) + logger.debug("block=%s" % block) + + priority = int(block_info.split("\nPriority: ")[1].split("\n")[0]) + logger.debug("priority=%i" % priority) + + conditions_info = block_info.split( + "\n********** Start Conditions **********" + )[1].split("\n********** End Conditions **********")[0] + n_conditions = conditions_info.count( + "******************** Start BoundaryCondition ********************" + ) + end_conditions = conditions_info.count( + "******************** End BoundaryCondition ********************" + ) + if end_conditions != n_conditions: + raise ValueError( + "Invalid string representation of BoundaryCondition, %s start blocks and %s end blocks" + % (n_conditions, end_conditions) + ) + logger.debug("n_conditions=%i" % n_conditions) + conditions = [] + for j in range(n_conditions): + logger.debug("Start j=%i" % j) + condition_info = conditions_info.split( + "\n******************** Start BoundaryCondition ********************" + )[j + 1].split( + "\n******************** End BoundaryCondition ********************" + )[ + 0 + ] + logger.debug("condition_info=%s" % condition_info) + condition_type = condition_info.split("\nType: ")[1].split("\n")[0] + condition_kwargs = ast.literal_eval( + condition_info.split("\nType: %s\n" % condition_type)[1] + ) + try: + condition_class = getattr(sys.modules[__name__], condition_type) + except: + try: + module_name = ( + condition_kwargs.values()[0].split("/")[-1].split(".")[0] + ) + spec = importlib.util.spec_from_file_location( + module_name, condition_kwargs[0] + ) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + condition_class = getattr(module, condition_type) + + if len(condition_kwargs) > 1: + condition_kwargs = condition_kwargs[1:] + else: + condition_kwargs = None + except: + return None + + if condition_kwargs is None: + condition = condition_class.from_string(condition_info) + else: + condition = condition_class.from_string( + condition_info, **condition_kwargs + ) + + conditions.append(condition) + logger.debug("condition=%s" % condition) + + fields_info = block_info.split("\n********** Start Fields **********")[ + 1 + ].split("\n********** End Fields **********")[0] + n_fields = fields_info.count( + "******************** Start Field ********************" + ) + end_fields = fields_info.count( + "******************** End Field ********************" + ) + if end_fields != n_fields: + raise ValueError( + "Invalid string representation of Field, %s start blocks and %s end blocks" + % (n_fields, end_fields) + ) + logger.debug("n_fields=%i" % n_fields) + fields = [] + for j in range(n_fields): + logger.debug("Start j=%i" % j) + field_info = fields_info.split( + "\n******************** Start Field ********************" + )[j + 1].split("\n******************** End Field ********************")[ + 0 + ] + logger.debug("field_info=%s" % field_info) + field_type = field_info.split("\nType: ")[1].split("\n")[0] + field_kwargs = ast.literal_eval( + field_info.split("\nType: %s\n" % field_type)[1] + ) + try: + field_class = getattr(sys.modules[__name__], field_type) + except: + try: + module_name = ( + field_kwargs.values()[0].split("/")[-1].split(".")[0] + ) + spec = importlib.util.spec_from_file_location( + module_name, field_kwargs[0] + ) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + field_class = getattr(module, field_type) + + if len(field_kwargs) > 1: + field_kwargs = field_kwargs[1:] + else: + field_kwargs = None + except: + return None + + if field_kwargs is None: + field = field_class.from_string(field_info) + else: + field = field_class.from_string(field_info, **field_kwargs) + + fields.append(field) + logger.debug("field=%s" % field) + + sb = cls( + config=block.config, + name=block.name, + description=block.description, + conditions=conditions, + priority=priority, + fields=fields, + **block.kwargs, + ) + sb._uuid = block._uuid + sb._start_time = block._start_time + sb._end_time = block._end_time + + logger.debug("sb=%s" % sb) + sbs.append(sb) + logger.debug("End i=%i" % i) + + if len(sbs) == 1: + return sbs[0] + return sbs + + def __str__(self): + """ + Return a string representation of the `~pyscope.telrun.ScheduleBlock`. + + Returns + ------- + `str` + A string representation of the `~pyscope.telrun.ScheduleBlock`. + + """ + logger.debug("ScheduleBlock.__str__ called") + s = "\n******************** Start ScheduleBlock ********************" + s += super().__str__() + s += "\nPriority: %i" % self.priority + s += "\n********** Start Conditions **********" + for condition in self.conditions: + s += "\n" + str(condition) + s += "\n********** End Conditions **********" + s += "\n********** Start Fields **********" + for field in self.fields: + s += "\n" + str(field) + s += "\n********** End Fields **********" + s += "\n******************** End ScheduleBlock ********************" + return s + + @property + def conditions(self): + """ + Get the list of `~pyscope.telrun.BoundaryCondition` objects for the `~pyscope.telrun.ScheduleBlock`. + + Returns + ------- + `list` of `~pyscope.telrun.BoundaryCondition` + The list of `~pyscope.telrun.BoundaryCondition` objects for the `~pyscope.telrun.ScheduleBlock`. + + """ + logger.debug("ScheduleBlock.conditions called") + return list(self._conditions) + + @conditions.setter + def conditions(self, value): + """ + Set the list of `~pyscope.telrun.BoundaryCondition` objects for the `~pyscope.telrun.ScheduleBlock`. + + Parameters + ---------- + conditions : `list` of `~pyscope.telrun.BoundaryCondition` + The list of `~pyscope.telrun.BoundaryCondition` objects for the `~pyscope.telrun.ScheduleBlock`. + + """ + logger.debug("ScheduleBlock.conditions(value=%s) called" % value) + + if not isinstance(value, list): + value = [value] + + for i, condition in enumerate(value): + if BoundaryCondition not in ( + condition.__class__, + *condition.__class__.__bases__, + ): + raise TypeError( + "conditions must be a list of BoundaryCondition objects. Got %s, %s instead for %s (number %i)" + % (condition.__class__, condition.__class__.__bases__, condition, i) + ) + + self._conditions = value + + @property + def priority(self): + """ + Get the priority level of the `~pyscope.telrun.ScheduleBlock`. + + Returns + ------- + `int` + The priority level of the `~pyscope.telrun.ScheduleBlock`. + + """ + return int(self._priority) + + @priority.setter + def priority(self, value): + """ + Set the priority level of the `~pyscope.telrun.ScheduleBlock`. + + Parameters + ---------- + priority : `int` + The priority level of the `~pyscope.telrun.ScheduleBlock`. + + """ + self._priority = int(value) + + @property + def fields(self): + """ + Get the list of `~pyscope.telrun.Field` objects for the `~pyscope.telrun.ScheduleBlock`. + + Returns + ------- + `list` of `~pyscope.telrun.Field` + The list of `~pyscope.telrun.Field` objects for the `~pyscope.telrun.ScheduleBlock`. + + """ + return list(self._fields) + + @fields.setter + def fields(self, value): + """ + Set the list of `~pyscope.telrun.Field` objects for the `~pyscope.telrun.ScheduleBlock`. + + Parameters + ---------- + fields : `list` of `~pyscope.telrun.Field` + The list of `~pyscope.telrun.Field` objects for the `~pyscope.telrun.ScheduleBlock`. + + """ + logger.debug("ScheduleBlock.fields(value=%s) called" % value) + + if not isinstance(value, list): + value = [value] + + for i, field in enumerate(value): + if Field not in (field.__class__, *field.__class__.__bases__): + raise TypeError( + "fields must be a list of Field objects. Got %s, %s instead for %s (number %i)" + % (field.__class__, field.__class__.__bases__, field, i) + ) + + self._fields = value diff --git a/pyscope/telrun/unallocated_block.py b/pyscope/telrun/unallocated_block.py index 42f72132..c42db27b 100644 --- a/pyscope/telrun/unallocated_block.py +++ b/pyscope/telrun/unallocated_block.py @@ -1,5 +1,182 @@ +import logging + +from astropy.time import Time + from ._block import _Block +logger = logging.getLogger(__name__) + class UnallocatedBlock(_Block): - pass + def __init__( + self, + start_time, + end_time, + *args, + config=None, + name=None, + description=None, + **kwargs + ): + """ + A block of time that is not allocated to any fields. + + Parameters + ---------- + start_time : `~astropy.time.Time`, required + The start time of the `~pyscope.telrun.UnallocatedBlock`. + + end_time : `~astropy.time.Time`, required + The end time of the `~pyscope.telrun.UnallocatedBlock`. + + *args : `tuple` + If provided, the next argument should be a string representation of a `~pyscope.telrun.UnallocatedBlock`. The `from_string` + class method will parse the string representation and create a new `~pyscope.telrun.UnallocatedBlock` object. The remaining + arguments will override the parsed values. + + config : `~pyscope.telrun.Configuration`, default: `None` + The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.UnallocatedBlock`. This `~pyscope.telrun.Configuration` will be + used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun.UnallocatedBlock`. + + name : `str`, default: `None` + A user-defined name for the `~pyscope.telrun.UnallocatedBlock`. This parameter does not change + the behavior of the `~pyscope.telrun.UnallocatedBlock`, but it can be useful for identifying the + `~pyscope.telrun.UnallocatedBlock` in a schedule. + + description : `str`, default: `None` + A user-defined description for the `~pyscope.telrun.UnallocatedBlock`. Similar to the `name` + parameter, this parameter does not change the behavior of the `~pyscope.telrun.UnallocatedBlock`. + + **kwargs : `dict` + A dictionary of keyword arguments that can be used to store additional information + about the `~pyscope.telrun.UnallocatedBlock`. This information can be used to store any additional + information that is not covered by the `configuration`, `name`, or `description` parameters + + """ + + if len(args) == 3 and isinstance(args[2], str): + self = self.from_string( + args[2], config=config, name=name, description=description, **kwargs + ) + self.start_time = args[0] + self.end_time = args[1] + elif len(args) == 1 and isinstance(args[0], str): + self = self.from_string( + args[0], config=config, name=name, description=description, **kwargs + ) + elif len(args) != 2: + raise ValueError("UnallocatedBlock requires 2 arguments.") + else: + self.start_time = start_time + self.end_time = end_time + + super().__init__( + *args, config=config, name=name, description=description, **kwargs + ) + + @classmethod + def from_string(cls, *args, config=None, name=None, description=None, **kwargs): + """ + Create a new `~pyscope.telrun.UnallocatedBlock` object from a string representation. + + Parameters + ---------- + string : `str`, required + The string representation of the `~pyscope.telrun.UnallocatedBlock`. + + Returns + ------- + `~pyscope.telrun.UnallocatedBlock` + A new `~pyscope.telrun.UnallocatedBlock` object created from the string representation. + """ + logger.debug("UnallocatedBlock.from_string()") + + if len(args) == 1: + string = args[0] + start_time = None + end_time = None + elif len(args) == 3: + string = args[2] + start_time = args[0] + end_time = args[1] + else: + raise ValueError("UnallocatedBlock.from_string requires 1 or 3 arguments.") + + n_blocks = string.count( + "******************** Start UnallocatedBlock ********************" + ) + end_blocks = string.count( + "******************** End UnallocatedBlock ********************" + ) + if n_blocks != end_blocks: + raise ValueError("UnallocatedBlock string representation is malformed.") + logger.debug("n_blocks=%i" % n_blocks) + blocks = [] + for i in range(n_blocks): + logger.debug("i=%i" % i) + block_info = string.split( + "******************** Start UnallocatedBlock ********************" + )[i + 1].split( + "******************** End UnallocatedBlock ********************" + )[ + 0 + ] + logger.debug("block_info=%s" % block_info) + + block = super().from_string( + block_info, config=config, name=name, description=description, **kwargs + ) + block._start_time = ( + Time(start_time) if start_time is not None else block._start_time + ) + block._end_time = ( + Time(end_time) if end_time is not None else block._end_time + ) + logger.debug("block=%s" % block) + + blocks.append(block) + + if len(blocks) == 1: + return blocks[0] + return blocks + + def __str__(self): + """ + Return a string representation of the `~pyscope.telrun.UnallocatedBlock`. + + Returns + ------- + `str` + A string representation of the `~pyscope.telrun.UnallocatedBlock`. + """ + logger.debug("UnallocatedBlock.__str__()") + s = "\n******************** Start UnallocatedBlock ********************\n" + s += super().__str__() + s += "\n******************** End UnallocatedBlock ********************" + return s + + @start_time.setter + def start_time(self, value): + """ + The start time of the `~pyscope.telrun.UnallocatedBlock`. + + Parameters + ---------- + value : `~astropy.time.Time`, required + The start time of the `~pyscope.telrun.UnallocatedBlock`. + """ + + self._start_time = Time(value) + + @end_time.setter + def end_time(self, value): + """ + The end time of the `~pyscope.telrun.UnallocatedBlock`. + + Parameters + ---------- + value : `~astropy.time.Time`, required + The end time of the `~pyscope.telrun.UnallocatedBlock`. + """ + + self._end_time = Time(value) From 7ab8c118fde2fdf199b8b46aaf88d656073fe349 Mon Sep 17 00:00:00 2001 From: WGolay Date: Wed, 2 Oct 2024 10:16:48 -0400 Subject: [PATCH 14/30] Block and field docs --- pyscope/telrun/__init__.py | 6 + pyscope/telrun/_block.py | 165 ++++++++------ pyscope/telrun/autofocus_field.py | 305 ++++++++++++++++++++++++++ pyscope/telrun/calibration_block.py | 9 + pyscope/telrun/dark_field.py | 162 +++++++++++++- pyscope/telrun/field.py | 286 +++++++++++++++++++++++- pyscope/telrun/flat_field.py | 194 ++++++++++++++++- pyscope/telrun/light_field.py | 323 +++++++++++++++++++++++++++- pyscope/telrun/observer.py | 7 + pyscope/telrun/schedule_block.py | 133 ++++++++---- pyscope/telrun/transition_field.py | 90 +++++++- pyscope/telrun/unallocated_block.py | 109 +++++----- requirements.txt | 7 +- 13 files changed, 1625 insertions(+), 171 deletions(-) create mode 100644 pyscope/telrun/autofocus_field.py create mode 100644 pyscope/telrun/calibration_block.py create mode 100644 pyscope/telrun/observer.py diff --git a/pyscope/telrun/__init__.py b/pyscope/telrun/__init__.py index 8df1974c..a249333f 100644 --- a/pyscope/telrun/__init__.py +++ b/pyscope/telrun/__init__.py @@ -9,9 +9,11 @@ logger = logging.getLogger(__name__) from .boundary_condition import BoundaryCondition +from .observer import Observer from .field import Field from .light_field import LightField +from .autofocus_field import AutofocusField from .dark_field import DarkField from .flat_field import FlatField from .transition_field import TransitionField @@ -20,6 +22,7 @@ from ._block import _Block from .schedule_block import ScheduleBlock +from .calibration_block import CalibrationBlock from .unallocated_block import UnallocatedBlock from .prioritizer import Prioritizer @@ -42,15 +45,18 @@ from .telrun_operator import TelrunOperator __all__ = [ + "Observer", "BoundaryCondition", "Field", "LightField", + "AutofocusField", "DarkField", "FlatField", "TransitionField", "Configuration", "_Block", "ScheduleBlock", + "CalibrationBlock", "UnallocatedBlock", "Prioritizer", "Optimizer", diff --git a/pyscope/telrun/_block.py b/pyscope/telrun/_block.py index c87870e7..cdfe5018 100644 --- a/pyscope/telrun/_block.py +++ b/pyscope/telrun/_block.py @@ -5,12 +5,13 @@ from astropy.time import Time from .configuration import Configuration +from .observer import Observer logger = logging.getLogger(__name__) class _Block: - def __init__(self, *args, config=None, name=None, description=None, **kwargs): + def __init__(self, config, observer, name="", description="", **kwargs): """ A class to represent a time range in the schedule. @@ -21,12 +22,7 @@ def __init__(self, *args, config=None, name=None, description=None, **kwargs): Parameters ---------- - *args : `tuple` - If provided, the first argument should be a string representation of a `~pyscope.telrun._Block`. The `from_string` - class method will parse the string representation and create a new `~pyscope.telrun._Block` object. The remaining - arguments will override the parsed values. - - configuration : `~pyscope.telrun.Configuration`, default: `None` + configuration : `~pyscope.telrun.Configuration`, required The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun._Block`. This `~pyscope.telrun.Configuration` will be used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun._Block` and will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` objects in the @@ -34,16 +30,20 @@ class method will parse the string representation and create a new `~pyscope.tel has a different `~pyscope.telrun.Configuration`, it will override the block `~pyscope.telrun.Configuration` for the duration of the `~pyscope.telrun.Field`. - name : `str`, default: `None` + observer : `~pyscope.telrun.Observer`, required + Associate this `~pyscope.telrun._Block` with an `~pyscope.telrun.Observer`. The `~pyscope.telrun.Observer` is a + bookkeeping object for an `~pyscope.observatory.Observatory` with multiple users/user groups. + + name : `str`, default : "" A user-defined name for the `~pyscope.telrun._Block`. This parameter does not change the behavior of the `~pyscope.telrun._Block`, but it can be useful for identifying the `~pyscope.telrun._Block` in a schedule. - description : `str`, default: `None` + description : `str`, default : "" A user-defined description for the `~pyscope.telrun._Block`. Similar to the `name` parameter, this parameter does not change the behavior of the `~pyscope.telrun._Block`. - kwargs : `dict` + **kwargs : `dict`, default : {} A dictionary of keyword arguments that can be used to store additional information about the `~pyscope.telrun._Block`. This information can be used to store any additional information that is not covered by the `configuration`, `name`, or `description` parameters. @@ -57,38 +57,25 @@ class method will parse the string representation and create a new `~pyscope.tel pyscope.telrun.Configuration : A class that represents the configuration of the telescope. pyscope.telrun.Field : A class that represents a field to observe. """ - if len(args) > 0: - self = self.from_string( - args[0], config=config, name=name, description=description, **kwargs - ) - return - logger.debug( - "\n\n\n----------------------------------------------------------------------" + "_Block(config=%s, observer=%s, name=%s, description=%s, **kwargs=%s)" + % (config, observer, name, description, kwargs) ) - logger.debug("Creating a new Block object...") - logger.debug("configuration=%s" % config) - logger.debug("name=%s" % name) - logger.debug("description=%s" % description) - logger.debug("kwargs=%s" % kwargs) - self.config = config + self.observer = observer self.name = name self.description = description self.kwargs = kwargs - self._uuid = uuid4() self._start_time = None self._end_time = None - logger.debug("Block ID: %s" % self.ID.hex) - logger.debug("Creating a new Block object... Done!") - logger.debug( - "----------------------------------------------------------------------\n\n\n" - ) + logger.debug("_Block() = %s" % self) @classmethod - def from_string(cls, string, config=None, name=None, description=None, **kwargs): + def from_string( + cls, string, config=None, observer=None, name="", description="", **kwargs + ): """ Create a new `~pyscope.telrun._Block` from a string representation. Additional arguments can be provided to override the parsed values. @@ -99,20 +86,23 @@ def from_string(cls, string, config=None, name=None, description=None, **kwargs) config : `~pyscope.telrun.Configuration`, default: `None` - name : `str`, default: `None` + observer : `~pyscope.telrun.Observer`, default: `None` - description : `str`, default: `None` + name : `str`, default : "" - kwargs : `dict` + description : `str`, default : "" + + kwargs : `dict`, default : {} Returns ------- block : `~pyscope.telrun._Block` """ - logger.debug("_Block.from_string called") - logger.debug("Creating a new Block object from string...") - logger.debug("string=%s" % string) + logger.debug( + "_Block.from_string(string=%s, config=%s, name=%s, description=%s, **kwargs=%s)" + % (string, config, name, description, kwargs) + ) # Parse the string representation to extract the block information block_info = string.split( @@ -120,17 +110,17 @@ def from_string(cls, string, config=None, name=None, description=None, **kwargs) )[1].split("\n******************** End Block Metadata ********************")[0] block_id = UUID(block_info.split("\nBlock ID: ")[1].split("\n")[0]) - name = block_info.split("\nName: ")[1].split("\n")[0] if name is None else name + name = block_info.split("\nName: ")[1].split("\n")[0] if name is "" else name description = ( block_info.split("\nDescription: ")[1].split("\n")[0] - if description is None + if description is "" else description ) kwargs = ( ast.literal_eval( block_info.split("\nKeyword Arguments: ")[1].split("\n")[0] ) - if kwargs is None + if kwargs is {} else kwargs ) start_time = ( @@ -143,18 +133,26 @@ def from_string(cls, string, config=None, name=None, description=None, **kwargs) if block_info.split("\nEnd Time: ")[1].split("\n")[0] != "None" else None ) + observer = Observer.from_string( + block_info.split("\nObserver: ")[1].split("\n")[0] + ) config = ( Configuration.from_string(block_info.split("\nConfiguration: ")[1]) if config is None else config ) - block = cls(config=config, name=name, description=description, **kwargs) + block = cls( + config=config, + observer=observer, + name=name, + description=description, + **kwargs + ) block._uuid = block_id block._start_time = start_time block._end_time = end_time - - logger.debug("Creating a new Block object from string... Done!") + logger.debug("block=%s" % block) return block def __str__(self): @@ -165,7 +163,7 @@ def __str__(self): ------- str : `str` """ - logger.debug("_Block.__str__ called") + logger.debug("_Block().__str__()") s = "\n******************** Start Block Metadata ********************" s += "\nBlock ID: %s" % self.ID.hex s += "\nName: %s" % self.name @@ -173,8 +171,10 @@ def __str__(self): s += "\nKeyword Arguments: %s" % self.kwargs s += "\nStart Time: %s" % self.start_time s += "\nEnd Time: %s" % self.end_time + s += "\nObserver: %s" % self.observer s += "\nConfiguration: %s" % self.config s += "\n******************** End Block Metadata ********************" + logger.debug("_Block().__str__() = %s" % s) return s def __repr__(self): @@ -185,7 +185,7 @@ def __repr__(self): ------- repr : `str` """ - logger.debug("_Block.__repr__ called") + logger.debug("_Block().__repr__()") return str(self) @property @@ -197,8 +197,8 @@ def config(self): ------- config : `~pyscope.telrun.Configuration` """ - logger.debug("_Block.config called") - return Configuration(self._config) + logger.debug("_Block().config == %s" % self._config) + return self._config @config.setter def config(self, value): @@ -209,8 +209,11 @@ def config(self, value): ---------- value : `~pyscope.telrun.Configuration` """ - logger.debug("_Block.config(value=%s) called" % value) - if Configuration not in (config.__class__, *config.__class__.__bases__): + logger.debug("_Block().config = %s" % value) + if ( + Configuration not in (config.__class__, *config.__class__.__bases__) + and value is not None + ): raise TypeError( "The config parameter must be a Configuration object (class=%s) or a subclass of Configuration (bases=%s), not a %s", Configuration.__class__, @@ -219,6 +222,40 @@ def config(self, value): ) self._config = value + @property + def observer(self): + """ + The `~pyscope.telrun.Observer` associated with the `~pyscope.telrun._Block`. + + Returns + ------- + observer : `~pyscope.telrun.Observer` + """ + logger.debug("_Block().observer == %s" % self._observer) + return self._observer + + @observer.setter + def observer(self, value): + """ + The `~pyscope.telrun.Observer` associated with the `~pyscope.telrun._Block`. + + Parameters + ---------- + value : `~pyscope.telrun.Observer` + """ + logger.debug("_Block().observer = %s" % value) + if ( + Observer not in (observer.__class__, *observer.__class__.__bases__) + and value is not None + ): + raise TypeError( + "The observer parameter must be an Observer object (class=%s) or a subclass of Observer (bases=%s), not a %s", + Observer.__class__, + Observer.__class__.__bases__, + type(observer), + ) + self._observer = value + @property def name(self): """ @@ -229,8 +266,8 @@ def name(self): name : `str` """ - logger.debug("_Block.name called") - return str(self._name) + logger.debug("_Block().name == %s" % self._name) + return self._name @name.setter def name(self, value): @@ -241,7 +278,7 @@ def name(self, value): ---------- value : `str` """ - logger.debug("_Block.name(value=%s) called" % value) + logger.debug("_Block().name = %s" % value) if type(value) is not str: raise TypeError( "The name parameter must be a string, not a %s", type(value) @@ -257,8 +294,8 @@ def description(self): ------- description : `str` """ - logger.debug("_Block.description called") - return str(self._description) + logger.debug("_Block().description == %s" % self._description) + return self._description @description.setter def description(self, value): @@ -270,7 +307,7 @@ def description(self, value): value : `str` """ - logger.debug("_Block.description(value=%s) called" % value) + logger.debug("_Block().description = %s" % value) if type(value) is not str: raise TypeError( "The description parameter must be a string, not a %s", type(value) @@ -287,8 +324,8 @@ def kwargs(self): kwargs : `dict` """ - logger.debug("_Block.kwargs called") - return dict(self._kwargs) + logger.debug("_Block().kwargs == %s" % self._kwargs) + return self._kwargs @kwargs.setter def kwargs(self, value): @@ -300,7 +337,7 @@ def kwargs(self, value): value : `dict` """ - logger.debug("_Block.kwargs(value=%s) called" % value) + logger.debug("_Block().kwargs = %s" % value) if type(value) is not dict: raise TypeError( "The kwargs parameter must be a dict, not a %s", type(value) @@ -314,11 +351,11 @@ def ID(self): Returns ------- - ID : `uuid.UUID` + ID : `~uuid.UUID` The unique identifier for the `~pyscope.telrun._Block`. """ - logger.debug("_Block.ID called") - return UUID(self._uuid) + logger.debug("_Block().ID == %s" % self._uuid) + return self._uuid @property def start_time(self): @@ -330,8 +367,8 @@ def start_time(self): start_time : `astropy.time.Time` The start time of the `~pyscope.telrun._Block`. """ - logger.debug("_Block.start_time called") - return Time(self._start_time) + logger.debug("_Block().start_time == %s" % self._start_time) + return self._start_time @property def end_time(self): @@ -343,5 +380,5 @@ def end_time(self): end_time : `astropy.time.Time` The end time of the `~pyscope.telrun._Block`. """ - logger.debug("_Block.end_time called") - return Time(self._end_time) + logger.debug("_Block().end_time == %s" % self._end_time) + return self._end_time diff --git a/pyscope/telrun/autofocus_field.py b/pyscope/telrun/autofocus_field.py new file mode 100644 index 00000000..bae3c4c1 --- /dev/null +++ b/pyscope/telrun/autofocus_field.py @@ -0,0 +1,305 @@ +import logging +from pathlib import Path + +from astropy import units as u + +from .light_field import LightField + +logger = logging.getLogger(__name__) + + +class AutofocusField(LightField): + @u.quantity_input(exp="s", timeout="s") + def __init__( + self, + target=None, + config=None, + repositioning=None, + dither=0 * u.arcsec, + exp=0 * u.s, + nexp=1, + midpoint=0, + nsteps=7, + stepsize=200, + timeout=180 * u.s, + out_fname=Path(""), + conditions=[], + **kwargs, + ): + """ + A request to complete an autofocus sequence on a target field. + + The `~pyscope.telrun.AutofocusField` is a special type of `~pyscope.telrun.LightField` that + contains options to override the default autofocus parameters. The target field + can be set to None to run the autofocus sequence on whatever target field the telescope + is currently tracking. The autofocus sequence will be run at the midpoint of the + exposure sequence with the specified number of steps and step size. At each step, + the telescope will move the focus position by the step size and take `nexp` exposures + of `exp` duration. The autofocus sequence will timeout after `timeout` seconds if the + sequence is not completed. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord`, default : `None` + The target field to autofocus on. If `None`, the telescope will autofocus on the + current target field. + + config : `~pyscope.telrun.Configuration`, default : `None` + The instrument configuration to use for the autofocus sequence. If `None`, the + default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. + + repositioning : 2-tuple of `~astropy.units.Quantity`, default : `None` + The position in pixels or arcseconds to reposition the telescope pointing + relative to the target coordinates using the `~pyscope.observatory.Observatory.repositioning` + method. The first element of the tuple is the x-axis offset in pixels or the right ascension + offset in arcseconds, and the second element is the y-axis offset in pixels or the declination + offset in arcseconds. If `None`, the telescope will not be reposition and the target will be observed + after a "blind" slew to the target coordinates. Repositioning will only happen once at the start + of the observation. + + dither : `~astropy.units.Quantity`, default : 0 arcsec + The dither radius in arcseconds or pixels to use for each exposure. If the + value is in arcseconds, the dither will be converted to pixels using the + pixel scale of the `~pyscope.observatory.Observatory` configuration. A new + dither position will be applied prior to each exposure. + + exp : `~astropy.units.Quantity`, default : 5 sec + The exposure time for each autofocus step. + + nexp : `int`, default : 1 + The number of exposures to take at each autofocus step. Multiple exposures can be used to + smooth over rapid seeing variations. + + midpoint : `int`, default : 0 + The absolute step number at which to center the autofocus sequence. If 0, the autofocus + sequence will occur at the current step number. + + nsteps : `int`, default : 7 + The number of autofocus steps to take. The total number of exposures will be `nexp * nsteps`. + + stepsize : `int`, default : 200 + The number of steps to move the focus position at each autofocus step. + + timeout : `~astropy.units.Quantity`, default : 180 sec + The maximum time to wait for the autofocus sequence to complete before timing out. + + out_fname : `pathlib.Path`, default : Path("") + The output filename for the autofocus sequence. If an empty `str`, the images will not be saved. + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + A `list` of observing conditions that must be satisfied to schedule the autofocus sequence. + + **kwargs : `dict`, default : {} + Additional keyword arguments to pass to the instrument for the autofocus sequence. + + """ + logger.debug( + "AutofocusField(target=%s, config=%s, repositioning=%s, dither=%s, exp=%s, nexp=%i, midpoint=%i, nsteps=%i, stepsize=%i, timeout=%s, out_fname=%s, conditions=%s, kwargs=%s)" + % ( + target, + config, + repositioning, + dither, + exp, + nexp, + midpoint, + nsteps, + stepsize, + timeout, + out_fname, + conditions, + kwargs, + ) + ) + + @classmethod + @u.quantity_input(exp="s", timeout="s") + def from_string( + cls, + string, + target=None, + config=None, + repositioning=None, + dither=0 * u.arcsec, + exp=0 * u.s, + nexp=1, + midpoint=0, + nsteps=7, + stepsize=200, + timeout=180 * u.s, + out_fname=Path(""), + conditions=[], + **kwargs, + ): + """ + Create a `~pyscope.telrun.AutofocusField` or a `list` of `~pyscope.telrun.AutofocusField` objects from a `str` representation of a `~pyscope.telrun.AutofocusField`. + The optional keyword arguments will overrride the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + config : `~pyscope.telrun.Configuration`, default : None + + repositioning : 2-tuple of `~astropy.units.Quantity`, default : None + + dither : `~astropy.units.Quantity`, default : 0 arcsec + + exp : `~astropy.units.Quantity`, default : 0 sec + + nexp : `int`, default : 1 + + midpoint : `int`, default : 0 + + nsteps : `int`, default : 7 + + stepsize : `int`, default : 200 + + timeout : `~astropy.units.Quantity`, default : 180 sec + + out_fname : `pathlib.Path`, default : Path("") + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + + **kwargs : `dict`, default : {} + + Returns + ------- + `~pyscope.telrun.AutofocusField` or `list` of `~pyscope.telrun.AutofocusField` + + """ + logger.debug( + "AutofocusField.from_string(string=%s, target=%s, config=%s, repositioning=%s, dither=%s, exp=%s, nexp=%i, midpoint=%i, nsteps=%i, stepsize=%i, timeout=%s, out_fname=%s, conditions=%s, kwargs=%s)" + % ( + string, + target, + config, + repositioning, + dither, + exp, + nexp, + midpoint, + nsteps, + stepsize, + timeout, + out_fname, + conditions, + kwargs, + ) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.AutofocusField`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.AutofocusField`. + + """ + logger.debug("AutofocusField().__str__() = %s" % self) + + @property + def midpoint(self): + """ + Return the midpoint of the autofocus sequence. + + Returns + ------- + `int` + The midpoint of the autofocus sequence. + + """ + logger.debug("AutofocusField().midpoint == %i" % self._midpoint) + + @midpoint.setter + def midpoint(self, value): + """ + Set the midpoint of the autofocus sequence. + + Parameters + ---------- + value : `int`, required + The midpoint of the autofocus sequence. + + """ + logger.debug("AutofocusField().midpoint = %i" % value) + + @property + def nsteps(self): + """ + Return the number of autofocus steps. + + Returns + ------- + `int` + The number of autofocus steps. + + """ + logger.debug("AutofocusField().nsteps == %i" % self._nsteps) + + @nsteps.setter + def nsteps(self, value): + """ + Set the number of autofocus steps. + + Parameters + ---------- + value : `int`, required + The number of autofocus steps. + + """ + logger.debug("AutofocusField().nsteps = %i" % value) + + @property + def stepsize(self): + """ + Return the step size of the autofocus sequence. + + Returns + ------- + `int` + The step size of the autofocus sequence. + + """ + logger.debug("AutofocusField().stepsize == %i" % self._stepsize) + + @stepsize.setter + def stepsize(self, value): + """ + Set the step size of the autofocus sequence. + + Parameters + ---------- + value : `int`, required + The step size of the autofocus sequence. + + """ + logger.debug("AutofocusField().stepsize = %i" % value) + + @property + def timeout(self): + """ + Return the autofocus sequence timeout. + + Returns + ------- + `~astropy.units.Quantity` + The autofocus sequence timeout. + + """ + logger.debug("AutofocusField().timeout == %s" % self._timeout) + + @timeout.setter + def timeout(self, value): + """ + Set the autofocus sequence timeout. + + Parameters + ---------- + value : `~astropy.units.Quantity`, required + The autofocus sequence timeout. + + """ + logger.debug("AutofocusField().timeout = %s" % value) diff --git a/pyscope/telrun/calibration_block.py b/pyscope/telrun/calibration_block.py new file mode 100644 index 00000000..68b130bb --- /dev/null +++ b/pyscope/telrun/calibration_block.py @@ -0,0 +1,9 @@ +import logging + +from ._block import _Block + +logger = logging.getLogger(__name__) + + +class CalibrationBlock(_Block): + pass diff --git a/pyscope/telrun/dark_field.py b/pyscope/telrun/dark_field.py index 02a83024..9312c561 100644 --- a/pyscope/telrun/dark_field.py +++ b/pyscope/telrun/dark_field.py @@ -1,5 +1,165 @@ +import logging +from pathlib import Path + +from astropy import units as u + from .field import Field +logger = logging.getLogger(__name__) + class DarkField(Field): - pass + @u.quantity_input(exp="s") + def __init__( + self, + target=None, + config=None, + exp=0 * u.s, + nexp=1, + out_fname=Path(""), + **kwargs, + ): + """ + A request to capture a dark frame. + + The basic class for collecting dark images. A `~pyscope.telrun.DarkField` is a `~pyscope.telrun.Field` + that does not require a target and can set the exposure time and number of exposures, as well as the output filename. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord`, default : `None` + The target field to observe. This is typically used to set a pointing that reduces + the cross-section of the detector to the sky to minimize the number of cosmic rays + that are captured in the dark frame. If `None`, the pointing will not change + and the dark frame will be taken in the current position of the telescope. + + config : `~pyscope.telrun.Config`, default : None + The instrument configuration to use for the observation. If None, the + default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. + + exp : `~astropy.units.Quantity`, default : 0 sec + The length of the exposure in seconds. + + nexp : `int`, default : 1 + The number of exposures to take. + + out_fname : `~pathlib.Path` or `str`, default : Path("") + The output filename for the observation. If not specified, the filename + will be automatically generated. + + **kwargs : dict, default : {} + Additional keyword arguments to pass to the instrument for the observation. + + """ + logger.debug( + "DarkField(target=%s, config=%s, exp=%s, nexp=%i, out_fname=%s, kwargs=%s)" + % (target, config, exp, nexp, out_fname, kwargs) + ) + + @classmethod + @u.quantity_input(exp="s") + def from_string( + cls, + string, + target=None, + config=None, + exp=0 * u.s, + nexp=1, + out_fname=Path(""), + **kwargs, + ): + """ + Create a `~pyscope.telrun.DarkField` or a `list` of `~pyscope.telrun.DarkField` objects from a `str` representation of a `~pyscope.telrun.DarkField`. + All optional parameters are used to override the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + target : `~astropy.coordinates.SkyCoord`, default : None + + config : `~pyscope.telrun.Config`, default : None + + exp : `~astropy.units.Quantity`, default : 0 sec + + nexp : `int`, default : 1 + + out_fname : `~pathlib.Path` or `str`, default : Path("") + + **kwargs : `dict`, default : {} + + """ + logger.debug( + "DarkField.from_string(string=%s, target=%s, config=%s, exp=%s, nexp=%i, out_fname=%s, kwargs=%s)" + % (string, target, config, exp, nexp, out_fname, kwargs) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.DarkField`. + + Returns + ------- + `str` + """ + pass + + @property + def exp(self): + """ + The exposure time for the dark frame. + + Returns + ------- + `~astropy.units.Quantity` + """ + return self._exp + + @exp.setter + @u.quantity_input(value="sec") + def exp(self, value): + pass + + @property + def nexp(self): + """ + The number of exposures to take. + + Returns + ------- + `int` + """ + return self._nexp + + @nexp.setter + def nexp(self, value): + """ + Set the number of exposures to take. + + Parameters + ---------- + value : `int`, required + """ + pass + + @property + def out_fname(self): + """ + The output filename for the dark frame. + + Returns + ------- + `~pathlib.Path` + """ + return self._out_fname + + @out_fname.setter + def out_fname(self, value): + """ + Set the output filename for the dark frame. + + Parameters + ---------- + value : `~pathlib.Path` or `str`, required + """ + pass diff --git a/pyscope/telrun/field.py b/pyscope/telrun/field.py index bf8ccb25..f648fa82 100644 --- a/pyscope/telrun/field.py +++ b/pyscope/telrun/field.py @@ -1,2 +1,286 @@ +import logging +from uuid import uuid4 + +from astropy import units as u + +logger = logging.getLogger(__name__) + + class Field: - pass + def __init__(self, target, config=None, **kwargs): + """ + A single target field and configuration for an observation. + + The `~pyscope.telrun.Field` is the basic unit of an observation. It contains + the target, instrument configuration, exposure time, number of exposures, and + output filename. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord`, required + The target field to observe. If the target has proper motion, ensure + that the reference epoch and the proper motions are set. + + config : `~pyscope.telrun.Config`, default : None + The instrument configuration to use for the observation. If None, the + default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. + + **kwargs : dict, default : {} + Additional keyword arguments to pass to the instrument for the observation. + + """ + logger.debug( + "Field(target=%s, config=%s, exp=%i, nexp=%i, out_fname=%s, kwargs=%s)" + % (target, config, exp, nexp, out_fname, kwargs) + ) + + self.target = target + self.config = config + self.kwargs = kwargs + self._uuid = uuid4() + self._est_duration = 0 * u.sec + self._exec_status = "Unscheduled" + self._exec_start = None + self._exec_end = None + self._exec_log = None + logger.debug("Field() = %s" % self) + + @classmethod + def from_string(cls, string, target=None, config=None, **kwargs): + """ + Create a `~pyscope.telrun.Field` or a `list` of `~pyscope.telrun.Field` objects from a `str` representation of a `~pyscope.telrun.Field`. + + Parameters + ---------- + string : `str`, required + + Returns + ------- + `~pyscope.telrun.Field` or `list` of `~pyscope.telrun.Field` + + """ + logger.debug( + "Field.from_string(string=%s, target=%s, config=%s, kwargs=%s)" + % (string, target, config, kwargs) + ) + + logger.debug("Field.from_string() = %s" % field) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.Field`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.Field`. + + """ + logger.debug("Field().__str__() = %s" % self) + + def __repr__(self): + """ + Return a `str` representation of the `~pyscope.telrun.Field`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.Field`. + + """ + return str(self) + + @property + def target(self): + """ + The target field to observe. + + Returns + ------- + `astropy.coordinates.SkyCoord` + The target field to observe. If the target has proper motion, ensure + that the reference epoch and the proper motions are set. + + """ + logger.debug("Field().target == %s" % self._target) + return self._pointing + + @target.setter + def target(self, value): + """ + Set the target field to observe. + + Parameters + ---------- + value : `~astropy.coordinates.SkyCoord`, required + The target field to observe. If the target has proper motion, ensure + that the reference epoch and the proper motions are set. + + """ + logger.debug("Field.target = %s" % value) + pass + + @property + def config(self): + """ + The instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + + Returns + ------- + `~pyscope.telrun.Configuration` + The instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + + """ + logger.debug("Field().config == %s" % self._config) + return self._config + + @config.setter + def config(self, value): + """ + Set the instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + + Parameters + ---------- + value : `~pyscope.telrun.Configuration`, required + The instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + + """ + logger.debug("Field().config = %s" % value) + pass + + @property + def kwargs(self): + """ + Additional keyword arguments to pass to the instrument. + + Returns + ------- + `dict` + Additional keyword arguments to pass to the instrument. + + """ + logger.debug("Field().kwargs == %s" % self._kwargs) + return self._kwargs + + @kwargs.setter + def kwargs(self, value): + """ + Set additional keyword arguments to pass to the instrument. + + Parameters + ---------- + value : `dict`, required + Additional keyword arguments to pass to the instrument. + + """ + logger.debug("Field().kwargs = %s" % value) + pass + + @property + def ID(self): + """ + The unique identifier for the `~pyscope.telrun.Field`. + + Returns + ------- + `uuid.UUID` + The unique identifier for the `~pyscope.telrun.Field`. + + """ + logger.debug("Field().ID == %s" % self._uuid) + return self._uuid + + @property + def est_duration(self): + """ + The estimated duration of the observation. This is estimated based on the + properties of the target, the instrument configuration, and the observing + conditions by the `~pyscope.observatory.Observatory`. + + Returns + ------- + `~astropy.units.Quantity` + The estimated duration of the observation. + + """ + logger.debug("Field().est_duration == %s" % self._est_duration) + return self._est_duration + + @property + def exec_status(self): + """ + The execution status of the observation. The status is used to track the + progress of the observation through the observation process. The status + can be one of the following: + - "_U_nscheduled" + - "_E_xpired" + - "_I_nvalid" + - "_B_uilt" + - "_Q_ueued" + - "_S_cheduled" + - "_W_aiting" + - "_A_borted" + - "_R_unning" + - "_F_ailed" + - "_P_artially Completed" + - "_C_ompleted" + These are all also uniquely specified by their first letter as a shorthand. + + Returns + ------- + `str` + The execution status of the observation. + + """ + logger.debug("Field().exec_status == %s" % self._exec_status) + return self._exec_status + + @property + def exec_start(self): + """ + The actual execution start time of the observation. This is set by the + `~pyscope.telrun.TelrunOperator` when the observation begins. + + Returns + ------- + `~astropy.time.Time` + The actual execution start time of the observation. + + """ + logger.debug("Field().exec_start == %s" % self._exec_start) + return self._exec_start + + @property + def exec_end(self): + """ + The actual execution end time of the observation. This is set by the + `~pyscope.telrun.TelrunOperator` when the observation ends. + + Returns + ------- + `~astropy.time.Time` + The actual execution end time of the observation. + + """ + logger.debug("Field().exec_end == %s" % self._exec_end) + return self._exec_end + + @property + def exec_log(self): + """ + The execution log of the observation. This is a list of `str` messages + that are generated by the `~pyscope.telrun.TelrunOperator` instance during + the execution of the observation. The log is used to track the progress + of the observation and to diagnose any issues in the resulting data that + may have occurred during the observation. The log is generated using the + Python `logging` module, see the `~pyscope.telrun.TelrunOperator` and the + `logging` module documentation for more information. + + Returns + ------- + `list` of `str` + The execution log of the observation recorded using the Python `logging` module. + + """ + logger.debug("Field().exec_log == %s" % self._exec_log) + return self._exec_log diff --git a/pyscope/telrun/flat_field.py b/pyscope/telrun/flat_field.py index fbe973d8..7d3ad124 100644 --- a/pyscope/telrun/flat_field.py +++ b/pyscope/telrun/flat_field.py @@ -1,5 +1,193 @@ -from .field import Field +import logging +from pathlib import Path +from astropy import units as u -class FlatField(Field): - pass +from .light_field import LightField + +logger = logging.getLogger(__name__) + + +class FlatField(LightField): + @u.quantity_input(exp="s") + def __init__( + self, + target="CoverCalibrator", + config=None, + dither=0 * u.arcsec, + exp=1 * u.s, + nexp=1, + auto_exp=0, + out_fname=Path(""), + conditions=[], + **kwargs, + ): + """ + A request to take flat field images. + + The `~pyscope.telrun.FlatField` class is a subclass of the `~pyscope.telrun.LightField` class + and supports several small differences specific to flat field imaging. First, the default target + is the `CoverCalibrator` which will point the telescope at a pre-configured location to take flat + field images. A user may change the target to any other valid `~astropy.coordinates.SkyCoord` for + taking sky flats. Some `CoverCalibrator` instruments may support the ability to adjust their + illumination level, and a user can change this by passing a `~pyscope.telrun.Configuration` with the + appropriate settings. + + A user taking sky flats may want to take advantage of the `auto_exp` feature + to automatically adjust the exposure time to a high SNR but not saturate the detector. The `dither` + parameter can be used to move the telescope pointing slightly between exposures to help correct for + bad pixels, the flat field illumination pattern of the `CoverCalibrator`, or to help with star + rejection in sky flats. + + It is typically advantageous to schedule flat fields in a separate `~pyscope.telrun.ScheduleBlock` + from science fields since the observing `~pyscope.telrun.BoundaryCondition` selections are often quite different. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord` or `str`, default : "CoverCalibrator" + The target field to observe. If the target has proper motion, ensure + that the reference epoch and the proper motions are set. The default is the + `CoverCalibrator` which is a pre-configured location to take flat field images. + + config : `~pyscope.telrun.Config`, default : None + The instrument configuration to use for the observation. If None, the + default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. + + dither : `~astropy.units.Quantity`, default : 0 arcsec + The dither radius in arcseconds or pixels to use for each exposure. If the + value is in arcseconds, the dither will be converted to pixels using the + pixel scale of the `~pyscope.observatory.Observatory` configuration. A new + dither position will be applied prior to each exposure. + + exp : `~astropy.units.Quantity`, default : 1 sec + The exposure time for each exposure. + + nexp : `int`, default : 1 + The number of exposures to take. + + auto_exp : `int`, default : 0 + The number of counts to aim for in each exposure. If 0, the exposure time will + not be adjusted. If the value is greater than 0, the exposure time will be adjusted + to reach the target counts. + + out_fname : `~pathlib.Path` or `str`, default : Path("") + The output filename for the flat field images. If the path is empty, the filenames + will be generated automatically. + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + A `list` of observing conditions that must be satisfied to schedule the flat fields. + + **kwargs : `dict`, default : {} + Additional keyword arguments to pass to the instrument for the flat fields. + + """ + logger.debug( + "FlatField(target=%s, config=%s, dither=%s, exp=%s, nexp=%i, auto_exp=%s, out_fname=%s, conditions=%s, kwargs=%s)" + % ( + target, + config, + dither, + exp, + nexp, + auto_exp, + out_fname, + conditions, + kwargs, + ) + ) + + @classmethod + @u.quantity_input(exp="s") + def from_string( + self, + string, + target=None, + config=None, + dither=0 * u.arcsec, + exp=1 * u.s, + nexp=1, + auto_exp=0, + out_fname=Path(""), + conditions=[], + **kwargs, + ): + """ + Create a `~pyscope.telrun.FlatField` or a `list` of `~pyscope.telrun.FlatField` objects from a `str` representation of a `~pyscope.telrun.FlatField`. + All optional parameters are used to override the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + target : `~astropy.coordinates.SkyCoord`, default : None + + config : `~pyscope.telrun.Config`, default : None + + dither : `~astropy.units.Quantity`, default : 0 arcsec + + exp : `~astropy.units.Quantity`, default : 1 sec + + nexp : `int`, default : 1 + + auto_exp : `int`, default : 0 + + out_fname : `pathlib.Path`, default : Path("") + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + + **kwargs : `dict`, default : {} + + """ + logger.debug( + "FlatField.from_string(string=%s, target=%s, config=%s, dither=%s, exp=%s, nexp=%i, auto_exp=%s, out_fname=%s, conditions=%s, kwargs=%s)" + % ( + string, + target, + config, + dither, + exp, + nexp, + auto_exp, + out_fname, + conditions, + kwargs, + ) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.FlatField`. + + Returns + ------- + `str` + """ + logger.debug("FlatField().__str__() = %s" % self) + + @property + def auto_exp(self): + """ + The number of counts to aim for in each exposure. If 0, the exposure time will + not be adjusted. If the value is greater than 0, the exposure time will be adjusted + to reach the target counts. + + Returns + ------- + `int` + """ + logger.debug("FlatField().auto_exp == %s" % self._auto_exp) + return self._auto_exp + + @auto_exp.setter + def auto_exp(self, value): + """ + Set the number of counts to aim for in each exposure. If 0, the exposure time will + not be adjusted. If the value is greater than 0, the exposure time will be adjusted + to reach the target counts. + + Parameters + ---------- + value : `int`, required + """ + logger.debug("FlatField().auto_exp = %s" % value) + pass diff --git a/pyscope/telrun/light_field.py b/pyscope/telrun/light_field.py index 21d386c7..860b7c54 100644 --- a/pyscope/telrun/light_field.py +++ b/pyscope/telrun/light_field.py @@ -1,5 +1,326 @@ +import logging +from pathlib import Path + +from astropy import units as u + from .field import Field +logger = logging.getLogger(__name__) + class LightField(Field): - pass + @u.quantity_input(exp="s") + def __init__( + self, + target, + config=None, + repositioning=None, + dither=0 * u.arcsec, + exp=0 * u.s, + nexp=1, + out_fname=Path(""), + conditions=[], + **kwargs + ): + """ + A single science target field and configuration for an observation. + + The `~pyscope.telrun.LightField` is the basic unit of a standard science observation. It contains the target, instrument + `~pyscope.telrun.Configuration`, `~pyscope.observatory.Observatory.repositioning` offsets, exposure time, number of exposures, + output filename `~pathlib.Path`, and scheduling `~pyscope.telrun.BoundaryCondition` objects. The `~pyscope.telrun.LightField` + is used by the `~pyscope.telrun.Observer` to build a `~pyscope.telrun.ScheduleBlock` for a given observation request. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord` or `str`, required + The target field to observe. If the target has proper motion, ensure + that the reference epoch and the proper motions are set. If a string is + passed, it will be converted to a `~astropy.coordinates.SkyCoord` object + using the `astropy.coordinates.SkyCoord.from_name` method. If that fails, + the class will attempt to resolve the target as an ephemeris object, first + using the `astropy.coordinates.get_body` method and then the + `astroquery.mpc.MPC.get_ephemeris` method. + + config : `~pyscope.telrun.Config`, default : `None` + The instrument configuration to use for the observation. If None, the + default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. + + repositioning : 2-`tuple` of `~astropy.units.Quantity`, default : `None` + The position in pixels or arcseconds to reposition the telescope pointing + relative to the target coordinates using the `~pyscope.observatory.Observatory.repositioning` + method. The first element of the tuple is the x-axis offset in pixels or the right ascension + offset in arcseconds, and the second element is the y-axis offset in pixels or the declination + offset in arcseconds. If `None`, the telescope will not be reposition and the target will be observed + after a "blind" slew to the target coordinates. Repositioning will only happen once at the start + of the observation. + + dither : `~astropy.units.Quantity`, default : 0 arcsec + The dither radius in arcseconds or pixels to use for each exposure. If the + value is in arcseconds, the dither will be converted to pixels using the + pixel scale of the `~pyscope.observatory.Observatory` configuration. A new + dither position will be applied prior to each exposure. + + exp : `~astropy.units.Quantity`, default : 0 sec + The length of the exposure in seconds. + + nexp : `int`, default : 1 + The number of exposures to take. + + out_fname : `~pathlib.Path` or `str`, default : Path("") + The output filename for the observation. If not specified, the filename + will be automatically generated. + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + A list of `~pyscope.telrun.BoundaryCondition` objects that define the scheduling + constraints for this field. The `~pyscope.telrun.Optimizer` inside the `~pyscope.telrun.Scheduler` + will use the `~pyscope.telrun.BoundaryCondition` objects to determine the best possible schedule. + + **kwargs : dict, default : {} + Additional keyword arguments to pass to the instrument for the observation. + + """ + logger.debug( + "LightField(target=%s, config=%s, repositioning=%s, dither=%s, exp=%s, nexp=%i, out_fname=%s, conditions=%s, kwargs=%s)" + % ( + target, + config, + repositioning, + dither, + exp, + nexp, + out_fname, + conditions, + kwargs, + ) + ) + + out_fname = Path(out_fname) + + @classmethod + @u.quantity_input(exp="s") + def from_string( + cls, + string, + target=None, + config=None, + repositioning=None, + dither=0 * u.arcsec, + exp=0 * u.s, + nexp=1, + out_fname=Path(""), + conditions=[], + **kwargs + ): + """ + Create a `~pyscope.telrun.LightField` or a `list` of `~pyscope.telrun.LightField` objects from a `str` representation of a `~pyscope.telrun.LightField`. + All non-required parameters are used to override the values in the `str` representation. + + Parameters + ---------- + string : `str`, required + + target : `~astropy.coordinates.SkyCoord` or `str`, default : `None` + + config : `~pyscope.telrun.Config`, default : `None` + + repositioning : 2-`tuple` of `~astropy.units.Quantity`, default : `None` + + dither : `~astropy.units.Quantity`, default : 0 arcsec + + exp : `~astropy.units.Quantity`, default : 0 sec + + nexp : `int`, default : 1 + + out_fname : `~pathlib.Path` or `str`, default : Path("") + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + + kwargs : dict, default : {} + + Returns + ------- + `~pyscope.telrun.LightField` or `list` of `~pyscope.telrun.LightField` + + """ + logger.debug( + "LightField.from_string(string=%s, target=%s, config=%s, repositioning=%s, dither=%s, exp=%s, nexp=%i, out_fname=%s, conditions=%s, kwargs=%s)" + % ( + string, + target, + config, + repositioning, + dither, + exp, + nexp, + out_fname, + conditions, + kwargs, + ) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.LightField`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.LightField`. + + """ + logger.debug("LightField().__str__() = %s" % self) + + @property + def repositioning(self): + """ + The pointing offset to reposition the telescope relative to the target coordinates. + + The position in detector pixels or arcseconds on the sky to reposition the telescope pointing + relative to the provided target coordinates using the `~pyscope.observatory.Observatory.repositioning` + method. The first element of the `tuple` is the x-axis offset in pixels or the right ascension + offset in arcseconds, and the second element is the y-axis offset in pixels or the declination + offset in arcseconds. If `None`, the telescope will not be repositioned and the target will be observed + after a so-called "blind" slew to the target coordinates. + + Returns + ------- + 2-`tuple` of `~astropy.units.Quantity` or `None` + The position in pixels or arcseconds to reposition the telescope pointing + relative to the target coordinates. + + """ + logger.debug("LightField().repositioning == %s" % self._repositioning) + return self._repositioning + + @repositioning.setter + def repositioning(self, value): + """ + Set the pointing offset to reposition the telescope relative to the target coordinates. + + Parameters + ---------- + value : 2-`tuple` of `~astropy.units.Quantity` or `None`, required + The position in pixels or arcseconds to reposition the telescope pointing + relative to the target coordinates. + + """ + logger.debug("LightField().repositioning = %s" % value) + + @property + def exp(self): + """ + The length of the exposure in seconds. + + Returns + ------- + `~astropy.units.Quantity` + The length of the exposure in seconds. + + """ + logger.debug("LightField().exp == %s" % self._exp) + return self._exp + + @exp.setter + @u.quantity_input(exp="sec") + def exp(self, value): + """ + Set the length of the exposure in seconds. + + Parameters + ---------- + value : `~astropy.units.Quantity`, required + The length of the exposure in seconds. + + """ + logger.debug("LightField().exp = %s" % value) + pass + + @property + def nexp(self): + """ + The number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + + Returns + ------- + `int` + The number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + + """ + logger.debug("LightField().nexp == %i" % self._nexp) + return self._nexp + + @nexp.setter + def nexp(self, value): + """ + Set the number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + + Parameters + ---------- + value : `int`, required + The number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + + """ + logger.debug("LightField().nexp = %i" % value) + pass + + @property + def out_fname(self): + """ + The output filename for the observation as a `~pathlib.Path`. + + Returns + ------- + `~pathlib.Path` + The output filename for the observation. + + """ + logger.debug("LightField().out_fname == %s" % self._out_fname) + return self._out_fname + + @out_fname.setter + def out_fname(self, value): + """ + Set the output filename for the observation. The filename should be compatible with the `~pathlib.Path` class. + + Parameters + ---------- + value : `~pathlib.Path` or `str`, required + The output filename for the observation. + + """ + logger.debug("LightField().out_fname = %s" % value) + pass + + @property + def conditions(self): + """ + The scheduling `~pyscope.telrun.BoundaryCondition` objects for this field. These constraints are used by + the `~pyscope.telrun.Optimizer` to determine the best possible schedule for the + `~pyscope.telrun.ScheduleBlock` containing this `~pyscope.telrun.LightField`. + + Returns + ------- + `list` of `~pyscope.telrun.BoundaryCondition` + A `list` of `~pyscope.telrun.BoundaryCondition` objects that define the scheduling + constraints for this `~pyscope.telrun.LightField`. + + """ + logger.debug("LightField().conditions == %s" % self._conditions) + return self._conditions + + @conditions.setter + def conditions(self, value): + """ + Set the scheduling `~pyscope.telrun.BoundaryCondition` objects for this field. These constraints are used by + the `~pyscope.telrun.Optimizer` to determine the best possible schedule for the + `~pyscope.telrun.ScheduleBlock` containing this `~pyscope.telrun.LightField`. + + Parameters + ---------- + value : `list` of `~pyscope.telrun.BoundaryCondition`, required + A `list` of `~pyscope.telrun.BoundaryCondition` objects that define the scheduling + constraints for this `~pyscope.telrun.LightField`. + + """ + logger.debug("LightField().conditions = %s" % value) + pass diff --git a/pyscope/telrun/observer.py b/pyscope/telrun/observer.py new file mode 100644 index 00000000..59e614b9 --- /dev/null +++ b/pyscope/telrun/observer.py @@ -0,0 +1,7 @@ +import logging + +logger = logging.getLogger(__name__) + + +class Observer: + pass diff --git a/pyscope/telrun/schedule_block.py b/pyscope/telrun/schedule_block.py index 1b799ff9..991ac8fe 100644 --- a/pyscope/telrun/schedule_block.py +++ b/pyscope/telrun/schedule_block.py @@ -10,10 +10,11 @@ class ScheduleBlock(_Block): def __init__( self, - *args, config=None, - name=None, - description=None, + observer=None, + name="", + description="", + project_code="", conditions=[], priority=0, fields=[], @@ -42,12 +43,7 @@ def __init__( Parameters ---------- - *args : `tuple` - If provided, the first argument should be a string representation of a `~pyscope.telrun.ScheduleBlock`. The `from_string` - class method will parse the string representation and create a new `~pyscope.telrun.ScheduleBlock` object. The remaining - arguments will override the parsed values. - - config : `~pyscope.telrun.Configuration`, default: `None` + config : `~pyscope.telrun.Configuration`, default : `None` The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.ScheduleBlock`. This `~pyscope.telrun.Configuration` will be used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun.ScheduleBlock` and will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` objects in the @@ -55,21 +51,29 @@ class method will parse the string representation and create a new `~pyscope.tel has a different `~pyscope.telrun.Configuration`, it will override the block `~pyscope.telrun.Configuration` for the duration of the `~pyscope.telrun.Field`. - name : `str`, default: `None` + observer : `~pyscope.telrun.Observer`, default : `None` + The `~pyscope.telrun.Observer` to use for the `~pyscope.telrun.ScheduleBlock`. + + name : `str`, default : "" A user-defined name for the `~pyscope.telrun.ScheduleBlock`. This parameter does not change the behavior of the `~pyscope.telrun.ScheduleBlock`, but it can be useful for identifying the `~pyscope.telrun.ScheduleBlock` in a schedule. - description : `str`, default: `None` + description : `str`, default : "" A user-defined description for the `~pyscope.telrun.ScheduleBlock`. Similar to the `name` parameter, this parameter does not change the behavior of the `~pyscope.telrun.ScheduleBlock`. - conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default: [] + project_code : `str`, default : "" + A user-defined project code for the `~pyscope.telrun.ScheduleBlock`. This parameter does not change + the behavior of the `~pyscope.telrun.ScheduleBlock`, but it can be useful for identifying the + `~pyscope.telrun.ScheduleBlock`. + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] A list of `~pyscope.telrun.BoundaryCondition` objects that define the constraints for all `~pyscope.telrun.Field` objects in the `~pyscope.telrun.ScheduleBlock`. The `~pyscope.telrun.Optimizer` inside the `~pyscope.telrun.Scheduler` will use the `~pyscope.telrun.BoundaryCondition` objects to determine the best possible schedule. - priority : `int`, default: 0 + priority : `int`, default : 0 The priority level of the `~pyscope.telrun.ScheduleBlock`. The `~pyscope.telrun.Prioritizer` inside the `~pyscope.telrun.Scheduler` will use this parameter to determine the best possible schedule. The highest priority level is 1 and decreasing priority levels are integers above 1. The lowest priority is 0, which @@ -77,48 +81,56 @@ class method will parse the string representation and create a new `~pyscope.tel `~pyscope.telrun.Scheduler`, but some more advanced scheduling algorithms may use results from the `~pyscope.telrun.Optimizer` to break ties. - fields : `list` of `~pyscope.telrun.Field`, default: [] + fields : `list` of `~pyscope.telrun.Field`, default : [] A list of `~pyscope.telrun.Field` objects to be scheduled in the `~pyscope.telrun.ScheduleBlock`. The `~pyscope.telrun.Field` objects will be executed in the order they are provided in the list. If the `~pyscope.telrun.Field` objects have different `~pyscope.telrun.Configuration` objects, the `~pyscope.telrun.Configuration` object for the `~pyscope.telrun.Field` will override the block `~pyscope.telrun.Configuration` for the duration of the `~pyscope.telrun.Field`. - **kwargs : `dict` + **kwargs : `dict`, default : {} A dictionary of keyword arguments that can be used to store additional information about the `~pyscope.telrun.ScheduleBlock`. This information can be used to store any additional - information that is not covered by the `configuration`, `name`, or `description` parameters. + information that is not covered by the `config`, `name`, or `description` parameters. """ - - if len(args) > 0: - self = self.from_string( - args[0], - config=config, - name=name, - description=description, - conditions=conditions, - priority=priority, - fields=fields, - **kwargs, + logger.debug( + "ScheduleBlock(config=%s, observer=%s, name=%s, description=%s, conditions=%s, priority=%s, fields=%s, **kwargs=%s)" + % ( + config, + observer, + name, + description, + conditions, + priority, + fields, + kwargs, ) - return + ) super().__init__( - *args, config=config, name=name, description=description, **kwargs + config=config, + observer=observer, + name=name, + description=description, + **kwargs, ) + self.project_code = project_code self.conditions = conditions self.priority = priority self.fields = fields + logger.debug("ScheduleBlock() = %s" % self) @classmethod def from_string( cls, string, config=None, - name=None, - description=None, + observer=None, + name="", + description="", + project_code="", conditions=[], priority=0, fields=[], @@ -129,18 +141,35 @@ def from_string( Parameters ---------- - string : `str` - A string representation of `~pyscope.telrun.ScheduleBlock`. + string : `str`, required + + config : `~pyscope.telrun.Configuration`, default : `None` + + observer : `~pyscope.telrun.Observer`, default : `None` + + name : `str`, default : "" + + description : `str`, default : "" + + project_code : `str`, default : "" + + conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] + + priority : `int`, default : 0 + + fields : `list` of `~pyscope.telrun.Field`, default : [] + + **kwargs : `dict`, default : {} Returns ------- `~pyscope.telrun.ScheduleBlock` or `list` of `~pyscope.telrun.ScheduleBlock` - A new `~pyscope.telrun.ScheduleBlock` object or a list of `~pyscope.telrun.ScheduleBlock` objects. """ - logger.debug("ScheduleBlock.from_string called") - logger.debug("Creating a new ScheduleBlock object from string...") - logger.debug("string=%s" % string) + logger.debug( + "ScheduleBlock.from_string(string=%s, config=%s, name=%s, description=%s, conditions=%s, priority=%s, fields=%s, **kwargs=%s)" + % (string, config, name, description, conditions, priority, fields, kwargs) + ) n_blocks = string.count( "******************** Start ScheduleBlock ********************" @@ -167,13 +196,20 @@ def from_string( logger.debug("block_info=%s" % block_info) block = super().from_string( - block_info, config=config, name=name, description=description, **kwargs + block_info, + config=config, + observer=observer, + name=name, + description=description, + **kwargs, ) logger.debug("block=%s" % block) priority = int(block_info.split("\nPriority: ")[1].split("\n")[0]) logger.debug("priority=%i" % priority) + project_code = block_info.split("\nProject Code: ")[1].split("\n")[0] + conditions_info = block_info.split( "\n********** Start Conditions **********" )[1].split("\n********** End Conditions **********")[0] @@ -296,8 +332,10 @@ def from_string( sb = cls( config=block.config, + observer=block.observer, name=block.name, description=block.description, + project_code=project_code, conditions=conditions, priority=priority, fields=fields, @@ -311,6 +349,7 @@ def from_string( sbs.append(sb) logger.debug("End i=%i" % i) + logger.debug("ScheduleBlock.from_string() = %s" % sbs) if len(sbs) == 1: return sbs[0] return sbs @@ -325,7 +364,7 @@ def __str__(self): A string representation of the `~pyscope.telrun.ScheduleBlock`. """ - logger.debug("ScheduleBlock.__str__ called") + logger.debug("ScheduleBlock().__str__()") s = "\n******************** Start ScheduleBlock ********************" s += super().__str__() s += "\nPriority: %i" % self.priority @@ -338,6 +377,7 @@ def __str__(self): s += "\n" + str(field) s += "\n********** End Fields **********" s += "\n******************** End ScheduleBlock ********************" + logger.debug("ScheduleBlock().__str__() = %s" % s) return s @property @@ -351,8 +391,8 @@ def conditions(self): The list of `~pyscope.telrun.BoundaryCondition` objects for the `~pyscope.telrun.ScheduleBlock`. """ - logger.debug("ScheduleBlock.conditions called") - return list(self._conditions) + logger.debug("ScheduleBlock().conditions == %s" % self._conditions) + return self._conditions @conditions.setter def conditions(self, value): @@ -365,7 +405,7 @@ def conditions(self, value): The list of `~pyscope.telrun.BoundaryCondition` objects for the `~pyscope.telrun.ScheduleBlock`. """ - logger.debug("ScheduleBlock.conditions(value=%s) called" % value) + logger.debug("ScheduleBlock().conditions = %s" % value) if not isinstance(value, list): value = [value] @@ -393,7 +433,8 @@ def priority(self): The priority level of the `~pyscope.telrun.ScheduleBlock`. """ - return int(self._priority) + logger.debug("ScheduleBlock().priority == %i" % self._priority) + return self._priority @priority.setter def priority(self, value): @@ -406,6 +447,7 @@ def priority(self, value): The priority level of the `~pyscope.telrun.ScheduleBlock`. """ + logger.debug("ScheduleBlock().priority = %i" % value) self._priority = int(value) @property @@ -419,7 +461,8 @@ def fields(self): The list of `~pyscope.telrun.Field` objects for the `~pyscope.telrun.ScheduleBlock`. """ - return list(self._fields) + logger.debug("ScheduleBlock().fields == %s" % self._fields) + return self._fields @fields.setter def fields(self, value): @@ -432,7 +475,7 @@ def fields(self, value): The list of `~pyscope.telrun.Field` objects for the `~pyscope.telrun.ScheduleBlock`. """ - logger.debug("ScheduleBlock.fields(value=%s) called" % value) + logger.debug("ScheduleBlock().fields = %s" % value) if not isinstance(value, list): value = [value] diff --git a/pyscope/telrun/transition_field.py b/pyscope/telrun/transition_field.py index 6a85acf8..46d6775d 100644 --- a/pyscope/telrun/transition_field.py +++ b/pyscope/telrun/transition_field.py @@ -1,5 +1,93 @@ +import logging + +import astropy.units as u + from .field import Field +logger = logging.getLogger(__name__) + class TransitionField(Field): - pass + @u.quantity_input(est_duration="s") + def __init__( + self, + target=None, + config=None, + est_duration=0 * u.s, + **kwargs, + ): + """ + Time for re-pointing and re-configuring the telescope between two fields. + + A `~pyscope.telrun.TransitionField` is a `~pyscope.telrun.Field` that represents a transition between two `~pyscope.telrun.Field` objects. + These are typically only created by the `~pyscope.observatory.Observatory` when building the `~pyscope.telrun.ScheduleBlock` but can be manually + requested to create a pause between two other `~pyscope.telrun.Field` objects. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord` or `None`, default : `None` + The target field to point to during the transition. If `None`, the telescope will not move during the transition. + + config : `~pyscope.telrun.Config` or `None`, default : `None` + The instrument configuration to use during the transition. If `None`, the configuration will not change during the transition. + + est_duration : `~astropy.units.Quantity`, default : 0 sec + The duration of the transition in seconds. Typically, the `~pyscope.observatory.Observatory` will calculate the duration and set this value. + + **kwargs : dict, default : {} + Additional keyword arguments to pass to the instrument for the observation + + """ + logger.debug( + "TransitionField(target=%s, config=%s, duration=%s, kwargs=%s)" + % (target, config, duration, kwargs) + ) + + @classmethod + @u.quantity_input(est_duration="s") + def from_string( + cls, + string, + target=None, + config=None, + est_duration=0 * u.s, + **kwargs, + ): + """ + Create a `~pyscope.telrun.TransitionField` or a `list` of `~pyscope.telrun.TransitionField` objects from a `str` + representation of a `~pyscope.telrun.TransitionField`. All optional parameters are used to override the parameters + extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + target : `~astropy.coordinates.SkyCoord` or `None`, default : `None` + + config : `~pyscope.telrun.Config` or `None`, default : `None` + + est_duration : `~astropy.units.Quantity`, default : 0 sec + + **kwargs : dict, default : {} + + Returns + ------- + `~pyscope.telrun.TransitionField` or `list` of ~pyscope.telrun.TransitionField` + + """ + logger.debug( + "TransitionField.from_string(string=%s, target=%s, config=%s, kwargs=%s)" + % (string, target, config, kwargs) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.TransitionField`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.TransitionField`. + + """ + logger.debug("TransitionField.__str__() == %s" % self) diff --git a/pyscope/telrun/unallocated_block.py b/pyscope/telrun/unallocated_block.py index c42db27b..6b82fc38 100644 --- a/pyscope/telrun/unallocated_block.py +++ b/pyscope/telrun/unallocated_block.py @@ -9,14 +9,7 @@ class UnallocatedBlock(_Block): def __init__( - self, - start_time, - end_time, - *args, - config=None, - name=None, - description=None, - **kwargs + self, start_time, end_time, config=None, name="", description="", **kwargs ): """ A block of time that is not allocated to any fields. @@ -29,53 +22,47 @@ def __init__( end_time : `~astropy.time.Time`, required The end time of the `~pyscope.telrun.UnallocatedBlock`. - *args : `tuple` - If provided, the next argument should be a string representation of a `~pyscope.telrun.UnallocatedBlock`. The `from_string` - class method will parse the string representation and create a new `~pyscope.telrun.UnallocatedBlock` object. The remaining - arguments will override the parsed values. - - config : `~pyscope.telrun.Configuration`, default: `None` + config : `~pyscope.telrun.Configuration`, default : `None` The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.UnallocatedBlock`. This `~pyscope.telrun.Configuration` will be used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun.UnallocatedBlock`. - name : `str`, default: `None` + name : `str`, default : "" A user-defined name for the `~pyscope.telrun.UnallocatedBlock`. This parameter does not change the behavior of the `~pyscope.telrun.UnallocatedBlock`, but it can be useful for identifying the `~pyscope.telrun.UnallocatedBlock` in a schedule. - description : `str`, default: `None` + description : `str`, default : "" A user-defined description for the `~pyscope.telrun.UnallocatedBlock`. Similar to the `name` parameter, this parameter does not change the behavior of the `~pyscope.telrun.UnallocatedBlock`. - **kwargs : `dict` + **kwargs : `dict`, default : {} A dictionary of keyword arguments that can be used to store additional information about the `~pyscope.telrun.UnallocatedBlock`. This information can be used to store any additional information that is not covered by the `configuration`, `name`, or `description` parameters """ - - if len(args) == 3 and isinstance(args[2], str): - self = self.from_string( - args[2], config=config, name=name, description=description, **kwargs - ) - self.start_time = args[0] - self.end_time = args[1] - elif len(args) == 1 and isinstance(args[0], str): - self = self.from_string( - args[0], config=config, name=name, description=description, **kwargs - ) - elif len(args) != 2: - raise ValueError("UnallocatedBlock requires 2 arguments.") - else: - self.start_time = start_time - self.end_time = end_time - + logger.debug( + "UnallocatedBlock(start_time=%s, end_time=%s, config=%s, name=%s, description=%s, **kwargs=%s)" + % (start_time, end_time, config, name, description, kwargs) + ) super().__init__( - *args, config=config, name=name, description=description, **kwargs + config=config, observer=None, name=name, description=description, **kwargs ) + self.start_time = start_time + self.end_time = end_time + logger.debug("UnallocatedBlock() = %s" % self) @classmethod - def from_string(cls, *args, config=None, name=None, description=None, **kwargs): + def from_string( + cls, + string, + start_time=None, + end_time=None, + config=None, + name=None, + description=None, + **kwargs + ): """ Create a new `~pyscope.telrun.UnallocatedBlock` object from a string representation. @@ -89,18 +76,10 @@ def from_string(cls, *args, config=None, name=None, description=None, **kwargs): `~pyscope.telrun.UnallocatedBlock` A new `~pyscope.telrun.UnallocatedBlock` object created from the string representation. """ - logger.debug("UnallocatedBlock.from_string()") - - if len(args) == 1: - string = args[0] - start_time = None - end_time = None - elif len(args) == 3: - string = args[2] - start_time = args[0] - end_time = args[1] - else: - raise ValueError("UnallocatedBlock.from_string requires 1 or 3 arguments.") + logger.debug( + "UnallocatedBlock.from_string(string=%s, config=%s, name=%s, description=%s, start_time=%s, end_time=%s, **kwargs=%s)" + % (string, config, name, description, start_time, end_time, kwargs) + ) n_blocks = string.count( "******************** Start UnallocatedBlock ********************" @@ -136,6 +115,7 @@ def from_string(cls, *args, config=None, name=None, description=None, **kwargs): blocks.append(block) + logger.debug("UnallocatedBlock.from_string() = %s" % blocks) if len(blocks) == 1: return blocks[0] return blocks @@ -149,12 +129,26 @@ def __str__(self): `str` A string representation of the `~pyscope.telrun.UnallocatedBlock`. """ - logger.debug("UnallocatedBlock.__str__()") + logger.debug("UnallocatedBlock().__str__()") s = "\n******************** Start UnallocatedBlock ********************\n" s += super().__str__() s += "\n******************** End UnallocatedBlock ********************" + logger.debug("UnallocatedBlock().__str__() = %s" % s) return s + @property + def start_time(self): + """ + The start time of the `~pyscope.telrun.UnallocatedBlock`. + + Returns + ------- + `~astropy.time.Time` + The start time of the `~pyscope.telrun.UnallocatedBlock`. + """ + logger.debug("UnallocatedBlock().start_time == %s" % self._start_time) + return self._start_time + @start_time.setter def start_time(self, value): """ @@ -165,9 +159,22 @@ def start_time(self, value): value : `~astropy.time.Time`, required The start time of the `~pyscope.telrun.UnallocatedBlock`. """ - + logger.debug("UnallocatedBlock().start_time = %s" % value) self._start_time = Time(value) + @property + def end_time(self): + """ + The end time of the `~pyscope.telrun.UnallocatedBlock`. + + Returns + ------- + `~astropy.time.Time` + The end time of the `~pyscope.telrun.UnallocatedBlock`. + """ + logger.debug("UnallocatedBlock().end_time == %s" % self._end_time) + return self._end_time + @end_time.setter def end_time(self, value): """ @@ -178,5 +185,5 @@ def end_time(self, value): value : `~astropy.time.Time`, required The end time of the `~pyscope.telrun.UnallocatedBlock`. """ - + logger.debug("UnallocatedBlock().end_time = %s" % value) self._end_time = Time(value) diff --git a/requirements.txt b/requirements.txt index 8b317f65..c8f2e30f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,9 +6,8 @@ ccdproc == 2.4.2 click == 8.1.7 cmcrameri == 1.9 markdown == 3.6 -numpy == 2.1.1 matplotlib == 3.9.1 -numpy == 2.1.0 +numpy == 2.1.1 oschmod == 0.3.12 paramiko == 3.4.1 photutils == 1.13.0 @@ -17,6 +16,6 @@ pywin32 == 306;platform_system=='Windows' scikit-image == 0.24.0 scipy == 1.14.1 smplotlib == 0.0.9 -tqdm == 4.66.5 timezonefinder == 6.5.2 -twirl == 0.4.2 \ No newline at end of file +tqdm == 4.66.5 +# twirl == 0.4.2 From 211d061eb431cc479fea895b1769789cfc1d6f1e Mon Sep 17 00:00:00 2001 From: WGolay Date: Wed, 2 Oct 2024 10:20:07 -0400 Subject: [PATCH 15/30] Add astroplan reqmt back in for docs --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index c8f2e30f..60baad7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ # alpyca == 2.0.4 # not in conda +astroplan == 0.9 astropy == 6.1.2 astroquery == 0.4.7 astroscrappy == 1.2.0 From 01ec07b9191ac359974891b9730997390e306927 Mon Sep 17 00:00:00 2001 From: WGolay Date: Wed, 2 Oct 2024 10:20:33 -0400 Subject: [PATCH 16/30] Docs only --- requirements.txt | 1 - setup.cfg | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 60baad7d..c8f2e30f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ # alpyca == 2.0.4 # not in conda -astroplan == 0.9 astropy == 6.1.2 astroquery == 0.4.7 astroscrappy == 1.2.0 diff --git a/setup.cfg b/setup.cfg index 4cf41432..aab03601 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,6 +62,7 @@ console_scripts = [options.extras_require] docs = + astroplan==0.9 sphinx==8.0.2 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 From 7efc32fc977006e55d4ec220bd1e7de2cfab18ec Mon Sep 17 00:00:00 2001 From: WGolay Date: Wed, 2 Oct 2024 10:29:57 -0400 Subject: [PATCH 17/30] Minor docs updates --- pyscope/telrun/dark_field.py | 2 +- pyscope/telrun/field.py | 2 +- pyscope/telrun/flat_field.py | 2 +- pyscope/telrun/light_field.py | 4 ++-- pyscope/telrun/transition_field.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyscope/telrun/dark_field.py b/pyscope/telrun/dark_field.py index 9312c561..85f696ec 100644 --- a/pyscope/telrun/dark_field.py +++ b/pyscope/telrun/dark_field.py @@ -33,7 +33,7 @@ def __init__( that are captured in the dark frame. If `None`, the pointing will not change and the dark frame will be taken in the current position of the telescope. - config : `~pyscope.telrun.Config`, default : None + config : `~pyscope.telrun.Configuration`, default : None The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. diff --git a/pyscope/telrun/field.py b/pyscope/telrun/field.py index f648fa82..98f521e9 100644 --- a/pyscope/telrun/field.py +++ b/pyscope/telrun/field.py @@ -21,7 +21,7 @@ def __init__(self, target, config=None, **kwargs): The target field to observe. If the target has proper motion, ensure that the reference epoch and the proper motions are set. - config : `~pyscope.telrun.Config`, default : None + config : `~pyscope.telrun.Configuration`, default : None The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. diff --git a/pyscope/telrun/flat_field.py b/pyscope/telrun/flat_field.py index 7d3ad124..9f60092a 100644 --- a/pyscope/telrun/flat_field.py +++ b/pyscope/telrun/flat_field.py @@ -49,7 +49,7 @@ def __init__( that the reference epoch and the proper motions are set. The default is the `CoverCalibrator` which is a pre-configured location to take flat field images. - config : `~pyscope.telrun.Config`, default : None + config : `~pyscope.telrun.Configuration`, default : None The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. diff --git a/pyscope/telrun/light_field.py b/pyscope/telrun/light_field.py index 860b7c54..ec290c9f 100644 --- a/pyscope/telrun/light_field.py +++ b/pyscope/telrun/light_field.py @@ -39,9 +39,9 @@ def __init__( using the `astropy.coordinates.SkyCoord.from_name` method. If that fails, the class will attempt to resolve the target as an ephemeris object, first using the `astropy.coordinates.get_body` method and then the - `astroquery.mpc.MPC.get_ephemeris` method. + `astroquery.solarsystem.MPC.get_ephemeris()` method. - config : `~pyscope.telrun.Config`, default : `None` + config : `~pyscope.telrun.Configuration`, default : `None` The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. diff --git a/pyscope/telrun/transition_field.py b/pyscope/telrun/transition_field.py index 46d6775d..e12a0422 100644 --- a/pyscope/telrun/transition_field.py +++ b/pyscope/telrun/transition_field.py @@ -28,7 +28,7 @@ def __init__( target : `~astropy.coordinates.SkyCoord` or `None`, default : `None` The target field to point to during the transition. If `None`, the telescope will not move during the transition. - config : `~pyscope.telrun.Config` or `None`, default : `None` + config : `~pyscope.telrun.Configuration` or `None`, default : `None` The instrument configuration to use during the transition. If `None`, the configuration will not change during the transition. est_duration : `~astropy.units.Quantity`, default : 0 sec From ed1b48b6ed3e4265e15802c93da8dff4194da6c3 Mon Sep 17 00:00:00 2001 From: WGolay Date: Fri, 4 Oct 2024 14:02:52 -0400 Subject: [PATCH 18/30] Many docs updates and development of instrument configuration --- .gitignore | 2 +- .../{pyscope.analysis.rst => analysis.rst} | 6 +- docs/source/api/index.rst | 10 +- ...yscope.observatory.rst => observatory.rst} | 6 +- .../{pyscope.reduction.rst => reduction.rst} | 6 +- .../api/{pyscope.telrun.rst => telrun.rst} | 6 +- .../api/{pyscope.utils.rst => utils.rst} | 6 +- pyscope/telrun/__init__.py | 9 +- pyscope/telrun/_block.py | 39 ++-- pyscope/telrun/autofocus_field.py | 4 +- pyscope/telrun/boundary_condition.py | 8 +- pyscope/telrun/configuration.py | 2 - pyscope/telrun/dark_field.py | 2 +- pyscope/telrun/field.py | 14 +- pyscope/telrun/flat_field.py | 4 +- pyscope/telrun/instrument_configuration.py | 182 ++++++++++++++++++ pyscope/telrun/light_field.py | 12 +- pyscope/telrun/observer.py | 12 +- pyscope/telrun/option.py | 17 ++ pyscope/telrun/project.py | 7 + pyscope/telrun/transition_field.py | 2 +- setup.cfg | 2 +- 22 files changed, 293 insertions(+), 65 deletions(-) rename docs/source/api/{pyscope.analysis.rst => analysis.rst} (80%) rename docs/source/api/{pyscope.observatory.rst => observatory.rst} (81%) rename docs/source/api/{pyscope.reduction.rst => reduction.rst} (81%) rename docs/source/api/{pyscope.telrun.rst => telrun.rst} (80%) rename docs/source/api/{pyscope.utils.rst => utils.rst} (80%) delete mode 100644 pyscope/telrun/configuration.py create mode 100644 pyscope/telrun/instrument_configuration.py create mode 100644 pyscope/telrun/option.py create mode 100644 pyscope/telrun/project.py diff --git a/.gitignore b/.gitignore index 29c98322..8770f918 100755 --- a/.gitignore +++ b/.gitignore @@ -141,7 +141,7 @@ dmypy.json *ascom.alpaca.simulators* *OmniSim* !coverage.xml -docs/source/api/auto_api/ +docs/source/api/pyscope* docs/source/_build/ pgHardware pgtest diff --git a/docs/source/api/pyscope.analysis.rst b/docs/source/api/analysis.rst similarity index 80% rename from docs/source/api/pyscope.analysis.rst rename to docs/source/api/analysis.rst index 5fdc8beb..3e08d940 100755 --- a/docs/source/api/pyscope.analysis.rst +++ b/docs/source/api/analysis.rst @@ -7,16 +7,16 @@ Classes ------- .. automodsumm:: pyscope.analysis :classes-only: - :toctree: auto_api + :toctree: . Functions --------- .. automodsumm:: pyscope.analysis :functions-only: - :toctree: auto_api + :toctree: . Variables --------------- .. automodsumm:: pyscope.analysis :variables-only: - :toctree: auto_api + :toctree: . diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 73025ff5..7b4ad081 100755 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -4,8 +4,8 @@ API Reference .. toctree:: :maxdepth: 3 - pyscope.observatory - pyscope.telrun - pyscope.reduction - pyscope.analysis - pyscope.utils + observatory + telrun + reduction + analysis + utils diff --git a/docs/source/api/pyscope.observatory.rst b/docs/source/api/observatory.rst similarity index 81% rename from docs/source/api/pyscope.observatory.rst rename to docs/source/api/observatory.rst index 21539e3a..84c03974 100755 --- a/docs/source/api/pyscope.observatory.rst +++ b/docs/source/api/observatory.rst @@ -7,16 +7,16 @@ Classes ------- .. automodsumm:: pyscope.observatory :classes-only: - :toctree: auto_api + :toctree: . Functions --------- .. automodsumm:: pyscope.observatory :functions-only: - :toctree: auto_api + :toctree: . Variables --------------- .. automodsumm:: pyscope.observatory :variables-only: - :toctree: auto_api + :toctree: . diff --git a/docs/source/api/pyscope.reduction.rst b/docs/source/api/reduction.rst similarity index 81% rename from docs/source/api/pyscope.reduction.rst rename to docs/source/api/reduction.rst index c1a9819d..f8eec485 100755 --- a/docs/source/api/pyscope.reduction.rst +++ b/docs/source/api/reduction.rst @@ -7,16 +7,16 @@ Classes ------- .. automodsumm:: pyscope.reduction :classes-only: - :toctree: auto_api + :toctree: . Functions --------- .. automodsumm:: pyscope.reduction :functions-only: - :toctree: auto_api + :toctree: . Variables --------------- .. automodsumm:: pyscope.reduction :variables-only: - :toctree: auto_api + :toctree: . diff --git a/docs/source/api/pyscope.telrun.rst b/docs/source/api/telrun.rst similarity index 80% rename from docs/source/api/pyscope.telrun.rst rename to docs/source/api/telrun.rst index 28b32041..901ffa45 100755 --- a/docs/source/api/pyscope.telrun.rst +++ b/docs/source/api/telrun.rst @@ -7,16 +7,16 @@ Classes ------- .. automodsumm:: pyscope.telrun :classes-only: - :toctree: auto_api + :toctree: . Functions --------- .. automodsumm:: pyscope.telrun :functions-only: - :toctree: auto_api + :toctree: . Variables --------------- .. automodsumm:: pyscope.telrun :variables-only: - :toctree: auto_api + :toctree: . diff --git a/docs/source/api/pyscope.utils.rst b/docs/source/api/utils.rst similarity index 80% rename from docs/source/api/pyscope.utils.rst rename to docs/source/api/utils.rst index b20f723b..cba57976 100755 --- a/docs/source/api/pyscope.utils.rst +++ b/docs/source/api/utils.rst @@ -7,16 +7,16 @@ Classes ------- .. automodsumm:: pyscope.utils :classes-only: - :toctree: auto_api + :toctree: . Functions --------- .. automodsumm:: pyscope.utils :functions-only: - :toctree: auto_api + :toctree: . Variables --------------- .. automodsumm:: pyscope.utils :variables-only: - :toctree: auto_api + :toctree: . diff --git a/pyscope/telrun/__init__.py b/pyscope/telrun/__init__.py index a249333f..60e3c50c 100644 --- a/pyscope/telrun/__init__.py +++ b/pyscope/telrun/__init__.py @@ -18,7 +18,10 @@ from .flat_field import FlatField from .transition_field import TransitionField -from .configuration import Configuration +from .option import Option +from .instrument_configuration import InstrumentConfiguration + +from .project import Project from ._block import _Block from .schedule_block import ScheduleBlock @@ -53,7 +56,9 @@ "DarkField", "FlatField", "TransitionField", - "Configuration", + "Option", + "InstrumentConfiguration", + "Project", "_Block", "ScheduleBlock", "CalibrationBlock", diff --git a/pyscope/telrun/_block.py b/pyscope/telrun/_block.py index cdfe5018..0f0b0f88 100644 --- a/pyscope/telrun/_block.py +++ b/pyscope/telrun/_block.py @@ -4,7 +4,7 @@ from astropy.time import Time -from .configuration import Configuration +from .instrument_configuration import InstrumentConfiguration from .observer import Observer logger = logging.getLogger(__name__) @@ -22,12 +22,12 @@ def __init__(self, config, observer, name="", description="", **kwargs): Parameters ---------- - configuration : `~pyscope.telrun.Configuration`, required - The `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun._Block`. This `~pyscope.telrun.Configuration` will be - used to set the telescope's `~pyscope.telrun.Configuration` at the start of the `~pyscope.telrun._Block` and - will act as the default `~pyscope.telrun.Configuration` for all `~pyscope.telrun.Field` objects in the - `~pyscope.telrun._Block` if a `~pyscope.telrun.Configuration` has not been provided. If a `~pyscope.telrun.Field` - has a different `~pyscope.telrun.Configuration`, it will override the block `~pyscope.telrun.Configuration` for the + configuration : `~pyscope.telrun.InstrumentConfiguration`, required + The `~pyscope.telrun.InstrumentConfiguration` to use for the `~pyscope.telrun._Block`. This `~pyscope.telrun.InstrumentConfiguration` will be + used to set the telescope's `~pyscope.telrun.InstrumentConfiguration` at the start of the `~pyscope.telrun._Block` and + will act as the default `~pyscope.telrun.InstrumentConfiguration` for all `~pyscope.telrun.Field` objects in the + `~pyscope.telrun._Block` if a `~pyscope.telrun.InstrumentConfiguration` has not been provided. If a `~pyscope.telrun.Field` + has a different `~pyscope.telrun.InstrumentConfiguration`, it will override the block `~pyscope.telrun.InstrumentConfiguration` for the duration of the `~pyscope.telrun.Field`. observer : `~pyscope.telrun.Observer`, required @@ -54,7 +54,7 @@ def __init__(self, config, observer, name="", description="", **kwargs): in a `~pyscope.telrun.Schedule`. pyscope.telrun.UnallocatedBlock : A subclass of `~pyscope.telrun._Block` that is used to represent unallocated time in a `~pyscope.telrun.Schedule`. - pyscope.telrun.Configuration : A class that represents the configuration of the telescope. + pyscope.telrun.InstrumentConfiguration : A class that represents the configuration of the telescope. pyscope.telrun.Field : A class that represents a field to observe. """ logger.debug( @@ -84,7 +84,7 @@ def from_string( ---------- string : `str` - config : `~pyscope.telrun.Configuration`, default: `None` + config : `~pyscope.telrun.InstrumentConfiguration`, default: `None` observer : `~pyscope.telrun.Observer`, default: `None` @@ -137,7 +137,9 @@ def from_string( block_info.split("\nObserver: ")[1].split("\n")[0] ) config = ( - Configuration.from_string(block_info.split("\nConfiguration: ")[1]) + InstrumentConfiguration.from_string( + block_info.split("\nConfiguration: ")[1] + ) if config is None else config ) @@ -191,11 +193,11 @@ def __repr__(self): @property def config(self): """ - The default `~pyscope.telrun.Configuration` for the `~pyscope.telrun._Block`. + The default `~pyscope.telrun.InstrumentConfiguration` for the `~pyscope.telrun._Block`. Returns ------- - config : `~pyscope.telrun.Configuration` + config : `~pyscope.telrun.InstrumentConfiguration` """ logger.debug("_Block().config == %s" % self._config) return self._config @@ -203,21 +205,22 @@ def config(self): @config.setter def config(self, value): """ - The default `~pyscope.telrun.Configuration` for the `~pyscope.telrun._Block`. + The default `~pyscope.telrun.InstrumentConfiguration` for the `~pyscope.telrun._Block`. Parameters ---------- - value : `~pyscope.telrun.Configuration` + value : `~pyscope.telrun.InstrumentConfiguration` """ logger.debug("_Block().config = %s" % value) if ( - Configuration not in (config.__class__, *config.__class__.__bases__) + InstrumentConfiguration + not in (config.__class__, *config.__class__.__bases__) and value is not None ): raise TypeError( - "The config parameter must be a Configuration object (class=%s) or a subclass of Configuration (bases=%s), not a %s", - Configuration.__class__, - Configuration.__class__.__bases__, + "The config parameter must be a InstrumentConfiguration object (class=%s) or a subclass of InstrumentConfiguration (bases=%s), not a %s", + InstrumentConfiguration.__class__, + InstrumentConfiguration.__class__.__bases__, type(config), ) self._config = value diff --git a/pyscope/telrun/autofocus_field.py b/pyscope/telrun/autofocus_field.py index bae3c4c1..3b8c9f32 100644 --- a/pyscope/telrun/autofocus_field.py +++ b/pyscope/telrun/autofocus_field.py @@ -44,7 +44,7 @@ def __init__( The target field to autofocus on. If `None`, the telescope will autofocus on the current target field. - config : `~pyscope.telrun.Configuration`, default : `None` + config : `~pyscope.telrun.InstrumentConfiguration`, default : `None` The instrument configuration to use for the autofocus sequence. If `None`, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. @@ -139,7 +139,7 @@ def from_string( ---------- string : `str`, required - config : `~pyscope.telrun.Configuration`, default : None + config : `~pyscope.telrun.InstrumentConfiguration`, default : None repositioning : 2-tuple of `~astropy.units.Quantity`, default : None diff --git a/pyscope/telrun/boundary_condition.py b/pyscope/telrun/boundary_condition.py index 5e81ae6c..82c4175d 100644 --- a/pyscope/telrun/boundary_condition.py +++ b/pyscope/telrun/boundary_condition.py @@ -1,2 +1,8 @@ +import logging + +logger = logging.getLogger(__name__) + + class BoundaryCondition: - pass + def __init__(self, *args, name="", description="", **kwargs): + pass diff --git a/pyscope/telrun/configuration.py b/pyscope/telrun/configuration.py deleted file mode 100644 index 12efa2f8..00000000 --- a/pyscope/telrun/configuration.py +++ /dev/null @@ -1,2 +0,0 @@ -class Configuration: - pass diff --git a/pyscope/telrun/dark_field.py b/pyscope/telrun/dark_field.py index 85f696ec..6e94cefa 100644 --- a/pyscope/telrun/dark_field.py +++ b/pyscope/telrun/dark_field.py @@ -33,7 +33,7 @@ def __init__( that are captured in the dark frame. If `None`, the pointing will not change and the dark frame will be taken in the current position of the telescope. - config : `~pyscope.telrun.Configuration`, default : None + config : `~pyscope.telrun.InstrumentConfiguration`, default : None The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. diff --git a/pyscope/telrun/field.py b/pyscope/telrun/field.py index 98f521e9..f638d5ab 100644 --- a/pyscope/telrun/field.py +++ b/pyscope/telrun/field.py @@ -21,7 +21,7 @@ def __init__(self, target, config=None, **kwargs): The target field to observe. If the target has proper motion, ensure that the reference epoch and the proper motions are set. - config : `~pyscope.telrun.Configuration`, default : None + config : `~pyscope.telrun.InstrumentConfiguration`, default : None The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. @@ -123,12 +123,12 @@ def target(self, value): @property def config(self): """ - The instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + The instrument `~pyscope.telrun.InstrumentConfiguration` to use for the `~pyscope.telrun.Field`. Returns ------- - `~pyscope.telrun.Configuration` - The instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + `~pyscope.telrun.InstrumentConfiguration` + The instrument `~pyscope.telrun.InstrumentConfiguration` to use for the `~pyscope.telrun.Field`. """ logger.debug("Field().config == %s" % self._config) @@ -137,12 +137,12 @@ def config(self): @config.setter def config(self, value): """ - Set the instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + Set the instrument `~pyscope.telrun.InstrumentConfiguration` to use for the `~pyscope.telrun.Field`. Parameters ---------- - value : `~pyscope.telrun.Configuration`, required - The instrument `~pyscope.telrun.Configuration` to use for the `~pyscope.telrun.Field`. + value : `~pyscope.telrun.InstrumentConfiguration`, required + The instrument `~pyscope.telrun.InstrumentConfiguration` to use for the `~pyscope.telrun.Field`. """ logger.debug("Field().config = %s" % value) diff --git a/pyscope/telrun/flat_field.py b/pyscope/telrun/flat_field.py index 9f60092a..f565dd45 100644 --- a/pyscope/telrun/flat_field.py +++ b/pyscope/telrun/flat_field.py @@ -30,7 +30,7 @@ def __init__( is the `CoverCalibrator` which will point the telescope at a pre-configured location to take flat field images. A user may change the target to any other valid `~astropy.coordinates.SkyCoord` for taking sky flats. Some `CoverCalibrator` instruments may support the ability to adjust their - illumination level, and a user can change this by passing a `~pyscope.telrun.Configuration` with the + illumination level, and a user can change this by passing a `~pyscope.telrun.InstrumentConfiguration` with the appropriate settings. A user taking sky flats may want to take advantage of the `auto_exp` feature @@ -49,7 +49,7 @@ def __init__( that the reference epoch and the proper motions are set. The default is the `CoverCalibrator` which is a pre-configured location to take flat field images. - config : `~pyscope.telrun.Configuration`, default : None + config : `~pyscope.telrun.InstrumentConfiguration`, default : None The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. diff --git a/pyscope/telrun/instrument_configuration.py b/pyscope/telrun/instrument_configuration.py new file mode 100644 index 00000000..f56143a5 --- /dev/null +++ b/pyscope/telrun/instrument_configuration.py @@ -0,0 +1,182 @@ +import logging + +logger = logging.getLogger(__name__) + + +class InstrumentConfiguration: + def __init__( + self, + name="", + description="", + observatory_identifier="", + nasmyth_port=None, + focus_offset=None, + position_angle_offset=None, + filt=None, + shutter_state=None, + readout_mode=None, + binning=None, + frame_position=None, + frame_size=None, + cooler_on=None, + cooler_setpoint=None, + flat_screen_brightness=None, + inherit_from="current", + **kwargs, + ): + """ + A group of `~pyscope.telrun.Option` objects that define the configuration of the + instrument being controlled by a `~pyscope.telrun.TelrunOperator`. + + The `~pyscope.telrun.InstrumentConfiguration` object is used to define the current + status of user-schedulable options for the `~pyscope.observatory.Observatory` + instrument. These include common options like a filter wheel selection, the + focus position, and the readout mode, among other options. + + If a user passes values for each keyword argument, the `~pyscope.telrun.InstrumentConfiguration` + will be treated as a "requested" configuration that will be attached to a + `~pyscope.telrun.Field` or `~pyscope.telrun.ScheduleBlock` object and applied to the + instrument when the observation is executed. + + If a user passes an `~pyscope.telrun.Option` object for each keyword argument, the + `~pyscope.telrun.InstrumentConfiguration` will be treated as the "current" configuration that + will be contained in the `pyscope.telrun.TelrunOperator.instrument_configuration` property. This + `~pyscope.telrun.InstrumentConfiguration` will be used to contain the current and default + status of the instrument options and will be used to create the "requested" configurations + for each `~pyscope.telrun.Field` or `~pyscope.telrun.ScheduleBlock` object that does not + specify a complete `~pyscope.telrun.InstrumentConfiguration`. + + If a user passes a `None` value for a keyword argument (which is the default value for all keyword arguments), the + `~pyscope.telrun.InstrumentConfiguration` will inherit from the `pyscope.telrun.TelrunOperator.instrument_configuration` + property using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument to determine which parameter to + inherit from the `~pyscope.telrun.Option` for each keyword argument. + + Parameters + ---------- + name : `str`, default : "" + The name of the `~pyscope.telrun.InstrumentConfiguration`. This is typically a human-readable + name that describes the configuration and does not change the behavior of the + instrument. + + description : `str`, default : "" + A description of the `~pyscope.telrun.InstrumentConfiguration`. This is typically a human-readable + description that describes the configuration and does not change the behavior of + the instrument. + + observatory_identifier : `str`, default : "" + The identifier for the observatory that this `~pyscope.telrun.InstrumentConfiguration` is + associated with. This is typically a human-readable identifier that the observatory + manager has set for their `~pyscope.observatory.Observatory` object. If a + `~pyscope.telrun.Scheduler` will only ever be used with one `~pyscope.observatory.Observatory` + object, this value is optional and will not impact the behavior of the `~pyscope.telrun.Scheduler`. + + nasmyth_port : `~pyscope.telrun.Option`, `int`, or `None`, default : `None` + The nasmyth port to use for the observation. If `None`, the nasmyth port will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. This is typically + only relevant for larger telescopes that have multiple nasmyth ports. + + focus_offset : `~pyscope.telrun.Option`, `~astropy.units.Quantity`, or `None`, default : `None` + The focus offset to use for the observation. If `None`, the focus offset will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. This is typically + only relevant for observatories with filters that are not parfocal. + + position_angle_offset : `~pyscope.telrun.Option`, `~astropy.units.Quantity`, or `None`, default : `None` + The position angle offset to use for the observation. If `None`, the position angle offset will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. This is useful for + instruments that require a specific position angle for the observation, e.g., slit spectrographs. + + filt : `~pyscope.telrun.Option`, `int`, `str`, `list`, or `None`, default : `None` + The filter to use for the observation. If `None`, the filter will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. If an `int` is passed, + the filter will be selected by the filter wheel index. If a `str` is passed, the filter will be selected + by the filter name. A `list` can be passed for backends with multiple `~pyscope.observatory.FilterWheel` + objects on the same backend of the `~pyscope.observatory.Observatory`. + + shutter_state : `~pyscope.telrun.Option`, `bool`, or `None`, default : `None` + The state of the shutter for the observation. If `None`, the shutter state will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. If `True`, the shutter + will be open. If `False`, the shutter will be closed. + + readout_mode : `~pyscope.telrun.Option`, `int`, `str`, or `None`, default : `None` + The readout mode to use for the observation. If `None`, the readout mode will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. If an `int` is passed, + the readout mode will be selected by the readout mode index. If a `str` is passed, the readout mode will be + selected by the readout mode name, which is typically provided by the observatory manager. + + binning : `~pyscope.telrun.Option`, `tuple`, or `None`, default : `None` + The binning to use for the observation. If `None`, the binning will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. The binning is typically + a `tuple` of the x and y binning factors and may not always be the same for both axes. However, the observatory + manager will usually provide info on what binning factors are available for the instrument. + + frame_position : `~pyscope.telrun.Option`, `tuple`, or `None`, default : `None` + The frame position to use for the observation. If `None`, the frame position will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. The frame position is + typically a `tuple` of the x and y pixel positions of the corner of the frame. This is useful for instruments + with multiple detectors or an extremely large detector with long readout times and a small region of interest + that would be beneficial to read out at a higher frame rate, e.g., exoplanet transit observations. + + frame_size : `~pyscope.telrun.Option`, `tuple`, or `None`, default : `None` + The frame size to use for the observation. If `None`, the frame size will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. The frame size is + typically a `tuple` of the x and y pixel sizes of the frame. This is useful for instruments with multiple + detectors or an extremely large detector with long readout times and a small region of interest that would + be beneficial to read out at a higher frame rate, e.g., exoplanet transit observations. + + cooler_on : `~pyscope.telrun.Option`, `bool`, or `None`, default : `None` + The state of the cooler for the observation. If `None`, the cooler state will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. If `True`, the cooler + will be on. If `False`, the cooler will be turned off. This is typically a setting only used in requests of + `~pyscope.telrun.DarkField` objects for calibration testing. + + cooler_setpoint : `~pyscope.telrun.Option`, `~astropy.units.Quantity`, or `None`, default : `None` + The setpoint temperature of the cooler for the observation. If `None`, the cooler setpoint will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. This is typically a setting + only used in requests of `~pyscope.telrun.DarkField` objects for producing dark images. + + flat_screen_brightness : `~pyscope.telrun.Option`, `int`, or `None`, default : `None` + The brightness of the flat screen for the observation. If `None`, the flat screen brightness will be inherited + from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + using the `~pyscope.telrun.InstrumentConfiguration.inherit_from` keyword argument. This is typically a setting + only used in requests of `~pyscope.telrun.FlatField` objects for producing flat field images. + + inherit_from : `str`, default : "current", {"current", "default"} + The parameter to inherit from the `~pyscope.telrun.Option` in the `~pyscope.telrun.TelrunOperator.instrument_configuration` + for each keyword argument when applying a "requested" configuration to the instrument. If "current", the current configuration + saved within the `~pyscope.telrun.Option` objects will be used. If "default", the default configuration set in each + `~pyscope.telrun.Option` will be used. + + **kwargs : `dict`, default : {} + Additional keyword arguments to pass to the instrument for the observation. These are typically + instrument-specific settings that are not covered by the standard `~pyscope.telrun.Option` objects. + + """ + + self._type = "telrun operator instrument configuration" + + @classmethod + def from_string(self): + pass + + def __str__(self): + pass + + def __repr__(self): + pass + + def __call__(self, **kwargs): + pass + + def update_from(self, instrument_configuration, inherit_from=None): + pass diff --git a/pyscope/telrun/light_field.py b/pyscope/telrun/light_field.py index ec290c9f..6c96d063 100644 --- a/pyscope/telrun/light_field.py +++ b/pyscope/telrun/light_field.py @@ -26,7 +26,7 @@ def __init__( A single science target field and configuration for an observation. The `~pyscope.telrun.LightField` is the basic unit of a standard science observation. It contains the target, instrument - `~pyscope.telrun.Configuration`, `~pyscope.observatory.Observatory.repositioning` offsets, exposure time, number of exposures, + `~pyscope.telrun.InstrumentConfiguration`, `~pyscope.observatory.Observatory.repositioning` offsets, exposure time, number of exposures, output filename `~pathlib.Path`, and scheduling `~pyscope.telrun.BoundaryCondition` objects. The `~pyscope.telrun.LightField` is used by the `~pyscope.telrun.Observer` to build a `~pyscope.telrun.ScheduleBlock` for a given observation request. @@ -41,7 +41,7 @@ def __init__( using the `astropy.coordinates.get_body` method and then the `astroquery.solarsystem.MPC.get_ephemeris()` method. - config : `~pyscope.telrun.Configuration`, default : `None` + config : `~pyscope.telrun.InstrumentConfiguration`, default : `None` The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. @@ -238,12 +238,12 @@ def exp(self, value): @property def nexp(self): """ - The number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + The number of exposures to take of this target field with the given `~pyscope.telrun.InstrumentConfiguration`. Returns ------- `int` - The number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + The number of exposures to take of this target field with the given `~pyscope.telrun.InstrumentConfiguration`. """ logger.debug("LightField().nexp == %i" % self._nexp) @@ -252,12 +252,12 @@ def nexp(self): @nexp.setter def nexp(self, value): """ - Set the number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + Set the number of exposures to take of this target field with the given `~pyscope.telrun.InstrumentConfiguration`. Parameters ---------- value : `int`, required - The number of exposures to take of this target field with the given `~pyscope.telrun.Configuration`. + The number of exposures to take of this target field with the given `~pyscope.telrun.InstrumentConfiguration`. """ logger.debug("LightField().nexp = %i" % value) diff --git a/pyscope/telrun/observer.py b/pyscope/telrun/observer.py index 59e614b9..8455f4b3 100644 --- a/pyscope/telrun/observer.py +++ b/pyscope/telrun/observer.py @@ -4,4 +4,14 @@ class Observer: - pass + def __init__( + self, + username, + password, + access_level, + full_name, + institution, + email, + phone, + ): + pass diff --git a/pyscope/telrun/option.py b/pyscope/telrun/option.py new file mode 100644 index 00000000..cb6687e3 --- /dev/null +++ b/pyscope/telrun/option.py @@ -0,0 +1,17 @@ +import logging + +logger = logging.getLogger(__name__) + + +class Option: + def __init__( + self, + name="", + instruments=None, + current_value=None, + default_value=None, + description="", + type="str", # str, int, float, bool, list, dict + **kwargs + ): + pass diff --git a/pyscope/telrun/project.py b/pyscope/telrun/project.py new file mode 100644 index 00000000..b0f1d2fe --- /dev/null +++ b/pyscope/telrun/project.py @@ -0,0 +1,7 @@ +import logging + +logger = logging.getLogger(__name__) + + +class Project: + pass diff --git a/pyscope/telrun/transition_field.py b/pyscope/telrun/transition_field.py index e12a0422..c50e9396 100644 --- a/pyscope/telrun/transition_field.py +++ b/pyscope/telrun/transition_field.py @@ -28,7 +28,7 @@ def __init__( target : `~astropy.coordinates.SkyCoord` or `None`, default : `None` The target field to point to during the transition. If `None`, the telescope will not move during the transition. - config : `~pyscope.telrun.Configuration` or `None`, default : `None` + config : `~pyscope.telrun.InstrumentConfiguration` or `None`, default : `None` The instrument configuration to use during the transition. If `None`, the configuration will not change during the transition. est_duration : `~astropy.units.Quantity`, default : 0 sec diff --git a/setup.cfg b/setup.cfg index aab03601..ad60f1f4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,7 +75,7 @@ tests = dev = docutils==0.21.2 black==24.8.0 - esbonio==0.16.4 + esbonio==0.16.5 isort==5.13.2 pre-commit==3.8.0 pytest==8.1.1 From 0a25ef4eeffa44e91944085db52cfb24bee66d46 Mon Sep 17 00:00:00 2001 From: WGolay Date: Mon, 7 Oct 2024 07:40:21 -0400 Subject: [PATCH 19/30] Instrument config docs --- pyscope/telrun/instrument_configuration.py | 217 ++++++++++++++++++++- 1 file changed, 209 insertions(+), 8 deletions(-) diff --git a/pyscope/telrun/instrument_configuration.py b/pyscope/telrun/instrument_configuration.py index f56143a5..c780ccd8 100644 --- a/pyscope/telrun/instrument_configuration.py +++ b/pyscope/telrun/instrument_configuration.py @@ -162,21 +162,222 @@ def __init__( instrument-specific settings that are not covered by the standard `~pyscope.telrun.Option` objects. """ + logger.debug( + """InstrumentConfiguration( + name=%s, + description=%s, + observatory_identifier=%s, + nasmyth_port=%s, + focus_offset=%s, + position_angle_offset=%s, + filt=%s, + shutter_state=%s, + readout_mode=%s, + binning=%s, + frame_position=%s, + frame_size=%s, + cooler_on=%s, + cooler_setpoint=%s, + flat_screen_brightness=%s, + inherit_from=%s, + kwargs=%s, + )""" + % ( + name, + description, + observatory_identifier, + nasmyth_port, + focus_offset, + position_angle_offset, + filt, + shutter_state, + readout_mode, + binning, + frame_position, + frame_size, + cooler_on, + cooler_setpoint, + flat_screen_brightness, + inherit_from, + kwargs, + ) + ) self._type = "telrun operator instrument configuration" @classmethod - def from_string(self): - pass + def from_string( + self, + string, + name=None, + description=None, + observatory_identifier=None, + nasmyth_port=None, + focus_offset=None, + position_angle_offset=None, + filt=None, + shutter_state=None, + readout_mode=None, + binning=None, + frame_position=None, + frame_size=None, + cooler_on=None, + cooler_setpoint=None, + flat_screen_brightness=None, + inherit_from=None, + **kwargs, + ): + """ + Create a `~pyscope.telrun.InstrumentConfiguration` or a `list` of `~pyscope.telrun.InstrumentConfiguration` objects + from a `str` representation of a `~pyscope.telrun.InstrumentConfiguration`. All optional parameters are used to + override the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + name : `str`, default : `None` + + description : `str`, default : `None` + + observatory_identifier : `str`, default : `None` + + nasmyth_port : `~pyscope.telrun.Option`, `int`, or `None`, default : `None` + + focus_offset : `~pyscope.telrun.Option`, `~astropy.units.Quantity`, or `None`, default : `None` + + position_angle_offset : `~pyscope.telrun.Option`, `~astropy.units.Quantity`, or `None`, default : `None` + + filt : `~pyscope.telrun.Option`, `int`, `str`, `list`, or `None`, default : `None` + + shutter_state : `~pyscope.telrun.Option`, `bool`, or `None`, default : `None` + + readout_mode : `~pyscope.telrun.Option`, `int`, `str`, or `None`, default : `None` + + binning : `~pyscope.telrun.Option`, `tuple`, or `None`, default : `None` + + frame_position : `~pyscope.telrun.Option`, `tuple`, or `None`, default : `None` + + frame_size : `~pyscope.telrun.Option`, `tuple`, or `None`, default : `None` + + cooler_on : `~pyscope.telrun.Option`, `bool`, or `None`, default : `None` + + cooler_setpoint : `~pyscope.telrun.Option`, `~astropy.units.Quantity`, or `None`, default : `None` + + flat_screen_brightness : `~pyscope.telrun.Option`, `int`, or `None`, default : `None` + + inherit_from : `str`, default : "current", {"current", "default"} + + **kwargs : `dict`, default : {} + + """ + logger.debug( + """InstrumentConfiguration.from_string( + string=%s, + name=%s, + description=%s, + observatory_identifier=%s, + nasmyth_port=%s, + focus_offset=%s, + position_angle_offset=%s, + filt=%s, + shutter_state=%s, + readout_mode=%s, + binning=%s, + frame_position=%s, + frame_size=%s, + cooler_on=%s, + cooler_setpoint=%s, + flat_screen_brightness=%s, + inherit_from=%s, + kwargs=%s, + )""" + % ( + string, + name, + description, + observatory_identifier, + nasmyth_port, + focus_offset, + position_angle_offset, + filt, + shutter_state, + readout_mode, + binning, + frame_position, + frame_size, + cooler_on, + cooler_setpoint, + flat_screen_brightness, + inherit_from, + kwargs, + ) + ) def __str__(self): - pass + """ + Return a `str` representation of the `~pyscope.telrun.InstrumentConfiguration`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.InstrumentConfiguration`. + """ + logger.debug("InstrumentConfiguration().__str__() = %s" % self) def __repr__(self): - pass + """ + Return a `str` representation of the `~pyscope.telrun.InstrumentConfiguration`. - def __call__(self, **kwargs): - pass + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.InstrumentConfiguration`. + """ + logger.debug("InstrumentConfiguration().__repr__() = %s" % self) + return str(self) - def update_from(self, instrument_configuration, inherit_from=None): - pass + def __call__(self, instrument_configuration, inherit_from=None): + """ + Update the `~pyscope.telrun.InstrumentConfiguration` object with the values from the + `instrument_configuration` object that contain the new values to update the + selection of options inside this `~pyscope.telrun.InstrumentConfiguration`. If the + `inherit_from` keyword argument is passed, the `~pyscope.telrun.InstrumentConfiguration` object + will override the default `inherit_from` value. + + If `inherit_from="current"`, the + `~pyscope.telrun.InstrumentConfiguration` will inherit from the current configuration + values saved in the `~pyscope.telrun.Option` objects. This is the default behavior of the class. + If `inherit_from="default"`, the + `~pyscope.telrun.InstrumentConfiguration` will inherit the `~pyscope.telrun.Option` default + values to fill in the values that are not specified in the `instrument_configuration`. + + Parameters + ---------- + instrument_configuration : `~pyscope.telrun.InstrumentConfiguration`, required + The `~pyscope.telrun.InstrumentConfiguration` object that contains the new values to update + the selection of options inside this `~pyscope.telrun.InstrumentConfiguration`. + + inherit_from : `str`, default : `None`, {"current", "default"} + The parameter to inherit from the `~pyscope.telrun.Option` in the `instrument_configuration` + for each keyword argument when applying a "requested" configuration to the instrument. If "current", the current configuration + saved within the `~pyscope.telrun.Option` objects will be used. If "default", the default configuration set in each + `~pyscope.telrun.Option` will be used. + + Returns + ------- + `~pyscope.telrun.InstrumentConfiguration` + The updated `~pyscope.telrun.InstrumentConfiguration` object with the new values from the + `instrument_configuration` object. + + """ + logger.debug( + """InstrumentConfiguration().__call__( + instrument_configuration=%s, + inherit_from=%s, + )""" + % ( + instrument_configuration, + inherit_from, + ) + ) From 2d7e96f79381a1e6544d102fa6e4965dea30b370 Mon Sep 17 00:00:00 2001 From: WGolay Date: Mon, 7 Oct 2024 07:55:11 -0400 Subject: [PATCH 20/30] Option docs --- pyscope/telrun/option.py | 131 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/pyscope/telrun/option.py b/pyscope/telrun/option.py index cb6687e3..44bc8b73 100644 --- a/pyscope/telrun/option.py +++ b/pyscope/telrun/option.py @@ -7,11 +7,138 @@ class Option: def __init__( self, name="", - instruments=None, + instruments=[], current_value=None, default_value=None, description="", type="str", # str, int, float, bool, list, dict **kwargs ): - pass + """ + Add a new schedulable `~pyscope.telrun.Option` to the `~pyscope.telrun.InstrumentConfiguration`. + + The `~pyscope.telrun.Option` class is used to define an `~pyscope.telrun.Option` that a user can set in a `~pyscope.telrun.ScheduleBlock` by + modifying the requested `~pyscope.telrun.InstrumentConfiguration`. This class is used to define the options that are available + to the user and to validate the values that are set for the options using an `~pyscope.telrun.InstrumentConfiguration` that + is contained in the `~pyscope.telrun.TelrunOperator`. + + Parameters + ---------- + name : `str`, default : "" + The name of the `~pyscope.telrun.Option`. This is used to identify the `~pyscope.telrun.Option` in the `~pyscope.telrun.InstrumentConfiguration` and + must be unique within the configuration. + + instruments : `list` of `str`, default : [] + The list of instruments used by the `~pyscope.telrun.Option`. This can be used to sort options by instrument. + + current_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + The current value of the `~pyscope.telrun.Option`. This is the value that will be used in the observation if the `~pyscope.telrun.Option` is not changed. + + default_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + The default value of the `~pyscope.telrun.Option`. This is the value that will be used if the `~pyscope.telrun.Option` is not set by the user. + + description : `str`, default : "" + A description of the `~pyscope.telrun.Option`. This is used to provide information to the user about the `~pyscope.telrun.Option`. + + type : `str`, default : "str" + The type of the `~pyscope.telrun.Option`. This can be one of "str", "int", "float", "bool", "list", or "dict". + + **kwargs : dict, default : {} + Additional keyword arguments to pass to the `~pyscope.telrun.Option`. If `type="list"` or `type="dict"`, requested values + will be validated against `vlist` or `vdict` respectively. If `type="int"` or `type="float"`, the value will be validated + against `min` and `max` if they are set. If `type="bool"`, the value will be converted to a `bool`. If `type="str"`, the + value will be validated against a list of `str` values in `vlist` if it is set or the length of the string will be validated + against `min` and `max` if they are set. + + """ + logger.debug( + "Option(name=%s, instruments=%s, current_value=%s, default_value=%s, description=%s, type=%s, kwargs=%s)" + % ( + name, + instruments, + current_value, + default_value, + description, + type, + kwargs, + ) + ) + + @classmethod + def from_string( + cls, + string, + name=None, + instruments=None, + current_value=None, + default_value=None, + description=None, + type=None, + **kwargs + ): + """ + Create a `~pyscope.telrun.Option` or a `list` of `~pyscope.telrun.Option` objects from a `str` representation of a `~pyscope.telrun.Option`. + All optional parameters are used to override the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + name : `str`, default : None + + instruments : `list` of `str`, default : None + + current_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + + default_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + + description : `str`, default : None + + type : `str`, default : None + + **kwargs : `dict`, default : {} + + Returns + ------- + `~pyscope.telrun.Option` or `list` of `~pyscope.telrun.Option` + + """ + logger.debug( + "Option.from_string(string=%s, name=%s, instruments=%s, current_value=%s, default_value=%s, description=%s, type=%s, kwargs=%s)" + % ( + string, + name, + instruments, + current_value, + default_value, + description, + type, + kwargs, + ) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.Option`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.Option`. + """ + logger.debug("Option().__str__() = %s" % self) + + def __repr__(self): + """ + Return a `str` representation of the `~pyscope.telrun.Option`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.Option`. + """ + logger.debug("Option().__repr__() = %s" % self) + return str(self) + + def __call__(self): + logger.debug("Option().__call__()") From 69eefbb8ef9b767b274e3204a97e7c125889f81a Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 8 Oct 2024 09:57:53 -0400 Subject: [PATCH 21/30] Start conditions docs --- pyscope/telrun/__init__.py | 40 +++-- pyscope/telrun/airmass_condition.py | 215 ++++++++++++++++++++++++++ pyscope/telrun/boundary_condition.py | 207 ++++++++++++++++++++++++- pyscope/telrun/coord_condition.py | 159 +++++++++++++++++++ pyscope/telrun/hourangle_condition.py | 127 +++++++++++++++ pyscope/telrun/moon_condition.py | 36 +++++ pyscope/telrun/snr_condition.py | 9 ++ pyscope/telrun/sun_condition.py | 37 +++++ pyscope/telrun/time_condition.py | 9 ++ 9 files changed, 824 insertions(+), 15 deletions(-) create mode 100644 pyscope/telrun/airmass_condition.py create mode 100644 pyscope/telrun/coord_condition.py create mode 100644 pyscope/telrun/hourangle_condition.py create mode 100644 pyscope/telrun/moon_condition.py create mode 100644 pyscope/telrun/snr_condition.py create mode 100644 pyscope/telrun/sun_condition.py create mode 100644 pyscope/telrun/time_condition.py diff --git a/pyscope/telrun/__init__.py b/pyscope/telrun/__init__.py index 60e3c50c..4c2c8a51 100644 --- a/pyscope/telrun/__init__.py +++ b/pyscope/telrun/__init__.py @@ -9,7 +9,13 @@ logger = logging.getLogger(__name__) from .boundary_condition import BoundaryCondition -from .observer import Observer +from .coord_condition import CoordinateCondition +from .hourangle_condition import HourAngleCondition +from .airmass_condition import AirmassCondition +from .sun_condition import SunCondition +from .moon_condition import MoonCondition +from .time_condition import TimeCondition +from .snr_condition import SNRCondition from .field import Field from .light_field import LightField @@ -21,13 +27,14 @@ from .option import Option from .instrument_configuration import InstrumentConfiguration -from .project import Project - from ._block import _Block from .schedule_block import ScheduleBlock from .calibration_block import CalibrationBlock from .unallocated_block import UnallocatedBlock +from .observer import Observer +from .project import Project + from .prioritizer import Prioritizer from .optimizer import Optimizer @@ -48,8 +55,14 @@ from .telrun_operator import TelrunOperator __all__ = [ - "Observer", "BoundaryCondition", + "CoordinateCondition", + "HourAngleCondition", + "AirmassCondition", + "SunCondition", + "MoonCondition", + "TimeCondition", + "SNRCondition", "Field", "LightField", "AutofocusField", @@ -58,11 +71,12 @@ "TransitionField", "Option", "InstrumentConfiguration", - "Project", "_Block", "ScheduleBlock", "CalibrationBlock", "UnallocatedBlock", + "Observer", + "Project", "Prioritizer", "Optimizer", "Queue", @@ -70,14 +84,14 @@ "Scheduler", "exoplanet_transits", "init_telrun_dir", - "mk_mosaic_schedule", - "rst", - "sch", - "schedtab", - "schedtel", - "reports", - "plot_schedule_gantt", - "plot_schedule_sky", + # "mk_mosaic_schedule", + # "rst", + # "sch", + # "schedtab", + # "schedtel", + # "reports", + # "plot_schedule_gantt", + # "plot_schedule_sky", "start_telrun_operator", "survey_builder", "TelrunOperator", diff --git a/pyscope/telrun/airmass_condition.py b/pyscope/telrun/airmass_condition.py new file mode 100644 index 00000000..77067361 --- /dev/null +++ b/pyscope/telrun/airmass_condition.py @@ -0,0 +1,215 @@ +import logging + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class AirmassCondition(BoundaryCondition): + def __init__(self, airmass_limit=3, formula="secant", weight=1): + """ + A condition that penalizes targets for higher airmass values up to a limit. + + This condition is used to restrict the airmass value of a target to a maximum value. The airmass + can be calculated using several different formulae. The default is a simple secant formula, + given by :math:`X = \\frac{1}{\\cos(z)}` where :math:`z` is the angle between the target and the zenith. This is the + analytic solution for a plane-parallel atmosphere. + + Other options include Schoenberg 1929[1]_, which is a geometric model for a non-refracting spherical atmosphere: + + .. math:: + X = \\frac{R_{\\oplus}}{y_{\\rm atm}}\\sqrt{\\cos^2(z) + 2\\frac{y_{\\rm atm}}{R_{\\oplus}} + \\left(\\frac{y_{\\rm atm}}{R_{\\oplus}}\\right)^2} - \\frac{R_{\\oplus}}{y_{\\rm atm}}\\cos(z) + + where :math:`R_{\\oplus} = 6371` km is the mean radius of the Earth and :math:`y_{\\rm atm} = \\frac{kT_0}{mg}` is the scale height of the atmosphere. For typical + values, the scale height is 8435 m. For this value, the airmass at the horizon is :math:`\\approx 38.87`. + + The class also supports several basic interpolative models for the airmass. We list these below: + + - Young & Irvine 1967[2]_: :math:`X = \\sec(z_t)\\left[1 - 0.0012(\\sec^2z_t - 1)\\right]` where :math:`z_t` is the "true" zenith angle. Note that this formula becomes zero at :math:`z_t \\approx 88^{\\circ}`. + + - Hardie 1962[3]_: :math:`X = \\sec(z) - 0.0018167(\\sec(z) - 1) - 0.002875(\\sec(z) - 1)^2 - 0.0008083(\\sec(z) - 1)^3` where :math:`z` is the zenith angle. This formula approaches negative infinity at the horizon and is typically used for up to :math:`z \\approx 85^{\\circ}`. + + - Rozenberg 1966[4]_: :math:`X = (\\cos z + 0.025\\exp(-11\\cos z))^{-1}`. The horizon airmass is :math:`\\approx 40`. + + - Kasten & Young 1989[5]_: :math:`X = \\frac{1}{\\cos z + 0.50572(96.07995^{\\circ} - \\left[z\\right]_{\\rm degrees})^{-1.6364}}`. The horizon airmass is :math:`\\approx 38.7`. Note the :math:`\\left[z\\right]_{\\rm degrees}` is the zenith angle in degrees but the :math:`\\cos z` is in radians as usual. + + - Young 1994[6]_: :math:`X = \\frac{1.002432\\cos^2z_t + 0.148386\\cos z_t + 0.0096467}{\\cos^3z_t + 0.149864\\cos^2z_t + 0.0102963\\cos z_t + 0.000303978}` + + - Pickering 2002[7]_: :math:`X = \\frac{1}{\\sin((90^{\\circ} - \\left[z\\right]_{\\rm degrees}) + 244/(165 + 47(90^{\\circ} - \\left[z\\right]_{\\rm degrees})^{1.1}))}` + + + These are all nicely discussed in the `Wikipedia article on Airmass `_, including this + figure that shows the airmass as a function of zenith angle for the different models: + + .. image:: https://upload.wikimedia.org/wikipedia/commons/d/d3/Viewing_angle_and_air_masses.svg + + The airmass is penalized linearly from `1` with the best linear quality score and decreases to `0` at the `airmass_limit`. + + Parameters + ---------- + airmass_limit : float, default : 3 + The maximum airmass value that is allowed. + + formula: `str`, default : "secant", {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} + The formula to use to calculate the airmass value. + + weight : float, default : 1 + The weight of the condition in the final score. The default is 1. + + References + ---------- + .. [1] `Schoenberg, E. 1929. Theoretische Photometrie, Ãœber die Extinktion des Lichtes in der Erdatmosphäre. In Handbuch der Astrophysik. Band II, erste Hälfte. Berlin: Springer. `_ + + .. [2] `Young & Irvine 1967 `_ + + .. [3] `Hardie 1962 `_ + + .. [4] `Rozenberg 1966 `_ + + .. [5] `Kasten & Young 1989 `_ + + .. [6] `Young 1994 `_ + + .. [7] `Pickering 2002 `_ + + """ + logger.debug("AirmassCondition(airmass_limit=%s, formula=%s, weight=%s)") + super().__init__( + func=self.calculate, + lqs_func=self.score, + weight=weight, + airmass_limit=airmass_limit, + formula=formula, + ) + self.airmass_limit = airmass_limit + self.formula = formula + + @classmethod + def from_string(self, string): + logger.debug("AirmassCondition.from_string(string=%s)" % string) + pass + + def __str__(self): + logger.debug("AirmassCondition().__str__()") + pass + + def calculate(self, target, time, location, **kwargs): + """ + Compute the airmass value for the target at the given time and location. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord`, required + The target to evaluate the condition for. + + time : `~astropy.time.Time`, required + The time to evaluate the condition at. + + location : `~astropy.coordinates.EarthLocation`, required + The location to evaluate the condition at. + + formula : `str`, default : "secant", {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} + The formula to use to calculate the airmass value. + + Returns + ------- + `float` + The airmass value for the target at the given time and location. + + """ + logger.debug( + "AirmassCondition().calculate(target=%s, time=%s, location=%s, kwargs=%s)" + % (target, time, location, kwargs) + ) + pass + + def score(self, value, **kwargs): + """ + Compute the score for the airmass condition. + + Parameters + ---------- + value : `float`, required + The airmass value to score. + + airmass_limit : `float`, optional + The maximum airmass value that is allowed. + + Returns + ------- + `float` + The linear quality score value (between `0` and `1`) for the airmass condition. + + """ + logger.debug("AirmassCondition().score(value=%s, kwargs=%s)" % (value, kwargs)) + pass + + @property + def airmass_limit(self): + """ + The maximum airmass value that is allowed. + + Returns + ------- + `float` + The maximum airmass value that is allowed. + + """ + logger.debug("AirmassCondition().airmass_limit == %s" % self._airmass_limit) + return self._airmass_limit + + @airmass_limit.setter + def airmass_limit(self, value): + """ + The maximum airmass value that is allowed. + + Parameters + ---------- + value : `float`, required + The maximum airmass value that is allowed + + """ + logger.debug("AirmassCondition().airmass_limit = %s" % value) + if value is not None: + if value <= 1: + raise ValueError("Airmass limit must be greater than 1") + self._airmass_limit = value + + @property + def formula(self): + """ + The formula to use to calculate the airmass value. + + Returns + ------- + `str` + The formula to use to calculate the airmass value. + + """ + logger.debug("AirmassCondition().formula == %s" % self._formula) + return self._formula + + @formula.setter + def formula(self, value): + """ + The formula to use to calculate the airmass value. + + Parameters + ---------- + value : `str`, required, {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} + The formula to use to calculate the airmass value. + + """ + logger.debug("AirmassCondition().formula = %s" % value) + if value not in [ + "secant", + "Schoenberg1929", + "Young+Irvine1967", + "Hardie1962", + "Rozenberg1966", + "KastenYoung1989", + "Young1994", + "Pickering2002", + ]: + raise ValueError("Invalid formula for airmass condition") + self._formula = value diff --git a/pyscope/telrun/boundary_condition.py b/pyscope/telrun/boundary_condition.py index 82c4175d..6c945f0e 100644 --- a/pyscope/telrun/boundary_condition.py +++ b/pyscope/telrun/boundary_condition.py @@ -4,5 +4,208 @@ class BoundaryCondition: - def __init__(self, *args, name="", description="", **kwargs): - pass + def __init__( + self, + func=None, + lqs_func=None, + weight=1, + **kwargs, + ): + """ + A class to hold a scoring function that evaluates a condition for a given target, time, and location. + + A generic class for defining a boundary condition as a function of `~astropy.time.Time`, + `~astropy.coordinates.EarthLocation`, and `~astropy.coordinates.SkyCoord` used by the + `~pyscope.telrun.Optimizer` inside the `~pyscope.telrun.Scheduler` to evaluate the quality + of an observation across a range of times (and potentially locations) for a given target. + + Users will most likely not need to interact with this class directly, but instead will use + the subclasses of `~pyscope.telrun.BoundaryCondition` that are defined in the `~pyscope.telrun` + module. Commonly used subclasses include `~pyscope.telrun.HourAngleCondition`, + `~pyscope.telrun.AirmassCondition`, and `~pyscope.telrun.SunCondition`. + + The `~pyscope.telrun.SNRCondition` is also an excellent choice that takes advantage of properties + of the `~pyscope.telrun.InstrumentConfiguration` and `~pyscope.telrun.LightField` to evaluate the + `~pyscope.observatory.Observatory` class's signal-to-noise ratio model for scheduling over a range + of times with dynamic conditions (e.g., a multi-night schedule across Moon bright time and dark time). + This is a convenient way to simulate a complete observation over a range of conditions and times + to evaluate the quality of the observation and gain access to the tools of the + `~pyscope.telrun.Observatory` class's signal-to-noise ratio model, such as the image simulation tool + for previewing the expected image and sources in the field. + + Parameters + ---------- + func : `function`, default : `None` + The function to evaluate the condition. This function should take a `~astropy.coordinates.SkyCoord`, + a `~astropy.time.Time`, and a `~astropy.coordinates.EarthLocation` as arguments and return a value + that will be used by the `lqs_func` to evaluate the condition. If `None`, the `lqs_func` function will + be used directly. + + lqs_func : `function`, default : `None` + The function to convert the output of the `func` into a linear quality score between `0` and `1`. + This function should take the output of the `func` and return a value between `0` and `1` that represents + the quality of the condition. If `None`, the output of the `func` will be used directly. + + weight : `float`, default : 1 + The weight of this condition relative to other conditions. This value is used by the `~pyscope.telrun.Optimizer` + inside the `~pyscope.telrun.Scheduler` to compute the overall quality of a `~pyscope.telrun.Field` or + or `~pyscope.telrun.ScheduleBlock` based on the conditions that are evaluated. The weight should be a positive value, + and the relative weight of each condition will be used to scale the output of the `lqs_func` function when computing the + overall score. A weight of `0` will effectively disable the condition from being used in the optimization, and a + weight of `1` is the default value. The weight can be set to any positive value to increase the relative importance of + the condition. The composite linear quality score is typically computed as the geometric mean of the individual condition + scores, so the weights are used to increase the power index of the geometric mean for each condition. Expressed + mathematically: + + .. math:: + Q = \\left( \\prod_{i=1}^{N} q_i^{w_i} \\right)^{1 / \\sum_{i=1}^{N} w_i} + + where :math:`Q` is the composite quality score, :math:`q_i` is the quality score of the :math:`i`-th condition, + and :math:`w_i` is the weight of the :math:`i`-th condition. The sum of the weights is used to normalize the + composite quality score to a value between `0` and `1`. The default weight of `1` is used to give equal weight to + all conditions, but users can adjust the weights to prioritize certain conditions over others. Since the weights + are used as exponents in the geometric mean, `float` weights are possible. + + **kwargs : `dict`, default : {} + Additional keyword arguments to pass to the condition functions for evaluation. + + + See Also + -------- + pyscope.telrun.CoordinateCondition + pyscope.telrun.HourAngleCondition + pyscope.telrun.AirmassCondition + pyscope.telrun.MoonCondition + pyscope.telrun.SunCondition + pyscope.telrun.TimeCondition + pyscope.telrun.SNRCondition + + """ + logger.debug( + "BoundaryCondition(func=%s, lqs_func=%s, weight=%s)" + % (func, lqs_func, weight) + ) + + if not callable(func) and not callable(lqs_func): + raise ValueError("Either func or lqs_func must be provided.") + self._func = func + self._lqs_func = lqs_func + self._weight = float(weight) + self._kwargs = kwargs + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.BoundaryCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.BoundaryCondition`. + """ + logger.debug("BoundaryCondition().__str__() = %s" % self) + + def __repr__(self): + """ + Return a `str` representation of the `~pyscope.telrun.BoundaryCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.BoundaryCondition`. + + """ + logger.debug("BoundaryCondition().__repr__()") + return str(self) + + def __call__(self, target, time, location, **kwargs): + """ + Evaluate the `~pyscope.telrun.BoundaryCondition` for a given target, time, and location. + + This is a shortcut for calling the `func` and `lqs_func` functions directly. The `func` function + evaluates the condition for the target, time, and location and returns a value that is then passed + to the `lqs_func` function to convert the value into a linear quality score between `0` and `1`. The + `lqs_func` function is optional, and if not provided, the output of the `func` function will be used + directly as the quality score. The code is essentially equivalent to: + + .. code-block:: python + + if func is not None and lqs_func is None: + value = func(target, time, location, **kwargs) + elif lqs_func is not None: + value = lqs_func(func(target, time, location, **kwargs), **kwargs) + else: + raise ValueError("Either func or lqs_func must be provided.") + + The `**kwargs` are passed to both the `func` and `lqs_func` functions as additional arguments. This is + used in some subclasses (such as the `~pyscope.telrun.SNRCondition`) to pass additional parameters (such as + an `~pyscope.observatory.Observatory`) to the `func` and `lqs_func` functions for evaluation. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord` + The target field to observe. + + time : `~astropy.time.Time` + The time of the observation. + + location : `~astropy.coordinates.EarthLocation` + The location of the observatory. + + **kwargs : dict + + Returns + ------- + `float` + A `float` value between `0` and `1` that represents the linear quality score of the condition. + + """ + logger.debug( + "BoundaryCondition().__call__(target=%s, time=%s, location=%s, kwargs=%s)" + % (target, time, location, kwargs) + ) + if kwargs is None: + kwargs = self._kwargs + + if self._func is not None and self._lqs_func is None: + value = self.calculate(target, time, location, **kwargs) + elif self._lqs_func is not None: + value = self.score( + self.calculate(target, time, location, **kwargs), **kwargs + ) + else: + raise ValueError("Either func or lqs_func must be provided.") + + return value + + def calculate(target, time, location, **kwargs): + return self._func(target, time, location, **kwargs) + + def score(value, **kwargs): + return self._lqs_func(value, **kwargs) + + @property + def weight(self): + """ + The weight of this condition relative to other conditions. + + The weight can be set to any positive value to increase the relative importance of the condition. The composite + linear quality score is typically computed as the geometric mean of the individual condition scores, so the weights + are used to increase the power index of the geometric mean for each condition. Expressed mathematically: + + .. math:: + + Q = \\left( \\prod_{i=1}^{N} q_i^{w_i} \\right)^{1 / \\sum_{i=1}^{N} w_i} + + where :math:`Q` is the composite quality score, :math:`q_i` is the quality score of the :math:`i`-th condition, + and :math:`w_i` is the weight of the :math:`i`-th condition. + """ + logger.debug("BoundaryCondition().weight == %s" % self._weight) + return self._weight + + @property + def kwargs(self): + """ + Additional keyword arguments to pass to the condition functions for evaluation. + """ + logger.debug("BoundaryCondition().kwargs == %s" % self._kwargs) + return self._kwargs diff --git a/pyscope/telrun/coord_condition.py b/pyscope/telrun/coord_condition.py new file mode 100644 index 00000000..7f419aca --- /dev/null +++ b/pyscope/telrun/coord_condition.py @@ -0,0 +1,159 @@ +import logging + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class CoordinateCondition(BoundaryCondition): + def __init__( + self, + coord_type="altaz", + coord_idx=0, + min_val=None, + max_val=None, + score_type="boolean", + **kwargs, + ): + """ + A restriction on the coordinates of the target viewed from a specific location + and at a specific time. + + A manager to hande restrictions of a source's location when it is observered. The + coordinates can be specified in right ascension and declination, galactic latitude + and longitude, or altitude and azimuth. + + Parameters + ---------- + coord_type : `str`, default : "altaz", {"altaz", "radec", "galactic"} + The type of coordinate system to use. The options are "altaz" for altitude + and azimuth, "radec" for right ascension and declination, and "galactic" for + galactic latitude and longitude. The default is "altaz". + + coord_idx : int, default : 0, {0, 1} + The index of the coordinate to use. The default is 0 for the first coordinate. This + parameter is ignored if `min_val` and `max_val` both contain two values for the + minimum and maximum values of each coordinate. + + min_val : `~astropy.units.Quantity`, default : `None` + The minimum value for the coordinate. If `None`, there is no minimum value. + + max_val : `~astropy.units.Quantity`, default : `None` + The maximum value for the coordinate. If `None`, there is no maximum value. + + score_type : `str`, default : "boolean", {"linear", "boolean"} + The type of scoring function to use. The options are "linear" for a linear + function, commonly used for altitude, and "boolean" for a binary function + that returns 1 if the condition is met and 0 if it is not, commonly used for + determining if the source is above or below the horizon. The default is "boolean". + + **kwargs : Additional keyword arguments to pass to the scoring function. + + ref_coord : `~astropy.coordinates.SkyCoord`, default : `None` + If `coord_type` is "radec" or "galactic", a user can specify a center value + and `min_val` and `max_val` will be interpreted as a minimum and maximum angular + separation of the target from the reference coordinate. + + """ + logger.debug( + """ + CoordinateCondition( + coord_type=%s, + coord_idx=%i, + min_val=%s, + max_val=%s, + score_type=%s, + kwargs=%s + )""" + % (coord_type, coord_idx, min_val, max_val, score_type, kwargs) + ) + + def from_string( + self, + string, + coord_type=None, + coord_idx=None, + min_val=None, + max_val=None, + score_type=None, + **kwargs, + ): + """ + Create a `~pyscope.telrun.CoordinateCondition` or a `list` of `~pyscope.telrun.CoordinateCondition` objects from a `str` representation of a `~pyscope.telrun.CoordinateCondition`. + All optional parameters are used to override the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + coord_type : `str`, default : None + + coord_idx : `int`, default : None + + min_val : `~astropy.units.Quantity`, default : None + + max_val : `~astropy.units.Quantity`, default : None + + score_type : `str`, default : None + + **kwargs : `dict`, default : {} + + """ + logger.debug( + "CoordinateCondition.from_string(string=%s, coord_type=%s, coord_idx=%s, min_val=%s, max_val=%s, score_type=%s, kwargs=%s)" + % (string, coord_type, coord_idx, min_val, max_val, score_type, kwargs) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.CoordinateCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.CoordinateCondition`. + + """ + logger.debug("CoordinateCondition().__str__()") + + def __repr__(self): + """ + Return a `str` representation of the `~pyscope.telrun.CoordinateCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.CoordinateCondition`. + + """ + logger.debug("CoordinateCondition().__repr__()") + return str(self) + + def __call__(self, target, time=None, location=None): + """ + Evaluate the condition for the target at the specified time and location. + + Parameters + ---------- + target : `~astropy.coordinates.SkyCoord`, required + The target to evaluate the condition for. + + time : `~astropy.time.Time`, default : None + The time to evaluate the condition at. Some conditions do not depend on time, + but others that do will require this parameter. + + location : `~astropy.coordinates.EarthLocation`, default : None + The location to evaluate the condition at. Some conditions do not depend on + location, but others that do will require this parameter. + + Returns + ------- + `float` + A `float` value between `0` and `1` that represents the linear quality score of the condition. + + """ + pass + + def plot(self, target, time, location, ax=None): + """ """ + pass diff --git a/pyscope/telrun/hourangle_condition.py b/pyscope/telrun/hourangle_condition.py new file mode 100644 index 00000000..81b7b00c --- /dev/null +++ b/pyscope/telrun/hourangle_condition.py @@ -0,0 +1,127 @@ +import logging + +import astropy.units as u + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class HourAngleCondition(BoundaryCondition): + def __init__( + self, + min_hour_angle=-6 * u.hourangle, + max_hour_angle=6 * u.hourangle, + score_type="linear", + ): + """ + A restriction on the hour angle over which a target can be observed. + + Contains a minimum and maximum hour angle, which are the hour angles at which the target is on the horizon + by default. Stricter conditions can be set by changing these values. The hour angle is the angular distance + along the celestial equator from the observer's meridian to the hour circle passing through the target. + By default, the score varies linearly with the hour angle zeroing at the minimum and maximum hour angles + and peaking at the meridian, however, this behavior can be changed by setting the `score_type` parameter. + + Parameters + ---------- + min_hour_angle : `~astropy.units.Quantity`, default : -6*u.hourangle + The minimum hour angle at which the target can be observed. The default is -6 hours (i.e., 6 hours + times 15 degrees per hour = 90 degrees east of the meridian, or the horizon in the east). + + max_hour_angle : `~astropy.units.Quantity`, default : 6*u.hourangle + The maximum hour angle at which the target can be observed. The default is 6 hours (i.e., 6 hours + times 15 degrees per hour = 90 degrees west of the meridian, or the horizon in the west). + + score_type : `str`, default : "linear", {"linear", "boolean"} + The type of scoring function to use. The options are "linear" for a linear function, commonly used + for optimizing the observing time, and "boolean" for a binary function that returns 1 if the condition + is met and 0 if it is not. The default is "linear". + + """ + logger.debug( + """HourAngleCondition( + min_hour_angle=%s, + max_hour_angle=%s, + score_type=%s + )""" + % (min_hour_angle, max_hour_angle, score_type) + ) + + @classmethod + def from_string(cls, string, min_hour_angle=None, max_hour_angle=None): + """ + Create a new `~pyscope.telrun.HourAngleCondition` or a `list` of `~pyscope.telrun.HourAngleCondition` + objects from a `str`. Any optional parameters are used to override the parameters extracted from the `str`. + + Parameters + ---------- + string : `str`, required + + min_hour_angle : `~astropy.units.Quantity`, default : None + + max_hour_angle : `~astropy.units.Quantity`, default : None + + Returns + ------- + `~pyscope.telrun.HourAngleCondition` or `list` of `~pyscope.telrun.HourAngleCondition` + + """ + logger.debug( + "HourAngleCondition.from_string(string=%s, min_hour_angle=%s, max_hour_angle=%s)" + % (string, min_hour_angle, max_hour_angle) + ) + + def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.HourAngleCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.HourAngleCondition`. + + """ + logger.debug("HourAngleCondition().__str__()") + + def __repr__(self): + """ + Return a `str` representation of the `~pyscope.telrun.HourAngleCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.HourAngleCondition`. + """ + logger.debug("HourAngleCondition().__repr__()") + return str(self) + + def __call__(self, time, location, target): + """ + Compute the score for the hour angle condition. + + Parameters + ---------- + time : `~astropy.time.Time`, required + The time at which the observation is to be made. + + location : `~astropy.coordinates.EarthLocation`, required + The location of the observer. + + target : `~astropy.coordinates.SkyCoord`, required + The target to evaluate the condition for. + + Returns + ------- + `float` + The score for the hour angle condition from `0` to `1`. + + """ + logger.debug( + "HourAngleCondition().__call__(time=%s, location=%s, target=%s)" + % (time, location, target) + ) + + def plot(self, time, location, target=None, ax=None): + """ """ + pass diff --git a/pyscope/telrun/moon_condition.py b/pyscope/telrun/moon_condition.py new file mode 100644 index 00000000..9957d648 --- /dev/null +++ b/pyscope/telrun/moon_condition.py @@ -0,0 +1,36 @@ +import logging + +from astropy import units as u + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class MoonCondition(BoundaryCondition): + def __init__( + self, + min_sep=0 * u.deg, + max_sep=180 * u.deg, + min_alt=-90 * u.deg, + max_alt=90 * u.deg, + min_illum=0, + max_illum=1, + ): + """ """ + pass + + def __str__(self): + pass + + def __repr__(self): + logger.debug("MoonCondition().__repr__()") + return str(self) + + def __call__(self, time=None, location=None, target=None): + """ """ + pass + + def plot(self, time, location, target=None, ax=None): + """ """ + pass diff --git a/pyscope/telrun/snr_condition.py b/pyscope/telrun/snr_condition.py new file mode 100644 index 00000000..48b9e31f --- /dev/null +++ b/pyscope/telrun/snr_condition.py @@ -0,0 +1,9 @@ +import logging + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class SNRCondition(BoundaryCondition): + pass diff --git a/pyscope/telrun/sun_condition.py b/pyscope/telrun/sun_condition.py new file mode 100644 index 00000000..f70deb3f --- /dev/null +++ b/pyscope/telrun/sun_condition.py @@ -0,0 +1,37 @@ +import logging + +from astropy import units as u + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class SunCondition(BoundaryCondition): + def __init__( + self, + min_sep=0 * u.deg, + max_sep=180 * u.deg, + min_alt=-90 * u.deg, + max_alt=90 * u.deg, + ): + """ """ + pass + + def from_string(self, string): + pass + + def __str__(self): + pass + + def __repr__(self): + logger.debug("SunCondition().__repr__()") + return str(self) + + def __call__(self, time=None, location=None, target=None): + """ """ + pass + + def plot(self, time, location, target=None, ax=None): + """ """ + pass diff --git a/pyscope/telrun/time_condition.py b/pyscope/telrun/time_condition.py new file mode 100644 index 00000000..77a51e5a --- /dev/null +++ b/pyscope/telrun/time_condition.py @@ -0,0 +1,9 @@ +import logging + +from .boundary_condition import BoundaryCondition + +logger = logging.getLogger(__name__) + + +class TimeCondition(BoundaryCondition): + pass From 6887f0fa2978f16c680f0faabf472e5eeea8b9b6 Mon Sep 17 00:00:00 2001 From: WGolay Date: Tue, 8 Oct 2024 12:26:11 -0400 Subject: [PATCH 22/30] Boundary conditions docs updates --- pyscope/telrun/airmass_condition.py | 136 ++++++++++----------------- pyscope/telrun/boundary_condition.py | 11 ++- pyscope/telrun/coord_condition.py | 77 ++++++++------- 3 files changed, 98 insertions(+), 126 deletions(-) diff --git a/pyscope/telrun/airmass_condition.py b/pyscope/telrun/airmass_condition.py index 77067361..c0b7e53f 100644 --- a/pyscope/telrun/airmass_condition.py +++ b/pyscope/telrun/airmass_condition.py @@ -6,16 +6,16 @@ class AirmassCondition(BoundaryCondition): - def __init__(self, airmass_limit=3, formula="secant", weight=1): + def __init__(self, airmass_limit=3, formula="Schoenberg1929", weight=1, **kwargs): """ A condition that penalizes targets for higher airmass values up to a limit. This condition is used to restrict the airmass value of a target to a maximum value. The airmass - can be calculated using several different formulae. The default is a simple secant formula, + can be calculated using several different formulae. The most simple is a secant formula, given by :math:`X = \\frac{1}{\\cos(z)}` where :math:`z` is the angle between the target and the zenith. This is the analytic solution for a plane-parallel atmosphere. - Other options include Schoenberg 1929[1]_, which is a geometric model for a non-refracting spherical atmosphere: + The default option is Schoenberg 1929[1]_, a geometric model for a non-refracting spherical atmosphere: .. math:: X = \\frac{R_{\\oplus}}{y_{\\rm atm}}\\sqrt{\\cos^2(z) + 2\\frac{y_{\\rm atm}}{R_{\\oplus}} + \\left(\\frac{y_{\\rm atm}}{R_{\\oplus}}\\right)^2} - \\frac{R_{\\oplus}}{y_{\\rm atm}}\\cos(z) @@ -43,7 +43,7 @@ def __init__(self, airmass_limit=3, formula="secant", weight=1): .. image:: https://upload.wikimedia.org/wikipedia/commons/d/d3/Viewing_angle_and_air_masses.svg - The airmass is penalized linearly from `1` with the best linear quality score and decreases to `0` at the `airmass_limit`. + The airmass is penalized linearly from `1` with the best linear quality score and decreases to `0` at the `airmass_limit` and beyond. Parameters ---------- @@ -56,6 +56,9 @@ def __init__(self, airmass_limit=3, formula="secant", weight=1): weight : float, default : 1 The weight of the condition in the final score. The default is 1. + **kwargs : dict, default : {} + Additional keyword arguments to pass to the `~pyscope.telrun.BoundaryCondition` constructor for storage in the `~pyscope.telrun.BoundaryCondition.kwargs` attribute. + References ---------- .. [1] `Schoenberg, E. 1929. Theoretische Photometrie, Ãœber die Extinktion des Lichtes in der Erdatmosphäre. In Handbuch der Astrophysik. Band II, erste Hälfte. Berlin: Springer. `_ @@ -74,75 +77,82 @@ def __init__(self, airmass_limit=3, formula="secant", weight=1): """ logger.debug("AirmassCondition(airmass_limit=%s, formula=%s, weight=%s)") - super().__init__( - func=self.calculate, - lqs_func=self.score, - weight=weight, - airmass_limit=airmass_limit, - formula=formula, - ) - self.airmass_limit = airmass_limit - self.formula = formula + super().__init__(func=self._func, lqs_func=self._lqs_func, **kwargs) @classmethod - def from_string(self, string): - logger.debug("AirmassCondition.from_string(string=%s)" % string) - pass + def from_string(self, string, airmass_limit=None, formula=None, weight=None): + """ + Create a `~pyscope.telrun.AirmassCondition` or a `list` of `~pyscope.telrun.AirmassCondition` objects from a `str` representation of a `~pyscope.telrun.AirmassCondition`. + Any optional parameters are used to override the parameters extracted from the `str` representation. + + Parameters + ---------- + string : `str`, required + + airmass_limit : `float`, default : None + + formula : `str`, default : None + + weight : `float`, default : None + + """ + logger.debug( + "AirmassCondition.from_string(string=%s, airmass_limit=%s, formula=%s, weight=%s)" + % (string, airmass_limit, formula, weight) + ) def __str__(self): + """ + Return a `str` representation of the `~pyscope.telrun.AirmassCondition`. + + Returns + ------- + `str` + A `str` representation of the `~pyscope.telrun.AirmassCondition`. + + """ logger.debug("AirmassCondition().__str__()") - pass - def calculate(self, target, time, location, **kwargs): + @staticmethod + def _func(target, time, location, formula="Schoenberg1929", **kwargs): """ - Compute the airmass value for the target at the given time and location. + Calculate the airmass value for the target. Parameters ---------- target : `~astropy.coordinates.SkyCoord`, required - The target to evaluate the condition for. - - time : `~astropy.time.Time`, required - The time to evaluate the condition at. - - location : `~astropy.coordinates.EarthLocation`, required - The location to evaluate the condition at. - - formula : `str`, default : "secant", {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} - The formula to use to calculate the airmass value. + The target to calculate the airmass value for. Returns ------- `float` - The airmass value for the target at the given time and location. + The airmass value for the target. """ - logger.debug( - "AirmassCondition().calculate(target=%s, time=%s, location=%s, kwargs=%s)" - % (target, time, location, kwargs) - ) - pass + logger.debug("AirmassCondition._func(target=%s)" % target) - def score(self, value, **kwargs): + @staticmethod + def _lqs_func(self, value, airmass_limit=3, **kwargs): """ - Compute the score for the airmass condition. + Calculate the linear quality score for the airmass value. Parameters ---------- value : `float`, required - The airmass value to score. + The airmass value for the target. - airmass_limit : `float`, optional + airmass_limit : `float`, default : 3 The maximum airmass value that is allowed. Returns ------- `float` - The linear quality score value (between `0` and `1`) for the airmass condition. + The linear quality score for the airmass value. """ - logger.debug("AirmassCondition().score(value=%s, kwargs=%s)" % (value, kwargs)) - pass + logger.debug( + "AirmassCondition._lqs_func(value=%s, max_val=%s)" % (value, max_val) + ) @property def airmass_limit(self): @@ -158,23 +168,6 @@ def airmass_limit(self): logger.debug("AirmassCondition().airmass_limit == %s" % self._airmass_limit) return self._airmass_limit - @airmass_limit.setter - def airmass_limit(self, value): - """ - The maximum airmass value that is allowed. - - Parameters - ---------- - value : `float`, required - The maximum airmass value that is allowed - - """ - logger.debug("AirmassCondition().airmass_limit = %s" % value) - if value is not None: - if value <= 1: - raise ValueError("Airmass limit must be greater than 1") - self._airmass_limit = value - @property def formula(self): """ @@ -188,28 +181,3 @@ def formula(self): """ logger.debug("AirmassCondition().formula == %s" % self._formula) return self._formula - - @formula.setter - def formula(self, value): - """ - The formula to use to calculate the airmass value. - - Parameters - ---------- - value : `str`, required, {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} - The formula to use to calculate the airmass value. - - """ - logger.debug("AirmassCondition().formula = %s" % value) - if value not in [ - "secant", - "Schoenberg1929", - "Young+Irvine1967", - "Hardie1962", - "Rozenberg1966", - "KastenYoung1989", - "Young1994", - "Pickering2002", - ]: - raise ValueError("Invalid formula for airmass condition") - self._formula = value diff --git a/pyscope/telrun/boundary_condition.py b/pyscope/telrun/boundary_condition.py index 6c945f0e..5f3efb42 100644 --- a/pyscope/telrun/boundary_condition.py +++ b/pyscope/telrun/boundary_condition.py @@ -177,12 +177,19 @@ def __call__(self, target, time, location, **kwargs): return value - def calculate(target, time, location, **kwargs): + def calculate(self, target, time, location, **kwargs): + if kwargs is None: + kwargs = self._kwargs return self._func(target, time, location, **kwargs) - def score(value, **kwargs): + def score(self, value, **kwargs): + if kwargs is None: + kwargs = self._kwargs return self._lqs_func(value, **kwargs) + def plot(self, target, time, location, **kwargs): + pass + @property def weight(self): """ diff --git a/pyscope/telrun/coord_condition.py b/pyscope/telrun/coord_condition.py index 7f419aca..7668c675 100644 --- a/pyscope/telrun/coord_condition.py +++ b/pyscope/telrun/coord_condition.py @@ -13,13 +13,14 @@ def __init__( min_val=None, max_val=None, score_type="boolean", + ref_coord=None, **kwargs, ): """ A restriction on the coordinates of the target viewed from a specific location and at a specific time. - A manager to hande restrictions of a source's location when it is observered. The + A manager to handle restrictions of a source's location when it is observered. The coordinates can be specified in right ascension and declination, galactic latitude and longitude, or altitude and azimuth. @@ -30,7 +31,7 @@ def __init__( and azimuth, "radec" for right ascension and declination, and "galactic" for galactic latitude and longitude. The default is "altaz". - coord_idx : int, default : 0, {0, 1} + coord_idx : `int`, default : 0, {0, 1} The index of the coordinate to use. The default is 0 for the first coordinate. This parameter is ignored if `min_val` and `max_val` both contain two values for the minimum and maximum values of each coordinate. @@ -47,12 +48,13 @@ def __init__( that returns 1 if the condition is met and 0 if it is not, commonly used for determining if the source is above or below the horizon. The default is "boolean". - **kwargs : Additional keyword arguments to pass to the scoring function. + ref_coord : `~astropy.coordinates.SkyCoord`, default : `None` + If `coord_type` is "radec" or "galactic", a user can specify a center value + and `min_val` and `max_val` will be interpreted as a minimum and maximum angular + separation of the target from the reference coordinate. - ref_coord : `~astropy.coordinates.SkyCoord`, default : `None` - If `coord_type` is "radec" or "galactic", a user can specify a center value - and `min_val` and `max_val` will be interpreted as a minimum and maximum angular - separation of the target from the reference coordinate. + **kwargs : `dict`, default : {} + Additional keyword arguments to pass to the `~pyscope.telrun.BoundaryCondition` constructor. """ logger.debug( @@ -63,11 +65,13 @@ def __init__( min_val=%s, max_val=%s, score_type=%s, + ref_coord=%s, kwargs=%s )""" - % (coord_type, coord_idx, min_val, max_val, score_type, kwargs) + % (coord_type, coord_idx, min_val, max_val, score_type, ref_coord, kwargs) ) + @classmethod def from_string( self, string, @@ -76,6 +80,7 @@ def from_string( min_val=None, max_val=None, score_type=None, + ref_coord=None, **kwargs, ): """ @@ -96,6 +101,8 @@ def from_string( score_type : `str`, default : None + ref_coord : `~astropy.coordinates.SkyCoord`, default : None + **kwargs : `dict`, default : {} """ @@ -116,44 +123,34 @@ def __str__(self): """ logger.debug("CoordinateCondition().__str__()") - def __repr__(self): - """ - Return a `str` representation of the `~pyscope.telrun.CoordinateCondition`. - - Returns - ------- - `str` - A `str` representation of the `~pyscope.telrun.CoordinateCondition`. - - """ - logger.debug("CoordinateCondition().__repr__()") - return str(self) + @staticmethod + def _func(): + pass - def __call__(self, target, time=None, location=None): - """ - Evaluate the condition for the target at the specified time and location. + @staticmethod + def _lqs_func(): + pass - Parameters - ---------- - target : `~astropy.coordinates.SkyCoord`, required - The target to evaluate the condition for. + @property + def coord_type(self): + pass - time : `~astropy.time.Time`, default : None - The time to evaluate the condition at. Some conditions do not depend on time, - but others that do will require this parameter. + @property + def coord_idx(self): + pass - location : `~astropy.coordinates.EarthLocation`, default : None - The location to evaluate the condition at. Some conditions do not depend on - location, but others that do will require this parameter. + @property + def min_val(self): + pass - Returns - ------- - `float` - A `float` value between `0` and `1` that represents the linear quality score of the condition. + @property + def max_val(self): + pass - """ + @property + def score_type(self): pass - def plot(self, target, time, location, ax=None): - """ """ + @property + def ref_coord(self): pass From 07d83a68a406f72c64f69d35f6a9c56a6e01582e Mon Sep 17 00:00:00 2001 From: ccolin Date: Mon, 28 Oct 2024 21:49:18 -0500 Subject: [PATCH 23/30] Docs for observatory summary and all of ascom_camera --- pyscope/observatory/__init__.py | 8 + pyscope/observatory/ascom_camera.py | 298 ++++++++++++++++++++++++++++ 2 files changed, 306 insertions(+) diff --git a/pyscope/observatory/__init__.py b/pyscope/observatory/__init__.py index b35554a1..b46b02e4 100644 --- a/pyscope/observatory/__init__.py +++ b/pyscope/observatory/__init__.py @@ -1,3 +1,11 @@ +""" +The `observatory` module provides classes and functions for controlling and automating +observatory hardware and operations. This currently includes support only for the +`Robert L. Mutel Telescope` at the University of Iowa, but is designed to be extensible +to all motorized observatory telescopes in the future. +Operations include managing telescopes, cameras, focuser, filter wheels, domes, and other +devices commonly found in an observatory setup. +""" # isort: skip_file import logging diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 15a98cd7..4af4bc83 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -11,6 +11,20 @@ class ASCOMCamera(ASCOMDevice, Camera): def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): + """ + Provides the `ASCOMCamera` class for controlling `ASCOM `_-compatible cameras. + + Parameters + ---------- + identifier : `str` + The device identifier. This can be the ProgID or the device number. + alpaca : `bool`, default : `False`, optional + Whether to use the Alpaca protocol for Alpaca-compatible devices. + device_number : `int`, default : 0, optional + The device number. This is only used if the identifier is a ProgID. + protocol : `str`, default : `http`, optional + The protocol to use for Alpaca-compatible devices. + """ super().__init__( identifier, alpaca=alpaca, @@ -25,6 +39,18 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): self._camera_time = True def AbortExposure(self): + """ + Abort the current exposure immediately and return camera to idle. + See `CanAbortExposure` for support and possible reasons to abort. + + Parameters + ---------- + None + + Returns + ------- + None + """ logger.debug(f"ASCOMCamera.AbortExposure() called") self._device.AbortExposure() @@ -59,22 +85,87 @@ def SetImageDataType(self): self._image_data_type = np.uint16 def PulseGuide(self, Direction, Duration): + """ + Moves scope in the given direction for the given interval or time at the rate + given by the :py:meth:`ASCOMTelescope.GuideRateRightAscension` and :py:meth:`ASCOMTelescope.GuideRateDeclination` properties. + + Parameters + ---------- + Direction : `GuideDirections `_ + The direction in which to move the scope. + The corresponding values are as follows: + + * 0 : North or +declination. + * 1 : South or -declination. + * 2 : East or +right ascension. + * 3 : West or -right ascension. + + Duration : `int` + The duration of the guide pulse in milliseconds. + + Returns + ------- + None + + Notes + ----- + Method returns immediately if hardware supports back-to-back guiding e.g. dual-axis moving. + Otherwise returns only after the guide pulse has completed. + """ logger.debug(f"ASCOMCamera.PulseGuide({Direction}, {Duration}) called") self._device.PulseGuide(Direction, Duration) def StartExposure(self, Duration, Light): + """ + Starts an exposure with a given duration and light status. Check `ImageReady` for operation completion. + + Parameters + ---------- + Duration : `float` + The exposure duration in seconds. Can be zero if `Light` is `False`. + + Light : `bool` + Whether the exposure is a light frame (`True`) or a dark frame (`False`). + + Returns + ------- + None + + Notes + ----- + `Duration` can be shorter than `ExposureMin` if used for dark frame or bias exposure. + Bias frame also allows a `Duration` of zero. + """ logger.debug(f"ASCOMCamera.StartExposure({Duration}, {Light}) called") self._last_exposure_duration = Duration self._last_exposure_start_time = str(Time.now()) self._device.StartExposure(Duration, Light) def StopExposure(self): + """ + Stops the current exposure gracefully. + + Parameters + ---------- + None + + Returns + ------- + None + + Notes + ----- + Readout process will initiate if stop is called during an exposure. + Ignored if readout is already in process. + """ logger.debug(f"ASCOMCamera.StopExposure() called") self._device.StopExposure() @property def BayerOffsetX(self): # pragma: no cover """ + The X/column offset of the Bayer filter array matrix. + .. warning:: This property is not implemented in the ASCOM Alpaca protocol. """ @@ -84,6 +175,8 @@ def BayerOffsetX(self): # pragma: no cover @property def BayerOffsetY(self): # pragma: no cover """ + The Y/row offset of the Bayer filter array matrix. (`int`) + .. warning:: This property is not implemented in the ASCOM Alpaca protocol. """ @@ -92,6 +185,11 @@ def BayerOffsetY(self): # pragma: no cover @property def BinX(self): + """ + The binning factor in the X/column direction. (`int`) + + Default is 1 after camera connection is established. + """ logger.debug(f"ASCOMCamera.BinX property called") return self._device.BinX @@ -102,6 +200,11 @@ def BinX(self, value): @property def BinY(self): + """ + The binning factor in the Y/row direction. (`int`) + + Default is 1 after camera connection is established. + """ logger.debug(f"ASCOMCamera.BinY property called") return self._device.BinY @@ -112,66 +215,115 @@ def BinY(self, value): @property def CameraState(self): + """ + The current operational state of the camera. (`CameraStates `_) + + Can be interpreted as `int` values in the following logic: + + * 0 : Camera is idle and ready to start an exposure. + * 1 : Camera exposure started but waiting (i.e. shutter, trigger, filter wheel, etc.). + * 2 : Camera exposure in progress. + * 3 : CCD readout in progress. + * 4 : Data downloading to PC. + * 5 : Camera in error condition, cannot continue. + """ logger.debug(f"ASCOMCamera.CameraState property called") return self._device.CameraState @property def CameraXSize(self): + """The width of the CCD chip in unbinned pixels. (`int`)""" logger.debug(f"ASCOMCamera.CameraXSize property called") return self._device.CameraXSize @property def CameraYSize(self): + """The height of the CCD chip in unbinned pixels. (`int`)""" logger.debug(f"ASCOMCamera.CameraYSize property called") return self._device.CameraYSize @property def CameraTime(self): + """Whether the camera supports the CameraTime property. (`bool`)""" logger.debug(f"ASCOMCamera.CameraTime property called") return self._camera_time @property def CanAbortExposure(self): + """ + Whether the camera can abort exposures imminently. (`bool`) + + Aborting is not synonymous with stopping an exposure. + Aborting immediately stops the exposure and discards the data. + Used for urgent situations such as errors or temperature concerns. + See `CanStopExposure` for gracious cancellation of an exposure. + """ logger.debug(f"ASCOMCamera.CanAbortExposure property called") return self._device.CanAbortExposure @property def CanAsymmetricBin(self): + """ + Whether the camera supports asymmetric binning such that + `BinX` != `BinY`. (`bool`) + """ logger.debug(f"ASCOMCamera.CanAsymmetricBin property called") return self._device.CanAsymmetricBin @property def CanFastReadout(self): + """Whether the camera supports fast readout mode. (`bool`)""" logger.debug(f"ASCOMCamera.CanFastReadout property called") return self._device.CanFastReadout @property def CanGetCoolerPower(self): + """Whether the camera's cooler power setting can be read. (`bool`)""" logger.debug(f"ASCOMCamera.CanGetCoolerPower property called") return self._device.CanGetCoolerPower @property def CanPulseGuide(self): + """ + Whether the camera supports pulse guiding, + see `definition `_. (`bool`) + """ logger.debug(f"ASCOMCamera.CanPulseGuide property called") return self._device.CanPulseGuide @property def CanSetCCDTemperature(self): + """ + Whether the camera's CCD temperature can be set. (`bool`) + + A false means either the camera uses an open-loop cooling system or + does not support adjusting the CCD temperature from software. + """ logger.debug(f"ASCOMCamera.CanSetCCDTemperature property called") return self._device.CanSetCCDTemperature @property def CanStopExposure(self): + """ + Whether the camera can stop exposures graciously. (`bool`) + + Stopping is not synonymous with aborting an exposure. + Stopping allows the camera to complete the current exposure cycle, then stop. + Image data up to the point of stopping is typically still available. + See `CanAbortExposure` for instant cancellation of an exposure. + """ logger.debug(f"ASCOMCamera.CanStopExposure property called") return self._device.CanStopExposure @property def CCDTemperature(self): + """The current CCD temperature in degrees Celsius. (`float`)""" logger.debug(f"ASCOMCamera.CCDTemperature property called") return self._device.CCDTemperature @property def CoolerOn(self): + """Whether the camera's cooler is on. (`bool`)""" logger.debug(f"ASCOMCamera.CoolerOn property called") return self._device.CoolerOn @@ -182,31 +334,53 @@ def CoolerOn(self, value): @property def CoolerPower(self): + """The current cooler power level as a percentage. (`float`)""" logger.debug(f"ASCOMCamera.CoolerPower property called") return self._device.CoolerPower @property def ElectronsPerADU(self): + """Gain of the camera in photoelectrons per analog-to-digital-unit. (`float`)""" logger.debug(f"ASCOMCamera.ElectronsPerADU() property called") return self._device.ElectronsPerADU @property def ExposureMax(self): + """The maximum exposure duration supported by `StartExposure` in seconds. (`float`)""" logger.debug(f"ASCOMCamera.ExposureMax property called") return self._device.ExposureMax @property def ExposureMin(self): + """ + The minimum exposure duration supported by `StartExposure` in seconds. (`float`) + + Non-zero number, except for bias frame acquisition, where an exposure < ExposureMin + may be possible. + """ logger.debug(f"ASCOMCamera.ExposureMin property called") return self._device.ExposureMin @property def ExposureResolution(self): + """ + The smallest increment in exposure duration supported by `StartExposure`. (`float`) + + This property could be useful if one wants to implement a 'spin control' interface + for fine-tuning exposure durations. + + Providing a `Duration` to `StartExposure` that is not a multiple of `ExposureResolution` + will choose the closest available value. + + A value of 0.0 indicates no minimum resolution increment, except that imposed by the + floating-point precision of `float` itself. + """ logger.debug(f"ASCOMCamera.ExposureResolution property called") return self._device.ExposureResolution @property def FastReadout(self): + """Whether the camera is in fast readout mode. (`bool`)""" logger.debug(f"ASCOMCamera.FastReadout property called") return self._device.FastReadout @@ -217,11 +391,30 @@ def FastReadout(self, value): @property def FullWellCapacity(self): + """ + The full well capacity of the camera in electrons with the + current camera settings. (`float`) + """ logger.debug(f"ASCOMCamera.FullWellCapacity property called") return self._device.FullWellCapacity @property def Gain(self): + """ + The camera's gain OR index of the selected camera gain description. + See below for more information. (`int`) + + Represents either the camera's gain in photoelectrons per analog-to-digital-unit, + or the 0-index of the selected camera gain description in the `Gains` array. + + Depending on a camera's capabilities, the driver can support none, one, or both + representation modes, but only one mode will be active at a time. + + To determine operational mode, read the `GainMin`, `GainMax`, and `Gains` properties. + + `ReadoutMode` may affect the gain of the camera, so it is recommended to set + driver behavior to ensure no conflictions occur if both `Gain` and `ReadoutMode` are used. + """ logger.debug(f"ASCOMCamera.Gain property called") return self._device.Gain @@ -232,26 +425,44 @@ def Gain(self, value): @property def GainMax(self): + """The maximum gain value supported by the camera. (`int`)""" logger.debug(f"ASCOMCamera.GainMax property called") return self._device.GainMax @property def GainMin(self): + """The minimum gain value supported by the camera. (`int`)""" logger.debug(f"ASCOMCamera.GainMin property called") return self._device.GainMin @property def Gains(self): + """ + 0-indexed array of camera gain descriptions supported by the camera. (`list` of `str`) + + Depending on implementation, the array may contain ISOs, or gain names. + """ logger.debug(f"ASCOMCamera.Gains property called") return self._device.Gains @property def HasShutter(self): + """ + Whether the camera has a mechanical shutter. (`bool`) + + If `False`, i.e. the camera has no mechanical shutter, the `StartExposure` + method will ignore the `Light` parameter. + """ logger.debug(f"ASCOMCamera.HasShutter property called") return self._device.HasShutter @property def HeatSinkTemperature(self): + """ + The current heat sink temperature in degrees Celsius. (`float`) + + The readout is only valid if `CanSetCCDTemperature` is `True`. + """ logger.debug(f"ASCOMCamera.HeatSinkTemperature property called") return self._device.HeatSinkTemperature @@ -304,16 +515,27 @@ def ImageArray(self): @property def ImageReady(self): + """ + Whether the camera has completed an exposure and the image is ready to be downloaded. (`bool`) + + If `False`, the `ImageArray` property will exit with an exception. + """ logger.debug(f"ASCOMCamera.ImageReady property called") return self._device.ImageReady @property def IsPulseGuiding(self): + """Whether the camera is currently pulse guiding. (`bool`)""" logger.debug(f"ASCOMCamera.IsPulseGuiding property called") return self._device.IsPulseGuiding @property def LastExposureDuration(self): + """ + The duration of the last exposure in seconds. (`float`) + + May differ from requested exposure time due to shutter latency, camera timing accuracy, etc. + """ logger.debug(f"ASCOMCamera.LastExposureDuration property called") last_exposure_duration = self._device.LastExposureDuration if last_exposure_duration is None or last_exposure_duration == 0: @@ -323,6 +545,11 @@ def LastExposureDuration(self): @property def LastExposureStartTime(self): + """ + The actual last exposure start time in FITS CCYY-MM-DDThh:mm:ss[.sss...] format. (`str`) + + The date string represents UTC time. + """ logger.debug(f"ASCOMCamera.LastExposureStartTime property called") last_time = self._device.LastExposureStartTime """ This code is needed to handle the case of the ASCOM ZWO driver @@ -346,21 +573,33 @@ def LastInputExposureDuration(self, value): @property def MaxADU(self): + """The maximum ADU value the camera is capable of producing. (`int`)""" logger.debug(f"ASCOMCamera.MaxADU property called") return self._device.MaxADU @property def MaxBinX(self): + """ + The maximum allowed binning factor in the X/column direction. (`int`) + + Value equivalent to `MaxBinY` if `CanAsymmetricBin` is `False`. + """ logger.debug(f"ASCOMCamera.MaxBinX property called") return self._device.MaxBinX @property def MaxBinY(self): + """ + The maximum allowed binning factor in the Y/row direction. (`int`) + + Value equivalent to `MaxBinX` if `CanAsymmetricBin` is `False`. + """ logger.debug(f"ASCOMCamera.MaxBinY property called") return self._device.MaxBinY @property def NumX(self): + """The width of the subframe in binned pixels. (`int`)""" logger.debug(f"ASCOMCamera.NumX property called") return self._device.NumX @@ -371,6 +610,7 @@ def NumX(self, value): @property def NumY(self): + """The height of the subframe in binned pixels. (`int`)""" logger.debug(f"ASCOMCamera.NumY property called") return self._device.NumY @@ -381,6 +621,21 @@ def NumY(self, value): @property def Offset(self): + """ + The camera's offset OR index of the selected camera offset description. + See below for more information. (`int`) + + Represents either the camera's offset, or the 0-index of the selected + camera offset description in the `Offsets` array. + + Depending on a camera's capabilities, the driver can support none, one, or both + representation modes, but only one mode will be active at a time. + + To determine operational mode, read the `OffsetMin`, `OffsetMax`, and `Offsets` properties. + + `ReadoutMode` may affect the gain of the camera, so it is recommended to set + driver behavior to ensure no conflictions occur if both `Gain` and `ReadoutMode` are used. + """ logger.debug(f"ASCOMCamera.Offset property called") return self._device.Offset @@ -391,36 +646,52 @@ def Offset(self, value): @property def OffsetMax(self): + """The maximum offset value supported by the camera. (`int`)""" logger.debug(f"ASCOMCamera.OffsetMax property called") return self._device.OffsetMax @property def OffsetMin(self): + """The minimum offset value supported by the camera. (`int`)""" logger.debug(f"ASCOMCamera.OffsetMin property called") return self._device.OffsetMin @property def Offsets(self): + """The array of camera offset descriptions supported by the camera. (`list` of `str`)""" logger.debug(f"ASCOMCamera.Offsets property called") return self._device.Offsets @property def PercentCompleted(self): + """ + The percentage of completion of the current operation. (`int`) + + As opposed to `CoolerPower`, this is represented as an integer + s.t. 0 <= PercentCompleted <= 100 instead of float. + """ logger.debug(f"ASCOMCamera.PercentCompleted property called") return self._device.PercentCompleted @property def PixelSizeX(self): + """The width of the CCD chip pixels in microns. (`float`)""" logger.debug(f"ASCOMCamera.PixelSizeX property called") return self._device.PixelSizeX @property def PixelSizeY(self): + """The height of the CCD chip pixels in microns. (`float`)""" logger.debug(f"ASCOMCamera.PixelSizeY property called") return self._device.PixelSizeY @property def ReadoutMode(self): + """ + Current readout mode of the camera as an index. (`int`) + + The index corresponds to the `ReadoutModes` array. + """ logger.debug(f"ASCOMCamera.ReadoutMode property called") return self._device.ReadoutMode @@ -431,21 +702,45 @@ def ReadoutMode(self, value): @property def ReadoutModes(self): + """The array of camera readout mode descriptions supported by the camera. (`list` of `str`)""" logger.debug(f"ASCOMCamera.ReadoutModes property called") return self._device.ReadoutModes @property def SensorName(self): + """ + The name of the sensor in the camera. (`str`) + + The name is the manufacturer's data sheet part number. + """ logger.debug(f"ASCOMCamera.SensorName property called") return self._device.SensorName @property def SensorType(self): + """ + The type of color information the camera sensor captures. (`SensorType `_) + + The default representations are as follows: + + * 0 : Monochrome with no Bayer encoding. + * 1 : Color without needing Bayer decoding. + * 2 : RGGB encoded Bayer array. + * 3 : CMYG encoded Bayer array. + * 4 : CMYG2 encoded Bayer array. + * 5 : LRGB Kodak TRUESENSE encoded Bayer array. + """ logger.debug(f"ASCOMCamera.SensorType property called") return self._device.SensorType @property def SetCCDTemperature(self): + """ + The set-target CCD temperature in degrees Celsius. (`float`) + + Contrary to `CCDTemperature`, which is the current CCD temperature, + this property is the target temperature for the cooler to reach. + """ logger.debug(f"ASCOMCamera.SetCCDTemperature property called") return self._device.SetCCDTemperature @@ -456,6 +751,7 @@ def SetCCDTemperature(self, value): @property def StartX(self): + """The set X/column position of the start subframe in binned pixels. (`int`)""" logger.debug(f"ASCOMCamera.StartX property called") return self._device.StartX @@ -466,6 +762,7 @@ def StartX(self, value): @property def StartY(self): + """The set Y/row position of the start subframe in binned pixels. (`int`)""" logger.debug(f"ASCOMCamera.StartY property called") return self._device.StartY @@ -476,6 +773,7 @@ def StartY(self, value): @property def SubExposureDuration(self): + """The duration of the subframe exposure interval in seconds. (`float`)""" logger.debug(f"ASCOMCamera.SubExposureDuration property called") return self._device.SubExposureDuration From 1b982f9f4a5751c28fb8bb749f041d0e484047fc Mon Sep 17 00:00:00 2001 From: Colin Nguyen <94422608+LLBlanc@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:15:45 -0500 Subject: [PATCH 24/30] Update ascom_camera.py Missed data type flag Signed-off-by: Colin Nguyen <94422608+LLBlanc@users.noreply.github.com> --- pyscope/observatory/ascom_camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 4af4bc83..25f6806a 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -164,7 +164,7 @@ def StopExposure(self): @property def BayerOffsetX(self): # pragma: no cover """ - The X/column offset of the Bayer filter array matrix. + The X/column offset of the Bayer filter array matrix. (`int`) .. warning:: This property is not implemented in the ASCOM Alpaca protocol. From 0f2e47ee06018fe0af4202541b4c6694d59cd21d Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 29 Oct 2024 18:56:02 -0500 Subject: [PATCH 25/30] Added tilde to preexisting doc typehints for sphinx cross-references --- .../observatory/collect_calibration_set.py | 34 ++++----- pyscope/observatory/observatory.py | 50 ++++++------- pyscope/reduction/avg_fits.py | 8 +- pyscope/reduction/avg_fits_ccdproc.py | 74 +++++++++---------- pyscope/reduction/maxim_pinpoint_wcs.py | 2 +- pyscope/telrun/airmass_condition.py | 14 ++-- pyscope/telrun/autofocus_field.py | 4 +- pyscope/telrun/boundary_condition.py | 2 +- pyscope/telrun/coord_condition.py | 12 +-- pyscope/telrun/dark_field.py | 8 +- pyscope/telrun/field.py | 6 +- pyscope/telrun/flat_field.py | 8 +- pyscope/telrun/hourangle_condition.py | 4 +- pyscope/telrun/light_field.py | 4 +- pyscope/telrun/mk_mosaic_schedule.py | 30 ++++---- pyscope/telrun/option.py | 18 ++--- pyscope/telrun/rst.py | 10 +-- pyscope/telrun/schedtab.py | 2 +- pyscope/telrun/telrun_operator.py | 6 +- pyscope/telrun/transition_field.py | 4 +- 20 files changed, 150 insertions(+), 150 deletions(-) diff --git a/pyscope/observatory/collect_calibration_set.py b/pyscope/observatory/collect_calibration_set.py index b51eae51..9d9727bf 100644 --- a/pyscope/observatory/collect_calibration_set.py +++ b/pyscope/observatory/collect_calibration_set.py @@ -126,39 +126,39 @@ def collect_calibration_set_cli( Parameters ---------- - observatory : str + observatory : `str` - camera : str, default="ccd" + camera : `str`, default="ccd" - readouts : list, default=[None] + readouts : `list`, default=[`None`] - binnings : list, default=[None] + binnings : `list`, default=[`None`] - repeat : int, default=1 + repeat : `int`, default=1 - dark_exposures : list, default=[] + dark_exposures : `list`, default=[] - filters : list, default=[] + filters : `list`, default=[] - filter_exposures : list, default=[] + filter_exposures : `list`, default=[] - filter_brightness : list, default=None + filter_brightness : `list`, default=`None` - home_telescope : bool, default=False + home_telescope : `bool`, default=`False` - target_counts : int, default=None + target_counts : `int`, default=`None` - check_cooler : bool, default=True + check_cooler : `bool`, default=`True` - tracking : bool, default=True + tracking : `bool`, default=`True` - dither_radius : float, default=0 + dither_radius : `float`, default=0 - save_path : str, default="./temp/" + save_path : `str`, default="./temp/" - new_dir : bool, default=True + new_dir : `bool`, default=`True` - verbose : int, default=0 + verbose : `int`, default=0 """ diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index 34dd3f96..c4add205 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -1894,13 +1894,13 @@ def dither_mount( Parameters ---------- - radius : float, optional (default = 1) + radius : `float`, optional (default = 1) The radius in arcseconds to dither the telescope. center_pos : `~astropy.coordinates.SkyCoord`, optional The center position to dither around. If None, the current pointing of the mount will be used. - seed : int, optional + seed : `int`, optional The seed to use for the random number generator. If None, the seed will be randomly generated. Returns @@ -1946,54 +1946,54 @@ def repositioning( Parameters ---------- - obj : str or `~astropy.coordinates.SkyCoord`, optional + obj : `str` or `~astropy.coordinates.SkyCoord`, optional The name of the target or a `~astropy.coordinates.SkyCoord` object of the target. If a string, a query will be made to the SIMBAD database to get the coordinates. If a `~astropy.coordinates.SkyCoord` - object, the coordinates will be used directly. If None, the ra and dec parameters must be specified. + object, the coordinates will be used directly. If `None`, the ra and dec parameters must be specified. ra : `~astropy.coordinates.Longitude`-like, optional The ICRS J2000 right ascension of the target that will initialize a `~astropy.coordinates.SkyCoord` object. This will override the ra value in the object parameter. dec : `~astropy.coordinates.Latitude`-like, optional The ICRS J2000 declination of the target that will initialize a `~astropy.coordinates.SkyCoord` object. This will override the dec value in the object parameter. - unit : tuple, optional + unit : `tuple`, optional The units of the ra and dec parameters. Default is ('hr', 'deg'). - frame : str, optional + frame : `str`, optional The coordinate frame of the ra and dec parameters. Default is 'icrs'. - target_x_pixel : float, optional + target_x_pixel : `float`, optional The desired x pixel location of the target. - target_y_pixel : float, optional + target_y_pixel : `float`, optional The desired y pixel location of the target. - initial_offset_dec : float, optional + initial_offset_dec : `float`, optional The initial offset of declination in arcseconds to use for the slew. Default is 0. Ignored if - do_initial_slew is False. - check_and_refine : bool, optional + do_initial_slew is `False`. + check_and_refine : `bool`, optional Whether or not to check the offset and refine the slew. Default is True. - max_attempts : int, optional + max_attempts : `int`, optional The maximum number of attempts to make to center the target. Default is 5. Ignored if check_and_refine is False. - tolerance : float, optional + tolerance : `float`, optional The tolerance in pixels to consider the target centered. Default is 3. Ignored if - check_and_refine is False. - exposure : float, optional + check_and_refine is `False`. + exposure : `float`, optional The exposure time in seconds to use for the centering images. Default is 10. - readout : int, optional + readout : `int`, optional The readout mode to use for the centering images. Default is 0. - save_images : bool, optional - Whether or not to save the centering images. Default is False. - save_path : str, optional + save_images : `bool`, optional + Whether or not to save the centering images. Default is `False`. + save_path : `str`, optional The path to save the centering images to. Default is the current directory. Ignored if - save_images is False. - settle_time : float, optional + save_images is `False`. + settle_time : `float`, optional The time in seconds to wait after the slew before checking the offset. Default is 5. - do_initial_slew : bool, optional - Whether or not to do the initial slew to the target. Default is True. If False, the current + do_initial_slew : `bool`, optional + Whether or not to do the initial slew to the target. Default is `True`. If `False`, the current telescope position will be used as the starting point for the centering routine. Returns ------- - success : bool - True if the target was successfully centered, False otherwise. + success : `bool` + `True` if the target was successfully centered, `False` otherwise. """ """logger.info( f"repositioning called with {obj}, {ra}, {dec}, {unit}, {frame}, {target_x_pixel}, {target_y_pixel}, {initial_offset_dec}, check and refine: {check_and_refine}, {max_attempts}, tol: {tolerance}, {exposure}, {readout}, {save_images}, {save_path}, {sync_mount}, {settle_time}, {do_initial_slew}" diff --git a/pyscope/reduction/avg_fits.py b/pyscope/reduction/avg_fits.py index dc81ae4a..d0a2ca35 100644 --- a/pyscope/reduction/avg_fits.py +++ b/pyscope/reduction/avg_fits.py @@ -84,19 +84,19 @@ def avg_fits_cli( fnames : path path of directory of images to average. - pre_normalize : bool, default=False + pre_normalize : `bool`, default=`False` Normalize each image by its own mean before combining. This mode is most useful for combining sky flats. - mode : str, default="0" + mode : `str`, default="0" Mode to use for averaging images (0 = median, 1 = mean). - datatype : str, default="float32" + datatype : `str`, default="float32" Data type to save out the averaged image. If pre_normalize is True, the data type will be float64. outfile : path Path to save averaged image. If not specified, the averaged image will be saved in the same directory as the input images with the first image's name and _avg.fts appended to it. - verbose : int, default=0 + verbose : `int`, default=0 Print verbose output. Returns diff --git a/pyscope/reduction/avg_fits_ccdproc.py b/pyscope/reduction/avg_fits_ccdproc.py index 3f499667..bbb4e76b 100644 --- a/pyscope/reduction/avg_fits_ccdproc.py +++ b/pyscope/reduction/avg_fits_ccdproc.py @@ -230,71 +230,71 @@ def avg_fits_ccdproc_cli( Parameters ---------- - outfile : str + outfile : `str` path to save combined image. - fnames : list + fnames : `list` list of image paths to combine. - method="median" : str, optional + method="median" : `str`, optional method to use for averaging images. Options are "median", "average", "sum" - datatype : np.datatype, optional - intermediate and resulting dtype for combined CCDs, by default np.uint16 + datatype : `numpy.dtype`, optional + intermediate and resulting dtype for combined CCDs, by default `numpy.uint16` - weights : np.ndarray, optional - Weights to be used when combining images. An array with the weight values. The dimensions should match the the dimensions of the data arrays being combined., by default None + weights : `numpy.ndarray`, optional + Weights to be used when combining images. An array with the weight values. The dimensions should match the the dimensions of the data arrays being combined; by default `None` - scale : callable or np.ndarray, optional - Scaling factor to be used when combining images. Images are multiplied by scaling prior to combining them. Scaling may be either a function, which will be applied to each image to determine the scaling factor, or a list or array whose length is the number of images in the Combiner, by default None + scale : `callable` or `numpy.ndarray`, optional + Scaling factor to be used when combining images. Images are multiplied by scaling prior to combining them. Scaling may be either a function, which will be applied to each image to determine the scaling factor, or a `list` or array whose length is the number of images in the Combiner, by default `None` - mem_limit : float, optional + mem_limit : `float`, optional Maximum memory which should be used while combining (in bytes), by default 16000000000.0 - clip_extrema : bool, optional - Set to True if you want to mask pixels using an IRAF-like minmax clipping algorithm. The algorithm will mask the lowest nlow values and the highest nhigh values before combining the values to make up a single pixel in the resulting image. For example, the image will be a combination of Nimages-low-nhigh pixel values instead of the combination of Nimages. Parameters below are valid only when clip_extrema is set to True, by default False + clip_extrema : `bool`, optional + Set to `True` if you want to mask pixels using an IRAF-like minmax clipping algorithm. The algorithm will mask the lowest nlow values and the highest nhigh values before combining the values to make up a single pixel in the resulting image. For example, the image will be a combination of Nimages-low-nhigh pixel values instead of the combination of Nimages. Parameters below are valid only when clip_extrema is set to `True`, by default `False` - nlow : int, optional + nlow : `int`, optional Number of low values to reject from the combination, by default 1 - nhigh : int, optional + nhigh : `int`, optional Number of high values to reject from the combination, by default 1 - minmax_clip : bool, optional - Set to True if you want to mask all pixels that are below minmax_clip_min or above minmax_clip_max before combining, by default False + minmax_clip : `bool`, optional + Set to `True` if you want to mask all pixels that are below minmax_clip_min or above minmax_clip_max before combining, by default `False` - minmax_clip_min : float, optional - All pixels with values below min_clip will be masked, by default None + minmax_clip_min : `float`, optional + All pixels with values below min_clip will be masked, by default `None` - minmax_clip_max : flaot, optional - All pixels with values above min_clip will be masked, by default None + minmax_clip_max : `float`, optional + All pixels with values above min_clip will be masked, by default `None` - sigma_clip : bool, optional - Set to True if you want to reject pixels which have deviations greater than those set by the threshold values. The algorithm will first calculated a baseline value using the function specified in func and deviation based on sigma_clip_dev_func and the input data array. Any pixel with a deviation from the baseline value greater than that set by sigma_clip_high_thresh or lower than that set by sigma_clip_low_thresh will be rejected, by default False + sigma_clip : `bool`, optional + Set to `True` if you want to reject pixels which have deviations greater than those set by the threshold values. The algorithm will first calculated a baseline value using the function specified in func and deviation based on sigma_clip_dev_func and the input data array. Any pixel with a deviation from the baseline value greater than that set by sigma_clip_high_thresh or lower than that set by sigma_clip_low_thresh will be rejected, by default `False` - sigma_clip_low_thresh : int, optional - Threshold for rejecting pixels that deviate below the baseline value. If negative value, then will be convert to a positive value. If None, no rejection will be done based on low_thresh, by default 3 + sigma_clip_low_thresh : `int`, optional + Threshold for rejecting pixels that deviate below the baseline value. If negative value, then will be convert to a positive value. If `None`, no rejection will be done based on low_thresh, by default 3 - sigma_clip_high_thresh : int, optional - Threshold for rejecting pixels that deviate above the baseline value. If None, no rejection will be done based on high_thresh, by default 3 + sigma_clip_high_thresh : `int`, optional + Threshold for rejecting pixels that deviate above the baseline value. If `None`, no rejection will be done based on high_thresh, by default 3 - sigma_clip_func : callable, optional - The statistic or callable function/object used to compute the center value for the clipping. If using a callable function/object and the axis keyword is used, then it must be able to ignore NaNs (e.g., numpy.nanmean) and it must have an axis keyword to return an array with axis dimension(s) removed. The default is 'median', by default None + sigma_clip_func : `callable`, optional + The statistic or callable function/object used to compute the center value for the clipping. If using a callable function/object and the axis keyword is used, then it must be able to ignore NaNs (e.g., `numpy.nanmean`) and it must have an axis keyword to return an array with axis dimension(s) removed. The default is 'median', by default `None` - sigma_clip_dev_func : callable, optional - The statistic or callable function/object used to compute the standard deviation about the center value. If using a callable function/object and the axis keyword is used, then it must be able to ignore NaNs (e.g., numpy.nanstd) and it must have an axis keyword to return an array with axis dimension(s) removed. The default is 'std', by default None + sigma_clip_dev_func : `callable`, optional + The statistic or callable function/object used to compute the standard deviation about the center value. If using a callable function/object and the axis keyword is used, then it must be able to ignore NaNs (e.g., `numpy.nanstd`) and it must have an axis keyword to return an array with axis dimension(s) removed. The default is 'std', by default `None` - combine_uncertainty_function : callable, optional - If None use the default uncertainty func when using average, median or sum combine, otherwise use the function provided, by default None + combine_uncertainty_function : `callable`, optional + If `None` use the default uncertainty func when using average, median or sum combine, otherwise use the function provided, by default `None` - overwrite_output : bool, optional - If output_file is specified, this is passed to the astropy.nddata.fits_ccddata_writer under the keyword overwrite; has no effect otherwise., by default False + overwrite_output : `bool`, optional + If `output_file` is specified, this is passed to the `astropy.nddata.fits_ccddata_writer` under the keyword overwrite; has no effect otherwise., by default `False` - unit : str, optional + unit : `str`, optional unit for CCDData objects, by default 'adu' - verbose : bool, optional - verbosity of logger, by default False + verbose : `bool`, optional + verbosity of logger, by default `False` """ if verbose: diff --git a/pyscope/reduction/maxim_pinpoint_wcs.py b/pyscope/reduction/maxim_pinpoint_wcs.py index cbfcadd1..16be6c7e 100644 --- a/pyscope/reduction/maxim_pinpoint_wcs.py +++ b/pyscope/reduction/maxim_pinpoint_wcs.py @@ -28,7 +28,7 @@ def maxim_pinpoint_wcs_cli(filepath): Parameters ---------- - filepath : str + filepath : `str` Returns ------- diff --git a/pyscope/telrun/airmass_condition.py b/pyscope/telrun/airmass_condition.py index c0b7e53f..0b2602b1 100644 --- a/pyscope/telrun/airmass_condition.py +++ b/pyscope/telrun/airmass_condition.py @@ -47,16 +47,16 @@ def __init__(self, airmass_limit=3, formula="Schoenberg1929", weight=1, **kwargs Parameters ---------- - airmass_limit : float, default : 3 + airmass_limit : `float`, default : 3 The maximum airmass value that is allowed. - formula: `str`, default : "secant", {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} + formula : `str`, default : "secant", {"secant", "Schoenberg1929", "Young+Irvine1967", "Hardie1962", "Rozenberg1966", "KastenYoung1989", "Young1994", "Pickering2002"} The formula to use to calculate the airmass value. - weight : float, default : 1 + weight : `float`, default : 1 The weight of the condition in the final score. The default is 1. - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Additional keyword arguments to pass to the `~pyscope.telrun.BoundaryCondition` constructor for storage in the `~pyscope.telrun.BoundaryCondition.kwargs` attribute. References @@ -89,11 +89,11 @@ def from_string(self, string, airmass_limit=None, formula=None, weight=None): ---------- string : `str`, required - airmass_limit : `float`, default : None + airmass_limit : `float`, default : `None` - formula : `str`, default : None + formula : `str`, default : `None` - weight : `float`, default : None + weight : `float`, default : `None` """ logger.debug( diff --git a/pyscope/telrun/autofocus_field.py b/pyscope/telrun/autofocus_field.py index 3b8c9f32..44fcd754 100644 --- a/pyscope/telrun/autofocus_field.py +++ b/pyscope/telrun/autofocus_field.py @@ -139,9 +139,9 @@ def from_string( ---------- string : `str`, required - config : `~pyscope.telrun.InstrumentConfiguration`, default : None + config : `~pyscope.telrun.InstrumentConfiguration`, default : `None` - repositioning : 2-tuple of `~astropy.units.Quantity`, default : None + repositioning : 2-tuple of `~astropy.units.Quantity`, default : `None` dither : `~astropy.units.Quantity`, default : 0 arcsec diff --git a/pyscope/telrun/boundary_condition.py b/pyscope/telrun/boundary_condition.py index 5f3efb42..b9b45743 100644 --- a/pyscope/telrun/boundary_condition.py +++ b/pyscope/telrun/boundary_condition.py @@ -151,7 +151,7 @@ def __call__(self, target, time, location, **kwargs): location : `~astropy.coordinates.EarthLocation` The location of the observatory. - **kwargs : dict + **kwargs : `dict` Returns ------- diff --git a/pyscope/telrun/coord_condition.py b/pyscope/telrun/coord_condition.py index 7668c675..4fc9fa1b 100644 --- a/pyscope/telrun/coord_condition.py +++ b/pyscope/telrun/coord_condition.py @@ -91,17 +91,17 @@ def from_string( ---------- string : `str`, required - coord_type : `str`, default : None + coord_type : `str`, default : `None` - coord_idx : `int`, default : None + coord_idx : `int`, default : `None` - min_val : `~astropy.units.Quantity`, default : None + min_val : `~astropy.units.Quantity`, default : `None` - max_val : `~astropy.units.Quantity`, default : None + max_val : `~astropy.units.Quantity`, default : `None` - score_type : `str`, default : None + score_type : `str`, default : `None` - ref_coord : `~astropy.coordinates.SkyCoord`, default : None + ref_coord : `~astropy.coordinates.SkyCoord`, default : `None` **kwargs : `dict`, default : {} diff --git a/pyscope/telrun/dark_field.py b/pyscope/telrun/dark_field.py index 6e94cefa..ce4b7b5e 100644 --- a/pyscope/telrun/dark_field.py +++ b/pyscope/telrun/dark_field.py @@ -33,7 +33,7 @@ def __init__( that are captured in the dark frame. If `None`, the pointing will not change and the dark frame will be taken in the current position of the telescope. - config : `~pyscope.telrun.InstrumentConfiguration`, default : None + config : `~pyscope.telrun.InstrumentConfiguration`, default : `None` The instrument configuration to use for the observation. If None, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. @@ -47,7 +47,7 @@ def __init__( The output filename for the observation. If not specified, the filename will be automatically generated. - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Additional keyword arguments to pass to the instrument for the observation. """ @@ -76,9 +76,9 @@ def from_string( ---------- string : `str`, required - target : `~astropy.coordinates.SkyCoord`, default : None + target : `~astropy.coordinates.SkyCoord`, default : `None` - config : `~pyscope.telrun.Config`, default : None + config : `~pyscope.telrun.Config`, default : `None` exp : `~astropy.units.Quantity`, default : 0 sec diff --git a/pyscope/telrun/field.py b/pyscope/telrun/field.py index f638d5ab..68ac6211 100644 --- a/pyscope/telrun/field.py +++ b/pyscope/telrun/field.py @@ -21,11 +21,11 @@ def __init__(self, target, config=None, **kwargs): The target field to observe. If the target has proper motion, ensure that the reference epoch and the proper motions are set. - config : `~pyscope.telrun.InstrumentConfiguration`, default : None - The instrument configuration to use for the observation. If None, the + config : `~pyscope.telrun.InstrumentConfiguration`, default : `None` + The instrument configuration to use for the observation. If `None`, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Additional keyword arguments to pass to the instrument for the observation. """ diff --git a/pyscope/telrun/flat_field.py b/pyscope/telrun/flat_field.py index f565dd45..c4004e48 100644 --- a/pyscope/telrun/flat_field.py +++ b/pyscope/telrun/flat_field.py @@ -49,8 +49,8 @@ def __init__( that the reference epoch and the proper motions are set. The default is the `CoverCalibrator` which is a pre-configured location to take flat field images. - config : `~pyscope.telrun.InstrumentConfiguration`, default : None - The instrument configuration to use for the observation. If None, the + config : `~pyscope.telrun.InstrumentConfiguration`, default : `None` + The instrument configuration to use for the observation. If `None`, the default configuration from the `~pyscope.telrun.ScheduleBlock` will be used. dither : `~astropy.units.Quantity`, default : 0 arcsec @@ -119,9 +119,9 @@ def from_string( ---------- string : `str`, required - target : `~astropy.coordinates.SkyCoord`, default : None + target : `~astropy.coordinates.SkyCoord`, default : `None` - config : `~pyscope.telrun.Config`, default : None + config : `~pyscope.telrun.Config`, default : `None` dither : `~astropy.units.Quantity`, default : 0 arcsec diff --git a/pyscope/telrun/hourangle_condition.py b/pyscope/telrun/hourangle_condition.py index 81b7b00c..9647bfbe 100644 --- a/pyscope/telrun/hourangle_condition.py +++ b/pyscope/telrun/hourangle_condition.py @@ -58,9 +58,9 @@ def from_string(cls, string, min_hour_angle=None, max_hour_angle=None): ---------- string : `str`, required - min_hour_angle : `~astropy.units.Quantity`, default : None + min_hour_angle : `~astropy.units.Quantity`, default : `None` - max_hour_angle : `~astropy.units.Quantity`, default : None + max_hour_angle : `~astropy.units.Quantity`, default : `None` Returns ------- diff --git a/pyscope/telrun/light_field.py b/pyscope/telrun/light_field.py index 6c96d063..a6fd9229 100644 --- a/pyscope/telrun/light_field.py +++ b/pyscope/telrun/light_field.py @@ -75,7 +75,7 @@ def __init__( constraints for this field. The `~pyscope.telrun.Optimizer` inside the `~pyscope.telrun.Scheduler` will use the `~pyscope.telrun.BoundaryCondition` objects to determine the best possible schedule. - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Additional keyword arguments to pass to the instrument for the observation. """ @@ -135,7 +135,7 @@ def from_string( conditions : `list` of `~pyscope.telrun.BoundaryCondition`, default : [] - kwargs : dict, default : {} + kwargs : `dict`, default : {} Returns ------- diff --git a/pyscope/telrun/mk_mosaic_schedule.py b/pyscope/telrun/mk_mosaic_schedule.py index f5c11609..87e5266e 100644 --- a/pyscope/telrun/mk_mosaic_schedule.py +++ b/pyscope/telrun/mk_mosaic_schedule.py @@ -156,55 +156,55 @@ def mk_mosaic_schedule_cli( Parameters ---------- - source : str + source : `str` Source name to resolve coordinates or a pair of ICRS ra,dec coordinates - filters : str or tuple of str + filters : `str` or `tuple` of `str` Filters to use. Multiple filters can be specified by a tuple, e.g. ('b', 'g', 'r') - exp_times : float or tuple of float + exp_times : `float` or `tuple` of `float` Exposure times to use. Multiple exposure times can be specified by a tuple, e.g. (10, 20) - observer : tuple of str + observer : `tuple` of `str` Observer name and 3-character code, e.g. ('Walter Golay', 'XWG') - overlap : float, default=5 + overlap : `float`, default=5 Overlap between pointings (arcmin). - fov : tuple of float, default=(20, 20) + fov : `tuple` of `float`, default=(20, 20) Field of view (arcmin). - grid_size : tuple of int, default=(3, 3) + grid_size : `tuple` of `int`, default=(3, 3) Grid size (nrows, ncols). - n_exp : int, default=1 + n_exp : `int`, default=1 Number of exposures per filter and exposure time. - readout : int, default=0 + readout : `int`, default=0 Readout mode as specified by the camera. - binning : str, default='1x1' + binning : `str`, default='1x1' Binning mode. - ut_start : str, optional + ut_start : `str`, optional UT start time of the observation in the ISOT format. If none given, the object will be scheduled for the best possible time. - comment : str, optional + comment : `str`, optional Comment to be added to the schedule. - write : str, optional + write : `str`, optional Write a .sch file with the given name. If none given from the command line, a name is generated from the observing code. Otherwise, a file will not be written and the ObservingBlock set will be returned. - verbose : int, {-1, 0, 1}, default=-1 + verbose : `int`, {-1, 0, 1}, default=-1 Verbosity level. 0: no debug messages, 1: debug messages. Default set to -1 for API use and 0 for CLI use. Returns ------- - obsblocks : `~astroplan.ObservingBlock` tuple or None + obsblocks : `~astroplan.ObservingBlock` `tuple` or `None` Set of ObservingBlocks for the mosaic observation. Raises diff --git a/pyscope/telrun/option.py b/pyscope/telrun/option.py index 44bc8b73..99fbf9e5 100644 --- a/pyscope/telrun/option.py +++ b/pyscope/telrun/option.py @@ -31,10 +31,10 @@ def __init__( instruments : `list` of `str`, default : [] The list of instruments used by the `~pyscope.telrun.Option`. This can be used to sort options by instrument. - current_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + current_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : `None` The current value of the `~pyscope.telrun.Option`. This is the value that will be used in the observation if the `~pyscope.telrun.Option` is not changed. - default_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + default_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : `None` The default value of the `~pyscope.telrun.Option`. This is the value that will be used if the `~pyscope.telrun.Option` is not set by the user. description : `str`, default : "" @@ -43,7 +43,7 @@ def __init__( type : `str`, default : "str" The type of the `~pyscope.telrun.Option`. This can be one of "str", "int", "float", "bool", "list", or "dict". - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Additional keyword arguments to pass to the `~pyscope.telrun.Option`. If `type="list"` or `type="dict"`, requested values will be validated against `vlist` or `vdict` respectively. If `type="int"` or `type="float"`, the value will be validated against `min` and `max` if they are set. If `type="bool"`, the value will be converted to a `bool`. If `type="str"`, the @@ -84,17 +84,17 @@ def from_string( ---------- string : `str`, required - name : `str`, default : None + name : `str`, default : `None` - instruments : `list` of `str`, default : None + instruments : `list` of `str`, default : `None` - current_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + current_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : `None` - default_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : None + default_value : `str`, `int`, `float`, `bool`, `list`, `dict`, default : `None` - description : `str`, default : None + description : `str`, default : `None` - type : `str`, default : None + type : `str`, default : `None` **kwargs : `dict`, default : {} diff --git a/pyscope/telrun/rst.py b/pyscope/telrun/rst.py index 3ea20e6e..b9c20326 100644 --- a/pyscope/telrun/rst.py +++ b/pyscope/telrun/rst.py @@ -79,22 +79,22 @@ def rst_cli(source=None, date=None, observatory="./config/observatory.cfg", verb Parameters ---------- - source : str, optional + source : `str`, optional Source name. - date : str [YYYY-MM-DD], optional + date : `str` [YYYY-MM-DD], optional Date of observation. If none is given, the current date at the observatory is used. - observatory : str or `~pyscope.observatory.Observatory`, default='./config/observatory.cfg' + observatory : `str` or `~pyscope.observatory.Observatory`, default='./config/observatory.cfg' Observatory configuration file or `~pyscope.observatory.Observatory` object. - verbose : int, {-1, 0, 1}, default=-1 + verbose : `int`, {-1, 0, 1}, default=-1 Verbosity level. 0: no debug messages, 1: debug messages. Default set to -1 for API use and 0 for CLI use. Returns ------- - events: list + events: `list` of `tuple` List of tuples containing the event name and a `~astropy.time.Time` object. Raises diff --git a/pyscope/telrun/schedtab.py b/pyscope/telrun/schedtab.py index 81d45b03..fc6b1ea6 100644 --- a/pyscope/telrun/schedtab.py +++ b/pyscope/telrun/schedtab.py @@ -18,7 +18,7 @@ def blocks_to_table(observing_blocks): Parameters ---------- - observing_blocks : list + observing_blocks : `list` A list of observing blocks. Returns diff --git a/pyscope/telrun/telrun_operator.py b/pyscope/telrun/telrun_operator.py index 368671d2..36f240d0 100644 --- a/pyscope/telrun/telrun_operator.py +++ b/pyscope/telrun/telrun_operator.py @@ -43,7 +43,7 @@ def __init__(self, telhome="./", gui=True, **kwargs): Parameters ---------- - telhome : str, optional + telhome : `str`, optional The path to the TelrunOperator home directory. The default is the current working directory. Telhome must have a specific directory structure, which is created if it does not exist. The directory structure and some relevant files are as follows:: @@ -84,8 +84,8 @@ def __init__(self, telhome="./", gui=True, **kwargs): observing blocks. The `images/` directory structure is dictated by the `~pyscope.reduction` scripts, which are used for rapid image calibration and reduction. - gui : bool, optional - Whether to start the GUI. Default is True. + gui : `bool`, optional + Whether to start the GUI. Default is `True`. **kwargs Keyword arguments to pass to the TelrunOperator constructor. More details in Other Parameters diff --git a/pyscope/telrun/transition_field.py b/pyscope/telrun/transition_field.py index c50e9396..980171c3 100644 --- a/pyscope/telrun/transition_field.py +++ b/pyscope/telrun/transition_field.py @@ -34,7 +34,7 @@ def __init__( est_duration : `~astropy.units.Quantity`, default : 0 sec The duration of the transition in seconds. Typically, the `~pyscope.observatory.Observatory` will calculate the duration and set this value. - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Additional keyword arguments to pass to the instrument for the observation """ @@ -68,7 +68,7 @@ def from_string( est_duration : `~astropy.units.Quantity`, default : 0 sec - **kwargs : dict, default : {} + **kwargs : `dict`, default : {} Returns ------- From cd415867ac2b5c2569d2bef226f96d66fd955a3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:11:20 +0000 Subject: [PATCH 26/30] Bump astropy from 6.1.2 to 6.1.4 Bumps [astropy](https://github.com/astropy/astropy) from 6.1.2 to 6.1.4. - [Release notes](https://github.com/astropy/astropy/releases) - [Changelog](https://github.com/astropy/astropy/blob/main/docs/changelog.rst) - [Commits](https://github.com/astropy/astropy/compare/v6.1.2...v6.1.4) --- updated-dependencies: - dependency-name: astropy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 68eeb74c..7c57ec64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ alpyca == 2.0.4 astroplan == 0.10 -astropy == 6.1.2 +astropy == 6.1.4 astroquery == 0.4.7 astroscrappy == 1.2.0 ccdproc == 2.4.2 From cb0c401ba0a4b5a1aaafa8b8249fe7a8711d1a83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:11:31 +0000 Subject: [PATCH 27/30] Bump pytest from 8.1.1 to 8.3.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.3.3. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.3.3) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4f1b5483..9013ad88 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,7 +68,7 @@ docs = sphinxcontrib-programoutput==0.17 tests = - pytest==8.1.1 + pytest==8.3.3 pytest-cov==5.0.0 pytest-doctestplus==1.2.1 @@ -79,7 +79,7 @@ dev = esbonio==0.16.4 isort==5.13.2 pre-commit==3.7.0 - pytest==8.1.1 + pytest==8.3.3 pytest-cov==5.0.0 pytest-order==1.3.0 rstcheck==6.2.1 From fa502e220731d0e31e6e8aa55ddcda8e6a5f9c48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:11:38 +0000 Subject: [PATCH 28/30] Bump numpy from 2.1.0 to 2.1.2 Bumps [numpy](https://github.com/numpy/numpy) from 2.1.0 to 2.1.2. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.1.0...v2.1.2) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 68eeb74c..eb8c86f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ccdproc == 2.4.2 click == 8.1.7 cmcrameri == 1.9 markdown == 3.6 -numpy == 2.1.0 +numpy == 2.1.2 matplotlib == 3.9.1 oschmod == 0.3.12 paramiko == 3.4.0 From 3a535a9bfddf1a2b1b8bb1fc6b73e40fcfaeafe3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:00:21 +0000 Subject: [PATCH 29/30] Bump isort/isort-action from 1.1.0 to 1.1.1 Bumps [isort/isort-action](https://github.com/isort/isort-action) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/isort/isort-action/releases) - [Changelog](https://github.com/isort/isort-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/isort/isort-action/compare/v1.1.0...v1.1.1) --- updated-dependencies: - dependency-name: isort/isort-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebed69f7..36ec6b65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,6 @@ jobs: with: src: pyscope jupyter: true - - uses: isort/isort-action@v1.1.0 + - uses: isort/isort-action@v1.1.1 with: configuration: profile=black From 4669a13fea83e2f4c0e8717b42be2d4387bfb551 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:00:23 +0000 Subject: [PATCH 30/30] Bump pypa/gh-action-pypi-publish from 1.10.0 to 1.11.0 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/pypi-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 77cf1689..0eb83074 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -34,4 +34,4 @@ jobs: - name: Build package run: python -m build - name: pypi-publish - uses: pypa/gh-action-pypi-publish@v1.10.0 + uses: pypa/gh-action-pypi-publish@v1.11.0