├── astronbs ├── notebooks │ ├── __init__.py │ ├── quick-color-template.ipynb │ ├── interactive-image-viewer.ipynb │ ├── folder-viewer-template.ipynb │ ├── reprojection_template.ipynb │ ├── light-image-combo-template.ipynb │ ├── color-mixer-template.ipynb │ └── reduction-template.ipynb ├── tests │ ├── __init__.py │ └── test_handlers.py ├── _version.py ├── __init__.py └── handlers.py ├── style ├── index.js ├── index.css └── base.css ├── setup.py ├── babel.config.js ├── .prettierignore ├── .eslintignore ├── CHANGELOG.md ├── .prettierrc ├── jupyter-config ├── nb-config │ └── astronbs.json └── server-config │ └── astronbs.json ├── install.json ├── conftest.py ├── src ├── __tests__ │ └── astronbs.spec.ts ├── handler.ts └── index.ts ├── .stylelintrc ├── ui-tests ├── package.json ├── playwright.config.js ├── tests │ └── astronbs.spec.ts ├── jupyter_server_test_config.py └── README.md ├── MANIFEST.in ├── tsconfig.json ├── schema └── plugin.json ├── jest.config.js ├── .eslintrc.js ├── .github └── workflows │ ├── update-integration-tests.yml │ ├── check-release.yml │ └── build.yml ├── LICENSE ├── .gitignore ├── RELEASE.md ├── pyproject.toml ├── README.md └── package.json /astronbs/notebooks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __import__('setuptools').setup() 2 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /astronbs/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Python unit tests for astronbs.""" 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@jupyterlab/testutils/lib/babel.config'); 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | astronbs 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests 6 | 7 | **/__tests__ 8 | ui-tests 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /astronbs/_version.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | __all__ = ["__version__"] 5 | 6 | __version__ = "0.1.0" 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "endOfLine": "auto" 6 | } 7 | -------------------------------------------------------------------------------- /jupyter-config/nb-config/astronbs.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "astronbs": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/server-config/astronbs.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "astronbs": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | See the JupyterLab Developer Guide for useful CSS Patterns: 3 | 4 | https://jupyterlab.readthedocs.io/en/stable/developer/css.html 5 | */ 6 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "astronbs", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package astronbs" 5 | } 6 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | pytest_plugins = ("jupyter_server.pytest_plugin", ) 4 | 5 | 6 | @pytest.fixture 7 | def jp_server_config(jp_server_config): 8 | return {"ServerApp": {"jpserver_extensions": {"astronbs": True}}} 9 | -------------------------------------------------------------------------------- /src/__tests__/astronbs.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests 3 | */ 4 | 5 | describe('astronbs', () => { 6 | it('should be tested', () => { 7 | expect(1 + 1).toEqual(2); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-recommended", 4 | "stylelint-config-standard", 5 | "stylelint-prettier/recommended" 6 | ], 7 | "rules": { 8 | "property-no-vendor-prefix": null, 9 | "selector-no-vendor-prefix": null, 10 | "value-no-vendor-prefix": null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /astronbs/tests/test_handlers.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | async def test_get_example(jp_fetch): 5 | # When 6 | response = await jp_fetch("astronbs", "get_example") 7 | 8 | # Then 9 | assert response.code == 200 10 | payload = json.loads(response.body) 11 | assert payload == { 12 | "data": "This is /astronbs/get_example endpoint!" 13 | } -------------------------------------------------------------------------------- /ui-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astronbs-ui-tests", 3 | "version": "1.0.0", 4 | "description": "JupyterLab astronbs Integration Tests", 5 | "private": true, 6 | "scripts": { 7 | "start": "jupyter lab --config jupyter_server_test_config.py", 8 | "test": "jlpm playwright test" 9 | }, 10 | "devDependencies": { 11 | "@jupyterlab/galata": "^4.3.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui-tests/playwright.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration for Playwright using default from @jupyterlab/galata 3 | */ 4 | const baseConfig = require('@jupyterlab/galata/lib/playwright-config'); 5 | 6 | module.exports = { 7 | ...baseConfig, 8 | webServer: { 9 | command: 'jlpm start', 10 | url: 'http://localhost:8888/lab', 11 | timeout: 120 * 1000, 12 | reuseExistingServer: !process.env.CI 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /ui-tests/tests/astronbs.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@jupyterlab/galata'; 2 | 3 | /** 4 | * Don't load JupyterLab webpage before running the tests. 5 | * This is required to ensure we capture all log messages. 6 | */ 7 | test.use({ autoGoto: false }); 8 | 9 | test('should emit an activation console message', async ({ page }) => { 10 | const logs: string[] = []; 11 | 12 | page.on('console', message => { 13 | logs.push(message.text()); 14 | }); 15 | 16 | await page.goto(); 17 | 18 | expect( 19 | logs.filter(s => s === 'JupyterLab extension astronbs is activated!') 20 | ).toHaveLength(1); 21 | }); 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include *.md 3 | include pyproject.toml 4 | recursive-include jupyter-config *.json 5 | include conftest.py 6 | 7 | include package.json 8 | include install.json 9 | include ts*.json 10 | include *.config.js 11 | include yarn.lock 12 | 13 | graft astronbs/labextension 14 | 15 | # Javascript files 16 | graft src 17 | graft style 18 | graft schema 19 | graft ui-tests 20 | prune **/node_modules 21 | prune lib 22 | prune binder 23 | 24 | # Patterns to exclude from any directory 25 | global-exclude *~ 26 | global-exclude *.pyc 27 | global-exclude *.pyo 28 | global-exclude .git 29 | global-exclude .ipynb_checkpoints 30 | -------------------------------------------------------------------------------- /ui-tests/jupyter_server_test_config.py: -------------------------------------------------------------------------------- 1 | """Server configuration for integration tests. 2 | 3 | !! Never use this configuration in production because it 4 | opens the server to the world and provide access to JupyterLab 5 | JavaScript objects through the global window variable. 6 | """ 7 | from tempfile import mkdtemp 8 | 9 | c.ServerApp.port = 8888 10 | c.ServerApp.port_retries = 0 11 | c.ServerApp.open_browser = False 12 | 13 | c.ServerApp.root_dir = mkdtemp(prefix='galata-test-') 14 | c.ServerApp.token = "" 15 | c.ServerApp.password = "" 16 | c.ServerApp.disable_check_xsrf = True 17 | c.LabApp.expose_app_in_browser = True 18 | 19 | # Uncomment to set server log level to debug level 20 | # c.ServerApp.log_level = "DEBUG" 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "lib": ["es2015", "dom"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmitOnError": true, 13 | "noImplicitAny": true, 14 | "noUnusedLocals": true, 15 | "preserveWatchOutput": true, 16 | "resolveJsonModule": true, 17 | "outDir": "lib", 18 | "rootDir": "src", 19 | "strict": true, 20 | "strictNullChecks": true, 21 | "skipLibCheck": true, 22 | "target": "es2020", 23 | "types": ["jest"] 24 | }, 25 | "include": ["src/*"] 26 | } 27 | -------------------------------------------------------------------------------- /schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.shortcuts": [], 3 | "title": "astronbs", 4 | "description": "astronbs settings.", 5 | "type": "object", 6 | "properties": {}, 7 | "additionalProperties": false, 8 | "jupyter.lab.menus": { 9 | "main": [ 10 | { 11 | "id": "jp-mainmenu-myextension", 12 | "label": "Astro", 13 | "items": [ 14 | { 15 | "command": "astronbs:reduction_template", 16 | "rank": 500 17 | }, 18 | { 19 | "command": "astronbs:reprojection_template", 20 | "rank": 500 21 | }, 22 | { 23 | "command": "astronbs:light_combo_template", 24 | "rank": 500 25 | } 26 | ], 27 | "rank": 50 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config'); 2 | 3 | const esModules = [ 4 | '@jupyterlab/', 5 | 'lib0', 6 | 'y\\-protocols', 7 | 'y\\-websocket', 8 | 'yjs' 9 | ].join('|'); 10 | 11 | const jlabConfig = jestJupyterLab(__dirname); 12 | 13 | const { 14 | moduleFileExtensions, 15 | moduleNameMapper, 16 | preset, 17 | setupFilesAfterEnv, 18 | setupFiles, 19 | testPathIgnorePatterns, 20 | transform 21 | } = jlabConfig; 22 | 23 | module.exports = { 24 | moduleFileExtensions, 25 | moduleNameMapper, 26 | preset, 27 | setupFilesAfterEnv, 28 | setupFiles, 29 | testPathIgnorePatterns, 30 | transform, 31 | automock: false, 32 | collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'], 33 | coverageDirectory: 'coverage', 34 | coverageReporters: ['lcov', 'text'], 35 | globals: { 36 | 'ts-jest': { 37 | tsconfig: 'tsconfig.json' 38 | } 39 | }, 40 | testRegex: 'src/.*/.*.spec.ts[x]?$', 41 | transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`] 42 | }; 43 | -------------------------------------------------------------------------------- /astronbs/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from ._version import __version__ 5 | from .handlers import setup_handlers 6 | 7 | 8 | 9 | HERE = Path(__file__).parent.resolve() 10 | 11 | 12 | with (HERE / "labextension" / "package.json").open() as fid: 13 | data = json.load(fid) 14 | 15 | 16 | def _jupyter_labextension_paths(): 17 | return [{ 18 | "src": "labextension", 19 | "dest": data["name"] 20 | }] 21 | 22 | 23 | 24 | def _jupyter_server_extension_points(): 25 | return [{ 26 | "module": "astronbs" 27 | }] 28 | 29 | 30 | def _load_jupyter_server_extension(server_app): 31 | """Registers the API handler to receive HTTP requests from the frontend extension. 32 | 33 | Parameters 34 | ---------- 35 | server_app: jupyterlab.labapp.LabApp 36 | JupyterLab application instance 37 | """ 38 | setup_handlers(server_app.web_app) 39 | server_app.log.info("Registered {name} server extension".format(**data)) 40 | 41 | 42 | # For backward compatibility with notebook server - useful for Binder/JupyterHub 43 | load_jupyter_server_extension = _load_jupyter_server_extension 44 | 45 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended' 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/naming-convention': [ 16 | 'error', 17 | { 18 | selector: 'interface', 19 | format: ['PascalCase'], 20 | custom: { 21 | regex: '^I[A-Z]', 22 | match: true 23 | } 24 | } 25 | ], 26 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-namespace': 'off', 29 | '@typescript-eslint/no-use-before-define': 'off', 30 | '@typescript-eslint/quotes': [ 31 | 'error', 32 | 'single', 33 | { avoidEscape: true, allowTemplateLiterals: false } 34 | ], 35 | curly: ['error', 'all'], 36 | eqeqeq: 'error', 37 | 'prefer-arrow-callback': 'error' 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/handler.ts: -------------------------------------------------------------------------------- 1 | import { URLExt } from '@jupyterlab/coreutils'; 2 | 3 | import { ServerConnection } from '@jupyterlab/services'; 4 | 5 | /** 6 | * Call the API extension 7 | * 8 | * @param endPoint API REST end point for the extension 9 | * @param init Initial values for the request 10 | * @returns The response body interpreted as JSON 11 | */ 12 | export async function requestAPI( 13 | endPoint = '', 14 | init: RequestInit = {} 15 | ): Promise { 16 | // Make request to Jupyter API 17 | const settings = ServerConnection.makeSettings(); 18 | const requestUrl = URLExt.join( 19 | settings.baseUrl, 20 | 'astronbs', // API Namespace 21 | endPoint 22 | ); 23 | 24 | let response: Response; 25 | try { 26 | response = await ServerConnection.makeRequest(requestUrl, init, settings); 27 | } catch (error) { 28 | throw new ServerConnection.NetworkError(error); 29 | } 30 | 31 | let data: any = await response.text(); 32 | 33 | if (data.length > 0) { 34 | try { 35 | data = JSON.parse(data); 36 | } catch (error) { 37 | console.log('Not a JSON response body.', response); 38 | } 39 | } 40 | 41 | if (!response.ok) { 42 | throw new ServerConnection.ResponseError(response, data.message || data); 43 | } 44 | 45 | return data; 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/update-integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: Update Playwright Snapshots 2 | 3 | on: 4 | issue_comment: 5 | types: [created, edited] 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | 13 | 14 | update-snapshots: 15 | if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'please update playwright snapshots') }} 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: Configure git to use https 25 | run: git config --global hub.protocol https 26 | 27 | - name: Checkout the branch from the PR that triggered the job 28 | run: hub pr checkout ${{ github.event.issue.number }} 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Install dependencies 33 | run: | 34 | set -eux 35 | jlpm 36 | python -m pip install . 37 | 38 | - uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | # Playwright knows how to start JupyterLab server 42 | start_server_script: 'null' 43 | test_folder: ui-tests 44 | -------------------------------------------------------------------------------- /astronbs/notebooks/quick-color-template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import matplotlib.pyplot as plt\n", 10 | "\n", 11 | "from astropy.visualization import make_lupton_rgb\n", 12 | "from astropy.nddata import CCDData" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "red = CCDData.read('fill in name')\n", 22 | "green = CCDData.read('fill in name')\n", 23 | "blue = CCDData.read('fill in name')" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "color = make_lupton_rgb(red, green, blue)\n", 33 | "\n", 34 | "plt.imshow(color, origin='lower')" 35 | ] 36 | } 37 | ], 38 | "metadata": { 39 | "kernelspec": { 40 | "display_name": "Python 3", 41 | "language": "python", 42 | "name": "python3" 43 | }, 44 | "language_info": { 45 | "codemirror_mode": { 46 | "name": "ipython", 47 | "version": 3 48 | }, 49 | "file_extension": ".py", 50 | "mimetype": "text/x-python", 51 | "name": "python", 52 | "nbconvert_exporter": "python", 53 | "pygments_lexer": "ipython3", 54 | "version": "3.8.3" 55 | } 56 | }, 57 | "nbformat": 4, 58 | "nbformat_minor": 4 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Matt Craig 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /astronbs/notebooks/interactive-image-viewer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from astrowidgets import ImageWidget" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 5, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "folder_name = 'calibrated-nights/2020-11-03/'" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 3, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "application/vnd.jupyter.widget-view+json": { 29 | "model_id": "90c16cde36d14c0db68aaf358be296db", 30 | "version_major": 2, 31 | "version_minor": 0 32 | }, 33 | "text/plain": [ 34 | "ImageWidget(children=(Image(value=b'\\xff\\xd8\\xff\\xe0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\xff\\x…" 35 | ] 36 | }, 37 | "metadata": {}, 38 | "output_type": "display_data" 39 | } 40 | ], 41 | "source": [ 42 | "iw = ImageWidget()\n", 43 | "iw" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "iw.load_fits(folder_name + 'file name here')" 53 | ] 54 | } 55 | ], 56 | "metadata": { 57 | "kernelspec": { 58 | "display_name": "Python 3", 59 | "language": "python", 60 | "name": "python3" 61 | }, 62 | "language_info": { 63 | "codemirror_mode": { 64 | "name": "ipython", 65 | "version": 3 66 | }, 67 | "file_extension": ".py", 68 | "mimetype": "text/x-python", 69 | "name": "python", 70 | "nbconvert_exporter": "python", 71 | "pygments_lexer": "ipython3", 72 | "version": "3.8.3" 73 | } 74 | }, 75 | "nbformat": 4, 76 | "nbformat_minor": 4 77 | } 78 | -------------------------------------------------------------------------------- /astronbs/notebooks/folder-viewer-template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Put this in either your jupyter-your.name folder\n", 8 | "\n", 9 | "or in a folder with images in it" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from astropy.table import Table\n", 19 | "from ccdproc import ImageFileCollection\n", 20 | "from reducer.image_browser import ImageBrowser" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": {}, 26 | "source": [ 27 | "## You will need to change the text below to match the folder you want" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "ifc = ImageFileCollection('calibrated-nights/2020-07-26-calibrated/')" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "ib = ImageBrowser(ifc, keys=['imagetyp', 'object'])" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "ib" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "kernelspec": { 60 | "display_name": "Python 3", 61 | "language": "python", 62 | "name": "python3" 63 | }, 64 | "language_info": { 65 | "codemirror_mode": { 66 | "name": "ipython", 67 | "version": 3 68 | }, 69 | "file_extension": ".py", 70 | "mimetype": "text/x-python", 71 | "name": "python", 72 | "nbconvert_exporter": "python", 73 | "pygments_lexer": "ipython3", 74 | "version": "3.8.3" 75 | } 76 | }, 77 | "nbformat": 4, 78 | "nbformat_minor": 4 79 | } 80 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | check_release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Install Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.9 23 | architecture: 'x64' 24 | - name: Install node 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: '14.x' 28 | 29 | 30 | - name: Get pip cache dir 31 | id: pip-cache 32 | run: | 33 | echo "::set-output name=dir::$(pip cache dir)" 34 | - name: Cache pip 35 | uses: actions/cache@v1 36 | with: 37 | path: ${{ steps.pip-cache.outputs.dir }} 38 | key: ${{ runner.os }}-pip-${{ hashFiles('package.json') }} 39 | restore-keys: | 40 | ${{ runner.os }}-pip- 41 | - name: Cache checked links 42 | uses: actions/cache@v2 43 | with: 44 | path: ~/.cache/pytest-link-check 45 | key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/.md') }}-md-links 46 | restore-keys: | 47 | ${{ runner.os }}-linkcheck- 48 | - name: Upgrade packaging dependencies 49 | run: | 50 | pip install --upgrade pip setuptools wheel jupyter-packaging~=0.10 --user 51 | - name: Install Dependencies 52 | run: | 53 | pip install . 54 | - name: Check Release 55 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 56 | with: 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | - name: Upload Distributions 60 | uses: actions/upload-artifact@v2 61 | with: 62 | name: astronbs-releaser-dist-${{ github.run_number }} 63 | path: .jupyter_releaser_checkout/dist 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | .eslintcache 5 | .stylelintcache 6 | *.egg-info/ 7 | .ipynb_checkpoints 8 | *.tsbuildinfo 9 | astronbs/labextension 10 | 11 | # Integration tests 12 | ui-tests/test-results/ 13 | ui-tests/playwright-report/ 14 | 15 | # Created by https://www.gitignore.io/api/python 16 | # Edit at https://www.gitignore.io/?templates=python 17 | 18 | ### Python ### 19 | # Byte-compiled / optimized / DLL files 20 | __pycache__/ 21 | *.py[cod] 22 | *$py.class 23 | 24 | # C extensions 25 | *.so 26 | 27 | # Distribution / packaging 28 | .Python 29 | build/ 30 | develop-eggs/ 31 | dist/ 32 | downloads/ 33 | eggs/ 34 | .eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | wheels/ 41 | pip-wheel-metadata/ 42 | share/python-wheels/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .nox/ 61 | .coverage 62 | .coverage.* 63 | .cache 64 | nosetests.xml 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # SageMath parsed files 90 | *.sage.py 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # Mr Developer 100 | .mr.developer.cfg 101 | .project 102 | .pydevproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | .dmypy.json 110 | dmypy.json 111 | 112 | # Pyre type checker 113 | .pyre/ 114 | 115 | # End of https://www.gitignore.io/api/python 116 | 117 | # OSX files 118 | .DS_Store 119 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of astronbs 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python 10 | packages. All of the Python 11 | packaging instructions in the `pyproject.toml` file to wrap your extension in a 12 | Python package. Before generating a package, we first need to install `build`. 13 | 14 | ```bash 15 | pip install build twine hatch 16 | ``` 17 | 18 | Bump the version using `hatch`. By default this will create a tag. 19 | See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details. 20 | 21 | ```bash 22 | hatch version 23 | ``` 24 | 25 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: 26 | 27 | ```bash 28 | python -m build 29 | ``` 30 | 31 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 32 | 33 | Then to upload the package to PyPI, do: 34 | 35 | ```bash 36 | twine upload dist/* 37 | ``` 38 | 39 | ### NPM package 40 | 41 | To publish the frontend part of the extension as a NPM package, do: 42 | 43 | ```bash 44 | npm login 45 | npm publish --access public 46 | ``` 47 | 48 | ## Automated releases with the Jupyter Releaser 49 | 50 | The extension repository should already be compatible with the Jupyter Releaser. 51 | 52 | Check out the [workflow documentation](https://github.com/jupyter-server/jupyter_releaser#typical-workflow) for more information. 53 | 54 | Here is a summary of the steps to cut a new release: 55 | 56 | - Fork the [`jupyter-releaser` repo](https://github.com/jupyter-server/jupyter_releaser) 57 | - Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the Github Secrets in the fork 58 | - Go to the Actions panel 59 | - Run the "Draft Changelog" workflow 60 | - Merge the Changelog PR 61 | - Run the "Draft Release" workflow 62 | - Run the "Publish Release" workflow 63 | 64 | ## Publishing to `conda-forge` 65 | 66 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html 67 | 68 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 69 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.3.1", "jupyterlab>=3.4.7,<4.0.0", "hatch-nodejs-version"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "astronbs" 7 | readme = "README.md" 8 | license = { file = "LICENSE" } 9 | requires-python = ">=3.7" 10 | classifiers = [ 11 | "Framework :: Jupyter", 12 | "Framework :: Jupyter :: JupyterLab", 13 | "Framework :: Jupyter :: JupyterLab :: 3", 14 | "Framework :: Jupyter :: JupyterLab :: Extensions", 15 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 16 | "License :: OSI Approved :: BSD License", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.7", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | ] 24 | dependencies = [ 25 | "jupyter_server>=1.6,<2" 26 | ] 27 | dynamic = ["version", "description", "authors", "urls", "keywords"] 28 | 29 | [project.optional-dependencies] 30 | test = [ 31 | "coverage", 32 | "pytest", 33 | "pytest-asyncio", 34 | "pytest-cov", 35 | "pytest-tornasync" 36 | ] 37 | 38 | [tool.hatch.version] 39 | source = "nodejs" 40 | 41 | [tool.hatch.metadata.hooks.nodejs] 42 | fields = ["description", "authors", "urls"] 43 | 44 | [tool.hatch.build] 45 | artifacts = ["astronbs/labextension"] 46 | 47 | [tool.hatch.build.targets.wheel.shared-data] 48 | "astronbs/labextension" = "share/jupyter/labextensions/astronbs" 49 | "install.json" = "share/jupyter/labextensions/astronbs/install.json" 50 | "jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d" 51 | "jupyter-config/nb-config" = "etc/jupyter/jupyter_notebook_config.d" 52 | 53 | [tool.hatch.build.targets.sdist] 54 | exclude = [".github"] 55 | 56 | [tool.hatch.build.hooks.jupyter-builder] 57 | dependencies = ["hatch-jupyter-builder>=0.5"] 58 | build-function = "hatch_jupyter_builder.npm_builder" 59 | ensured-targets = [ 60 | "astronbs/labextension/static/style.js", 61 | "astronbs//labextension/package.json", 62 | ] 63 | skip-if-exists = ["astronbs/labextension/static/style.js"] 64 | 65 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs] 66 | build_cmd = "build:prod" 67 | npm = ["jlpm"] 68 | 69 | [tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs] 70 | build_cmd = "install:extension" 71 | npm = ["jlpm"] 72 | source_dir = "src" 73 | build_dir = "astronbs/labextension" 74 | 75 | [tool.jupyter-releaser.options] 76 | version_cmd = "hatch version" 77 | -------------------------------------------------------------------------------- /ui-tests/README.md: -------------------------------------------------------------------------------- 1 | # Integration Testing 2 | 3 | This folder contains the integration tests of the extension. 4 | 5 | They are defined using [Playwright](https://playwright.dev/docs/intro) test runner 6 | and [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) helper. 7 | 8 | The Playwright configuration is defined in [playwright.config.js](./playwright.config.js). 9 | 10 | The JupyterLab server configuration to use for the integration test is defined 11 | in [jupyter_server_test_config.py](./jupyter_server_test_config.py). 12 | 13 | The default configuration will produce video for failing tests and an HTML report. 14 | 15 | ## Run the tests 16 | 17 | > All commands are assumed to be executed from the root directory 18 | 19 | To run the tests, you need to: 20 | 21 | 1. Compile the extension: 22 | 23 | ```sh 24 | jlpm install 25 | jlpm build:prod 26 | ``` 27 | 28 | > Check the extension is installed in JupyterLab. 29 | 30 | 2. Install test dependencies (needed only once): 31 | 32 | ```sh 33 | cd ./ui-tests 34 | jlpm install 35 | jlpm playwright install 36 | cd .. 37 | ``` 38 | 39 | 3. Execute the [Playwright](https://playwright.dev/docs/intro) tests: 40 | 41 | ```sh 42 | cd ./ui-tests 43 | jlpm playwright test 44 | ``` 45 | 46 | Test results will be shown in the terminal. In case of any test failures, the test report 47 | will be opened in your browser at the end of the tests execution; see 48 | [Playwright documentation](https://playwright.dev/docs/test-reporters#html-reporter) 49 | for configuring that behavior. 50 | 51 | ## Update the tests snapshots 52 | 53 | > All commands are assumed to be executed from the root directory 54 | 55 | If you are comparing snapshots to validate your tests, you may need to update 56 | the reference snapshots stored in the repository. To do that, you need to: 57 | 58 | 1. Compile the extension: 59 | 60 | ```sh 61 | jlpm install 62 | jlpm build:prod 63 | ``` 64 | 65 | > Check the extension is installed in JupyterLab. 66 | 67 | 2. Install test dependencies (needed only once): 68 | 69 | ```sh 70 | cd ./ui-tests 71 | jlpm install 72 | jlpm playwright install 73 | cd .. 74 | ``` 75 | 76 | 3. Execute the [Playwright](https://playwright.dev/docs/intro) command: 77 | 78 | ```sh 79 | cd ./ui-tests 80 | jlpm playwright test -u 81 | ``` 82 | 83 | > Some discrepancy may occurs between the snapshots generated on your computer and 84 | > the one generated on the CI. To ease updating the snapshots on a PR, you can 85 | > type `please update playwright snapshots` to trigger the update by a bot on the CI. 86 | > Once the bot has computed new snapshots, it will commit them to the PR branch. 87 | 88 | ## Create tests 89 | 90 | > All commands are assumed to be executed from the root directory 91 | 92 | To create tests, the easiest way is to use the code generator tool of playwright: 93 | 94 | 1. Compile the extension: 95 | 96 | ```sh 97 | jlpm install 98 | jlpm build:prod 99 | ``` 100 | 101 | > Check the extension is installed in JupyterLab. 102 | 103 | 2. Install test dependencies (needed only once): 104 | 105 | ```sh 106 | cd ./ui-tests 107 | jlpm install 108 | jlpm playwright install 109 | cd .. 110 | ``` 111 | 112 | 3. Execute the [Playwright code generator](https://playwright.dev/docs/codegen): 113 | 114 | ```sh 115 | cd ./ui-tests 116 | jlpm playwright codegen localhost:8888 117 | ``` 118 | 119 | ## Debug tests 120 | 121 | > All commands are assumed to be executed from the root directory 122 | 123 | To debug tests, a good way is to use the inspector tool of playwright: 124 | 125 | 1. Compile the extension: 126 | 127 | ```sh 128 | jlpm install 129 | jlpm build:prod 130 | ``` 131 | 132 | > Check the extension is installed in JupyterLab. 133 | 134 | 2. Install test dependencies (needed only once): 135 | 136 | ```sh 137 | cd ./ui-tests 138 | jlpm install 139 | jlpm playwright install 140 | cd .. 141 | ``` 142 | 143 | 3. Execute the Playwright tests in [debug mode](https://playwright.dev/docs/debug): 144 | 145 | ```sh 146 | cd ./ui-tests 147 | PWDEBUG=1 jlpm playwright test 148 | ``` 149 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Base Setup 18 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 19 | 20 | - name: Install dependencies 21 | run: python -m pip install -U jupyterlab~=3.1 22 | 23 | - name: Lint the extension 24 | run: | 25 | set -eux 26 | jlpm 27 | jlpm run lint:check 28 | 29 | - name: Test the extension 30 | run: | 31 | set -eux 32 | jlpm run test 33 | 34 | - name: Build the extension 35 | run: | 36 | set -eux 37 | python -m pip install .[test] 38 | 39 | pytest -vv -r ap --cov astronbs 40 | jupyter server extension list 41 | jupyter server extension list 2>&1 | grep -ie "astronbs.*OK" 42 | 43 | jupyter labextension list 44 | jupyter labextension list 2>&1 | grep -ie "astronbs.*OK" 45 | python -m jupyterlab.browser_check 46 | 47 | - name: Package the extension 48 | run: | 49 | set -eux 50 | 51 | pip install build 52 | python -m build 53 | pip uninstall -y "astronbs" jupyterlab 54 | 55 | - name: Upload extension packages 56 | uses: actions/upload-artifact@v2 57 | with: 58 | name: extension-artifacts 59 | path: dist/astronbs* 60 | if-no-files-found: error 61 | 62 | test_isolated: 63 | needs: build 64 | runs-on: ubuntu-latest 65 | 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v2 69 | - name: Install Python 70 | uses: actions/setup-python@v2 71 | with: 72 | python-version: '3.9' 73 | architecture: 'x64' 74 | - uses: actions/download-artifact@v2 75 | with: 76 | name: extension-artifacts 77 | - name: Install and Test 78 | run: | 79 | set -eux 80 | # Remove NodeJS, twice to take care of system and locally installed node versions. 81 | sudo rm -rf $(which node) 82 | sudo rm -rf $(which node) 83 | 84 | pip install "jupyterlab~=3.1" astronbs*.whl 85 | 86 | 87 | jupyter server extension list 88 | jupyter server extension list 2>&1 | grep -ie "astronbs.*OK" 89 | 90 | jupyter labextension list 91 | jupyter labextension list 2>&1 | grep -ie "astronbs.*OK" 92 | python -m jupyterlab.browser_check --no-chrome-test 93 | 94 | integration-tests: 95 | name: Integration tests 96 | needs: build 97 | runs-on: ubuntu-latest 98 | 99 | env: 100 | PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/pw-browsers 101 | 102 | steps: 103 | - name: Checkout 104 | uses: actions/checkout@v2 105 | 106 | - name: Base Setup 107 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 108 | 109 | - name: Download extension package 110 | uses: actions/download-artifact@v2 111 | with: 112 | name: extension-artifacts 113 | 114 | - name: Install the extension 115 | run: | 116 | set -eux 117 | python -m pip install "jupyterlab~=3.1" astronbs*.whl 118 | 119 | - name: Install dependencies 120 | working-directory: ui-tests 121 | env: 122 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 123 | run: jlpm install 124 | 125 | - name: Set up browser cache 126 | uses: actions/cache@v2 127 | with: 128 | path: | 129 | ${{ github.workspace }}/pw-browsers 130 | key: ${{ runner.os }}-${{ hashFiles('ui-tests/yarn.lock') }} 131 | 132 | - name: Install browser 133 | run: jlpm playwright install chromium 134 | working-directory: ui-tests 135 | 136 | - name: Execute integration tests 137 | working-directory: ui-tests 138 | run: | 139 | jlpm playwright test 140 | 141 | - name: Upload Playwright Test report 142 | if: always() 143 | uses: actions/upload-artifact@v2 144 | with: 145 | name: astronbs-playwright-tests 146 | path: | 147 | ui-tests/test-results 148 | ui-tests/playwright-report 149 | -------------------------------------------------------------------------------- /astronbs/notebooks/reprojection_template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Reprojection template\n", 8 | "\n", 9 | "Use this when you want to change a series of images so that they are aligned with one of the imags." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from pathlib import Path\n", 19 | "\n", 20 | "import numpy as np\n", 21 | "\n", 22 | "from astropy.nddata import CCDData\n", 23 | "from ccdproc import ImageFileCollection\n", 24 | "from reproject import reproject_interp" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## Change the values here" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "image_folder = '.' # '.' means the same folder as the notebook. \n", 41 | "\n", 42 | "ipath = Path(image_folder)\n", 43 | "\n", 44 | "# This can be any one of the images. If one image is offset from all \n", 45 | "# of the others do not choose that image.\n", 46 | "reference_image = 'kelt-16-b-S001-R001-C004-r.fit'\n", 47 | "\n", 48 | "# You can call this anything you want.\n", 49 | "folder_for_shifted_images = 'shifted'\n", 50 | "\n", 51 | "# Subract median background value?\n", 52 | "subtract_median = True" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "### Run the rest but you should not need to modify anything" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "p = Path(folder_for_shifted_images)\n", 69 | "p.mkdir(exist_ok=True)\n", 70 | "ccd_ref = CCDData.read(ipath / reference_image)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "ifc = ImageFileCollection(image_folder)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "wcs_ref = ccd_ref.wcs" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "### This cell does this shifting. It will update as it completes images." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "n_images = len(ifc.summary) + 1\n", 105 | "i = 0\n", 106 | "\n", 107 | "for ccd, fname in ifc.ccds(return_fname=True):\n", 108 | " print(f'\\n{i} done 🎉, {n_images - i - 1} left\\n')\n", 109 | " i = i + 1\n", 110 | " print(f'\\tStarting to shift image {fname}...')\n", 111 | " outfile = p / fname\n", 112 | " if outfile.exists():\n", 113 | " print(f'\\t\\t...skipping because it is already shifted')\n", 114 | " continue\n", 115 | " \n", 116 | " if subtract_median:\n", 117 | " median = np.nanmedian(ccd.data)\n", 118 | " else:\n", 119 | " median = 0\n", 120 | " \n", 121 | " new_data, footprint = reproject_interp(ccd, wcs_ref, \n", 122 | " shape_out=ccd_ref.shape, order=1)\n", 123 | " ccd.data = new_data - median\n", 124 | " ccd.wcs = wcs_ref\n", 125 | " ccd.write(p / fname)\n", 126 | " print(f'\\t\\t...Finished shifting image')\n", 127 | "\n", 128 | "print('\\n🎉 All done ✨')" 129 | ] 130 | } 131 | ], 132 | "metadata": { 133 | "kernelspec": { 134 | "display_name": "Python 3 (ipykernel)", 135 | "language": "python", 136 | "name": "python3" 137 | }, 138 | "language_info": { 139 | "codemirror_mode": { 140 | "name": "ipython", 141 | "version": 3 142 | }, 143 | "file_extension": ".py", 144 | "mimetype": "text/x-python", 145 | "name": "python", 146 | "nbconvert_exporter": "python", 147 | "pygments_lexer": "ipython3", 148 | "version": "3.10.6" 149 | } 150 | }, 151 | "nbformat": 4, 152 | "nbformat_minor": 4 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # astronbs 2 | 3 | [![Github Actions Status](https://github.com/gmwcraig/astronbs/workflows/Build/badge.svg)](https://github.com/gmwcraig/astronbs/actions/workflows/build.yml) 4 | JupyterLab extension to place template notebooks in t current directory 5 | 6 | This extension is composed of a Python package named `astronbs` 7 | for the server extension and a NPM package named `astronbs` 8 | for the frontend extension. 9 | 10 | ## Requirements 11 | 12 | - JupyterLab >= 3.0 13 | 14 | ## Install 15 | 16 | To install the extension, execute: 17 | 18 | ```bash 19 | pip install astronbs 20 | ``` 21 | 22 | ## Uninstall 23 | 24 | To remove the extension, execute: 25 | 26 | ```bash 27 | pip uninstall astronbs 28 | ``` 29 | 30 | ## Troubleshoot 31 | 32 | If you are seeing the frontend extension, but it is not working, check 33 | that the server extension is enabled: 34 | 35 | ```bash 36 | jupyter server extension list 37 | ``` 38 | 39 | If the server extension is installed and enabled, but you are not seeing 40 | the frontend extension, check the frontend extension is installed: 41 | 42 | ```bash 43 | jupyter labextension list 44 | ``` 45 | 46 | ## Contributing 47 | 48 | ### Development install 49 | 50 | Note: You will need NodeJS to build the extension package. 51 | 52 | The `jlpm` command is JupyterLab's pinned version of 53 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 54 | `yarn` or `npm` in lieu of `jlpm` below. 55 | 56 | ```bash 57 | # Clone the repo to your local environment 58 | # Change directory to the astronbs directory 59 | # Install package in development mode 60 | pip install -e . 61 | # Link your development version of the extension with JupyterLab 62 | jupyter labextension develop . --overwrite 63 | # Server extension must be manually installed in develop mode 64 | jupyter server extension enable astronbs 65 | # Rebuild extension Typescript source after making changes 66 | jlpm build 67 | ``` 68 | 69 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. 70 | 71 | ```bash 72 | # Watch the source directory in one terminal, automatically rebuilding when needed 73 | jlpm watch 74 | # Run JupyterLab in another terminal 75 | jupyter lab 76 | ``` 77 | 78 | With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). 79 | 80 | By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: 81 | 82 | ```bash 83 | jupyter lab build --minimize=False 84 | ``` 85 | 86 | ### Development uninstall 87 | 88 | ```bash 89 | # Server extension must be manually disabled in develop mode 90 | jupyter server extension disable astronbs 91 | pip uninstall astronbs 92 | ``` 93 | 94 | In development mode, you will also need to remove the symlink created by `jupyter labextension develop` 95 | command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` 96 | folder is located. Then you can remove the symlink named `astronbs` within that folder. 97 | 98 | ### Testing the extension 99 | 100 | #### Server tests 101 | 102 | This extension is using [Pytest](https://docs.pytest.org/) for Python code testing. 103 | 104 | Install test dependencies (needed only once): 105 | 106 | ```sh 107 | pip install -e ".[test]" 108 | ``` 109 | 110 | To execute them, run: 111 | 112 | ```sh 113 | pytest -vv -r ap --cov astronbs 114 | ``` 115 | 116 | #### Frontend tests 117 | 118 | This extension is using [Jest](https://jestjs.io/) for JavaScript code testing. 119 | 120 | To execute them, execute: 121 | 122 | ```sh 123 | jlpm 124 | jlpm test 125 | ``` 126 | 127 | #### Integration tests 128 | 129 | This extension uses [Playwright](https://playwright.dev/docs/intro/) for the integration tests (aka user level tests). 130 | More precisely, the JupyterLab helper [Galata](https://github.com/jupyterlab/jupyterlab/tree/master/galata) is used to handle testing the extension in JupyterLab. 131 | 132 | More information are provided within the [ui-tests](./ui-tests/README.md) README. 133 | 134 | ### Packaging the extension 135 | 136 | See [RELEASE](RELEASE.md) 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astronbs", 3 | "version": "0.12.0", 4 | "description": "JupyterLab extension to place template notebooks in t current directory", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/mwcraig/astronbs", 11 | "bugs": { 12 | "url": "https://github.com/mwcraig/astronbs/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": { 16 | "name": "Matt Craig", 17 | "email": "mattwcraig@gmail.com" 18 | }, 19 | "files": [ 20 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 21 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}", 22 | "schema/*.json" 23 | ], 24 | "main": "lib/index.js", 25 | "types": "lib/index.d.ts", 26 | "style": "style/index.css", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/mwcraig/astronbs.git" 30 | }, 31 | "scripts": { 32 | "build": "jlpm build:lib && jlpm build:labextension:dev", 33 | "build:prod": "jlpm clean && jlpm build:lib && jlpm build:labextension", 34 | "build:labextension": "jupyter labextension build .", 35 | "build:labextension:dev": "jupyter labextension build --development True .", 36 | "build:lib": "tsc", 37 | "clean": "jlpm clean:lib", 38 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 39 | "clean:lintcache": "rimraf .eslintcache .stylelintcache", 40 | "clean:labextension": "rimraf astronbs/labextension", 41 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", 42 | "eslint": "jlpm eslint:check --fix", 43 | "eslint:check": "eslint . --cache --ext .ts,.tsx", 44 | "install:extension": "jlpm build", 45 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", 46 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", 47 | "prettier": "jlpm prettier:base --write --list-different", 48 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 49 | "prettier:check": "jlpm prettier:base --check", 50 | "stylelint": "jlpm stylelint:check --fix", 51 | "stylelint:check": "stylelint --cache \"style/**/*.css\"", 52 | "test": "jest --coverage", 53 | "watch": "run-p watch:src watch:labextension", 54 | "watch:src": "tsc -w", 55 | "watch:labextension": "jupyter labextension watch ." 56 | }, 57 | "dependencies": { 58 | "@jupyterlab/application": "^3.1.0", 59 | "@jupyterlab/settingregistry": "^3.1.0", 60 | "@jupyterlab/coreutils": "^5.1.0", 61 | "@jupyterlab/services": "^6.1.0", 62 | "@jupyterlab/launcher": "^3.1.0", 63 | "@jupyterlab/ui-components": "^3.1.0", 64 | "@jupyterlab/notebook": "^3.1.0", 65 | "@jupyterlab/docmanager": "^3.1.0" 66 | }, 67 | "devDependencies": { 68 | "@babel/core": "^7.0.0", 69 | "@babel/preset-env": "^7.0.0", 70 | "@jupyterlab/builder": "^3.1.0", 71 | "@jupyterlab/testutils": "^3.0.0", 72 | "@types/jest": "^26.0.0", 73 | "@typescript-eslint/eslint-plugin": "^4.8.1", 74 | "@typescript-eslint/parser": "^4.8.1", 75 | "eslint": "^7.14.0", 76 | "eslint-config-prettier": "^6.15.0", 77 | "eslint-plugin-prettier": "^3.1.4", 78 | "jest": "^26.0.0", 79 | "mkdirp": "^1.0.3", 80 | "npm-run-all": "^4.1.5", 81 | "prettier": "^2.1.1", 82 | "rimraf": "^3.0.2", 83 | "stylelint": "^14.3.0", 84 | "stylelint-config-prettier": "^9.0.3", 85 | "stylelint-config-recommended": "^6.0.0", 86 | "stylelint-config-standard": "~24.0.0", 87 | "stylelint-prettier": "^2.0.0", 88 | "typescript": "~4.1.3", 89 | "ts-jest": "^26.0.0" 90 | }, 91 | "sideEffects": [ 92 | "style/*.css", 93 | "style/index.js" 94 | ], 95 | "styleModule": "style/index.js", 96 | "publishConfig": { 97 | "access": "public" 98 | }, 99 | "jupyterlab": { 100 | "discovery": { 101 | "server": { 102 | "managers": [ 103 | "pip" 104 | ], 105 | "base": { 106 | "name": "astronbs" 107 | } 108 | } 109 | }, 110 | "extension": true, 111 | "outputDir": "astronbs/labextension", 112 | "schemaDir": "schema" 113 | }, 114 | "jupyter-releaser": { 115 | "hooks": { 116 | "before-build-npm": [ 117 | "python -m pip install jupyterlab~=3.1", 118 | "jlpm" 119 | ], 120 | "before-build-python": [ 121 | "jlpm clean:all" 122 | ] 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /astronbs/notebooks/light-image-combo-template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Light image combination template" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Click on a code cell (has grey background) then press Shift-Enter (at the same time) to run a code cell. That will add the controls (buttons, etc) you use to do the reduction one-by-one; then use them for reduction." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "from pathlib import Path\n", 24 | "\n", 25 | "import reducer.gui\n", 26 | "import reducer.astro_gui as astro_gui\n", 27 | "from reducer.image_browser import ImageBrowser\n", 28 | "\n", 29 | "from ccdproc import ImageFileCollection\n", 30 | "\n", 31 | "from reducer import __version__\n", 32 | "print(__version__)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "# Enter name of directory that contains your data in the cell below " 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "data_dir = 'shifted'\n", 49 | "destination_dir = 'combined'\n", 50 | "\n", 51 | "path = Path(\".\") / destination_dir\n", 52 | "path.mkdir(exist_ok=True)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "# Load your data set" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "images = ImageFileCollection(location=data_dir, keywords='*')" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": {}, 74 | "source": [ 75 | "# Image Summary\n", 76 | "\n", 77 | "## Includes browser and image/metadata viewer\n", 78 | "\n", 79 | "This is not, strictly speaking, part of reduction, but is a handy way to take a quick look at your files." 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "fits_browser = ImageBrowser(images, keys=['imagetyp', 'exposure'])\n", 89 | "fits_browser.display()" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "## Combine light images\n", 97 | "\n", 98 | "Again, note the presence of ``Group by``. If you typically use twilight flats you will almost certainly want to group by filter, not by filter and exposure." 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 108 | "\n", 109 | "lcom = astro_gui.Combiner(description=\"Combine light images\",\n", 110 | " toggle_type='button',\n", 111 | " file_name_base='combined_light',\n", 112 | " group_by='filter',\n", 113 | " image_source=images,\n", 114 | " apply_to={'imagetyp': 'light'},\n", 115 | " destination=destination_dir)\n", 116 | "lcom.display()" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "print(lcom)" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "## Look at the result: do the combined images look different?" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "reduced_browser = ImageBrowser(reduced_collection, keys=['imagetyp', 'exposure'])\n", 151 | "reduced_browser.display()" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [] 160 | } 161 | ], 162 | "metadata": { 163 | "kernelspec": { 164 | "display_name": "Python 3 (ipykernel)", 165 | "language": "python", 166 | "name": "python3" 167 | }, 168 | "language_info": { 169 | "codemirror_mode": { 170 | "name": "ipython", 171 | "version": 3 172 | }, 173 | "file_extension": ".py", 174 | "mimetype": "text/x-python", 175 | "name": "python", 176 | "nbconvert_exporter": "python", 177 | "pygments_lexer": "ipython3", 178 | "version": "3.10.6" 179 | } 180 | }, 181 | "nbformat": 4, 182 | "nbformat_minor": 4 183 | } 184 | -------------------------------------------------------------------------------- /astronbs/notebooks/color-mixer-template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "f96a32d6-562f-42e9-b990-8c9bf742be4a", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from matplotlib import pyplot as plt\n", 11 | "import matplotlib.image as mimg\n", 12 | "\n", 13 | "import ipywidgets as ipw\n", 14 | "import numpy as np\n", 15 | "\n", 16 | "from astropy.nddata import CCDData, block_reduce\n", 17 | "from astropy.visualization import AsymmetricPercentileInterval, make_lupton_rgb\n", 18 | "\n", 19 | "from astrowidgets.bqplot import ImageWidget" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "id": "e7eab765-6264-4e78-b5ff-5c283aa6eb21", 25 | "metadata": {}, 26 | "source": [ 27 | "# Another color image maker" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "id": "ecb25d48-34c8-42cd-a329-4a9d182223ca", 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "image_widgets = dict(\n", 38 | " red=ImageWidget(),\n", 39 | " green=ImageWidget(),\n", 40 | " blue=ImageWidget()\n", 41 | ")" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "4f0a1283-3099-4025-9738-c2f8e5b02982", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "red = CCDData.read('combined_light_filter_rp.fit')\n", 52 | "greenish = CCDData.read('combined_light_filter_gp.fit')\n", 53 | "blue = CCDData.read('combined_light_filter_B.fit')" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "id": "0265c0df-59da-4c61-ae18-aa4cb8d9299f", 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "reduce_fac = 8\n", 64 | "red_sm = block_reduce(red.data, reduce_fac)\n", 65 | "green_sm = block_reduce(greenish.data, reduce_fac)\n", 66 | "blue_sm = block_reduce(blue.data, reduce_fac)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "id": "ce154da2-28f6-49f3-a236-4256ec742fb9", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "image_widgets['red'].load_array(red_sm)\n", 77 | "image_widgets['green'].load_array(green_sm)\n", 78 | "image_widgets['blue'].load_array(blue_sm)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "id": "2da65382-e5e7-4ba7-aa91-180f2b12f8de", 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "def make_slider():\n", 89 | " slider = ipw.FloatRangeSlider(min=0, max=100, step=0.1, \n", 90 | " description='Set black and white',\n", 91 | " style={'description_width': 'initial'},\n", 92 | " continuous_update=False,\n", 93 | " layout={'width': '100%'}\n", 94 | " )\n", 95 | " return slider" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "e6f38c83-2953-4590-b5a2-d917873e49b0", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "level_sliders = dict(\n", 106 | " red=make_slider(),\n", 107 | " green=make_slider(),\n", 108 | " blue=make_slider()\n", 109 | ")" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "id": "2bd4b3a3-99f5-4ca2-8ad0-22da797de458", 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "def make_observer(color):\n", 120 | " def observer(change):\n", 121 | " minval, maxval = change['new']\n", 122 | " image_widgets[color].cuts = AsymmetricPercentileInterval(minval, maxval)\n", 123 | " \n", 124 | " return observer" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "id": "1b6d6815-6edd-4246-9e80-57fbbb314c7b", 130 | "metadata": {}, 131 | "source": [ 132 | "## 1. Adjust each of the combined image (r, g, b) so that the background is black and you can see the detail you want" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "id": "add69fe3-0270-4d82-9767-201218fe6320", 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "tab_set = ipw.Tab()\n", 143 | "kids = []\n", 144 | "boxes = {}\n", 145 | "for idx, color in enumerate(['red', 'green', 'blue']):\n", 146 | " boxes[color] = ipw.VBox(children=[level_sliders[color], image_widgets[color]])\n", 147 | " this_observer = make_observer(color)\n", 148 | " level_sliders[color].observe(this_observer, names='value')\n", 149 | " this_observer(dict(new=level_sliders[color].value))\n", 150 | " tab_set.set_title(idx, color)\n", 151 | " kids.append(boxes[color])\n", 152 | "\n", 153 | "\n", 154 | "tab_set.children = kids\n", 155 | "tab_set" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "7c26daf1-2eae-4f34-a348-f6010ef297dc", 161 | "metadata": {}, 162 | "source": [ 163 | "## Run the cells below to save red, green, blue as png" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "id": "445ed707-53de-4a1e-ab06-55081dfd32b7", 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "def save_png(x, color):\n", 174 | " shape = np.array(x.shape)\n", 175 | " # Reverse order for reasons I do not understand...\n", 176 | " shape = shape[::-1]\n", 177 | " # Replace NaNs with black pixels\n", 178 | " x = np.nan_to_num(x)\n", 179 | " mimg.imsave(f'{color}.png', x, format='png', cmap='gray')\n", 180 | " \n", 181 | "def save_all():\n", 182 | " x = image_widgets['red'].cuts(red.data)\n", 183 | " save_png(x, 'red')\n", 184 | " x = image_widgets['green'].cuts(greenish.data)\n", 185 | " save_png(x, 'green')\n", 186 | " x = image_widgets['blue'].cuts(blue.data)\n", 187 | " save_png(x, 'blue')" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "id": "65c0ff9b-d92c-4e20-9173-7da5057f6a56", 194 | "metadata": {}, 195 | "outputs": [], 196 | "source": [ 197 | "save_all()" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "id": "8786abfc-15a1-428f-8ed7-2640b55266df", 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "plt.figure(figsize=(10, 10))\n", 208 | "comb = np.zeros(list(red_sm.shape) + [3])\n", 209 | "def quick_color_rgb(r=0.5, g=0.5, b=0.5):\n", 210 | " red_sc = r * image_widgets['red'].cuts(red_sm) \n", 211 | " green_sc = g * image_widgets['green'].cuts(green_sm)\n", 212 | " blue_sc = b * image_widgets['blue'].cuts(blue_sm)\n", 213 | " comb[:, :, 0] = red_sc\n", 214 | " comb[:, :, 1] = green_sc\n", 215 | " comb[:, :, 2] = blue_sc\n", 216 | " plt.figure(figsize=(8, 8))\n", 217 | " max_img = np.nanmax(comb)\n", 218 | " plt.title(f'{max_img:.3f} {r=:.2f} {g=:.2f} {b=:.2f}')\n", 219 | " plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)\n", 220 | " plt.imshow(comb/max_img)" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "id": "313acf99-a290-42e2-af35-2eec5b9ef855", 226 | "metadata": { 227 | "tags": [] 228 | }, 229 | "source": [ 230 | "## Adjust the contribution of the red, green and blue images to the final image" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "id": "c83aec13-923f-4d54-b4d3-0d21aec27271", 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "ranges = (0, 1, 0.01)\n", 241 | "ipw.interact(quick_color_rgb, r=ranges, g=ranges, b=ranges)" 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": null, 247 | "id": "3604de8b-c0a3-41d2-9c60-da7e63e58f6d", 248 | "metadata": {}, 249 | "outputs": [], 250 | "source": [] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "id": "f29b53f2-c562-400e-8e2a-f5676d51e994", 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "def full_res(r, g, b):\n", 260 | " plt.figure(figsize=(20, 20))\n", 261 | " red_sc = r * image_widgets['red'].cuts(red.data) \n", 262 | " green_sc = g * image_widgets['green'].cuts(greenish.data)\n", 263 | " blue_sc = b * image_widgets['blue'].cuts(blue.data)\n", 264 | " comb = np.zeros(list(red.data.shape) + [3])\n", 265 | " comb[:, :, 0] = red_sc\n", 266 | " comb[:, :, 1] = green_sc\n", 267 | " comb[:, :, 2] = blue_sc\n", 268 | " max_img = np.nanmax(comb)\n", 269 | " plt.imshow(comb/max_img)\n", 270 | " plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "id": "2f5f3994-5a98-4e03-a71b-0957c33573bf", 277 | "metadata": {}, 278 | "outputs": [], 279 | "source": [ 280 | "full_res(.51, 0.38, 0.55)\n" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "id": "f9ad55c5-5d99-433a-abd8-cf065b474b45", 287 | "metadata": {}, 288 | "outputs": [], 289 | "source": [ 290 | "plt.savefig('color.png')" 291 | ] 292 | } 293 | ], 294 | "metadata": { 295 | "kernelspec": { 296 | "display_name": "Python 3 (ipykernel)", 297 | "language": "python", 298 | "name": "python3" 299 | }, 300 | "language_info": { 301 | "codemirror_mode": { 302 | "name": "ipython", 303 | "version": 3 304 | }, 305 | "file_extension": ".py", 306 | "mimetype": "text/x-python", 307 | "name": "python", 308 | "nbconvert_exporter": "python", 309 | "pygments_lexer": "ipython3", 310 | "version": "3.9.13" 311 | } 312 | }, 313 | "nbformat": 4, 314 | "nbformat_minor": 5 315 | } 316 | -------------------------------------------------------------------------------- /astronbs/handlers.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | import re 4 | from importlib.resources import files 5 | 6 | from jupyter_server.base.handlers import APIHandler 7 | from jupyter_server.utils import url_path_join 8 | import tornado 9 | 10 | 11 | def hello(nb_name, package=''): 12 | """ 13 | Return the contents of the hello world notebook. 14 | """ 15 | if not package: 16 | return files('astronbs').joinpath('notebooks').joinpath(nb_name).read_text() 17 | else: 18 | pieces = package.split('.') 19 | pkg_path = files(pieces[0]) 20 | for piece in pieces[1:]: 21 | pkg_path = pkg_path.joinpath(piece) 22 | return pkg_path.joinpath(nb_name).read_text() 23 | 24 | # class NBMaker: 25 | # def __init__(self, path): 26 | # self.path = Path(path) 27 | 28 | # def __call__(self, file): 29 | # f = Path(file) 30 | # ext = f.suffix() 31 | # stem = f.stem() 32 | # match = re.match(stem + '(\d*)' + ext) 33 | # if match: 34 | # digits = match.group(1) 35 | 36 | # pass 37 | 38 | class RouteHandler(APIHandler): 39 | # The following decorator should be present on all verb methods (head, get, post, 40 | # patch, put, delete, options) to ensure only authorized user can request the 41 | # Jupyter server 42 | @tornado.web.authenticated 43 | def get(self): 44 | p = Path('.') 45 | print(p.absolute()) 46 | self.finish(json.dumps({ 47 | "data": "This is /wooty-woot/get_example endpoint!" 48 | })) 49 | 50 | @tornado.web.authenticated 51 | def post(self): 52 | input_data = self.get_json_body() 53 | p = Path('.') 54 | nb_content = hello('reduction-template.ipynb') 55 | 56 | (p / input_data['path'] / 'reduction_template.ipynb').write_text(nb_content) 57 | response = { 58 | 'path': str(Path(input_data['path']) / 'reduction_template.ipynb'), 59 | 'content': '' 60 | } 61 | self.finish(json.dumps(response)) 62 | 63 | 64 | class RouteHandler2(APIHandler): 65 | # The following decorator should be present on all verb methods (head, get, post, 66 | # patch, put, delete, options) to ensure only authorized user can request the 67 | # Jupyter server 68 | @tornado.web.authenticated 69 | def get(self): 70 | p = Path('.') 71 | print(p.absolute()) 72 | self.finish(json.dumps({ 73 | "data": "This is /wooty-woot/get_example endpoint!" 74 | })) 75 | 76 | @tornado.web.authenticated 77 | def post(self): 78 | input_data = self.get_json_body() 79 | p = Path('.') 80 | nb_name = 'reprojection_template.ipynb' 81 | nb_content = hello(nb_name) 82 | 83 | (p / input_data['path'] / nb_name).write_text(nb_content) 84 | response = { 85 | 'path': str(Path(input_data['path']) / nb_name), 86 | 'content': '' 87 | } 88 | self.finish(json.dumps(response)) 89 | 90 | 91 | class RouteHandler3(APIHandler): 92 | # The following decorator should be present on all verb methods (head, get, post, 93 | # patch, put, delete, options) to ensure only authorized user can request the 94 | # Jupyter server 95 | @tornado.web.authenticated 96 | def get(self): 97 | p = Path('.') 98 | print(p.absolute()) 99 | self.finish(json.dumps({ 100 | "data": "This is /wooty-woot/get_example endpoint!" 101 | })) 102 | 103 | @tornado.web.authenticated 104 | def post(self): 105 | input_data = self.get_json_body() 106 | p = Path('.') 107 | nb_name = 'light-image-combo-template.ipynb' 108 | nb_content = hello(nb_name) 109 | 110 | (p / input_data['path'] / nb_name).write_text(nb_content) 111 | response = { 112 | 'path': str(Path(input_data['path']) / nb_name), 113 | 'content': '' 114 | } 115 | self.finish(json.dumps(response)) 116 | 117 | 118 | class RouteHandler10(APIHandler): 119 | # The following decorator should be present on all verb methods (head, get, post, 120 | # patch, put, delete, options) to ensure only authorized user can request the 121 | # Jupyter server 122 | @tornado.web.authenticated 123 | def get(self): 124 | p = Path('.') 125 | print(p.absolute()) 126 | self.finish(json.dumps({ 127 | "data": "This is /wooty-woot/get_example endpoint!" 128 | })) 129 | 130 | @tornado.web.authenticated 131 | def post(self): 132 | input_data = self.get_json_body() 133 | p = Path('.') 134 | nb_name = 'folder-viewer-template.ipynb' 135 | nb_content = hello(nb_name) 136 | 137 | (p / input_data['path'] / nb_name).write_text(nb_content) 138 | response = { 139 | 'path': str(Path(input_data['path']) / nb_name), 140 | 'content': '' 141 | } 142 | self.finish(json.dumps(response)) 143 | 144 | 145 | class RouteHandler11(APIHandler): 146 | # The following decorator should be present on all verb methods (head, get, post, 147 | # patch, put, delete, options) to ensure only authorized user can request the 148 | # Jupyter server 149 | @tornado.web.authenticated 150 | def get(self): 151 | p = Path('.') 152 | print(p.absolute()) 153 | self.finish(json.dumps({ 154 | "data": "This is /wooty-woot/get_example endpoint!" 155 | })) 156 | 157 | @tornado.web.authenticated 158 | def post(self): 159 | input_data = self.get_json_body() 160 | p = Path('.') 161 | nb_name = 'interactive-image-viewer.ipynb' 162 | nb_content = hello(nb_name) 163 | 164 | (p / input_data['path'] / nb_name).write_text(nb_content) 165 | response = { 166 | 'path': str(Path(input_data['path']) / nb_name), 167 | 'content': '' 168 | } 169 | self.finish(json.dumps(response)) 170 | 171 | 172 | class RouteHandler12(APIHandler): 173 | # The following decorator should be present on all verb methods (head, get, post, 174 | # patch, put, delete, options) to ensure only authorized user can request the 175 | # Jupyter server 176 | @tornado.web.authenticated 177 | def get(self): 178 | p = Path('.') 179 | print(p.absolute()) 180 | self.finish(json.dumps({ 181 | "data": "This is /wooty-woot/get_example endpoint!" 182 | })) 183 | 184 | @tornado.web.authenticated 185 | def post(self): 186 | input_data = self.get_json_body() 187 | p = Path('.') 188 | nb_name = 'quick-color-template.ipynb' 189 | nb_content = hello(nb_name) 190 | 191 | (p / input_data['path'] / nb_name).write_text(nb_content) 192 | response = { 193 | 'path': str(Path(input_data['path']) / nb_name), 194 | 'content': '' 195 | } 196 | self.finish(json.dumps(response)) 197 | 198 | 199 | class RouteHandler13(APIHandler): 200 | # The following decorator should be present on all verb methods (head, get, post, 201 | # patch, put, delete, options) to ensure only authorized user can request the 202 | # Jupyter server 203 | @tornado.web.authenticated 204 | def get(self): 205 | p = Path('.') 206 | print(p.absolute()) 207 | self.finish(json.dumps({ 208 | "data": "This is /wooty-woot/get_example endpoint!" 209 | })) 210 | 211 | @tornado.web.authenticated 212 | def post(self): 213 | input_data = self.get_json_body() 214 | p = Path('.') 215 | nb_name = 'color-mixer-template.ipynb' 216 | nb_content = hello(nb_name) 217 | 218 | (p / input_data['path'] / nb_name).write_text(nb_content) 219 | response = { 220 | 'path': str(Path(input_data['path']) / nb_name), 221 | 'content': '' 222 | } 223 | self.finish(json.dumps(response)) 224 | 225 | 226 | class RouteHandler4(APIHandler): 227 | # The following decorator should be present on all verb methods (head, get, post, 228 | # patch, put, delete, options) to ensure only authorized user can request the 229 | # Jupyter server 230 | @tornado.web.authenticated 231 | def get(self): 232 | p = Path('.') 233 | print(p.absolute()) 234 | self.finish(json.dumps({ 235 | "data": "This is /wooty-woot/get_example endpoint!" 236 | })) 237 | 238 | @tornado.web.authenticated 239 | def post(self): 240 | input_data = self.get_json_body() 241 | p = Path('.') 242 | nb_name = input_data['nb_name'] 243 | pkg_path = input_data['package_path'] 244 | nb_content = hello(nb_name, package=pkg_path) 245 | 246 | (p / input_data['path'] / nb_name).write_text(nb_content) 247 | response = { 248 | 'path': str(Path(input_data['path']) / nb_name), 249 | 'content': '' 250 | } 251 | self.finish(json.dumps(response)) 252 | 253 | 254 | def setup_handlers(web_app): 255 | host_pattern = ".*$" 256 | 257 | base_url = web_app.settings["base_url"] 258 | route_pattern = url_path_join(base_url, "astronbs", "reduction_template") 259 | route_pattern2 = url_path_join(base_url, "astronbs", "reprojection_template") 260 | route_pattern3 = url_path_join(base_url, "astronbs", "light_combo_template") 261 | route_pattern4 = url_path_join(base_url, "astronbs", "nb_make") 262 | route_pattern10 = url_path_join(base_url, "astronbs", "folder_viewer_template") 263 | route_pattern11 = url_path_join(base_url, "astronbs", "interactive_image_viewer") 264 | route_pattern12 = url_path_join(base_url, "astronbs", "quick_color_template") 265 | route_pattern13 = url_path_join(base_url, "astronbs", "color_mixer_template") 266 | 267 | handlers = [ 268 | (route_pattern, RouteHandler), 269 | (route_pattern2, RouteHandler2), 270 | (route_pattern3, RouteHandler3), 271 | (route_pattern4, RouteHandler4), 272 | (route_pattern10, RouteHandler10), 273 | (route_pattern11, RouteHandler11), 274 | (route_pattern12, RouteHandler12), 275 | (route_pattern13, RouteHandler13), 276 | ] 277 | web_app.add_handlers(host_pattern, handlers) 278 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin 4 | } from '@jupyterlab/application'; 5 | 6 | import { ISettingRegistry } from '@jupyterlab/settingregistry'; 7 | import { IFileBrowserFactory } from '@jupyterlab/filebrowser'; 8 | import { IDocumentManager } from '@jupyterlab/docmanager'; 9 | import { ILauncher } from '@jupyterlab/launcher'; 10 | import { imageIcon } from '@jupyterlab/ui-components'; 11 | 12 | import { requestAPI } from './handler'; 13 | 14 | /** 15 | * Initialization data for the astronbs extension. 16 | */ 17 | const plugin: JupyterFrontEndPlugin = { 18 | id: 'astronbs:plugin', 19 | autoStart: true, 20 | optional: [ 21 | ISettingRegistry, 22 | ILauncher, 23 | IFileBrowserFactory, 24 | IDocumentManager 25 | ], 26 | activate: ( 27 | app: JupyterFrontEnd, 28 | settingRegistry: ISettingRegistry | null, 29 | launcher: ILauncher | null, 30 | fileBrowser: IFileBrowserFactory, 31 | docManager: IDocumentManager | null 32 | ) => { 33 | console.log('JupyterLab extension astronbs is activated!'); 34 | 35 | if (settingRegistry) { 36 | settingRegistry 37 | .load(plugin.id) 38 | .then(settings => { 39 | console.log('astronbs settings loaded:', settings.composite); 40 | }) 41 | .catch(reason => { 42 | console.error('Failed to load settings for astronbs.', reason); 43 | }); 44 | } 45 | 46 | app.commands.addCommand('astronbs:reduction_template', { 47 | // code to run when this command is executed 48 | execute: () => { 49 | const reply = requestAPI('reduction_template', { 50 | body: JSON.stringify({ 51 | path: fileBrowser.defaultBrowser.model.path, 52 | package_path: 'astronbs.notebooks', 53 | nb_name: 'reduction_template.ipynb' 54 | }), 55 | method: 'POST' 56 | }); 57 | console.log(reply); 58 | reply.then(data => { 59 | console.log(data); 60 | if (docManager) { 61 | docManager.open(data['path']); 62 | } 63 | }); 64 | }, 65 | icon: imageIcon, 66 | label: 'Reduction Template' 67 | }); 68 | app.commands.addCommand('astronbs:reprojection_template', { 69 | // code to run when this command is executed 70 | execute: () => { 71 | const reply = requestAPI('reprojection_template', { 72 | body: JSON.stringify({ path: fileBrowser.defaultBrowser.model.path }), 73 | method: 'POST' 74 | }); 75 | console.log(reply); 76 | reply.then(data => { 77 | console.log(data); 78 | if (docManager) { 79 | docManager.open(data['path']); 80 | } 81 | }); 82 | }, 83 | icon: imageIcon, 84 | label: 'Reprojection Template' 85 | }); 86 | 87 | app.commands.addCommand('astronbs:light_combo_template', { 88 | // code to run when this command is executed 89 | execute: () => { 90 | const reply = requestAPI('light_combo_template', { 91 | body: JSON.stringify({ path: fileBrowser.defaultBrowser.model.path }), 92 | method: 'POST' 93 | }); 94 | console.log('I am back in open2'); 95 | console.log(reply); 96 | reply.then(data => { 97 | console.log(data); 98 | if (docManager) { 99 | docManager.open(data['path']); 100 | } 101 | }); 102 | }, 103 | icon: imageIcon, 104 | label: 'Light Combo Template' 105 | }); 106 | 107 | app.commands.addCommand('astronbs:folder_viewer_template', { 108 | // code to run when this command is executed 109 | execute: () => { 110 | const reply = requestAPI('folder_viewer_template', { 111 | body: JSON.stringify({ path: fileBrowser.defaultBrowser.model.path }), 112 | method: 'POST' 113 | }); 114 | console.log('I am back in open15'); 115 | console.log(reply); 116 | reply.then(data => { 117 | console.log(data); 118 | if (docManager) { 119 | docManager.open(data['path']); 120 | } 121 | }); 122 | }, 123 | icon: imageIcon, 124 | label: 'Folder Image Viewer' 125 | }); 126 | 127 | app.commands.addCommand('astronbs:interactive_image_viewer', { 128 | // code to run when this command is executed 129 | execute: () => { 130 | const reply = requestAPI('interactive_image_viewer', { 131 | body: JSON.stringify({ path: fileBrowser.defaultBrowser.model.path }), 132 | method: 'POST' 133 | }); 134 | console.log('I am back in open11'); 135 | console.log(reply); 136 | reply.then(data => { 137 | console.log(data); 138 | if (docManager) { 139 | docManager.open(data['path']); 140 | } 141 | }); 142 | }, 143 | icon: imageIcon, 144 | label: 'Interactive Image Viewer' 145 | }); 146 | 147 | app.commands.addCommand('astronbs:quick_color_template', { 148 | // code to run when this command is executed 149 | execute: () => { 150 | const reply = requestAPI('quick_color_template', { 151 | body: JSON.stringify({ path: fileBrowser.defaultBrowser.model.path }), 152 | method: 'POST' 153 | }); 154 | console.log('I am back in open11'); 155 | console.log(reply); 156 | reply.then(data => { 157 | console.log(data); 158 | if (docManager) { 159 | docManager.open(data['path']); 160 | } 161 | }); 162 | }, 163 | icon: imageIcon, 164 | label: 'Quick Color Image' 165 | }); 166 | 167 | app.commands.addCommand('astronbs:color_mixer_template', { 168 | // code to run when this command is executed 169 | execute: () => { 170 | const reply = requestAPI('color_mixer_template', { 171 | body: JSON.stringify({ path: fileBrowser.defaultBrowser.model.path }), 172 | method: 'POST' 173 | }); 174 | console.log('I am back in open11'); 175 | console.log(reply); 176 | reply.then(data => { 177 | console.log(data); 178 | if (docManager) { 179 | docManager.open(data['path']); 180 | } 181 | }); 182 | }, 183 | icon: imageIcon, 184 | label: 'Color Mixer' 185 | }); 186 | 187 | app.commands.addCommand('astronbs:01_seeing_profile', { 188 | // code to run when this command is executed 189 | execute: () => { 190 | const reply = requestAPI('nb_make', { 191 | body: JSON.stringify({ 192 | path: fileBrowser.defaultBrowser.model.path, 193 | package_path: 'stellarphot.notebooks.photometry', 194 | nb_name: '01-viewer-seeing-template.ipynb' 195 | }), 196 | method: 'POST' 197 | }); 198 | console.log(reply); 199 | reply.then(data => { 200 | console.log(data); 201 | if (docManager) { 202 | docManager.open(data['path']); 203 | } 204 | }); 205 | }, 206 | icon: imageIcon, 207 | label: '01 Seeing Profile' 208 | }); 209 | 210 | app.commands.addCommand('astronbs:02_comp_stars', { 211 | // code to run when this command is executed 212 | execute: () => { 213 | const reply = requestAPI('nb_make', { 214 | body: JSON.stringify({ 215 | path: fileBrowser.defaultBrowser.model.path, 216 | package_path: 'stellarphot.notebooks.photometry', 217 | nb_name: '02-comp-star-plotter-template.ipynb' 218 | }), 219 | method: 'POST' 220 | }); 221 | console.log(reply); 222 | reply.then(data => { 223 | console.log(data); 224 | if (docManager) { 225 | docManager.open(data['path']); 226 | } 227 | }); 228 | }, 229 | icon: imageIcon, 230 | label: '02 Comparison Stars' 231 | }); 232 | 233 | app.commands.addCommand('astronbs:03_do_photometry', { 234 | // code to run when this command is executed 235 | execute: () => { 236 | const reply = requestAPI('nb_make', { 237 | body: JSON.stringify({ 238 | path: fileBrowser.defaultBrowser.model.path, 239 | package_path: 'stellarphot.notebooks.photometry', 240 | nb_name: '03-photometry-template.ipynb' 241 | }), 242 | method: 'POST' 243 | }); 244 | console.log(reply); 245 | reply.then(data => { 246 | console.log(data); 247 | if (docManager) { 248 | docManager.open(data['path']); 249 | } 250 | }); 251 | }, 252 | icon: imageIcon, 253 | label: '03 Do photometry' 254 | }); 255 | 256 | app.commands.addCommand('astronbs:04_transform_pared_back', { 257 | // code to run when this command is executed 258 | execute: () => { 259 | const reply = requestAPI('nb_make', { 260 | body: JSON.stringify({ 261 | path: fileBrowser.defaultBrowser.model.path, 262 | package_path: 'stellarphot.notebooks.photometry', 263 | nb_name: '04-transform-pared-back.ipynb' 264 | }), 265 | method: 'POST' 266 | }); 267 | console.log(reply); 268 | reply.then(data => { 269 | console.log(data); 270 | if (docManager) { 271 | docManager.open(data['path']); 272 | } 273 | }); 274 | }, 275 | icon: imageIcon, 276 | label: '04 Transform photometry' 277 | }); 278 | 279 | app.commands.addCommand('astronbs:05_relative_flux', { 280 | // code to run when this command is executed 281 | execute: () => { 282 | const reply = requestAPI('nb_make', { 283 | body: JSON.stringify({ 284 | path: fileBrowser.defaultBrowser.model.path, 285 | package_path: 'stellarphot.notebooks.photometry', 286 | nb_name: '05-relative-flux-calculation-template.ipynb' 287 | }), 288 | method: 'POST' 289 | }); 290 | console.log(reply); 291 | reply.then(data => { 292 | console.log(data); 293 | if (docManager) { 294 | docManager.open(data['path']); 295 | } 296 | }); 297 | }, 298 | icon: imageIcon, 299 | label: '05 Relative flux' 300 | }); 301 | 302 | app.commands.addCommand('astronbs:06_transit_fitting', { 303 | // code to run when this command is executed 304 | execute: () => { 305 | const reply = requestAPI('nb_make', { 306 | body: JSON.stringify({ 307 | path: fileBrowser.defaultBrowser.model.path, 308 | package_path: 'stellarphot.notebooks.photometry', 309 | nb_name: '06-transit-fit-template.ipynb' 310 | }), 311 | method: 'POST' 312 | }); 313 | console.log(reply); 314 | reply.then(data => { 315 | console.log(data); 316 | if (docManager) { 317 | docManager.open(data['path']); 318 | } 319 | }); 320 | }, 321 | icon: imageIcon, 322 | label: '06 Transit fitting' 323 | }); 324 | 325 | app.commands.addCommand('astronbs:07_transit_fitting_exotic', { 326 | // code to run when this command is executed 327 | execute: () => { 328 | const reply = requestAPI('nb_make', { 329 | body: JSON.stringify({ 330 | path: fileBrowser.defaultBrowser.model.path, 331 | package_path: 'stellarphot.notebooks.photometry', 332 | nb_name: '07-transit-fit-with-exotic.ipynb' 333 | }), 334 | method: 'POST' 335 | }); 336 | console.log(reply); 337 | reply.then(data => { 338 | console.log(data); 339 | if (docManager) { 340 | docManager.open(data['path']); 341 | } 342 | }); 343 | }, 344 | icon: imageIcon, 345 | label: '07 Transit fitting EXOTIC' 346 | }); 347 | 348 | // Add item to launcher 349 | if (launcher) { 350 | launcher.add({ 351 | command: 'astronbs:reduction_template', 352 | category: 'Astro', 353 | rank: 0 354 | }); 355 | 356 | launcher.add({ 357 | command: 'astronbs:reprojection_template', 358 | category: 'Astro', 359 | rank: 10 360 | }); 361 | 362 | launcher.add({ 363 | command: 'astronbs:light_combo_template', 364 | category: 'Astro', 365 | rank: 20 366 | }); 367 | 368 | launcher.add({ 369 | command: 'astronbs:folder_viewer_template', 370 | category: 'Astro', 371 | rank: 30 372 | }); 373 | 374 | launcher.add({ 375 | command: 'astronbs:interactive_image_viewer', 376 | category: 'Astro', 377 | rank: 40 378 | }); 379 | 380 | launcher.add({ 381 | command: 'astronbs:quick_color_template', 382 | category: 'Astro', 383 | rank: 50 384 | }); 385 | 386 | launcher.add({ 387 | command: 'astronbs:color_mixer_template', 388 | category: 'Astro', 389 | rank: 60 390 | }); 391 | 392 | launcher.add({ 393 | command: 'astronbs:01_seeing_profile', 394 | category: 'Photometry', 395 | rank: 0 396 | }); 397 | launcher.add({ 398 | command: 'astronbs:02_comp_stars', 399 | category: 'Photometry', 400 | rank: 0 401 | }); 402 | launcher.add({ 403 | command: 'astronbs:03_do_photometry', 404 | category: 'Photometry', 405 | rank: 0 406 | }); 407 | launcher.add({ 408 | command: 'astronbs:04_transform_pared_back', 409 | category: 'Photometry', 410 | rank: 0 411 | }); 412 | launcher.add({ 413 | command: 'astronbs:05_relative_flux', 414 | category: 'Photometry', 415 | rank: 0 416 | }); 417 | launcher.add({ 418 | command: 'astronbs:06_transit_fitting', 419 | category: 'Photometry', 420 | rank: 0 421 | }); 422 | launcher.add({ 423 | command: 'astronbs:07_transit_fitting_exotic', 424 | category: 'Photometry', 425 | rank: 0 426 | }); 427 | } 428 | 429 | requestAPI('get_example') 430 | .then(data => { 431 | console.log(data); 432 | }) 433 | .catch(reason => { 434 | console.error( 435 | `The astronbs server extension appears to be missing.\n${reason}` 436 | ); 437 | }); 438 | } 439 | }; 440 | 441 | export default plugin; 442 | -------------------------------------------------------------------------------- /astronbs/notebooks/reduction-template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Reducer: (Put your name here)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Reviewer: (Put your name here)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "# jupyter notebook crash course" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "Click on a code cell (has grey background) then press Shift-Enter (at the same time) to run a code cell. That will add the controls (buttons, etc) you use to do the reduction one-by-one; then use them for reduction." 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "from pathlib import Path\n", 38 | "\n", 39 | "from ipyautoui.custom import FileChooser\n", 40 | "import ipywidgets as ipw\n", 41 | "\n", 42 | "import reducer.gui\n", 43 | "import reducer.astro_gui as astro_gui\n", 44 | "from reducer.image_browser import ImageBrowser\n", 45 | "\n", 46 | "from ccdproc import ImageFileCollection\n", 47 | "\n", 48 | "from reducer import __version__\n", 49 | "print(__version__)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "# Enter name of directory that contains your data in the cell below, " 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "select_uncalibrated_data = FileChooser(show_only_dirs=True)\n", 66 | "select_uncalibrated_data" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "data_dir = select_uncalibrated_data.value\n", 76 | "print(f'Uncalibrated data is in {data_dir}')" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "## Your calibrated images will go into a folder called \"reduced\"" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": null, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "destination_dir = 'reduced'\n", 93 | "\n", 94 | "path = Path(\".\") / destination_dir\n", 95 | "path.mkdir(exist_ok=True)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "# Type any comments about this dataset here\n", 103 | "\n", 104 | "Double-click on the cell to start editing it." 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "# Load your data set" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "images = ImageFileCollection(location=data_dir, keywords='*')" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "# Image Summary\n", 128 | "\n", 129 | "## Includes browser and image/metadata viewer\n", 130 | "\n", 131 | "This is not, strictly speaking, part of reduction, but is a handy way to take a quick look at your files." 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "fits_browser = ImageBrowser(images, keys=['imagetyp', 'exposure'])\n", 141 | "fits_browser.display()" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "# Check names of image types" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "metadata": {}, 155 | "outputs": [], 156 | "source": [ 157 | "input_imagetypes = sorted(set(images.summary[\"imagetyp\"]))\n", 158 | "types = [\"bias\", \"dark\", \"flat\", \"light\"]\n", 159 | "\n", 160 | "widgets = {}\n", 161 | "for ty in types:\n", 162 | " match_types = [in_im for in_im in input_imagetypes if ty in in_im.lower()]\n", 163 | " widgets[ty] = ipw.Dropdown(description=ty.capitalize(), options=input_imagetypes)\n", 164 | " widgets[ty].value = match_types[0] if match_types else input_imagetypes[0]\n", 165 | " \n", 166 | "title = ipw.HTML(value=\"Choose the keyword in your images for each type\")\n", 167 | "vb = ipw.VBox(children=[title] + list(widgets.values()))\n", 168 | "vb\n" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": null, 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "imagetype_map = {k: v.value for k, v in widgets.items()}" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "imagetype_map" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": { 192 | "tags": [] 193 | }, 194 | "source": [ 195 | "# Make a combined bias image" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "## Reduce the bias images" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": {}, 209 | "outputs": [], 210 | "source": [ 211 | "bias_reduction = astro_gui.Reduction(description='Reduce bias frames',\n", 212 | " toggle_type='button',\n", 213 | " allow_bias=False,\n", 214 | " allow_dark=False,\n", 215 | " allow_flat=False,\n", 216 | " input_image_collection=images,\n", 217 | " imagetype_map=imagetype_map,\n", 218 | " apply_to={'imagetyp': 'bias'},\n", 219 | " destination=destination_dir)\n", 220 | "bias_reduction.display()" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": null, 226 | "metadata": {}, 227 | "outputs": [], 228 | "source": [ 229 | "print(bias_reduction)" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "## Combine bias images to make combined bias" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": null, 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 246 | "bias = astro_gui.Combiner(description=\"Combined Bias Settings\",\n", 247 | " toggle_type='button',\n", 248 | " file_name_base='combined_bias',\n", 249 | " image_source=reduced_collection,\n", 250 | " imagetype_map=imagetype_map,\n", 251 | " apply_to={'imagetyp': 'bias'},\n", 252 | " destination=destination_dir)\n", 253 | "bias.display()" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "metadata": {}, 260 | "outputs": [], 261 | "source": [ 262 | "print(bias)" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "# Make a combined dark" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "## Reduce dark images" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 286 | "dark_reduction = astro_gui.Reduction(description='Reduce dark frames',\n", 287 | " toggle_type='button',\n", 288 | " allow_bias=True,\n", 289 | " master_source=reduced_collection,\n", 290 | " allow_dark=False,\n", 291 | " allow_flat=False,\n", 292 | " input_image_collection=images,\n", 293 | " imagetype_map=imagetype_map,\n", 294 | " destination=destination_dir,\n", 295 | " apply_to={'imagetyp': 'dark'})\n", 296 | "\n", 297 | "dark_reduction.display()" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": null, 303 | "metadata": {}, 304 | "outputs": [], 305 | "source": [ 306 | "print(dark_reduction)" 307 | ] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "metadata": {}, 312 | "source": [ 313 | "## Combine reduced darks\n", 314 | "\n", 315 | "Note the ``Group by`` option in the controls that appear after you execute the cell below. `reducer` will make a master for each value of the FITS keyword listed in ``Group by``. By default this keyword is named ``exposure`` for darks, so if you have darks with exposure times of 10 sec, 15 sec and 120 sec you will get three master darks, one for each exposure time." 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": null, 321 | "metadata": {}, 322 | "outputs": [], 323 | "source": [ 324 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 325 | "\n", 326 | "dark = astro_gui.Combiner(description=\"Make Combined Dark(s)\",\n", 327 | " toggle_type='button',\n", 328 | " file_name_base='combined_dark',\n", 329 | " group_by='exposure',\n", 330 | " image_source=reduced_collection,\n", 331 | " imagetype_map=imagetype_map,\n", 332 | " apply_to={'imagetyp': 'dark'},\n", 333 | " destination=destination_dir)\n", 334 | "dark.display()" 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": null, 340 | "metadata": {}, 341 | "outputs": [], 342 | "source": [ 343 | "print(dark)" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | "# Make combined flats" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "metadata": {}, 356 | "source": [ 357 | "## Reduce flat images" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": null, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 367 | "flat_reduction = astro_gui.Reduction(description='Reduce flat frames',\n", 368 | " toggle_type='button',\n", 369 | " allow_bias=True,\n", 370 | " master_source=reduced_collection,\n", 371 | " allow_dark=True,\n", 372 | " allow_flat=False,\n", 373 | " input_image_collection=images,\n", 374 | " imagetype_map=imagetype_map,\n", 375 | " destination=destination_dir,\n", 376 | " apply_to={'imagetyp': 'flat'})\n", 377 | "\n", 378 | "flat_reduction.display()" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": null, 384 | "metadata": {}, 385 | "outputs": [], 386 | "source": [ 387 | "print(flat_reduction)" 388 | ] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "metadata": {}, 393 | "source": [ 394 | "## Build combined flats\n", 395 | "\n", 396 | "Again, note the presence of ``Group by``. If you typically use twilight flats you will almost certainly want to group by filter, not by filter and exposure." 397 | ] 398 | }, 399 | { 400 | "cell_type": "code", 401 | "execution_count": null, 402 | "metadata": {}, 403 | "outputs": [], 404 | "source": [ 405 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 406 | "\n", 407 | "flat = astro_gui.Combiner(description=\"Make Combined Flat(s)\",\n", 408 | " toggle_type='button',\n", 409 | " file_name_base='combined_flat',\n", 410 | " group_by='filter',\n", 411 | " image_source=reduced_collection,\n", 412 | " imagetype_map=imagetype_map,\n", 413 | " apply_to={'imagetyp': 'flat'},\n", 414 | " destination=destination_dir)\n", 415 | "flat.display()" 416 | ] 417 | }, 418 | { 419 | "cell_type": "code", 420 | "execution_count": null, 421 | "metadata": {}, 422 | "outputs": [], 423 | "source": [ 424 | "print(flat)" 425 | ] 426 | }, 427 | { 428 | "cell_type": "markdown", 429 | "metadata": {}, 430 | "source": [ 431 | "# Reduce the science images\n", 432 | "\n", 433 | "There is some autmatic matching going on here:\n", 434 | "\n", 435 | "+ If darks are subtracted a dark of the same edxposure time will be used, if available. If not, and dark scaling is enabled, the dark with the closest exposure time will be scaled to match the science image.\n", 436 | "+ If the dark you want to scale appears not to be bias-subtracted an error will be raised.\n", 437 | "+ Flats are matched by filter." 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": null, 443 | "metadata": {}, 444 | "outputs": [], 445 | "source": [ 446 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')\n", 447 | "light_reduction = astro_gui.Reduction(description='Reduce light frames',\n", 448 | " toggle_type='button',\n", 449 | " allow_cosmic_ray=True,\n", 450 | " master_source=reduced_collection,\n", 451 | " input_image_collection=images,\n", 452 | " imagetype_map=imagetype_map,\n", 453 | " destination=destination_dir,\n", 454 | " apply_to={'imagetyp': 'light'})\n", 455 | "\n", 456 | "light_reduction.display()" 457 | ] 458 | }, 459 | { 460 | "cell_type": "markdown", 461 | "metadata": {}, 462 | "source": [ 463 | "## Wonder what the reduced images look like? Make another image browser..." 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": null, 469 | "metadata": {}, 470 | "outputs": [], 471 | "source": [ 472 | "reduced_collection = ImageFileCollection(location=destination_dir, keywords='*')" 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "execution_count": null, 478 | "metadata": {}, 479 | "outputs": [], 480 | "source": [ 481 | "reduced_browser = ImageBrowser(reduced_collection, keys=['imagetyp', 'filter'])\n", 482 | "reduced_browser.display()" 483 | ] 484 | }, 485 | { 486 | "cell_type": "code", 487 | "execution_count": null, 488 | "metadata": {}, 489 | "outputs": [], 490 | "source": [] 491 | } 492 | ], 493 | "metadata": { 494 | "kernelspec": { 495 | "display_name": "Python 3 (ipykernel)", 496 | "language": "python", 497 | "name": "python3" 498 | }, 499 | "language_info": { 500 | "codemirror_mode": { 501 | "name": "ipython", 502 | "version": 3 503 | }, 504 | "file_extension": ".py", 505 | "mimetype": "text/x-python", 506 | "name": "python", 507 | "nbconvert_exporter": "python", 508 | "pygments_lexer": "ipython3", 509 | "version": "3.10.6" 510 | } 511 | }, 512 | "nbformat": 4, 513 | "nbformat_minor": 4 514 | } 515 | --------------------------------------------------------------------------------