├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github └── workflows │ ├── anaconda-build.yaml │ ├── pypi-build.yaml │ └── unittest.yaml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── docs ├── Makefile ├── Pytplot HTML Tutorial.ipynb ├── make.bat ├── pytplot_tutorial.html ├── source │ ├── _images │ │ ├── 2d.png │ │ ├── 3d.png │ │ ├── altitude.html │ │ ├── extra_x_axes.png │ │ ├── gui.png │ │ ├── interactivity.png │ │ ├── map.html │ │ ├── sample.html │ │ ├── sample.png │ │ ├── spec_slicer.png │ │ └── specslicer.html │ ├── conf.py │ ├── helper_functions.rst │ ├── index.rst │ ├── interactivity.rst │ ├── introduction.rst │ ├── linking.rst │ ├── math_routines.rst │ ├── options.rst │ ├── plotting.rst │ ├── reading_in_data.rst │ ├── tplot_options.rst │ └── tplot_variables.rst └── test_data.tplot ├── meta.yaml ├── pyqtgraphtestertemp.py ├── pytplot ├── AncillaryPlots │ ├── __init__.py │ ├── position_mars_2d.py │ ├── position_mars_3d.py │ └── spec_slicer.py ├── HTMLPlotter │ ├── CustomModels │ │ ├── __init__.py │ │ ├── colorbarsidetitle.py │ │ └── timestamp.py │ ├── TVarFigure1D.py │ ├── TVarFigureAlt.py │ ├── TVarFigureMap.py │ ├── TVarFigureSpec.py │ ├── __init__.py │ └── generate.py ├── QtPlotter │ ├── CustomAxis │ │ ├── AxisItem.py │ │ ├── BlankAxis.py │ │ ├── DateAxis.py │ │ ├── NonLinearAxis.py │ │ └── __init__.py │ ├── CustomImage │ │ ├── ColorbarImage.py │ │ ├── UpdatingImage.py │ │ └── __init__.py │ ├── CustomLegend │ │ ├── CustomLegend.py │ │ └── __init__.py │ ├── CustomLinearRegionItem │ │ └── CustomLinearRegionItem.py │ ├── CustomViewBox │ │ ├── CustomVB.py │ │ ├── NoPaddingPlot.py │ │ └── __init__.py │ ├── PyTPlot_Exporter.py │ ├── TVarFigure1D.py │ ├── TVarFigureAlt.py │ ├── TVarFigureAxisOnly.py │ ├── TVarFigureMap.py │ ├── TVarFigureSpec.py │ ├── __init__.py │ └── generate.py ├── __init__.py ├── del_data.py ├── exporters │ ├── __init__.py │ ├── tplot_ascii.py │ └── tplot_save.py ├── get_data.py ├── get_timespan.py ├── get_ylimits.py ├── importers │ ├── __init__.py │ ├── cdf_to_tplot.py │ ├── netcdf_to_tplot.py │ ├── sts_to_tplot.py │ └── tplot_restore.py ├── link.py ├── options.py ├── replace_data.py ├── sampledata │ └── test_data.tplot ├── spedas_colorbar.py ├── store_data.py ├── timebar.py ├── timespan.py ├── timestamp.py ├── tlimit.py ├── tplot.py ├── tplot_copy.py ├── tplot_math │ ├── __init__.py │ ├── add.py │ ├── add_across.py │ ├── avg_res_data.py │ ├── clip.py │ ├── crop.py │ ├── deflag.py │ ├── degap.py │ ├── derive.py │ ├── divide.py │ ├── examples.py │ ├── flatten.py │ ├── interp_nan.py │ ├── join_vec.py │ ├── multiply.py │ ├── pwr_spec.py │ ├── resample.py │ ├── spec_mult.py │ ├── split_vec.py │ ├── subtract.py │ └── tinterp.py ├── tplot_names.py ├── tplot_options.py ├── tplot_rename.py ├── tplot_utilities.py ├── version.txt ├── xlim.py ├── ylim.py └── zlim.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── test_alt_plot.py ├── test_cdf_to_tplot.py ├── test_maps.py ├── test_netcdf_to_tplot.py ├── test_qt_import.py ├── test_tplot_math.py └── testfiles ├── g15_xrs_2s_20170619_20170619.nc ├── mvn_euv_l2_bands_20170619_v09_r03.cdf └── mvn_swe_l2_svyspec_20170619_v04_r04.cdf /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # OS 2 | ARG VARIANT=bullseye 3 | FROM --platform=linux/amd64 mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} 4 | RUN sudo apt-get update 5 | RUN sudo apt-get install -y libgtk-3-dev 6 | RUN sudo apt-get install -y libxcb-xinerama0 7 | RUN sudo apt-get install -y libxcb-render-util0 8 | RUN sudo apt-get install -y libxcb-randr0 9 | RUN sudo apt-get install -y libxcb-shape0 10 | RUN sudo apt-get install -y libxcb-icccm4 11 | RUN sudo apt-get install -y libxcb-image0 12 | RUN sudo apt-get install -y libxcb-keysyms1 13 | RUN sudo apt-get install -y libxkbcommon-x11-0 14 | RUN sudo apt-get install -y python3 15 | RUN sudo apt-get install -y python3-pip 16 | RUN pip install pyspedas 17 | RUN pip install numpy>=1.20.0 18 | RUN pip install "bokeh>=1.1,<3.0" 19 | RUN pip install pyqtgraph>=0.11.1 20 | RUN pip install pandas 21 | RUN pip install matplotlib 22 | RUN pip install scipy 23 | RUN pip install cdflib 24 | RUN pip install xarray 25 | RUN pip install pyqt5>=5.15.2 26 | RUN pip install pyqtwebengine>=5.15.2 27 | # The libraries below are not needed for pytplot to work as is, but are listed as requirements for PyQT5 28 | # If things don't work, maybe try installing these 29 | #RUN sudo apt-get install -y libfontconfig1 30 | #RUN sudo apt-get install -y libfreetype6 31 | #RUN sudo apt-get install -y libxext6 32 | #RUN sudo apt-get install -y libxcb1 33 | #RUN sudo apt-get install -y libx11-6 34 | #RUN sudo apt-get install -y libice6 35 | #RUN sudo apt-get install -y libglib2.0-0 36 | #RUN sudo apt-get install -y libpthread-stubs0-dev 37 | #RUN sudo apt-get install -y libsm6 38 | #RUN sudo apt-get install -y libxcb-util-dev 39 | #RUN sudo apt-get install -y libxrender-dev 40 | #RUN sudo apt-get install -y libxcb-render0 41 | #RUN sudo apt-get install -y libxkbcommon0 42 | #RUN sudo apt-get install -y libxcb-sync1 43 | #RUN sudo apt-get install -y libxcb-xfixes0 44 | #RUN sudo apt-get install -y libxcb-shm0 -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hello", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "args": { 6 | "VARIANT": "bullseye" 7 | } 8 | }, 9 | "features": { 10 | "ghcr.io/devcontainers/features/desktop-lite:1": { 11 | "version": "latest" 12 | } 13 | }, 14 | "forwardPorts": [6080] 15 | } -------------------------------------------------------------------------------- /.github/workflows/anaconda-build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install dependencies 12 | run: | 13 | cd $HOME 14 | # wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh 15 | wget https://repo.anaconda.com/miniconda/Miniconda3-py38_4.10.3-Linux-x86_64.sh -O miniconda.sh 16 | chmod +x miniconda.sh 17 | bash miniconda.sh -b -p $HOME/miniconda 18 | export PATH="$HOME/miniconda/bin:$PATH" 19 | sudo apt-get -y install libgl1-mesa-glx 20 | sudo apt-get -y install libsm6 libxrender1 libfontconfig1 libxcomposite-dev libxcursor1 libxi6 libxtst6 libxss1 libxrandr2 libasound2 libegl1 21 | sudo apt-get -y install xvfb 22 | $HOME/miniconda/bin/conda install astropy xarray numpy scipy pyopengl pytest flake8 six coverage bokeh pyqtgraph matplotlib --yes 23 | $HOME/miniconda/bin/conda install pyqt --yes 24 | $HOME/miniconda/bin/conda install pip --yes 25 | $HOME/miniconda/bin/conda install -c anaconda netcdf4 --yes 26 | $HOME/miniconda/bin/conda install -c mavensdc cdflib --yes 27 | $HOME/miniconda/bin/conda install keyring --yes 28 | - name: Run unit tests 29 | run: | 30 | xvfb-run $HOME/miniconda/bin/pytest -rsv $GITHUB_WORKSPACE/tests 31 | cd $GITHUB_WORKSPACE 32 | $HOME/miniconda/bin/python setup.py sdist 33 | - name: Publish to Anaconda 34 | env: 35 | ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_TOKEN }} 36 | if: startsWith(github.event.ref, 'refs/tags') 37 | run: | 38 | alias python=$HOME/miniconda/bin/python 39 | alias pip=$HOME/miniconda/bin/pip 40 | $HOME/miniconda/bin/pip install twine 41 | $HOME/miniconda/bin/conda install conda-build --yes 42 | $HOME/miniconda/bin/conda install anaconda-client --yes 43 | $HOME/miniconda/bin/conda config --add channels mavensdc 44 | $HOME/miniconda/bin/conda-build --python 3.6 --output-folder . . 45 | $HOME/miniconda/bin/conda convert -p linux-32 linux-64/*.tar.bz2 46 | $HOME/miniconda/bin/conda convert -p win-32 linux-64/*.tar.bz2 47 | $HOME/miniconda/bin/conda convert -p osx-64 linux-64/*.tar.bz2 48 | $HOME/miniconda/bin/conda convert -p win-64 linux-64/*.tar.bz2 49 | $HOME/miniconda/bin/anaconda upload --label main linux-64/*.tar.bz2 50 | $HOME/miniconda/bin/anaconda upload --label main osx-64/*.tar.bz2 51 | $HOME/miniconda/bin/anaconda upload --label main win-64/*.tar.bz2 52 | $HOME/miniconda/bin/anaconda upload --label main win-32/*.tar.bz2 53 | $HOME/miniconda/bin/anaconda upload --label main linux-32/*.tar.bz2 54 | rm -r linux-64 55 | rm -r linux-32 56 | rm -r osx-64 57 | rm -r win-64 58 | rm -r win-32 59 | $HOME/miniconda/bin/conda-build --python 3.7 --output-folder . . 60 | $HOME/miniconda/bin/conda convert -p linux-32 linux-64/*.tar.bz2 61 | $HOME/miniconda/bin/conda convert -p win-32 linux-64/*.tar.bz2 62 | $HOME/miniconda/bin/conda convert -p osx-64 linux-64/*.tar.bz2 63 | $HOME/miniconda/bin/conda convert -p win-64 linux-64/*.tar.bz2 64 | $HOME/miniconda/bin/anaconda upload --label main linux-64/*.tar.bz2 65 | $HOME/miniconda/bin/anaconda upload --label main osx-64/*.tar.bz2 66 | $HOME/miniconda/bin/anaconda upload --label main win-64/*.tar.bz2 67 | $HOME/miniconda/bin/anaconda upload --label main win-32/*.tar.bz2 68 | $HOME/miniconda/bin/anaconda upload --label main linux-32/*.tar.bz2 69 | rm -r linux-64 70 | rm -r linux-32 71 | rm -r osx-64 72 | rm -r win-64 73 | rm -r win-32 74 | -------------------------------------------------------------------------------- /.github/workflows/pypi-build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install dependencies 12 | run: | 13 | cd $HOME 14 | # wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh 15 | wget https://repo.anaconda.com/miniconda/Miniconda3-py38_4.10.3-Linux-x86_64.sh -O miniconda.sh 16 | chmod +x miniconda.sh 17 | bash miniconda.sh -b -p $HOME/miniconda 18 | export PATH="$HOME/miniconda/bin:$PATH" 19 | sudo apt-get -y install libgl1-mesa-glx 20 | sudo apt-get -y install libsm6 libxrender1 libfontconfig1 libxcomposite-dev libxcursor1 libxi6 libxtst6 libxss1 libxrandr2 libasound2 libegl1 21 | sudo apt-get -y install xvfb 22 | $HOME/miniconda/bin/conda install astropy xarray numpy scipy pyopengl pytest flake8 six coverage bokeh pyqtgraph matplotlib --yes 23 | $HOME/miniconda/bin/conda install pyqt --yes 24 | $HOME/miniconda/bin/conda install pip --yes 25 | $HOME/miniconda/bin/conda install -c anaconda netcdf4 --yes 26 | $HOME/miniconda/bin/conda install -c mavensdc cdflib --yes 27 | $HOME/miniconda/bin/conda install keyring --yes 28 | - name: Run unit tests 29 | run: | 30 | xvfb-run $HOME/miniconda/bin/pytest -rsv $GITHUB_WORKSPACE/tests 31 | cd $GITHUB_WORKSPACE 32 | $HOME/miniconda/bin/python setup.py sdist 33 | - name: Publish to PyPI 34 | uses: pypa/gh-action-pypi-publish@master 35 | if: startsWith(github.event.ref, 'refs/tags') 36 | with: 37 | password: ${{ secrets.pypi_password }} 38 | -------------------------------------------------------------------------------- /.github/workflows/unittest.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Install dependencies 12 | run: | 13 | cd $HOME 14 | # wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh 15 | wget https://repo.anaconda.com/miniconda/Miniconda3-py38_4.10.3-Linux-x86_64.sh -O miniconda.sh 16 | chmod +x miniconda.sh 17 | bash miniconda.sh -b -p $HOME/miniconda 18 | export PATH="$HOME/miniconda/bin:$PATH" 19 | sudo apt-get -y install libgl1-mesa-glx 20 | sudo apt-get -y install libsm6 libxrender1 libfontconfig1 libxcomposite-dev libxcursor1 libxi6 libxtst6 libxss1 libxrandr2 libasound2 libegl1 21 | sudo apt-get -y install xvfb 22 | $HOME/miniconda/bin/conda install astropy xarray numpy scipy pyopengl pytest flake8 six coverage bokeh pyqtgraph matplotlib --yes 23 | $HOME/miniconda/bin/conda install pyqt --yes 24 | $HOME/miniconda/bin/conda install pip --yes 25 | $HOME/miniconda/bin/conda install -c anaconda netcdf4 --yes 26 | $HOME/miniconda/bin/conda install -c mavensdc cdflib --yes 27 | $HOME/miniconda/bin/conda install keyring --yes 28 | - name: Run unit tests 29 | run: | 30 | xvfb-run $HOME/miniconda/bin/pytest -rsv $GITHUB_WORKSPACE/tests 31 | cd $GITHUB_WORKSPACE 32 | $HOME/miniconda/bin/python setup.py sdist 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tar 2 | *.pyc 3 | *.tar.gz 4 | *.egg-info/ 5 | *.tar.gz 6 | *.tgz 7 | /.project 8 | /.pydevproject 9 | *.ipynb 10 | /*.html 11 | *.zip 12 | *.prefs 13 | pytplot/TESTING.py 14 | pytplot/Testing2.py 15 | pytplot/OTHER_TESTING.py 16 | pytplot/TESTING_2.py 17 | *.tab 18 | pytplot/TESTING.py 19 | pytplot/TESTING.py 20 | pytplot/TESTING.py 21 | .spyproject/ 22 | test.py 23 | /testing.py 24 | /3dtesting.py 25 | /testing2.py 26 | .idea/ 27 | .eggs/ 28 | *.orig 29 | docs/build/* 30 | pydata 31 | maven_data 32 | themis_data 33 | pydata 34 | 35 | # Sublime-github package stores a github token in this file 36 | # https://packagecontrol.io/packages/sublime-github 37 | GitHub.sublime-settings 38 | 39 | .svn/ 40 | 41 | # Windows thumbnail cache files 42 | Thumbs.db 43 | ehthumbs.db 44 | ehthumbs_vista.db 45 | 46 | # Dump file 47 | *.stackdump 48 | 49 | # Folder config file 50 | [Dd]esktop.ini 51 | 52 | # Recycle Bin used on file shares 53 | $RECYCLE.BIN/ 54 | 55 | # Windows Installer files 56 | *.cab 57 | *.msi 58 | *.msix 59 | *.msm 60 | *.msp 61 | 62 | # Windows shortcuts 63 | *.lnk 64 | 65 | # General 66 | .DS_Store 67 | .AppleDouble 68 | .LSOverride 69 | 70 | # Files that might appear in the root of a volume 71 | .DocumentRevisions-V100 72 | .fseventsd 73 | .Spotlight-V100 74 | .TemporaryItems 75 | .Trashes 76 | .VolumeIcon.icns 77 | .com.apple.timemachine.donotpresent 78 | 79 | # Directories potentially created on remote AFP share 80 | .AppleDB 81 | .AppleDesktop 82 | Network Trash Folder 83 | Temporary Items 84 | .apdisk 85 | 86 | # Byte-compiled / optimized / DLL files 87 | __pycache__/ 88 | *.py[cod] 89 | *$py.class 90 | 91 | # C extensions 92 | *.so 93 | 94 | # Environments 95 | .env 96 | .venv 97 | env/ 98 | venv/ 99 | ENV/ 100 | env.bak/ 101 | venv.bak/ 102 | 103 | # Dropbox settings and caches 104 | .dropbox 105 | .dropbox.attr 106 | .dropbox.cache 107 | 108 | # Visual Studio Code dir 109 | .vscode 110 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full-vnc 2 | RUN sudo apt-get update 3 | RUN sudo apt-get install -y libgtk-3-dev 4 | RUN sudo apt-get install -y libxcb-xinerama0 5 | RUN sudo apt-get install -y libxcb-render-util0 6 | RUN sudo apt-get install -y libxcb-randr0 7 | RUN sudo apt-get install -y libxcb-shape0 8 | RUN sudo apt-get install -y libxcb-icccm4 9 | RUN sudo apt-get install -y libxcb-image0 10 | RUN sudo apt-get install -y libxcb-keysyms1 11 | RUN sudo apt-get install -y libxkbcommon-x11-0 12 | # The libraries below are not needed for pytplot to work as is, but are listed as requirements for PyQT5 13 | # If things don't work, maybe try installing these 14 | #RUN sudo apt-get install -y libfontconfig1 15 | #RUN sudo apt-get install -y libfreetype6 16 | #RUN sudo apt-get install -y libxext6 17 | #RUN sudo apt-get install -y libxcb1 18 | #RUN sudo apt-get install -y libx11-6 19 | #RUN sudo apt-get install -y libice6 20 | #RUN sudo apt-get install -y libglib2.0-0 21 | #RUN sudo apt-get install -y libpthread-stubs0-dev 22 | #RUN sudo apt-get install -y libsm6 23 | #RUN sudo apt-get install -y libxcb-util-dev 24 | #RUN sudo apt-get install -y libxrender-dev 25 | #RUN sudo apt-get install -y libxcb-render0 26 | #RUN sudo apt-get install -y libxkbcommon0 27 | #RUN sudo apt-get install -y libxcb-sync1 28 | #RUN sudo apt-get install -y libxcb-xfixes0 29 | #RUN sudo apt-get install -y libxcb-shm0 30 | RUN sudo rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | image: 6 | file: .gitpod.Dockerfile 7 | 8 | tasks: 9 | - init: pip install -r requirements.txt 10 | - init: pip install pyspedas 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MAVEN SDC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include README.md 3 | include LICENSE 4 | include test_data.tplot 5 | include docs/*.html 6 | include docs/*.txt 7 | include pytplot/*.coffee 8 | include pytplot/sampledata/*.tplot 9 | include pytplot/sampledata/*.pytplot 10 | include pytplot/tplot_math 11 | recursive-include pytplot/HTMLPlotter * 12 | recursive-include pytplot/QtPlotter * 13 | include pytplot/version.txt 14 | exclude tplot2.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build](https://github.com/MAVENSDC/PyTplot/workflows/build/badge.svg)](https://github.com/MAVENSDC/PyTplot/actions) 2 | [![DOI](https://zenodo.org/badge/68843190.svg)](https://zenodo.org/badge/latestdoi/68843190) 3 | 4 | 5 | Full Documentation here: [https://pytplot.readthedocs.io/en/latest/](https://pytplot.readthedocs.io/en/latest/) 6 | 7 | 8 | Pytplot is a python package which aims to mimic the functionality of the IDL "tplot" libraries. 9 | 10 | These plots have several user interaction tools built in, such as zooming and panning. The can be exported as standalone HTML files (to retain their interactivity) or as static PNG files. 11 | 12 | Pytplot can be used in python scripts, or interactively through IPython and the Jupyter notebook. 13 | 14 | 15 | Quick Start 16 | =========== 17 | 18 | Install Python 19 | --------------- 20 | 21 | You will need the Anaconda distribution of Python 3 in order to run pytplot. 22 | 23 | `Anaconda `_ comes with a suite of packages that are useful for data science. 24 | 25 | 26 | Install pytplot 27 | ---------------- 28 | 29 | Open up a terminal, and type:: 30 | 31 | pip install pytplot 32 | 33 | This will install pytplot and all of it's dependencies. 34 | 35 | You will also need to install nodejs. This can be done through Anaconda with the following command:: 36 | 37 | conda install -c bokeh nodejs 38 | 39 | Running Pytplot 40 | --------------- 41 | 42 | To start using pytplot in a similar manner to IDL tplot, start up an interactive environment through the terminal command:: 43 | 44 | ipython 45 | 46 | or, if you prefer the jupyter interactive notebook:: 47 | 48 | jupyter notebook 49 | 50 | then, just import the package by typing the command:: 51 | 52 | import pytplot 53 | 54 | A demo/tutorial can be found here: `docs/pytplot_tutorial.html `_. 55 | 56 | A full description of each function can be found in `docs/build/index.html `_. 57 | 58 | Alternatively, the PDF version is located in `docs/build/PyTplot.pdf `_. 59 | 60 | Contact 61 | ============= 62 | 63 | If you have any suggestions or notice any problems, don't hesitate to contact Bryan Harter: harter@lasp.colorado.edu 64 | 65 | 66 | Copyright 2019 Regents of the University of Colorado. All Rights Reserved. 67 | Released under the MIT license. 68 | This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 69 | Verify current version before use at: https://github.com/MAVENSDC/PyTplot 70 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | ########## 3 | pytplot 4 | ########## 5 | 6 | Pytplot is a python package which aims to mimic the functionality of the IDL "tplot" libraries. The primary routine (tplot) generates HTML files for the specified plots, and automatically opens the files in a Qt interface. 7 | 8 | These plots have several user interaction tools built in, such as zooming and panning. The can be exported as standalone HTML files (to retain their interactivity) or as static PNG files. 9 | 10 | Pytplot can be used in python scripts, or interactively through IPython and the Jupyter notebook. 11 | 12 | How It Works 13 | ============= 14 | 15 | Data is read into pytplot by using the "store_data" command. Each dataset is assigned a unique name by the user. 16 | 17 | The data is stored in a "tplot variable" class. The tplot variables contain all the information required to create a plot of the dataset. The details of the plot, such as axis titles, types, line colors, etc, can be changed through other functions in pytplot. 18 | 19 | When you are ready to create a graph of your dataset(s), supply the dataset names you wish to plot to the "tplot" function, and a graph will be generated. 20 | 21 | 22 | 23 | Install Python 24 | ============= 25 | 26 | You will need the Anaconda distribution of Python 3 in order to run pytplot. 27 | 28 | `Anaconda `_ comes with a suite of packages that are useful for data science. 29 | 30 | 31 | Install pytplot 32 | ============= 33 | 34 | Open up a terminal, and type:: 35 | 36 | pip install pytplot 37 | 38 | This will install pytplot and all of it's dependencies. 39 | 40 | You will also need to install nodejs. This can be done through Anaconda with the following command:: 41 | 42 | conda install -c bokeh nodejs 43 | 44 | Running Pytplot 45 | ============= 46 | 47 | To start using pytplot in a similar manner to IDL tplot, start up an interactive environment through the terminal command:: 48 | 49 | ipython 50 | 51 | or, if you prefer the jupyter interactive notebook:: 52 | 53 | jupyter notebook 54 | 55 | then, just import the package by typing the command:: 56 | 57 | import pytplot 58 | 59 | A demo/tutorial can be found here: `docs/pytplot_tutorial.html `_. 60 | 61 | A full description of each function can be found in `docs/build/index.html `_. 62 | 63 | Alternatively, the PDF version is located in `docs/build/PyTplot.pdf `_. 64 | 65 | Contact 66 | ============= 67 | 68 | If you have any suggestions or notice any problems, don't hesitate to contact Bryan Harter: harter@lasp.colorado.edu 69 | 70 | 71 | # Copyright 2017 Regents of the University of Colorado. All Rights Reserved. 72 | # Released under the MIT license. 73 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 74 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/_images/2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/2d.png -------------------------------------------------------------------------------- /docs/source/_images/3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/3d.png -------------------------------------------------------------------------------- /docs/source/_images/extra_x_axes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/extra_x_axes.png -------------------------------------------------------------------------------- /docs/source/_images/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/gui.png -------------------------------------------------------------------------------- /docs/source/_images/interactivity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/interactivity.png -------------------------------------------------------------------------------- /docs/source/_images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/sample.png -------------------------------------------------------------------------------- /docs/source/_images/spec_slicer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/source/_images/spec_slicer.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../..')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'pytplot' 21 | copyright = '2019, Bryan Harter' 22 | author = 'Bryan Harter' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon'] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # List of patterns, relative to source directory, that match files and 36 | # directories to ignore when looking for source files. 37 | # This pattern also affects html_static_path and html_extra_path. 38 | exclude_patterns = [] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = '.rst' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | 59 | autodoc_mock_imports = ['sip', 'PyQt5', 'PyQt5.QtGui', 'PyQt5.QtCore', 'PyQt5.QtWidgets'] -------------------------------------------------------------------------------- /docs/source/helper_functions.rst: -------------------------------------------------------------------------------- 1 | Helper Functions 2 | ================= 3 | 4 | These are functions that help get or set attributes of the data. All of these were basically mapped one-to-one from IDL routines. 5 | 6 | .. autofunction:: pytplot.del_data 7 | .. autofunction:: pytplot.get_data 8 | .. autofunction:: pytplot.tplot_copy 9 | .. autofunction:: pytplot.replace_data 10 | .. autofunction:: pytplot.get_timespan 11 | .. autofunction:: pytplot.get_ylimits 12 | .. autofunction:: pytplot.timebar 13 | .. autofunction:: pytplot.timespan 14 | .. autofunction:: pytplot.timestamp 15 | .. autofunction:: pytplot.tplot_names 16 | .. autofunction:: pytplot.tplot_rename 17 | .. autofunction:: pytplot.xlim 18 | .. autofunction:: pytplot.ylim 19 | .. autofunction:: pytplot.zlim -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. pytplot documentation master file, created by 2 | sphinx-quickstart on Tue Jun 18 11:12:58 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | PyTplot's Documentation 7 | ======================= 8 | 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | introduction 14 | tplot_variables 15 | reading_in_data 16 | options 17 | tplot_options 18 | helper_functions 19 | plotting 20 | linking 21 | interactivity 22 | math_routines 23 | -------------------------------------------------------------------------------- /docs/source/linking.rst: -------------------------------------------------------------------------------- 1 | Linking Two Variables and Non-Time Series Plots 2 | ================================================== 3 | 4 | Sometimes plots other than time series may be desired. Typically, this is either altitude or latitude/longitude plots. 5 | 6 | Because tplot variables always have time on their x-axis, in order to get these types of plots you need to tell pytplot that two variables 7 | are related to one another. 8 | 9 | In other words, if you have a tplot variable for altitude named "spacecraft_alt" and while the data is stored in the variable "spacecraft_data", you can tell pytplot that the two are related with the following command:: 10 | 11 | pytplot.link("spacecraft_data", "spacecraft_alt", link_type = 'alt') 12 | 13 | If the data are not on the same time cadence, the linked variable will be interpolated to match that of the data variable at the time of plotting. 14 | 15 | 16 | Why Linking? 17 | -------------- 18 | One may ask, why not just allow altitude/latitude/longitude to be the xaxis of the tplot variables? 19 | The answer, aside from the fact that this is a time-series manipulation library, is that time is still retained in the "backend" of the plots. 20 | For instance, the time value will display when you hover over the data, 21 | and if you mark specific times with either a vertical Time Bar or Region of Interest, then that point/area will be highlighted on the plot. 22 | 23 | 24 | Altitude Plot Example 25 | --------------------- 26 | 27 | The following uses data taken from the MAVEN mission:: 28 | 29 | # Store Data from a dictionary variable named "insitu" (read into python from elsewhere) 30 | pytplot.store_data('sc_alt', data={'x':insitu['Time'] , 'y':insitu['SPACECRAFT']['ALTITUDE']}) 31 | pytplot.store_data('euv_low', data={'x':insitu['Time'] , 'y':insitu['EUV']['IRRADIANCE_LOW']}) 32 | pytplot.store_data('euv_lyman', data={'x':insitu['Time'] , 'y':insitu['EUV']['IRRADIANCE_LYMAN']}) 33 | pytplot.store_data('euv_mid', data={'x':insitu['Time'] , 'y':insitu['EUV']['IRRADIANCE_MID']}) 34 | 35 | # Link the EUV variables to "sc_alt" 36 | pytplot.link(["euv_mid"], "sc_alt", link_type='alt') 37 | pytplot.link(["euv_low"], "sc_alt", link_type='alt') 38 | pytplot.link(["euv_lyman"], "sc_alt", link_type='alt') 39 | #Specify that you'd like to overplot 40 | pytplot.store_data('euv', data=["euv_low","euv_mid","euv_lyman"]) 41 | 42 | #Set Plot options 43 | pytplot.options("euv", 'alt', 1) 44 | pytplot.options("euv", 'ylog', 1) 45 | pytplot.ylim("euv", .000001, .02) 46 | pytplot.options("euv", "legend_names", ["Low", "Mid", "Lyman"]) 47 | 48 | #Add a big blue marker at 3:23:00 49 | pytplot.timebar('2015-12-25 03:23:00', varname='euv', color='blue', thick=10) 50 | 51 | #Plot! 52 | pytplot.tplot("euv") 53 | 54 | 55 | 56 | 57 | .. raw:: html 58 | :file: _images/altitude.html 59 | 60 | 61 | 62 | 63 | 64 | Map Plot Example 65 | ---------------- 66 | 67 | The following is the same example as above, but this time plotted vs latitude and longitude over the surface of Mars. 68 | It also plots only the euv_lyman variable:: 69 | 70 | # Store Data from a dictionary variable named "insitu" (read into python from elsewhere) 71 | pytplot.store_data('sc_lon', data={'x':insitu['Time'] , 'y':insitu['SPACECRAFT']['SUB_SC_LONGITUDE']}) 72 | pytplot.store_data('sc_lat', data={'x':insitu['Time'] , 'y':insitu['SPACECRAFT']['SUB_SC_LATITUDE']}) 73 | pytplot.store_data('euv_lyman', data={'x':insitu['Time'] , 'y':insitu['EUV']['IRRADIANCE_LYMAN']}) 74 | 75 | # Link latitude and longitude 76 | pytplot.link(["euv_lyman"], "sc_lat", link_type='lat') 77 | pytplot.link(["euv_lyman"], "sc_lon", link_type='lon') 78 | 79 | # Set Plot options 80 | pytplot.options("euv_lyman", 'map', 1) 81 | pytplot.options("euv_lyman", 'zlog', 1) 82 | pytplot.options("euv_lyman", 'basemap', 'C:/maps/MOLA_BW_2500x1250.jpg')) 83 | 84 | #Add a big blue marker at 3:23:00 85 | pytplot.timebar('2015-12-25 03:23:00', varname='euv_lyman', color='blue', thick=20) 86 | 87 | # Plot! 88 | pytplot.tplot("euv_lyman") 89 | 90 | .. raw:: html 91 | :file: _images/map.html 92 | 93 | 94 | If pyqtgraph is used to plot the map plot instead, a little marker will appear on the map at the time the user is hovering over with their mouse. -------------------------------------------------------------------------------- /docs/source/math_routines.rst: -------------------------------------------------------------------------------- 1 | Math Routines 2 | ====================== 3 | 4 | Arithmetic 5 | ---------- 6 | .. autofunction:: pytplot.tplot_math.add 7 | .. autofunction:: pytplot.tplot_math.subtract 8 | .. autofunction:: pytplot.tplot_math.multiply 9 | .. autofunction:: pytplot.tplot_math.divide 10 | .. autofunction:: pytplot.tplot_math.crop 11 | .. autofunction:: pytplot.tplot_math.tinterp 12 | 13 | Add Across Columns 14 | ------------------ 15 | .. autofunction:: pytplot.tplot_math.add_across 16 | 17 | Average over time 18 | ----------------- 19 | .. autofunction:: pytplot.tplot_math.avg_res_data 20 | 21 | Clip Data 22 | --------- 23 | .. autofunction:: pytplot.tplot_math.clip 24 | 25 | Deflag Data 26 | ----------- 27 | .. autofunction:: pytplot.tplot_math.deflag 28 | 29 | Degap Data 30 | ---------- 31 | .. autofunction:: pytplot.tplot_math.degap 32 | 33 | Derivative 34 | ---------- 35 | .. autofunction:: pytplot.tplot_math.derive 36 | 37 | Flatten Data 38 | ------------ 39 | .. autofunction:: pytplot.tplot_math.flatten 40 | 41 | Interpolate through NaN values 42 | ------------------------------ 43 | .. autofunction:: pytplot.tplot_math.interp_nan 44 | 45 | Join/Split Data 46 | --------------- 47 | .. autofunction:: pytplot.tplot_math.join_vec 48 | .. autofunction:: pytplot.tplot_math.split_vec 49 | 50 | Power Spectrum 51 | -------------- 52 | .. autofunction:: pytplot.tplot_math.pwr_spec 53 | 54 | Resample Data 55 | ------------- 56 | .. autofunction:: pytplot.tplot_math.resample 57 | 58 | Spectrum Multiplication 59 | ----------------------- 60 | .. autofunction:: pytplot.tplot_math.spec_mult 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/source/options.rst: -------------------------------------------------------------------------------- 1 | Options 2 | ============= 3 | 4 | .. autofunction:: pytplot.options -------------------------------------------------------------------------------- /docs/source/plotting.rst: -------------------------------------------------------------------------------- 1 | Plotting 2 | ============= 3 | 4 | tplot function 5 | -------------- 6 | 7 | 8 | All plotting is called through the "tplot()" function. If you'd like to customize how you plots are displayed, 9 | it is assume that you have set them up prior to calling this function. 10 | 11 | .. autofunction:: pytplot.tplot 12 | 13 | 14 | Oveplotting 15 | ----------- 16 | 17 | To combine two or more variables in the same plot, you need to create a new variable like so:: 18 | 19 | pytplot.store_data("new_variable", data=["variable1_to_overplot", "variable2_to_overplot"]) 20 | 21 | Then when you plot this new variable, it will be a combination of the two variables given in "data". 22 | 23 | .. note:: 24 | Each variable should still retain the plot options you set for it, but I am still working out the kinks. 25 | 26 | 27 | Extra X axes 28 | ------------- 29 | 30 | A commonly used feature of tplot is adding extra x axes (in addition to time), on the bottom of the plot. 31 | 32 | To do so in pytplot, specify which tplot variable(s) you'd like to to be included on the axis by passing them to the "var_label" option in tplot:: 33 | 34 | pytplot.tplot("variable1", var_label = ["variable2", "variable3"]) 35 | 36 | .. note:: 37 | Unfortunately, in the Bokeh plots currently the extra x axes must be linearly increasing in order to display properly. Hopefully we can determine a way to map variables onto the axes at some point in the future. 38 | 39 | 40 | A common use case would be orbit numbers or spacecraft position. Here is an example of multiple x axes below: 41 | 42 | .. image:: _images/extra_x_axes.png 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/source/reading_in_data.rst: -------------------------------------------------------------------------------- 1 | Reading in Data 2 | ============================= 3 | 4 | Manual Input 5 | ------------ 6 | .. autofunction:: pytplot.store_data 7 | 8 | CDF Reader 9 | ---------- 10 | .. autofunction:: pytplot.cdf_to_tplot 11 | 12 | NetCDF Reader 13 | ------------- 14 | .. autofunction:: pytplot.netcdf_to_tplot 15 | 16 | IDL Restore 17 | ----------- 18 | .. autofunction:: pytplot.tplot_restore 19 | -------------------------------------------------------------------------------- /docs/source/tplot_options.rst: -------------------------------------------------------------------------------- 1 | Tplot Options 2 | ===================== 3 | 4 | .. autofunction:: pytplot.tplot_options -------------------------------------------------------------------------------- /docs/source/tplot_variables.rst: -------------------------------------------------------------------------------- 1 | Tplot Variables 2 | ================ 3 | 4 | Every piece of data read into tplot is stored into global "tplot variable" (sometimes called "tvar" in the documentation). 5 | 6 | Every routine that deals with these variables uses simply their names to reference them, you do not need to pass the actual variables themselves around from function to function. 7 | 8 | If you would like to access the variable directly, they are stored in a global OrderedDict object called "data_quants", and can be found like so:: 9 | 10 | pytplot.data_quants['tplot_variable_name_here'] 11 | 12 | Tplot variables are kept global because it is not unheard of for >50 variables to be read in from a single file satellite data file, and naming/keeping track of each variable can become cumbersome. 13 | 14 | 15 | Internal Structure 16 | ------------------ 17 | 18 | Users do not necessarily need to know the internal details of the tplot variables to use the library, but if you'd like to use tplot variables in other libraries this is good information to know. 19 | 20 | "Tplot variable" is really just a fancy name for an Xarray DataArrray object. Tplot variables are just a type of DataArray that have standardized attributes so that PyTplot knows how to work with them 21 | 22 | * There is always a "time" coordinate, and that time is in seconds since 1970. 23 | * For spectogram data, there is always a "spec_bins" coordinate that keeps track of the bin sizes on the yaxis 24 | * There is a "plot_options" attribute given to each tplot variable that is a large nested dictionary that keeps track of all plotting objects for the variable. 25 | * The dimensions stored are always "time", "y", "v", and for more mutlidimensional data there is "v2", "v3", etc 26 | 27 | You can also access the pure underlying numpy arrays like so:: 28 | 29 | #Data 30 | pytplot.data_quants['variable_name'].values 31 | #Time 32 | pytplot.data_quants['variable_name'].coords['time'].values 33 | #Spec bins 34 | pytplot.data_quants['variable_name'].coords['spec_bins'].values 35 | 36 | -------------------------------------------------------------------------------- /docs/test_data.tplot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/docs/test_data.tplot -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "pytplot" %} 2 | {% set version = "1.7.28" %} 3 | 4 | package: 5 | name: "{{ name|lower }}" 6 | version: "{{ version }}" 7 | 8 | source: 9 | git_url: https://github.com/MAVENSDC/pytplot.git 10 | git_rev: v1.7.28 11 | 12 | build: 13 | number: 0 14 | script: "{{ PYTHON }} -m pip install . -vv" 15 | 16 | requirements: 17 | build: 18 | - setuptools 19 | - python 20 | - pip 21 | run: 22 | - python 23 | - numpy 24 | - bokeh 25 | - pandas 26 | - matplotlib 27 | - scipy 28 | - xarray 29 | - pyqtgraph 30 | - cdflib 31 | 32 | about: 33 | home: "https://github.com/MAVENSDC/pytplot" 34 | license: "MIT" 35 | summary: "Pytplot is an effort to replicate the functionality IDL tplot library in python" 36 | doc_url: "https://pytplot.readthedocs.io" 37 | dev_url: "https://github.com/MAVENSDC/pyptlot" 38 | 39 | extra: 40 | recipe-maintainers: 41 | - MAVENSDC 42 | -------------------------------------------------------------------------------- /pyqtgraphtestertemp.py: -------------------------------------------------------------------------------- 1 | import pyspedas 2 | pyspedas.mms.fgm() 3 | 4 | from pytplot import tplot 5 | tplot(['mms1_fgm_b_gse_srvy_l2_btot', 'mms1_fgm_b_gse_srvy_l2_bvec']) -------------------------------------------------------------------------------- /pytplot/AncillaryPlots/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/AncillaryPlots/__init__.py -------------------------------------------------------------------------------- /pytplot/AncillaryPlots/position_mars_2d.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | import pytplot 3 | 4 | 5 | def position_mars_2d(temp=None): 6 | ''' 7 | This is a simple plot that shows spacecraft position relative to Mars in MSO coordinates 8 | ''' 9 | 10 | # Set Mars Diameter constant 11 | MARS_DIAMETER = 3389.5 * 2 12 | 13 | # Set up the 2D interactive plot 14 | window = pytplot.tplot_utilities.get_available_qt_window(name='2D_MARS') 15 | window.newlayout(pg.GraphicsWindow()) 16 | window.resize(900, 300) 17 | window.setWindowTitle('Mars Position 2D Window') 18 | 19 | # Add the 3 plots for 3 different views 20 | plot1 = window.centralWidget().addPlot(title='Position MSO x-y', row=0, col=0) 21 | plot1.setLabel('left', text = 'MSO Y', units = 'km') 22 | plot1.setLabel('bottom', text='MSO X', units='km') 23 | plot2 = window.centralWidget().addPlot(title='Position MSO y-z', row=0, col=1) 24 | plot2.setLabel('left', text='MSO Z', units='km') 25 | plot2.setLabel('bottom', text='MSO Y', units='km') 26 | plot3 = window.centralWidget().addPlot(title='Position MSO x-z', row=0, col=2) 27 | plot3.setLabel('left', text='MSO Z', units='km') 28 | plot3.setLabel('bottom', text='MSO X', units='km') 29 | 30 | 31 | 32 | # Make it so that whenever this first starts up, you just have an empty plot 33 | plot_data1 = plot1.plot([], [], symbol='o', symbol_size=14) 34 | plot_data2 = plot2.plot([], [], symbol='o', symbol_size=14) 35 | plot_data3 = plot3.plot([], [], symbol='o', symbol_size=14) 36 | 37 | # Add the representation of mars on the first plot 38 | mars1 = pg.QtGui.QGraphicsEllipseItem(-1*MARS_DIAMETER/2,-1*MARS_DIAMETER/2,MARS_DIAMETER,MARS_DIAMETER) 39 | mars1.setPen(pg.mkPen(None)) 40 | mars1.setBrush(pg.mkBrush('r')) 41 | mars1.setZValue(-1) 42 | mars1.setSpanAngle(180 * 16) 43 | mars1.setStartAngle(-90 * 16) 44 | mars1_dark = pg.QtGui.QGraphicsEllipseItem(-1 * MARS_DIAMETER / 2, -1 * MARS_DIAMETER / 2, MARS_DIAMETER, MARS_DIAMETER) 45 | mars1_dark.setPen(pg.mkPen(None)) 46 | mars1_dark.setBrush(pg.mkBrush(.2)) 47 | mars1_dark.setZValue(-1) 48 | mars1_dark.setSpanAngle(180 * 16) 49 | mars1_dark.setStartAngle(90 * 16) 50 | plot1.vb.addItem(mars1) 51 | plot1.vb.addItem(mars1_dark) 52 | plot1.setXRange(-10000, 10000) 53 | plot1.setYRange(-10000, 10000) 54 | 55 | # Add the representation of mars on the second plot 56 | mars2 = pg.QtGui.QGraphicsEllipseItem(-1 *MARS_DIAMETER / 2, -1 * MARS_DIAMETER / 2, MARS_DIAMETER, MARS_DIAMETER) 57 | mars2.setPen(pg.mkPen(None)) 58 | mars2.setBrush(pg.mkBrush('r')) 59 | mars2.setZValue(-1) 60 | plot2.vb.addItem(mars2) 61 | plot2.setXRange(-10000, 10000) 62 | plot2.setYRange(-10000, 10000) 63 | 64 | # Add the representation of mars on the third plot 65 | mars3 = pg.QtGui.QGraphicsEllipseItem(-1 * MARS_DIAMETER / 2, -1 * MARS_DIAMETER / 2, MARS_DIAMETER, MARS_DIAMETER) 66 | mars3.setPen(pg.mkPen(None)) 67 | mars3.setBrush(pg.mkBrush('r')) 68 | mars3.setZValue(-1) 69 | mars3.setSpanAngle(180 * 16) 70 | mars3.setStartAngle(-90 * 16) 71 | mars3_dark = pg.QtGui.QGraphicsEllipseItem(-1 * MARS_DIAMETER / 2, -1 * MARS_DIAMETER / 2, MARS_DIAMETER, MARS_DIAMETER) 72 | mars3_dark.setPen(pg.mkPen(None)) 73 | mars3_dark.setBrush(pg.mkBrush(.2)) 74 | mars3_dark.setZValue(-1) 75 | mars3_dark.setSpanAngle(180 * 16) 76 | mars3_dark.setStartAngle(90 * 16) 77 | plot3.vb.addItem(mars3) 78 | plot3.vb.addItem(mars3_dark) 79 | plot3.setXRange(-10000, 10000) 80 | plot3.setYRange(-10000, 10000) 81 | 82 | # Define the update function on mouse moved events 83 | def update(t, name): 84 | # Get the xarray for x/y/z positions of the spacecraft 85 | if 'x' in pytplot.data_quants[name].attrs['plot_options']['links']: 86 | x_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['x']] 87 | y_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['y']] 88 | z_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['z']] 89 | elif 'mso_x' in pytplot.data_quants[name].attrs['plot_options']['links']: 90 | x_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['mso_x']] 91 | y_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['mso_y']] 92 | z_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['mso_z']] 93 | else: 94 | return 95 | 96 | # Use nearest neighbor to find closest x/y/z values 97 | new_x = x_tvar.sel(time=t, method='nearest').values 98 | new_y = y_tvar.sel(time=t, method='nearest').values 99 | new_z = z_tvar.sel(time=t, method='nearest').values 100 | 101 | # This moves the Mars spheres either to the front or the back depending on the location of the spacecraft 102 | if new_z < 0: 103 | mars1.setZValue(1) 104 | mars1_dark.setZValue(1) 105 | else: 106 | mars1.setZValue(-1) 107 | mars1_dark.setZValue(-1) 108 | if new_x < 0: 109 | mars2.setZValue(1) 110 | else: 111 | mars2.setZValue(-1) 112 | if new_y < 0: 113 | mars3.setZValue(1) 114 | mars3_dark.setZValue(1) 115 | else: 116 | mars3.setZValue(-1) 117 | mars3_dark.setZValue(-1) 118 | 119 | # Sets the position of the spacecraft in each window 120 | plot_data1.setData([new_x], [new_y]) 121 | plot_data2.setData([new_y], [new_z]) 122 | plot_data3.setData([new_x], [new_z]) 123 | 124 | # Register the update function to pytplot 125 | pytplot.hover_time.register_listener(update) 126 | 127 | # Turn on the window! 128 | window.show() 129 | 130 | -------------------------------------------------------------------------------- /pytplot/AncillaryPlots/position_mars_3d.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | 3 | def position_mars_3d(temp=None): 4 | ''' 5 | This is a simple 3D window that shows a spacecraft in MSO coordinates. 6 | This tool will look for links to the tplot variable that are named either "x/y/z" or "mso_(x/y/z)" 7 | ''' 8 | 9 | # Import 3D functionality from opengl 10 | try: 11 | import pyqtgraph.opengl as gl 12 | from pyqtgraph.Qt import QtGui 13 | from OpenGL.GL import glLightModelfv, glLightfv, GL_LIGHT0, GL_POSITION, \ 14 | GL_LIGHT_MODEL_AMBIENT, GL_LIGHTING, glEnable, GL_COLOR_MATERIAL, \ 15 | GL_AMBIENT, GL_SPECULAR, GL_DIFFUSE, glMaterial, GL_FRONT_AND_BACK, \ 16 | GL_AMBIENT_AND_DIFFUSE, GL_FRONT, glColorMaterial, GL_PROJECTION, \ 17 | glMatrixMode, glLoadIdentity, glTexParameterf 18 | except: 19 | raise("In order to use the 3D position viewing tool you must pip install PyOpenGL") 20 | 21 | # Tell Pytplot about new window 22 | window = pytplot.tplot_utilities.get_available_qt_window(name='3D_MARS') 23 | window.resize(1000, 600) 24 | window.setWindowTitle('3D Mars Window') 25 | 26 | # Defining a new class that keeps track of spacecraft position and moves the 27 | class PlanetView(gl.GLViewWidget): 28 | spacecraft_x = 0 29 | spacecraft_y = 0 30 | spacecraft_z = 0 31 | tvar_name = 'temp' # Store the name of the tplot variable stored, so we know if we need to redraw the orbit 32 | def paintGL(self, region=None, viewport=None, useItemNames=False): 33 | glLightfv(GL_LIGHT0, GL_POSITION, [-100000,0,0,0]) 34 | super().paintGL(region=region, viewport=viewport, useItemNames=useItemNames) 35 | 36 | plot1 = PlanetView() 37 | 38 | # Set up the "sun" 39 | glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [.3,.3,.3,1.0]) 40 | light_position = [-100000,0,0,0] 41 | glLightfv(GL_LIGHT0, GL_POSITION, light_position) 42 | glEnable(GL_LIGHTING) 43 | glEnable(GL_LIGHT0) 44 | glEnable(GL_COLOR_MATERIAL) 45 | glLightfv(GL_LIGHT0, GL_AMBIENT, [1,1,1,0]) 46 | glLightfv(GL_LIGHT0, GL_DIFFUSE, [1,1,1,1]) 47 | glLightfv(GL_LIGHT0, GL_SPECULAR, [1,0,0,0]) 48 | 49 | # Create Mars and spacecraft icons (assuming spherical spacecraft) 50 | md = gl.MeshData.sphere(rows=100, cols=220) 51 | mars = gl.GLMeshItem(meshdata=md, smooth=True, color=(.5, 0, 0, 1), glOptions='opaque') 52 | mars.translate(0, 0, 0) 53 | mars.scale(3390, 3390, 3390) 54 | spacecraft = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 1, 1, 1)) 55 | spacecraft.translate(plot1.spacecraft_x, plot1.spacecraft_y, plot1.spacecraft_z) 56 | 57 | spacecraft.scale(200, 200, 200) 58 | orbit_path = gl.GLLinePlotItem() 59 | plot1.addItem(mars) 60 | plot1.addItem(spacecraft) 61 | plot1.addItem(orbit_path) 62 | glMaterial(GL_FRONT_AND_BACK, GL_SPECULAR, [.5,.5,.5,1]) 63 | glEnable(GL_COLOR_MATERIAL) 64 | glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) 65 | 66 | # Put the planetview plot into the pytplot window 67 | window.setCentralWidget(plot1) 68 | 69 | # Move around the camera 70 | glMatrixMode(GL_PROJECTION) 71 | glLoadIdentity() 72 | plot1.setModelview() 73 | plot1.setCameraPosition(distance=30000, azimuth=0, elevation=0) 74 | 75 | # Turn on the window! 76 | window.show() 77 | 78 | # Define the function that is called on new hover_time updates 79 | def update(t,name): 80 | # Move spacecraft back to 0,0,0 81 | previous_x = plot1.spacecraft_x 82 | previous_y = plot1.spacecraft_y 83 | previous_z = plot1.spacecraft_z 84 | previous_tvar = plot1.tvar_name 85 | 86 | spacecraft.translate(-1*previous_x, -1*previous_y, -1*previous_z) 87 | 88 | # Get the xarray for x/y/z positions of the spacecraft 89 | if 'x' in pytplot.data_quants[name].attrs['plot_options']['links']: 90 | x_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['x']] 91 | y_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['y']] 92 | z_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['z']] 93 | elif 'mso_x' in pytplot.data_quants[name].attrs['plot_options']['links']: 94 | x_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['mso_x']] 95 | y_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['mso_y']] 96 | z_tvar = pytplot.data_quants[pytplot.data_quants[name].attrs['plot_options']['links']['mso_z']] 97 | else: 98 | return 99 | 100 | if name != previous_tvar: 101 | import numpy as np 102 | pathasdf = np.array((x_tvar.data, y_tvar.data, z_tvar.data), dtype=float).T 103 | orbit_path.setData(pos = pathasdf) 104 | 105 | 106 | # Get the nearest x/y/z of the hover time 107 | new_x = x_tvar.sel(time=t, method='nearest').values 108 | new_y = y_tvar.sel(time=t, method='nearest').values 109 | new_z = z_tvar.sel(time=t, method='nearest').values 110 | 111 | # Move the spacecraft 112 | spacecraft.translate(new_x,new_y,new_z) 113 | plot1.spacecraft_x, plot1.spacecraft_y, plot1.spacecraft_z = new_x, new_y, new_z 114 | plot1.tvar_name = name 115 | plot1.paintGL() 116 | 117 | # Register the above update function to the called functions 118 | pytplot.hover_time.register_listener(update) 119 | 120 | return 121 | 122 | -------------------------------------------------------------------------------- /pytplot/HTMLPlotter/CustomModels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/HTMLPlotter/CustomModels/__init__.py -------------------------------------------------------------------------------- /pytplot/HTMLPlotter/CustomModels/colorbarsidetitle.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from bokeh.models.annotations import ColorBar 7 | from bokeh.util.compiler import TypeScript 8 | 9 | ts_code = ''' 10 | import {ColorBar, ColorBarView} from "models/annotations/color_bar" 11 | import * as p from "core/properties" 12 | import {Context2d} from "core/util/canvas" 13 | 14 | const SHORT_DIM = 25 15 | 16 | export class ColorBarSideTitleView extends ColorBarView { 17 | model: ColorBarSideTitle 18 | protected image: HTMLCanvasElement 19 | 20 | compute_legend_dimensions(): {width: number, height: number} { 21 | const image_dimensions = this._computed_image_dimensions() 22 | const [image_height, image_width] = [image_dimensions.height, image_dimensions.width] 23 | 24 | const label_extent = this._get_label_extent() 25 | const title_extent = this._title_extent() 26 | const tick_extent = this._tick_extent() 27 | const {padding} = this.model 28 | 29 | let legend_height: number, legend_width: number 30 | legend_height = image_height 31 | legend_width = image_width + tick_extent + label_extent + title_extent + 2*padding 32 | 33 | return {width: legend_width, height: legend_height} 34 | } 35 | 36 | protected _draw_image(ctx: Context2d): void { 37 | const image = this._computed_image_dimensions() 38 | ctx.save() 39 | ctx.setImageSmoothingEnabled(false) 40 | ctx.globalAlpha = this.model.scale_alpha 41 | ctx.drawImage(this.image, 0, 0, image.width, image.height) 42 | if (this.visuals.bar_line.doit) { 43 | this.visuals.bar_line.set_value(ctx) 44 | ctx.strokeRect(0, 0, image.width, image.height) 45 | } 46 | ctx.restore() 47 | } 48 | 49 | protected _draw_title(ctx: Context2d): void { 50 | if (!this.visuals.title_text.doit) 51 | return 52 | const label_extent = this._get_label_extent() 53 | const tick_extent = this._tick_extent() 54 | const {padding} = this.model 55 | ctx.save() 56 | this.visuals.title_text.set_value(ctx) 57 | ctx.rotate(-Math.PI/2) 58 | ctx.fillText(this.model.title, -this.plot_view.frame._height.value * 0.5 , this.image.width + tick_extent + label_extent + 4*padding) 59 | ctx.textAlign="center" 60 | ctx.restore() 61 | } 62 | 63 | /*protected*/ _get_image_offset(): {x: number, y: number} { 64 | // Returns image offset relative to legend bounding box 65 | const x = this.model.padding 66 | const y = 0 67 | return {x, y} 68 | } 69 | 70 | _computed_image_dimensions(): {height: number, width: number} { 71 | const frame_height = this.plot_view.frame._height.value 72 | let height: number, width: number 73 | if (this.model.height == 'auto') { 74 | height = frame_height 75 | } else 76 | height = this.model.height 77 | 78 | width = this.model.width == 'auto' ? SHORT_DIM : this.model.width 79 | 80 | return {width, height} 81 | } 82 | 83 | // }}} 84 | } 85 | 86 | export namespace ColorBarSideTitle { 87 | export type Attrs = p.AttrsOf 88 | 89 | export type Props = ColorBar.Props 90 | } 91 | 92 | export interface ColorBarSideTitle extends ColorBarSideTitle.Attrs {} 93 | 94 | export class ColorBarSideTitle extends ColorBar { 95 | properties: ColorBarSideTitle.Props 96 | 97 | constructor(attrs?: Partial) { 98 | super(attrs) 99 | } 100 | 101 | static initClass(): void { 102 | this.prototype.default_view = ColorBarSideTitleView 103 | } 104 | } 105 | 106 | ColorBarSideTitle.initClass() 107 | 108 | ''' 109 | 110 | 111 | class ColorBarSideTitle(ColorBar): 112 | __implementation__ = TypeScript(ts_code) -------------------------------------------------------------------------------- /pytplot/HTMLPlotter/CustomModels/timestamp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from bokeh.core.properties import String 7 | from bokeh.models import LayoutDOM 8 | from bokeh.util.compiler import TypeScript 9 | 10 | JS_CODE = ''' 11 | import * as p from "core/properties" 12 | import {div, empty} from "core/dom" 13 | import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom" 14 | import {LayoutItem} from "core/layout" 15 | 16 | export class TimeStampView extends LayoutDOMView { 17 | model: TimeStamp 18 | 19 | initialize(): void { 20 | super.initialize() 21 | } 22 | render(): void { 23 | empty(this.el) 24 | this.el.appendChild(div({ 25 | style: { 26 | 'width': '800px', 27 | 'word-spacing': '10px', 28 | 'font-size': '11px', 29 | 'color': '#000000', 30 | 'background-color': '#ffffff', 31 | }, 32 | }, `${this.model.text}`)) 33 | } 34 | 35 | _update_layout(): void { 36 | this.layout = new LayoutItem() 37 | this.layout.set_sizing(this.box_sizing()) 38 | } 39 | 40 | get child_models(): LayoutDOM[] { 41 | return [] 42 | } 43 | } 44 | 45 | 46 | export namespace TimeStamp { 47 | export type Attrs = p.AttrsOf 48 | 49 | export type Props = LayoutDOM.Props & { 50 | text: p.Property 51 | } 52 | } 53 | 54 | export interface TimeStamp extends TimeStamp.Attrs {} 55 | 56 | export class TimeStamp extends LayoutDOM { 57 | properties: TimeStamp.Props 58 | static initClass(): void { 59 | 60 | this.prototype.default_view = TimeStampView 61 | 62 | this.define({ 63 | text: [ p.String ] 64 | }) 65 | } 66 | } 67 | 68 | TimeStamp.initClass() 69 | 70 | ''' 71 | 72 | 73 | class TimeStamp(LayoutDOM): 74 | __implementation__ = TypeScript(JS_CODE) 75 | text = String(default = "Testing") 76 | 77 | -------------------------------------------------------------------------------- /pytplot/HTMLPlotter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from .TVarFigure1D import TVarFigure1D 7 | from .TVarFigureMap import TVarFigureMap 8 | from .TVarFigureAlt import TVarFigureAlt 9 | from .TVarFigureSpec import TVarFigureSpec 10 | from .generate import generate_stack -------------------------------------------------------------------------------- /pytplot/HTMLPlotter/generate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from __future__ import division 7 | import pytplot 8 | from bokeh.models import LinearAxis, Range1d 9 | from .CustomModels.timestamp import TimeStamp 10 | from bokeh.layouts import gridplot 11 | from bokeh.io import doc 12 | import numpy as np 13 | 14 | 15 | def generate_stack(name, 16 | var_label=None, 17 | auto_color=True, 18 | combine_axes=True, 19 | slice=True, 20 | vert_spacing=25): 21 | 22 | doc.curdoc().clear() 23 | num_plots = len(name) 24 | 25 | # Name for .html file containing plots 26 | out_name = "" 27 | 28 | if isinstance(var_label, int): 29 | var_label = list(pytplot.data_quants.keys())[var_label] 30 | 31 | # Vertical Box layout to store plots 32 | all_plots = [] 33 | axis_types = [] 34 | i = 0 35 | 36 | # Configure plot sizes 37 | total_psize = 0 38 | j = 0 39 | while j < num_plots: 40 | total_psize += pytplot.data_quants[name[j]].attrs['plot_options']['extras']['panel_size'] 41 | j += 1 42 | 43 | p_to_use = pytplot.tplot_opt_glob['window_size'][1]/total_psize 44 | 45 | # Create all plots 46 | while i < num_plots: 47 | last_plot = (i == num_plots-1) 48 | 49 | p_height = int(pytplot.data_quants[name[i]].attrs['plot_options']['extras']['panel_size'] * p_to_use) 50 | p_width = pytplot.tplot_opt_glob['window_size'][0] 51 | 52 | # Check plot type 53 | new_fig = _get_figure_class(name[i], auto_color=auto_color, slice=slice, show_xaxis=last_plot) 54 | 55 | new_fig.setsize(height=p_height, width=p_width) 56 | 57 | if i == 0: 58 | new_fig.add_title() 59 | 60 | axis_types.append(new_fig.getaxistype()) 61 | 62 | new_fig.buildfigure() 63 | 64 | # Change background color to black if option for it is set - CHECK ONCE WE'VE UPGRADED OUR BOKEH THAT THIS IS 65 | # THE RIGHT WAY TO DO THIS 66 | if pytplot.tplot_opt_glob['black_background']: 67 | new_fig.background_fill_color = '#000000' 68 | else: 69 | new_fig.background_fill_color = '#FFFFFF' 70 | 71 | # Add name of variable to output file name 72 | if last_plot: 73 | out_name += name[i] 74 | else: 75 | out_name += name[i] + '+' 76 | 77 | # Add plot to GridPlot layout 78 | all_plots.append(new_fig.getfig()) 79 | i = i+1 80 | 81 | # Add the time stamp to the stack 82 | total_string = "" 83 | if 'time_stamp' in pytplot.extra_layouts: 84 | total_string = pytplot.extra_layouts['time_stamp'] 85 | 86 | ts = TimeStamp(text=total_string) 87 | pytplot.extra_layouts['data_time'] = ts 88 | all_plots.append([pytplot.extra_layouts['data_time']]) 89 | 90 | # Add extra x axes if applicable 91 | if var_label is not None: 92 | if not isinstance(var_label, list): 93 | var_label = [var_label] 94 | x_axes = [] 95 | x_axes_index = 0 96 | for new_x_axis in var_label: 97 | # TODO: Bokeh only handles linear plots for now!!! Fix?! 98 | axis_data_quant = pytplot.data_quants[new_x_axis] 99 | axis_start = np.float(axis_data_quant.min(skipna=True).values) 100 | axis_end = np.float(axis_data_quant.max(skipna=True).values) 101 | x_axes.append(Range1d(start=axis_start, end=axis_end)) 102 | k = 0 103 | while k < num_plots: 104 | all_plots[k][0].extra_x_ranges['extra_'+str(new_x_axis)] = x_axes[x_axes_index] 105 | k += 1 106 | all_plots[k-1][0].add_layout(LinearAxis(x_range_name='extra_'+str(new_x_axis)), 'below') 107 | all_plots[k-1][0].plot_height += 22 108 | x_axes_index += 1 109 | 110 | # Set all plots' x_range and plot_width to that of the bottom plot 111 | # so all plots will pan and be resized together. 112 | first_type = {} 113 | if combine_axes: 114 | k = 0 115 | while k < len(axis_types): 116 | if axis_types[k][0] not in first_type: 117 | first_type[axis_types[k][0]] = k 118 | else: 119 | all_plots[k][0].x_range = all_plots[first_type[axis_types[k][0]]][0].x_range 120 | if axis_types[k][1]: 121 | all_plots[k][0].y_range = all_plots[first_type[axis_types[k][0]]][0].y_range 122 | k += 1 123 | 124 | return gridplot(all_plots) 125 | 126 | 127 | def _get_figure_class(tvar_name, auto_color=True, slice=False, show_xaxis=True): 128 | if 'plotter' in pytplot.data_quants[tvar_name].attrs['plot_options']['extras'] \ 129 | and pytplot.data_quants[tvar_name].attrs['plot_options']['extras']['plotter'] in pytplot.bokeh_plotters: 130 | cls = pytplot.bokeh_plotters[pytplot.data_quants[tvar_name].attrs['plot_options']['extras']['plotter']] 131 | else: 132 | spec_keyword = pytplot.data_quants[tvar_name].attrs['plot_options']['extras'].get('spec', False) 133 | alt_keyword = pytplot.data_quants[tvar_name].attrs['plot_options']['extras'].get('alt', False) 134 | map_keyword = pytplot.data_quants[tvar_name].attrs['plot_options']['extras'].get('map', False) 135 | if spec_keyword: 136 | cls = pytplot.bokeh_plotters['bkTVarFigureSpec'] 137 | elif alt_keyword: 138 | cls = pytplot.bokeh_plotters['bkTVarFigureAlt'] 139 | elif map_keyword: 140 | cls = pytplot.bokeh_plotters['bkTVarFigureMap'] 141 | else: 142 | cls = pytplot.bokeh_plotters['bkTVarFigure1D'] 143 | return cls(tvar_name, auto_color=auto_color, slice=slice, show_xaxis=show_xaxis) 144 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomAxis/BlankAxis.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | 3 | 4 | class BlankAxis(pg.AxisItem): 5 | #Will need to override other functions in the future for this one 6 | #Right now it chooses weird stupid places for ticks 7 | def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True): 8 | pg.AxisItem.__init__(self, orientation=orientation, pen=pen, linkView=linkView, parent=parent, maxTickLength=maxTickLength, showValues=showValues) 9 | 10 | def tickStrings(self, values, scale, spacing): 11 | strns = [] 12 | for _ in values: 13 | try: 14 | strns.append('') 15 | except ValueError: 16 | strns.append('') 17 | return strns 18 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomAxis/DateAxis.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | import datetime 3 | 4 | class DateAxis(pg.AxisItem): 5 | """ 6 | This class takes in tplot time variables and creates ticks/tick labels 7 | depending on the time length of the data. 8 | """ 9 | def tickStrings(self, values, scale, spacing): 10 | strns = [] 11 | if not values: 12 | return strns 13 | for x in values: 14 | try: 15 | rng = max(values)-min(values) 16 | if rng < 0.001: 17 | string = '%f' 18 | label1 = '%b %d -' 19 | label2 = ' %b %d, %Y' 20 | strns.append(str(round(int(datetime.datetime.utcfromtimestamp(x).strftime(string))/1000000, 6))) 21 | continue 22 | # less than 1 sec of data 23 | elif 0.001 <= rng < 1: 24 | string = '%f' 25 | label1 = '%b %d -' 26 | label2 = ' %b %d, %Y' 27 | strns.append(str(round(int(datetime.datetime.utcfromtimestamp(x).strftime(string))/1000000, 3))) 28 | continue 29 | # Less than five minutes' worth of data 30 | elif 1 <= rng < 300: 31 | # Show hour, min, and sec. 32 | string = '%H:%M:%S' 33 | label1 = '%b %d -' 34 | label2 = ' %b %d, %Y' 35 | # Between five minutes' and four days' worth of data 36 | elif 300 <= rng < 3600*24*4: 37 | # If a new day (some day at 00:00:00 UTC), go ahead and actually 38 | # write out the date 39 | if x % 86400 == 0: 40 | # show YYYY-MM-DD 41 | string = '%Y-%m-%d' 42 | label1 = '%b %d -' 43 | label2 = ' %b %d, %Y' 44 | else: 45 | # Just show hour and min. 46 | string = '%H:%M' 47 | label1 = '%b %d -' 48 | label2 = ' %b %d, %Y' 49 | # Between four days' worth of data and ~ 4 months of data 50 | elif 3600*24*4 <= rng < 4*3600*24*30: 51 | # To keep things uncrowded, just putting month & day, and not the hour/min as well 52 | string = '%m-%d' 53 | label1 = '%b %d -' 54 | label2 = ' %b %d, %Y' 55 | # Between ~ 4 months worth of data and two years' of worth of data 56 | elif 4*3600*24*30 <= rng < 3600*24*30*24: 57 | # Show abbreviated month name and full year (YYYY) 58 | string = '%b-%Y' 59 | label1 = '%Y -' 60 | label2 = ' %Y' 61 | # Greater than two years' worth of data 62 | elif rng >= 3600*24*30*24: 63 | # Just show the year (YYYY) 64 | string = '%Y' 65 | label1 = '' 66 | label2 = '' 67 | strns.append(datetime.datetime.utcfromtimestamp(x).strftime(string)) 68 | except ValueError: 69 | # Windows can't handle dates before 1970 70 | strns.append(' ') 71 | 72 | return strns 73 | 74 | def tickSpacing(self, minVal, maxVal, size): 75 | rng = maxVal - minVal 76 | levels = [(1, 0)] 77 | if rng < 0.001: 78 | levels = [(0.0001, 0)] 79 | elif 0.001 <= rng < 0.01: 80 | levels = [(0.001, 0)] 81 | elif 0.01 <= rng < 0.2: 82 | levels = [(0.01, 0)] 83 | elif 0.2 <= rng < 1: 84 | levels = [(0.1, 0)] 85 | elif 1 <= rng < 3: 86 | levels = [(0.5, 0)] 87 | elif 3 <= rng < 4: 88 | # Show ticks every second if you're looking at < four seconds' worth of data 89 | levels = [(1, 0)] 90 | elif 4 <= rng < 15: 91 | # Show ticks every two seconds if you're looking between four and 15 seconds' worth of data 92 | levels = [(2, 0)] 93 | elif 15 <= rng < 60: 94 | # Show ticks every five seconds if you're looking between 15 seconds' and one minutes' worth of data 95 | levels = [(5, 0)] 96 | elif 60 <= rng < 300: 97 | # Show ticks every 30 seconds if you're looking between 1 and 5 minutes worth of data 98 | levels = [(30, 0)] 99 | elif 300 <= rng < 600: 100 | # Show ticks every minute if you're looking between 5 and 10 minutes worth of data 101 | levels = [(60, 0)] 102 | elif 600 <= rng < 1800: 103 | # Show ticks every 5 minutes if you're looking between 10 and 30 minutes worth of data 104 | levels = [(300, 0)] 105 | elif 1800 <= rng < 3600: 106 | # Show ticks every 15 minutes if you're looking between 30 minutes' and one hours' worth of data 107 | levels = [(900, 0)] 108 | elif 3600 <= rng < 3600*2: 109 | # Show ticks every 30 minutes if you're looking between one and two hours' worth of data 110 | levels = [(1800, 0)] 111 | elif 3600*2 <= rng < 3600*6: 112 | # Show ticks every hour if you're looking between two and six hours' worth of data 113 | levels = [(3600, 0)] 114 | elif 3600*6 <= rng < 3600*12: 115 | # Show ticks every two hours if you're looking between six and 12 hours' worth of data 116 | levels = [(7200, 0)] 117 | elif 3600*12 <= rng < 3600*24: 118 | # Show ticks every four hours if you're looking between 12 hours' and one days' worth of data 119 | levels = [(14400, 0)] 120 | elif 3600*24 <= rng < 3600*24*2: 121 | # Show ticks every six hours if you're looking at between one and two days' worth of data 122 | levels = [(21600, 0)] 123 | elif 3600*24*2 <= rng < 3600*24*4: 124 | # show ticks every 12 hours if you're looking between two and four days' worth of data 125 | levels = [(43200, 0)] 126 | elif 3600*24*4 <= rng < 3600*24*30: 127 | # show ticks every two days if data between four days and ~ 1 month 128 | levels = [(172800, 0)] 129 | elif 3600*24*30 <= rng < 2.0*3600*24*30: 130 | # show ticks every four days if data between 1 month and 2 months 131 | levels = [(345600, 0)] 132 | elif 2*3600*24*30 <= rng < 4*3600*24*30: 133 | # show ticks every 7 days if data between 2 month and 4 months 134 | levels = [(604800, 0)] 135 | elif 4*3600*24*30 <= rng < 3600*24*30*24: 136 | # show ticks ~ every month if data between ~ 4 months and ~ two years 137 | levels = [(2.592e+6, 0)] 138 | elif rng >= 3600*24*30*24: 139 | # show ticks ~ every year if data > two years 140 | # show ~ every year 141 | levels = [(3.154e+7, 0)] 142 | return levels 143 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomAxis/NonLinearAxis.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | 3 | 4 | class NonLinearAxis(pg.AxisItem): 5 | 6 | def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True, mapping_function=None, num_ticks=4): 7 | pg.AxisItem.__init__(self, orientation=orientation, pen=pen, linkView=linkView, parent=parent, maxTickLength=maxTickLength, showValues=showValues) 8 | self.f = mapping_function 9 | self.num_ticks = num_ticks 10 | 11 | def tickStrings(self, values, scale, spacing): 12 | strns = [] 13 | for x in values: 14 | try: 15 | strns.append(str(int(self.f(x)))) 16 | except ValueError: 17 | strns.append('') 18 | return strns 19 | 20 | def tickValues(self, minVal, maxVal, size): 21 | minVal, maxVal = sorted((minVal, maxVal)) 22 | minVal *= self.scale 23 | maxVal *= self.scale 24 | ticks=[] 25 | xrange = maxVal - minVal 26 | for i in range(0, self.num_ticks+1): 27 | ticks.append(minVal+(i*xrange/self.num_ticks)) 28 | return [(1.0,ticks)] 29 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomAxis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/QtPlotter/CustomAxis/__init__.py -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomImage/ColorbarImage.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | from pyqtgraph.Qt import QtCore 3 | import numpy as np 4 | import collections 5 | from pyqtgraph import functions as fn 6 | from pyqtgraph import debug as debug 7 | from pyqtgraph.Point import Point 8 | from collections.abc import Callable 9 | 10 | class ColorbarImage(pg.ImageItem): 11 | ''' 12 | For the most part, this class is exactly the same as pg.ImageItem. 13 | 14 | This exist literally only because collections.Callable became collections.abc.Callable 15 | and it was causing errors. 16 | ''' 17 | 18 | def render(self): 19 | # Convert data to QImage for display. 20 | 21 | profile = debug.Profiler() 22 | if self.image is None or self.image.size == 0: 23 | return 24 | if isinstance(self.lut, collections.abc.Callable): 25 | lut = self.lut(self.image) 26 | else: 27 | lut = self.lut 28 | 29 | if self.autoDownsample: 30 | # reduce dimensions of image based on screen resolution 31 | o = self.mapToDevice(QtCore.QPointF(0, 0)) 32 | x = self.mapToDevice(QtCore.QPointF(1, 0)) 33 | y = self.mapToDevice(QtCore.QPointF(0, 1)) 34 | w = Point(x - o).length() 35 | h = Point(y - o).length() 36 | if w == 0 or h == 0: 37 | self.qimage = None 38 | return 39 | xds = max(1, int(1.0 / w)) 40 | yds = max(1, int(1.0 / h)) 41 | axes = [1, 0] if self.axisOrder == 'row-major' else [0, 1] 42 | image = fn.downsample(self.image, xds, axis=axes[0]) 43 | image = fn.downsample(image, yds, axis=axes[1]) 44 | self._lastDownsample = (xds, yds) 45 | else: 46 | image = self.image 47 | 48 | # if the image data is a small int, then we can combine levels + lut 49 | # into a single lut for better performance 50 | levels = self.levels 51 | if levels is not None and levels.ndim == 1 and image.dtype in (np.ubyte, np.uint16): 52 | if self._effectiveLut is None: 53 | eflsize = 2 ** (image.itemsize * 8) 54 | ind = np.arange(eflsize) 55 | minlev, maxlev = levels 56 | levdiff = maxlev - minlev 57 | levdiff = 1 if levdiff == 0 else levdiff # don't allow division by 0 58 | if lut is None: 59 | efflut = fn.rescaleData(ind, scale=255. / levdiff, 60 | offset=minlev, dtype=np.ubyte) 61 | else: 62 | lutdtype = np.min_scalar_type(lut.shape[0] - 1) 63 | efflut = fn.rescaleData(ind, scale=(lut.shape[0] - 1) / levdiff, 64 | offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0] - 1)) 65 | efflut = lut[efflut] 66 | 67 | self._effectiveLut = efflut 68 | lut = self._effectiveLut 69 | levels = None 70 | 71 | # Assume images are in column-major order for backward compatibility 72 | # (most images are in row-major order) 73 | 74 | if self.axisOrder == 'col-major': 75 | image = image.transpose((1, 0, 2)[:image.ndim]) 76 | 77 | argb, alpha = fn.makeARGB(image, lut=lut, levels=levels) 78 | self.qimage = fn.makeQImage(argb, alpha, transpose=False) 79 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomImage/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/QtPlotter/CustomImage/__init__.py -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomLegend/CustomLegend.py: -------------------------------------------------------------------------------- 1 | 2 | import pyqtgraph as pg 3 | 4 | from pyqtgraph import GraphicsWidget 5 | from pyqtgraph import LabelItem 6 | from pyqtgraph import functions as fn 7 | from pyqtgraph.Point import Point 8 | 9 | __all__ = ['LegendItem'] 10 | 11 | class CustomLegendItem(pg.LegendItem): 12 | 13 | def addItem(self,name1,name2): 14 | label1 = LabelItem(name1) 15 | label2 = LabelItem(name2) 16 | row = self.layout.rowCount() 17 | self.items.append((label1, label2)) 18 | self.layout.addItem(label1, row, 0) 19 | self.layout.addItem(label2, row, 1) 20 | self.updateSize() 21 | 22 | def removeItem(self, name): 23 | for label, data in self.items: 24 | if label.text == name: # hit 25 | self.items.remove( (label, data) ) # remove from itemlist 26 | self.layout.removeItem(label) # remove from layout 27 | label.close() # remove from drawing 28 | self.layout.removeItem(data) 29 | data.close() 30 | self.updateSize() # redraq box 31 | 32 | def setItem(self, label_name, new_data): 33 | for label, data in self.items: 34 | if label.text == label_name: 35 | data.setText(new_data) 36 | return 37 | self.addItem(label_name, new_data) 38 | 39 | def paint(self, p, *args): 40 | p.setPen(fn.mkPen(255,255,255,0)) 41 | p.setBrush(fn.mkBrush(0,0,0,190)) 42 | p.drawRect(self.boundingRect()) 43 | 44 | def hoverEvent(self, ev): 45 | #ev.acceptDrags(QtCore.Qt.LeftButton) 46 | return 47 | 48 | def mouseDragEvent(self, ev): 49 | return 50 | #if ev.button() == QtCore.Qt.LeftButton: 51 | # dpos = ev.pos() - ev.lastPos() 52 | # self.autoAnchor(self.pos() + dpos) 53 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomLegend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/QtPlotter/CustomLegend/__init__.py -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomLinearRegionItem/CustomLinearRegionItem.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | from pyqtgraph import functions as fn 3 | 4 | 5 | class CustomLinearRegionItem(pg.LinearRegionItem): 6 | """Inheriting the LinearRegionItem class because if someone hovers over a region of interest box, 7 | I don't want it to have a different alpha value than when the mouse is not over the box.""" 8 | 9 | def setMouseHover(self, hover): 10 | # Inform the item that the mouse is(not) hovering over it 11 | if self.mouseHovering == hover: 12 | return 13 | self.mouseHovering = hover 14 | if hover: 15 | c = self.brush.color() 16 | c.setAlpha(c.alpha() * 1) 17 | self.currentBrush = fn.mkBrush(c) 18 | else: 19 | self.currentBrush = self.brush 20 | self.update() 21 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomViewBox/CustomVB.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | 3 | 4 | class CustomVB(pg.ViewBox): 5 | 6 | def mouseDragEvent(self, ev, axis=None): 7 | return 8 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomViewBox/NoPaddingPlot.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | 3 | 4 | class NoPaddingPlot(pg.ViewBox): 5 | 6 | def suggestPadding(self, axis): 7 | l = self.width() if axis == 0 else self.height() 8 | if l > 0: 9 | padding = 0.002 10 | else: 11 | padding = 0.002 12 | return padding -------------------------------------------------------------------------------- /pytplot/QtPlotter/CustomViewBox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/QtPlotter/CustomViewBox/__init__.py -------------------------------------------------------------------------------- /pytplot/QtPlotter/PyTPlot_Exporter.py: -------------------------------------------------------------------------------- 1 | import pyqtgraph as pg 2 | import numpy as np 3 | import pyqtgraph.functions as fn 4 | from pyqtgraph.Qt import QtCore, QtGui 5 | from pyqtgraph.exporters import Exporter 6 | from pyqtgraph.parametertree import Parameter 7 | 8 | 9 | class PytplotExporter(pg.exporters.ImageExporter): 10 | 11 | def __init__(self, item): 12 | Exporter.__init__(self, item) 13 | tr = self.getTargetRect() 14 | if isinstance(item, QtGui.QGraphicsItem): 15 | scene = item.scene() 16 | else: 17 | scene = item 18 | # CHANGE: Used to be scene.views()[0].backgroundBrush() 19 | # That wasn't how to access the background of a GraphicsLayout object 20 | bgbrush = scene.backgroundBrush() 21 | bg = bgbrush.color() 22 | if bgbrush.style() == QtCore.Qt.NoBrush: 23 | bg.setAlpha(0) 24 | 25 | self.params = Parameter(name='params', type='group', children=[ 26 | {'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)}, 27 | {'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)}, 28 | {'name': 'antialias', 'type': 'bool', 'value': True}, 29 | {'name': 'background', 'type': 'color', 'value': bg}, 30 | ]) 31 | 32 | self.params.param('width').sigValueChanged.connect(self.widthChanged) 33 | self.params.param('height').sigValueChanged.connect(self.heightChanged) 34 | 35 | def export(self, fileName=None, toBytes=False, copy=False): 36 | if fileName is None and not toBytes and not copy: 37 | if pg.Qt.USE_PYSIDE: 38 | filter = ["*." + str(f) for f in QtGui.QImageWriter.supportedImageFormats()] 39 | else: 40 | filter = ["*." + bytes(f).decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] 41 | preferred = ['*.png', '*.tif', '*.jpg'] 42 | for p in preferred[::-1]: 43 | if p in filter: 44 | filter.remove(p) 45 | filter.insert(0, p) 46 | self.fileSaveDialog(filter=filter) 47 | return 48 | 49 | targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) 50 | sourceRect = self.getSourceRect() 51 | 52 | 53 | # self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32) 54 | # self.png.fill(pyqtgraph.mkColor(self.params['background'])) 55 | w, h = self.params['width'], self.params['height'] 56 | if w == 0 or h == 0: 57 | raise Exception("Cannot export image with size=0 (requested export size is %dx%d)" % (w, h)) 58 | bg = np.empty((int(self.params['width']), int(self.params['height']), 4), dtype=np.ubyte) 59 | color = self.params['background'] 60 | bg[:, :, 0] = color.blue() 61 | bg[:, :, 1] = color.green() 62 | bg[:, :, 2] = color.red() 63 | bg[:, :, 3] = color.alpha() 64 | self.png = fn.makeQImage(bg, alpha=True) 65 | 66 | # set resolution of image: 67 | origTargetRect = self.getTargetRect() 68 | resolutionScale = targetRect.width() / origTargetRect.width() 69 | 70 | painter = QtGui.QPainter(self.png) 71 | # dtr = painter.deviceTransform() 72 | try: 73 | self.setExportMode(True, 74 | {'antialias': self.params['antialias'], 'background': self.params['background'], 75 | 'painter': painter, 'resolutionScale': resolutionScale}) 76 | painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias']) 77 | # CHANGE: Rendering the scence twice onto the QImage. The first time, make it one pixel in size. 78 | # Next, render the full thing. No idea why we need to render is twice, but we do. 79 | self.getScene().render(painter, QtCore.QRectF(0, 0, 1, 1), QtCore.QRectF(0, 0, 1, 1)) 80 | self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) 81 | finally: 82 | self.setExportMode(False) 83 | painter.end() 84 | 85 | if copy: 86 | QtGui.QApplication.clipboard().setImage(self.png) 87 | elif toBytes: 88 | return self.png 89 | else: 90 | self.png.save(fileName) 91 | 92 | def getPaintItems(self, root=None): 93 | """Return a list of all items that should be painted in the correct order.""" 94 | if root is None: 95 | root = self.item 96 | preItems = [] 97 | postItems = [] 98 | if isinstance(root, QtGui.QGraphicsScene): 99 | childs = [i for i in root.items() if i.parentItem() is None] 100 | rootItem = [] 101 | else: 102 | # CHANGE: For GraphicsLayouts, there is no function for childItems(), so I just 103 | # replaced it with .items() 104 | try: 105 | childs = root.childItems() 106 | except: 107 | childs = root.items() 108 | rootItem = [root] 109 | childs.sort(key=lambda a: a.zValue()) 110 | while len(childs) > 0: 111 | ch = childs.pop(0) 112 | tree = self.getPaintItems(ch) 113 | 114 | if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or ( 115 | ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): 116 | preItems.extend(tree) 117 | else: 118 | postItems.extend(tree) 119 | return preItems + rootItem + postItems 120 | 121 | def getTargetRect(self): 122 | # CHANGE: Used to return self.item.sceneBoundingRect(). GraphicsLayouts don't have a 123 | # sceneBoundingRect(), but they have a rect() which appears to work just as well. 124 | return self.item.rect() 125 | 126 | def getSourceRect(self): 127 | # CHANGE: Used to return self.item.mapRectToDevice(self.item.boundingRect()). GraphicsLayouts don't have a 128 | # sceneBoundingRect() OR a mapRectToDevice, but they have a rect() which appears to work just as well. 129 | return self.item.rect() 130 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/TVarFigureAxisOnly.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | import pyqtgraph as pg 8 | from pyqtgraph.Qt import QtCore, QtGui 9 | from scipy import interpolate 10 | from .CustomAxis.NonLinearAxis import NonLinearAxis 11 | from .CustomViewBox.CustomVB import CustomVB 12 | from .CustomAxis.AxisItem import AxisItem 13 | 14 | 15 | class TVarFigureAxisOnly(pg.GraphicsLayout): 16 | def __init__(self, tvar_name): 17 | self.tvar_name = tvar_name 18 | 19 | # Sets up the layout of the Tplot Object 20 | pg.GraphicsLayout.__init__(self) 21 | self.layout.setHorizontalSpacing(10) 22 | self.layout.setContentsMargins(0, 0, 0, 0) 23 | 24 | if pytplot.tplot_opt_glob['black_background']: 25 | self.labelStyle = {'font-size': 26 | str(pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras']['char_size']) 27 | + 'pt', 'color': '#FFF'} 28 | else: 29 | self.labelStyle = {'font-size': 30 | str(pytplot.data_quants[self.tvar_name].attrs['plot_options']['extras']['char_size']) 31 | + 'pt', 'color': '#000'} 32 | 33 | vb = CustomVB(enableMouse=False) 34 | self.yaxis = AxisItem("left") 35 | self.yaxis.setLabel(pytplot.data_quants[self.tvar_name].attrs['plot_options']['yaxis_opt']['axis_label'], **self.labelStyle) 36 | self.yaxis.label.setRotation(0) 37 | qt_transform = pg.QtGui.QTransform() 38 | qt_transform.translate(0,-40) 39 | self.yaxis.label.setTransform(qt_transform) 40 | #self.yaxis.label.translate(0, -40) 41 | mapping_function = interpolate.interp1d(pytplot.data_quants[self.tvar_name].coords['time'].values, pytplot.data_quants[self.tvar_name].values) 42 | if 'var_label_ticks' in pytplot.data_quants[self.tvar_name].attrs['plot_options']: 43 | num_ticks = pytplot.data_quants[self.tvar_name].attrs['plot_options']['var_label_ticks'] 44 | else: 45 | num_ticks=5 46 | xaxis = NonLinearAxis(orientation='bottom', mapping_function=mapping_function, num_ticks=num_ticks) 47 | 48 | # Set the font size of the axes 49 | font = QtGui.QFont() 50 | font.setPixelSize(pytplot.tplot_opt_glob['axis_font_size']) 51 | xaxis.setTickFont(font) 52 | 53 | self.plotwindow = self.addPlot(row=0, col=0, axisItems={'bottom': xaxis, 'left': self.yaxis}, viewBox=vb, colspan=1) 54 | self.plotwindow.buttonsHidden = True 55 | self.plotwindow.setMaximumHeight(20) 56 | 57 | # Set up the view box needed for the legends 58 | self.legendvb = pg.ViewBox(enableMouse=False) 59 | self.legendvb.setMaximumWidth(100) 60 | self.addItem(self.legendvb, 0, 1) 61 | -------------------------------------------------------------------------------- /pytplot/QtPlotter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from .TVarFigure1D import TVarFigure1D 7 | from .TVarFigureAlt import TVarFigureAlt 8 | from .TVarFigureAxisOnly import TVarFigureAxisOnly 9 | from .TVarFigureSpec import TVarFigureSpec 10 | from .TVarFigureMap import TVarFigureMap 11 | from .generate import generate_stack 12 | try: 13 | from .PyTPlot_Exporter import PytplotExporter 14 | except: 15 | pass -------------------------------------------------------------------------------- /pytplot/QtPlotter/generate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | 7 | from __future__ import division 8 | import pytplot 9 | from pyqtgraph import LabelItem 10 | import pyqtgraph as pg 11 | from .TVarFigureAxisOnly import TVarFigureAxisOnly 12 | 13 | 14 | def generate_stack(name, 15 | var_label=None, 16 | combine_axes=True, 17 | vert_spacing=25): 18 | 19 | # Set plot backgrounds to black if that tplot_option is set 20 | if pytplot.tplot_opt_glob['black_background']: 21 | pg.setConfigOptions(background='k') 22 | else: 23 | pg.setConfigOptions(background='w') 24 | 25 | new_stack = pg.GraphicsLayoutWidget() 26 | 27 | # Variables needed for pyqtgraph plots 28 | xaxis_thickness = 35 29 | varlabel_xaxis_thickness = 20 30 | title_thickness = 50 31 | 32 | # Setting up the pyqtgraph window 33 | new_stack.setWindowTitle(pytplot.tplot_opt_glob['title_text']) 34 | new_stack.resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) 35 | 36 | # Vertical Box layout to store plots 37 | all_plots = [] 38 | axis_types = [] 39 | i = 0 40 | num_plots = len(name) 41 | 42 | # Configure plot sizes 43 | total_psize = 0 44 | j = 0 45 | while j < num_plots: 46 | total_psize += pytplot.data_quants[name[j]].attrs['plot_options']['extras']['panel_size'] 47 | j += 1 48 | 49 | if var_label is not None: 50 | if not isinstance(var_label, list): 51 | var_label = [var_label] 52 | varlabel_correction = len(var_label) * varlabel_xaxis_thickness 53 | else: 54 | varlabel_correction = 0 55 | 56 | p_to_use = \ 57 | (pytplot.tplot_opt_glob['window_size'][1] - xaxis_thickness - title_thickness - varlabel_correction) / total_psize 58 | 59 | # Whether or not there is a title row in pyqtgraph 60 | titlerow = 0 61 | spacing_in_pixels = vert_spacing 62 | new_stack.ci.layout.setSpacing(spacing_in_pixels) 63 | 64 | # Create all plots 65 | while i < num_plots: 66 | last_plot = (i == num_plots - 1) 67 | 68 | p_height = int(pytplot.data_quants[name[i]].attrs['plot_options']['extras']['panel_size'] * p_to_use) 69 | 70 | if last_plot: 71 | p_height += xaxis_thickness 72 | if i == 0: 73 | if _set_pyqtgraph_title(new_stack): 74 | titlerow = 1 75 | new_stack.ci.layout.setRowPreferredHeight(i + titlerow, p_height) 76 | new_fig = _get_figure_class(name[i], show_xaxis=last_plot) 77 | 78 | new_stack.addItem(new_fig, row=i + titlerow, col=0) 79 | 80 | axis_types.append(new_fig.getaxistype()) 81 | new_fig.buildfigure() 82 | 83 | # Add plot to GridPlot layout 84 | all_plots.append(new_fig.getfig()) 85 | 86 | i = i + 1 87 | 88 | # Add extra x axes if applicable 89 | if var_label is not None: 90 | x_axes_index = 0 91 | for new_x_axis in var_label: 92 | new_axis = TVarFigureAxisOnly(new_x_axis) 93 | new_stack.addItem(new_axis, row=num_plots + titlerow + x_axes_index, col=0) 94 | x_axes_index += 1 95 | axis_types.append(('time', False)) 96 | all_plots.append(new_axis) 97 | 98 | # Ensure all plots in the stack have the same y axis width 99 | maximum_y_axis_width = 0 100 | for plot in all_plots: 101 | if maximum_y_axis_width < plot.yaxis.getWidth(): 102 | maximum_y_axis_width = plot.yaxis.getWidth() 103 | 104 | for plot in all_plots: 105 | if 'yaxis_width' in pytplot.tplot_opt_glob: 106 | plot.yaxis.setWidth(pytplot.tplot_opt_glob['yaxis_width']) 107 | else: 108 | plot.yaxis.setWidth(maximum_y_axis_width) 109 | 110 | # Set all plots' x_range and plot_width to that of the bottom plot 111 | # so all plots will pan and be resized together. 112 | first_type = {} 113 | if combine_axes: 114 | k = 0 115 | while k < len(axis_types): 116 | if axis_types[k][0] not in first_type: 117 | first_type[axis_types[k][0]] = k 118 | else: 119 | all_plots[k].plotwindow.setXLink(all_plots[first_type[axis_types[k][0]]].plotwindow) 120 | k += 1 121 | 122 | return new_stack 123 | 124 | 125 | def _set_pyqtgraph_title(layout): 126 | """ 127 | Private function to add a title to the first row of the window. 128 | Returns True if a Title is set. Else, returns False. 129 | """ 130 | title_set = False 131 | if 'title_size' in pytplot.tplot_opt_glob: 132 | size = pytplot.tplot_opt_glob['title_size'] 133 | if 'title_text' in pytplot.tplot_opt_glob: 134 | title_set = True 135 | if pytplot.tplot_opt_glob['title_text'] != '' and pytplot.tplot_opt_glob['black_background']: 136 | layout.addItem(LabelItem(pytplot.tplot_opt_glob['title_text'], size=size, color='w'), row=0, col=0) 137 | else: 138 | layout.addItem(LabelItem(pytplot.tplot_opt_glob['title_text'], size=size, color='k'), row=0, col=0) 139 | return title_set 140 | 141 | 142 | def _get_figure_class(tvar_name, show_xaxis=True): 143 | if 'plotter' in pytplot.data_quants[tvar_name].attrs['plot_options']['extras'] \ 144 | and pytplot.data_quants[tvar_name].attrs['plot_options']['extras']['plotter'] in \ 145 | pytplot.qt_plotters: 146 | cls = pytplot.qt_plotters[pytplot.data_quants[tvar_name].attrs['plot_options']['extras']['plotter']] 147 | else: 148 | spec_keyword = pytplot.data_quants[tvar_name].attrs['plot_options']['extras'].get('spec', False) 149 | alt_keyword = pytplot.data_quants[tvar_name].attrs['plot_options']['extras'].get('alt', False) 150 | map_keyword = pytplot.data_quants[tvar_name].attrs['plot_options']['extras'].get('map', False) 151 | if spec_keyword: 152 | cls = pytplot.qt_plotters['qtTVarFigureSpec'] 153 | elif alt_keyword: 154 | cls = pytplot.qt_plotters['qtTVarFigureAlt'] 155 | elif map_keyword: 156 | cls = pytplot.qt_plotters['qtTVarFigureMap'] 157 | else: 158 | cls = pytplot.qt_plotters['qtTVarFigure1D'] 159 | return cls(tvar_name, show_xaxis=show_xaxis) 160 | -------------------------------------------------------------------------------- /pytplot/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from _collections import OrderedDict 7 | from . import HTMLPlotter 8 | import os 9 | import sys 10 | import xarray as xr 11 | 12 | # xarray options 13 | xr.set_options(keep_attrs=True) 14 | 15 | # runs without Qt 16 | if not 'PYTPLOT_NO_GRAPHICS' in os.environ: 17 | using_graphics = True 18 | else: 19 | print("Turning off qt graphics. Bokeh plotting is still enabled.") 20 | using_graphics = False 21 | 22 | try: 23 | import google.colab 24 | using_graphics = False 25 | except: 26 | pass 27 | 28 | # This variable will be constantly changed depending on what x value the user is hovering over 29 | class HoverTime(object): 30 | hover_time = 0 31 | functions_to_call = [] 32 | 33 | def register_listener(self, fn): 34 | self.functions_to_call.append(fn) 35 | return 36 | 37 | # New time is time we're hovering over, grabbed from TVarFigure(1D/Spec/Alt/Map) 38 | # name is whatever tplot we're currently hovering over (relevant for 2D interactive 39 | # plots that appear when plotting a spectrogram). 40 | def change_hover_time(self, new_time, name=None): 41 | self.hover_time = new_time 42 | for f in self.functions_to_call: 43 | try: 44 | f(self.hover_time, name) 45 | except Exception as e: 46 | print(e) 47 | return 48 | 49 | if using_graphics: 50 | try: 51 | import pyqtgraph as pg 52 | from pyqtgraph.Qt import QtWidgets 53 | 54 | #Note: This is absolutely required for windows systems to work currently 55 | #But presumably it will be fixed at some point in the future 56 | if sys.platform.startswith('win'): 57 | pg.ptime.time = lambda: 0 58 | 59 | pg.setConfigOptions(imageAxisOrder='row-major') 60 | pg.setConfigOptions(background='w') 61 | 62 | 63 | class PlotWindow(QtWidgets.QMainWindow): 64 | def __init__(self): 65 | super().__init__() 66 | 67 | def init_savepng(self, exporter): 68 | if exporter is None: 69 | return 70 | # Set up the save PNG button/call exportpng function that activates when user presses button 71 | exportdatapngaction = QtWidgets.QAction("Save PNG", self) 72 | exportdatapngaction.triggered.connect(lambda: self.exportpng(exporter)) 73 | 74 | # Set up menu bar to display and call creation of save PNG button 75 | menubar = self.menuBar() 76 | menubar.setNativeMenuBar(False) 77 | menubar.addAction(exportdatapngaction) 78 | self.setWindowTitle('PyTplot Window') 79 | 80 | def exportpng(self, exporter): 81 | if exporter is None: 82 | print("Cannot save the image. Try installing h5py to get around this issue.") 83 | return 84 | # Function called by save PNG button to grab the image from the plot window and save it 85 | fname = QtWidgets.QFileDialog.getSaveFileName(self, 'Open file', 'pytplot.png', filter="png (*.png *.)") 86 | exporter.parameters()['width'] = tplot_opt_glob['window_size'][0] 87 | exporter.parameters()['height'] = tplot_opt_glob['window_size'][1] 88 | exporter.export(fname[0]) 89 | 90 | def newlayout(self, layout): 91 | # Needed for displaying plots 92 | self.setCentralWidget(layout) 93 | 94 | except Exception as e: 95 | print("Qt graphics import failed with error " + str(e)) 96 | print("Turning off qt graphics. Bokeh plotting is still enabled.") 97 | using_graphics = False 98 | 99 | # Global Variables 100 | hover_time = HoverTime() 101 | data_quants = OrderedDict() 102 | interactive_window = None # 2D interactive window that appears whenever plotting spectrograms w/ tplot. 103 | # If option 't_average' is set by user, then x and y values on this plot are the average of the user-specified 104 | # number of seconds for which the cursor location should be averaged. 105 | static_window = None # 2D window showing data at certain point in time from a spectrogram plot. 106 | static_tavg_window = None # 2D window showing averaged y and z data for a specified time range from a spectrogram plot. 107 | tplot_opt_glob = dict(tools="xpan,crosshair,reset", 108 | min_border_top=12, min_border_bottom=12, 109 | title_align='center', window_size=[800, 800], 110 | title_size='12pt', title_text='', crosshair=True, 111 | data_gap=0, black_background=False, axis_font_size=12, axis_tick_num=[(0, 1000000000), (3, 1),], 112 | y_axis_zoom=False) 113 | lim_info = {} 114 | extra_layouts = {} 115 | 116 | if using_graphics: 117 | pytplotWindow_names = [] 118 | pytplotWindows = [] # This is a list that will hold future qt windows 119 | from . import QtPlotter 120 | 121 | qt_plotters = {'qtTVarFigure1D': QtPlotter.TVarFigure1D, 122 | 'qtTVarFigureSpec': QtPlotter.TVarFigureSpec, 123 | 'qtTVarFigureAlt': QtPlotter.TVarFigureAlt, 124 | 'qtTVarFigureMap': QtPlotter.TVarFigureMap} 125 | 126 | bokeh_plotters = {'bkTVarFigure1D': HTMLPlotter.TVarFigure1D, 127 | 'bkTVarFigureMap': HTMLPlotter.TVarFigureMap, 128 | 'bkTVarFigureAlt': HTMLPlotter.TVarFigureAlt, 129 | 'bkTVarFigureSpec': HTMLPlotter.TVarFigureSpec} 130 | 131 | from .store_data import store_data 132 | from .tplot import tplot 133 | from .get_data import get_data 134 | from .xlim import xlim 135 | from .ylim import ylim 136 | from .zlim import zlim 137 | from .tlimit import tlimit 138 | from pytplot.exporters.tplot_save import tplot_save 139 | from .tplot_names import tplot_names 140 | from pytplot.importers.tplot_restore import tplot_restore 141 | from .get_timespan import get_timespan 142 | from .tplot_options import tplot_options 143 | from .tplot_rename import tplot_rename 144 | from .tplot_copy import tplot_copy 145 | from .replace_data import replace_data 146 | from .get_ylimits import get_ylimits 147 | from .timebar import timebar 148 | from .del_data import del_data 149 | from .timespan import timespan 150 | from .options import options 151 | from .timestamp import timestamp 152 | from pytplot.importers.cdf_to_tplot import cdf_to_tplot 153 | from pytplot.importers.netcdf_to_tplot import netcdf_to_tplot 154 | from pytplot.importers.sts_to_tplot import sts_to_tplot 155 | from .tplot_utilities import compare_versions 156 | from .link import link 157 | from pytplot.tplot_math import * 158 | 159 | # Start the App 160 | if using_graphics: 161 | try: 162 | pg.mkQApp() 163 | except Exception as e: 164 | print("Qt graphics import failed with error " + str(e)) 165 | print("Turning off qt graphics. Bokeh plotting is still enabled.") 166 | using_graphics = False 167 | 168 | # Ok. In possibly the weirdest turn of events, I get a warning that interrupts Qt specplots 169 | # if I DO NOT import this library. There is an error about collections.abc in the ImageItem.render() 170 | # function in pyqtgraph that completely works FINE as long as I've imported this library somewhere before 171 | # that render function being called. Why?? 172 | import requests 173 | -------------------------------------------------------------------------------- /pytplot/del_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | import fnmatch 8 | 9 | def del_data(name=None): 10 | """ 11 | This function will delete tplot variables that are already stored in memory. 12 | 13 | Parameters: 14 | name : str 15 | Name of the tplot variable to be deleted. If no name is provided, then 16 | all tplot variables will be deleted. 17 | 18 | Returns: 19 | None 20 | 21 | Examples: 22 | >>> # Delete Variable 1 23 | >>> import pytplot 24 | >>> pytplot.del_data("Variable1") 25 | 26 | """ 27 | if name is None: 28 | tplot_names = list(pytplot.data_quants.keys()) 29 | for i in tplot_names: 30 | del pytplot.data_quants[i] 31 | return 32 | 33 | if not isinstance(name, list): 34 | name = [name] 35 | 36 | entries = [] 37 | for i in name: 38 | if ('?' in i) or ('*' in i): 39 | for j in pytplot.data_quants.keys(): 40 | if isinstance(pytplot.data_quants[j], dict): 41 | # NRV variable 42 | var_verif = fnmatch.fnmatch(pytplot.data_quants[j]['name'], i) 43 | if var_verif == 1: 44 | entries.append(pytplot.data_quants[j]['name']) 45 | else: 46 | continue 47 | else: 48 | var_verif = fnmatch.fnmatch(pytplot.data_quants[j].name, i) 49 | if var_verif == 1: 50 | entries.append(pytplot.data_quants[j].name) 51 | else: 52 | continue 53 | for key in entries: 54 | if key in pytplot.data_quants: 55 | del pytplot.data_quants[key] 56 | elif i not in pytplot.data_quants.keys(): 57 | print(str(i) + " is currently not in pytplot.") 58 | return 59 | 60 | else: 61 | if isinstance(pytplot.data_quants[i], dict): 62 | temp_data_quants = pytplot.data_quants[i] 63 | str_name = temp_data_quants['name'] 64 | else: 65 | temp_data_quants = pytplot.data_quants[i] 66 | str_name = temp_data_quants.name 67 | 68 | del pytplot.data_quants[str_name] 69 | 70 | return -------------------------------------------------------------------------------- /pytplot/exporters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/exporters/__init__.py -------------------------------------------------------------------------------- /pytplot/exporters/tplot_ascii.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | 8 | def tplot_ascii(tvar, filename=None, extension='.csv'): 9 | # grab data, prepend index column 10 | if filename == None: 11 | filename = tvar 12 | # save data 13 | pytplot.data_quants[tvar].to_pandas().to_csv(filename + extension) 14 | # only try to save spec_bins (y-values in spectrograms) if we're sure they exist 15 | if 'spec_bins' in pytplot.data_quants[tvar].coords.keys(): 16 | pytplot.data_quants[tvar].coords['spec_bins'].to_pandas().to_csv(filename + '_v' + extension) 17 | 18 | -------------------------------------------------------------------------------- /pytplot/exporters/tplot_save.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pickle 7 | import pytplot 8 | 9 | def tplot_save(names, filename=None): 10 | """ 11 | This function will save tplot variables into a single file by using the python "pickle" function. 12 | This file can then be "restored" using tplot_restore. This is useful if you want to end the pytplot session, 13 | but save all of your data/options. All variables and plot options can be read back into tplot with the 14 | "tplot_restore" command. 15 | 16 | Parameters: 17 | names : str/list 18 | A string or a list of strings of the tplot variables you would like saved. 19 | filename : str, optional 20 | The filename where you want to save the file. 21 | 22 | Returns: 23 | None 24 | 25 | Examples: 26 | >>> # Save a single tplot variable 27 | >>> import pytplot 28 | >>> x_data = [1,2,3,4,5] 29 | >>> y_data = [1,2,3,4,5] 30 | >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) 31 | >>> pytplot.ylim('Variable1', 2, 4) 32 | >>> pytplot.save('Variable1', filename='C:/temp/variable1.pytplot') 33 | 34 | """ 35 | if isinstance(names,int): 36 | names = list(pytplot.data_quants.keys())[names-1] 37 | if not isinstance(names, list): 38 | names = [names] 39 | 40 | #Check that we have all available data 41 | for name in names: 42 | if not isinstance(pytplot.data_quants[name], dict): # not a NRV variable 43 | # variable is a time series 44 | for oplot_name in pytplot.data_quants[name].attrs['plot_options']['overplots']: 45 | if oplot_name not in names: 46 | names.append(oplot_name) 47 | 48 | #Pickle it up 49 | to_pickle =[] 50 | for name in names: 51 | if name not in pytplot.data_quants.keys(): 52 | print("That name is currently not in pytplot") 53 | return 54 | to_pickle.append(pytplot.data_quants[name]) 55 | 56 | num_quants = len(to_pickle) 57 | to_pickle = [num_quants] + to_pickle 58 | temp_tplot_opt_glob = pytplot.tplot_opt_glob 59 | to_pickle.append(temp_tplot_opt_glob) 60 | 61 | if filename==None: 62 | filename='var_'+'-'.join(names)+'.pytplot' 63 | 64 | out_file = open(filename, "wb") 65 | pickle.dump(to_pickle, out_file) 66 | out_file.close() 67 | 68 | return -------------------------------------------------------------------------------- /pytplot/get_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | from collections import namedtuple 8 | 9 | def get_data(name, xarray=False, metadata=False): 10 | """ 11 | This function extracts the data from the tplot Variables stored in memory. 12 | 13 | Parameters: 14 | name : str 15 | Name of the tplot variable 16 | xarray : bool 17 | Keep the variable as an xarray object 18 | metadata : bool 19 | Return the metadata of the object (the attr dictionary) instead of the actual data 20 | 21 | 22 | Returns: tuple of data/dimensions/metadata stored in pytplot 23 | time_val : numpy array of seconds since 1970 24 | data_val : n-dimensional array of data 25 | spec_bins_val (if exists) : spectral bins if the plot is a spectrogram 26 | v1_val (if exists) : numpy array of v1 dimension coordinates 27 | v2_val {if exists} : numpy array of v2 dimension coordinates 28 | v3_val (if exists) : numpy array of v3 dimension coordinates 29 | 30 | 31 | Examples: 32 | >>> # Retrieve the data from Variable 1 33 | >>> import pytplot 34 | >>> x_data = [1,2,3,4,5] 35 | >>> y_data = [1,2,3,4,5] 36 | >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) 37 | >>> time, data = pytplot.get_data("Variable1") 38 | 39 | """ 40 | 41 | if name not in pytplot.data_quants.keys(): 42 | print("That name is currently not in pytplot") 43 | return 44 | 45 | temp_data_quant = pytplot.data_quants[name] 46 | 47 | if isinstance(temp_data_quant, dict): 48 | # non-record varying variables are stored as dicts 49 | return temp_data_quant['data'] 50 | 51 | if xarray: 52 | return temp_data_quant 53 | 54 | if metadata: 55 | return temp_data_quant.attrs 56 | 57 | if 'v1' in temp_data_quant.coords.keys() and 'v2' in temp_data_quant.coords.keys() and 'v3' in temp_data_quant.coords.keys(): 58 | variable = namedtuple('variable', ['times', 'y', 'v1', 'v2', 'v3']) 59 | return variable(temp_data_quant.time.values, temp_data_quant.data, temp_data_quant.coords['v1'].values, temp_data_quant.coords['v2'].values, temp_data_quant.coords['v3'].values) 60 | elif 'v1' in temp_data_quant.coords.keys() and 'v2' in temp_data_quant.coords.keys(): 61 | variable = namedtuple('variable', ['times', 'y', 'v1', 'v2']) 62 | return variable(temp_data_quant.time.values, temp_data_quant.data, temp_data_quant.coords['v1'].values, temp_data_quant.coords['v2'].values) 63 | elif 'v1' in temp_data_quant.coords.keys(): 64 | variable = namedtuple('variable', ['times', 'y', 'v1']) 65 | return variable(temp_data_quant.time.values, temp_data_quant.data, temp_data_quant.coords['v1'].values) 66 | elif 'v' in temp_data_quant.coords.keys(): 67 | variable = namedtuple('variable', ['times', 'y', 'v']) 68 | return variable(temp_data_quant.time.values, temp_data_quant.data, temp_data_quant.coords['v'].values) 69 | elif 'spec_bins' in temp_data_quant.coords.keys(): 70 | variable = namedtuple('variable', ['times', 'y', 'v']) 71 | return variable(temp_data_quant.time.values, temp_data_quant.data, temp_data_quant.coords['spec_bins'].values) 72 | variable = namedtuple('variable', ['times', 'y']) 73 | 74 | return variable(temp_data_quant.time.values, temp_data_quant.data) 75 | -------------------------------------------------------------------------------- /pytplot/get_timespan.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | from . import tplot_utilities 8 | 9 | 10 | def get_timespan(name): 11 | """ 12 | This function extracts the time span from the Tplot Variables stored in memory. 13 | 14 | Parameters: 15 | name : str 16 | Name of the tplot variable 17 | 18 | Returns: 19 | time_begin : float 20 | The beginning of the time series 21 | time_end : float 22 | The end of the time series 23 | 24 | Examples: 25 | >>> # Retrieve the time span from Variable 1 26 | >>> import pytplot 27 | >>> x_data = [1,2,3,4,5] 28 | >>> y_data = [1,2,3,4,5] 29 | >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) 30 | >>> time1, time2 = pytplot.get_timespan("Variable1") 31 | 32 | """ 33 | 34 | if name not in pytplot.data_quants.keys(): 35 | print("That name is currently not in pytplot") 36 | return 37 | 38 | return pytplot.data_quants[name].attrs['plot_options']['trange'][0], pytplot.data_quants[name].attrs['plot_options']['trange'][1] -------------------------------------------------------------------------------- /pytplot/get_ylimits.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | 8 | 9 | def get_ylimits(name, trg=None): 10 | """ 11 | This function will get extract the y-limits from the Tplot Variables stored in memory. 12 | 13 | Parameters: 14 | name : str 15 | Name of the tplot variable 16 | trg : list, optional 17 | The time range that you would like to look in 18 | 19 | Returns: 20 | ymin : float 21 | The minimum value of y 22 | ymax : float 23 | The maximum value of y 24 | 25 | Examples: 26 | >>> # Retrieve the y-limits from Variable 1 27 | >>> import pytplot 28 | >>> x_data = [1,2,3,4,5] 29 | >>> y_data = [1,2,3,4,5] 30 | >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) 31 | >>> y1, y2 = pytplot.get_ylimits("Variable1") 32 | 33 | """ 34 | if isinstance(name, int): 35 | name = list(pytplot.data_quants.keys())[name-1] 36 | if not isinstance(name, list): 37 | name = [name] 38 | name_num = len(name) 39 | ymin = None 40 | ymax = None 41 | 42 | for i in range(name_num): 43 | 44 | if name[i] not in pytplot.data_quants.keys(): 45 | print(str(name[i]) + " is currently not in pytplot.") 46 | return 47 | y = pytplot.data_quants[name[i]] 48 | 49 | # Slice the data around a time range 50 | if trg is not None: 51 | y = y.sel(time=slice(trg[0], trg[1])) 52 | 53 | ymin = y.min(skipna=True) 54 | ymax = y.max(skipna=False) 55 | 56 | return ymin, ymax 57 | -------------------------------------------------------------------------------- /pytplot/importers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/importers/__init__.py -------------------------------------------------------------------------------- /pytplot/importers/netcdf_to_tplot.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xarray as xr 3 | from pytplot import tplot, store_data 4 | import pytplot 5 | import calendar 6 | 7 | 8 | def change_time_to_unix_time(time_var): 9 | from netCDF4 import num2date 10 | # A function that takes a variable with units of 'seconds/minutes/hours/etc. since YYYY-MM-DD:HH:MM:SS/etc 11 | # and converts the variable to seconds since epoch 12 | units = time_var.units 13 | dates = num2date(time_var[:], units=units) 14 | unix_times = list() 15 | for date in dates: 16 | unix_time = calendar.timegm(date.timetuple()) 17 | unix_times.append(unix_time) 18 | return unix_times 19 | 20 | 21 | def netcdf_to_tplot(filenames, time ='', prefix='', suffix='', plot=False, merge=False): 22 | ''' 23 | This function will automatically create tplot variables from CDF files. 24 | 25 | Parameters: 26 | filenames : str/list of str 27 | The file names and full paths of netCDF files. 28 | time: str 29 | The name of the netCDF file's time variable. 30 | prefix: str 31 | The tplot variable names will be given this prefix. By default, 32 | no prefix is added. 33 | suffix: str 34 | The tplot variable names will be given this suffix. By default, 35 | no suffix is added. 36 | plot: bool 37 | The data is plotted immediately after being generated. All tplot 38 | variables generated from this function will be on the same plot. 39 | By default, a plot is not created. 40 | merge: bool 41 | If True, then data from different netCDF files will be merged into 42 | a single pytplot variable. 43 | 44 | Returns: 45 | List of tplot variables created. 46 | 47 | Examples: 48 | >>> #Create tplot variables from a GOES netCDF file 49 | >>> import pytplot 50 | >>> file = "/Users/user_name/goes_files/g15_epead_a16ew_1m_20171201_20171231.nc" 51 | >>> pytplot.netcdf_to_tplot(file, prefix='mvn_') 52 | 53 | >>> #Add a prefix, and plot immediately. 54 | >>> import pytplot 55 | >>> file = "/Users/user_name/goes_files/g15_epead_a16ew_1m_20171201_20171231.nc" 56 | >>> pytplot.netcdf_to_tplot(file, prefix='goes_prefix_', plot=True) 57 | 58 | ''' 59 | 60 | from netCDF4 import Dataset 61 | 62 | stored_variables = [] 63 | 64 | if isinstance(filenames, str): 65 | filenames = [filenames] 66 | elif isinstance(filenames, list): 67 | filenames = filenames 68 | else: 69 | print("Invalid filenames input.") 70 | #return stored_variables 71 | 72 | for filename in filenames: 73 | 74 | # Read in file 75 | file = Dataset(filename, "r+") 76 | 77 | # Creating dictionary that will contain variables and their attributes 78 | vars_and_atts = {} 79 | for name, variable in file.variables.items(): 80 | vars_and_atts[name] = {} 81 | for attrname in variable.ncattrs(): 82 | vars_and_atts[name][attrname] = getattr(variable, attrname) 83 | 84 | # Filling in missing values for each variable with np.nan (if values are not already nan) 85 | # and saving the masked variables to a new dictionary 86 | masked_vars = {} # Dictionary containing properly masked variables 87 | for var in vars_and_atts.keys(): 88 | reg_var = file.variables[var] 89 | try: 90 | var_fill_value = vars_and_atts[var]['missing_value'] 91 | if np.isnan(var_fill_value) != True: 92 | # We want to force missing values to be nan so that plots don't look strange 93 | var_mask = np.ma.masked_where(reg_var == np.float32(var_fill_value), reg_var) 94 | var_filled = np.ma.filled(var_mask, np.nan) 95 | masked_vars[var] = var_filled 96 | elif np.isnan(var_fill_value) == True: 97 | # missing values are already np.nan, don't need to do anything 98 | var_filled = reg_var 99 | masked_vars[var] = var_filled 100 | except: # continue # Go to next iteration, this variable doesn't have data to mask (probably just a descriptor variable (i.e., 'base_time') 101 | var_filled = reg_var 102 | masked_vars[var] = var_filled 103 | 104 | # Most files are from GOES data, which seems to usually have 'time_tag' in them that contain time information. 105 | # There is an exception filter below that will allow a user to pick a different time variable if time_tag doesn't exist. 106 | if time != '': 107 | time_var = file[time] 108 | unix_times = change_time_to_unix_time(time_var) 109 | 110 | elif time == '': 111 | time = input('Please enter time variable name. \nVariable list: {l}'.format(l=vars_and_atts.keys())) 112 | while True: 113 | if time not in vars_and_atts.keys(): 114 | # Making sure we input a valid response (i.e., the variable exists in the dataset), and also avoiding 115 | # plotting a time variable against time.... because I don't even know what that would mean and uncover. 116 | print('Not a valid variable name, please try again.') 117 | continue 118 | elif time in vars_and_atts.keys(): 119 | time_var = time 120 | unix_times = change_time_to_unix_time(time_var) 121 | 122 | for i,var in enumerate(file.variables): 123 | # Here, we are making sure that the variables are time-based, otherwise we don't want to store them as tplot variables! 124 | if 'record' in file[var].dimensions[0] or 'time' in file[var].dimensions[0]: 125 | # Store the data now, as well as merge variables if that's desired 126 | var_name = prefix + var + suffix 127 | to_merge = False 128 | if (var_name in pytplot.data_quants.keys() and (merge == True)): 129 | prev_data_quant = pytplot.data_quants[var_name].values 130 | to_merge = True 131 | 132 | tplot_data = {'x': unix_times, 'y': masked_vars[var]} 133 | store_data(var_name, tplot_data) 134 | if var_name not in stored_variables: 135 | stored_variables.append(var_name) 136 | 137 | if to_merge == True: 138 | cur_data_quant = pytplot.data_quants[var_name].values 139 | merged_data = [prev_data_quant, cur_data_quant] 140 | pytplot.data_quants[var_name].values = xr.concat(merged_data).values 141 | 142 | # If we are interested in seeing a quick plot of the variables, do it 143 | if plot: 144 | tplot(stored_variables) 145 | else: 146 | # If the variable isn't time-bound, we're going to look at the next variable 147 | continue 148 | 149 | return stored_variables 150 | 151 | -------------------------------------------------------------------------------- /pytplot/link.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | import numpy as np 8 | 9 | def link(names, link_name, link_type='alt'): 10 | ''' 11 | Simply adds metadata to the tplot variables, specifying which other tplot variables 12 | contain other coordinates, typically positional information. 13 | 14 | Parameters: 15 | names: str or list of str 16 | The names to link 17 | link_name: str 18 | The tplot variable to link to the names 19 | link_type: str 20 | The relationship that link_name has to the names. Values can be 21 | lat, lon, alt, x, y, z, mso_x, mso_y, mso_z 22 | ''' 23 | 24 | link_type = link_type.lower() 25 | if not isinstance(names, list): 26 | names = [names] 27 | 28 | for i in names: 29 | if i not in pytplot.data_quants.keys(): 30 | print(str(i) + " is not currently in pytplot.") 31 | return 32 | 33 | if isinstance(i,int): 34 | i = list(pytplot.data_quants.keys())[i-1] 35 | 36 | pytplot.data_quants[pytplot.data_quants[i].name].attrs['plot_options']['links'][link_type] = link_name 37 | 38 | return 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /pytplot/replace_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | import numpy as np 8 | from pytplot import tplot_utilities as utilities 9 | from copy import deepcopy 10 | from collections import OrderedDict 11 | 12 | 13 | def replace_data(tplot_name, new_data): 14 | """ 15 | This function will replace all of the data in a tplot variable 16 | 17 | Parameters: 18 | tplot_name : str 19 | The name of the tplot variable 20 | new_data : np array (or something that can be converted to a np.array) 21 | The data to replace the 22 | 23 | Returns: 24 | None 25 | 26 | Examples: 27 | >>> # Copy Variable 1 into a new Variable 2 28 | >>> import pytplot 29 | >>> pytplot.replace_data("Variable1", [[1,2,3,4],[5,6,7,8]]) 30 | 31 | """ 32 | 33 | # if old name input is a number, convert to corresponding name 34 | if isinstance(tplot_name, int): 35 | tplot_name = pytplot.data_quants[tplot_name].name 36 | 37 | # check if old name is in current dictionary 38 | if tplot_name not in pytplot.data_quants.keys(): 39 | print(f"{tplot_name} is currently not in pytplot") 40 | return 41 | 42 | new_data_np = np.asarray(new_data) 43 | shape_old = pytplot.data_quants[tplot_name].values.shape 44 | shape_new = new_data_np.shape 45 | if shape_old != shape_new: 46 | print(f"Dimensions do not match for replace data. {shape_new} does not equal {shape_old}. Returning...") 47 | return 48 | 49 | pytplot.data_quants[tplot_name].values = new_data_np 50 | 51 | pytplot.data_quants[tplot_name].attrs['plot_options']['yaxis_opt']['y_range'] = utilities.get_y_range(pytplot.data_quants[tplot_name]) 52 | 53 | return 54 | -------------------------------------------------------------------------------- /pytplot/sampledata/test_data.tplot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/pytplot/sampledata/test_data.tplot -------------------------------------------------------------------------------- /pytplot/timebar.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from . import tplot_utilities 7 | from bokeh.models import Span 8 | import pytplot 9 | 10 | def timebar(t, varname = None, databar = False, delete = False, color = 'black', thick = 1, dash = False): 11 | """ 12 | This function will add a vertical bar to all time series plots. This is useful if you 13 | want to bring attention to a specific time. 14 | 15 | Parameters: 16 | t : flt/list 17 | The time in seconds since Jan 01 1970 to place the vertical bar. If a list of numbers are supplied, 18 | multiple bars will be created. If "databar" is set, then "t" becomes the point on the y axis to 19 | place a horizontal bar. 20 | varname : str/list, optional 21 | The variable(s) to add the vertical bar to. If not set, the default is to add it to all current plots. 22 | databar : bool, optional 23 | This will turn the timebar into a horizontal data bar. If this is set True, then variable "t" becomes 24 | the point on the y axis to place a horizontal bar. 25 | delete : bool, optional 26 | If set to True, at lease one varname must be supplied. The timebar at point "t" for variable "varname" 27 | will be removed. 28 | color : str 29 | The color of the bar 30 | thick : int 31 | The thickness of the bar 32 | dash : bool 33 | If set to True, the bar is dashed rather than solid 34 | 35 | Returns: 36 | None 37 | 38 | Examples: 39 | >>> # Place a green time bar at 2017-07-17 00:00:00 40 | >>> import pytplot 41 | >>> pytplot.timebar(1500249600, color='green') 42 | 43 | >>> # Place a dashed data bar at 5500 on the y axis 44 | >>> pytplot.timebar(5500, dashed=True, databar=True) 45 | 46 | >>> Place 3 magenta time bars of thickness 5 47 | at [2015-12-26 05:20:01, 2015-12-26 08:06:40, 2015-12-26 08:53:19] 48 | for variable 'sgx' plot 49 | >>> pytplot.timebar([1451107201,1451117200,1451119999],'sgx',color='m',thick=5) 50 | 51 | """ 52 | 53 | # make sure t entered is a list 54 | if not isinstance(t, list): 55 | t = [t] 56 | 57 | # if entries in list not numerical, run str_to_int 58 | if not isinstance(t[0], (int, float, complex)): 59 | t1 = [] 60 | for time in t: 61 | t1.append(tplot_utilities.str_to_int(time)) 62 | t = t1 63 | 64 | dim = 'height' 65 | if databar is True: 66 | dim = 'width' 67 | 68 | dash_pattern = 'solid' 69 | if dash is True: 70 | dash_pattern = 'dashed' 71 | 72 | 73 | if delete is True: 74 | tplot_utilities.timebar_delete(t, varname, dim) 75 | return 76 | 77 | #if no varname specified, add timebars to every plot 78 | if varname is None: 79 | num_bars = len(t) 80 | for i in range(num_bars): 81 | tbar = {} 82 | tbar['location'] = t[i] 83 | tbar['dimension'] = dim 84 | tbar['line_color'] = pytplot.tplot_utilities.rgb_color(color)[0] 85 | tbar['line_width'] = thick 86 | tbar['line_dash'] = dash_pattern 87 | for name in pytplot.data_quants: 88 | temp_data_quants = pytplot.data_quants[name] 89 | if isinstance(temp_data_quants, dict): 90 | # non-record varying variable 91 | continue 92 | temp_data_quants.attrs['plot_options']['time_bar'].append(tbar) 93 | #if varname specified 94 | else: 95 | if not isinstance(varname, list): 96 | varname = [varname] 97 | for j in varname: 98 | if j not in pytplot.data_quants.keys(): 99 | print(str(j) + "is currently not in pytplot") 100 | else: 101 | num_bars = len(t) 102 | for i in range(num_bars): 103 | tbar = {} 104 | tbar['location'] = t[i] 105 | tbar['dimension'] = dim 106 | tbar['line_color'] = pytplot.tplot_utilities.rgb_color(color)[0] 107 | tbar['line_width'] = thick 108 | tbar['line_dash'] = dash_pattern 109 | temp_data_quants = pytplot.data_quants[j] 110 | if isinstance(temp_data_quants, dict): 111 | # non-record varying variable 112 | continue 113 | temp_data_quants.attrs['plot_options']['time_bar'].append(tbar) 114 | return -------------------------------------------------------------------------------- /pytplot/timespan.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from . import tplot_utilities 7 | from .xlim import xlim 8 | 9 | def timespan(t1, dt, keyword = 'days'): 10 | """ 11 | This function will set the time range for all time series plots. This is a wrapper for the function "xlim" to 12 | better handle time axes. 13 | 14 | Parameters: 15 | t1 : flt/str 16 | The time to start all time series plots. Can be given in seconds since epoch, or as a string 17 | in the format "YYYY-MM-DD HH:MM:SS" 18 | dt : flt 19 | The time duration of the plots. Default is number of days. 20 | keyword : str 21 | Sets the units of the "dt" variable. Days, hours, minutes, and seconds are all accepted. 22 | 23 | Returns: 24 | None 25 | 26 | Examples: 27 | >>> # Set the timespan to be 2017-07-17 00:00:00 plus 1 day 28 | >>> import pytplot 29 | >>> pytplot.timespan(1500249600, 1) 30 | 31 | >>> # The same as above, but using different inputs 32 | >>> pytplot.timespan("2017-07-17 00:00:00", 24, keyword='hours') 33 | 34 | """ 35 | 36 | if keyword is 'days': 37 | dt *= 86400 38 | elif keyword is 'hours': 39 | dt *= 3600 40 | elif keyword is 'minutes': 41 | dt *= 60 42 | elif keyword is 'seconds': 43 | dt *= 1 44 | else: 45 | print("Invalid 'keyword' option.\nEnum(None, 'hours', 'minutes', 'seconds', 'days')") 46 | 47 | if not isinstance(t1, (int, float, complex)): 48 | t1 = tplot_utilities.str_to_int(t1) 49 | t2 = t1+dt 50 | xlim(t1, t2) 51 | 52 | return -------------------------------------------------------------------------------- /pytplot/timestamp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | import datetime 8 | 9 | def timestamp(val): 10 | """ 11 | This function will turn on a time stamp that shows up at the bottom of every generated plot. 12 | 13 | Parameters 14 | val str 15 | A string that can either be 'on' or 'off'. 16 | 17 | Returns 18 | None 19 | 20 | Examples 21 | # Turn on the timestamp 22 | import pytplot 23 | pytplot.timestamp('on') 24 | """ 25 | 26 | 27 | 28 | if val is 'on': 29 | todaystring = datetime.datetime.now().strftime('%Y-%m-%d %H%M%S') 30 | pytplot.extra_layouts['time_stamp'] = todaystring 31 | else: 32 | if 'time_stamp' in pytplot.extra_layouts: 33 | del pytplot.extra_layouts['time_stamp'] 34 | 35 | return -------------------------------------------------------------------------------- /pytplot/tlimit.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | from __future__ import division 7 | import pytplot 8 | 9 | from .xlim import xlim 10 | 11 | def tlimit(arg): 12 | 13 | if arg is 'last': 14 | xlast = pytplot.lim_info['xlast'] 15 | pytplot.lim_info['xlast'] = pytplot.tplot_opt_glob['x_range'] 16 | pytplot.tplot_opt_glob['x_range'] = xlast 17 | elif arg is 'full': 18 | pytplot.tplot_opt_glob['x_range'] = pytplot.lim_info['xfull'] 19 | elif isinstance(arg, list): 20 | minn = arg[0] 21 | maxx = arg[1] 22 | xlim(minn, maxx) 23 | 24 | return -------------------------------------------------------------------------------- /pytplot/tplot_copy.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | from copy import deepcopy 8 | from collections import OrderedDict 9 | 10 | 11 | def tplot_copy(old_name, new_name): 12 | """ 13 | This function will copy a tplot variables that is already stored in memory. 14 | 15 | Parameters: 16 | name : str 17 | Old name of the Tplot Variable 18 | new_name : str 19 | Name of the copied Tplot Variable 20 | 21 | Returns: 22 | None 23 | 24 | Examples: 25 | >>> # Copy Variable 1 into a new Variable 2 26 | >>> import pytplot 27 | >>> pytplot.tplot_copy("Variable1", "Variable2") 28 | 29 | """ 30 | 31 | # if old name input is a number, convert to corresponding name 32 | if isinstance(old_name, int): 33 | if isinstance(pytplot.data_quants[old_name], dict): 34 | old_name = pytplot.data_quants[old_name]['name'] 35 | else: 36 | old_name = pytplot.data_quants[old_name].name 37 | 38 | # check if old name is in current dictionary 39 | if old_name not in pytplot.data_quants.keys(): 40 | print("That name is currently not in pytplot") 41 | return 42 | 43 | # Add a new data quantity with the copied data 44 | if isinstance(pytplot.data_quants[old_name], dict): 45 | # old variable is a non-record varying variable 46 | pytplot.store_data(new_name, data={'y': pytplot.data_quants[old_name]['data']}) 47 | else: 48 | attr_dict = deepcopy(pytplot.data_quants[old_name].attrs) 49 | data_dict = {} 50 | data_dict['x'] = pytplot.data_quants[old_name].coords['time'].values 51 | data_dict['y'] = pytplot.data_quants[old_name].values 52 | if len(data_dict['y'].shape) <= 2: 53 | pass 54 | else: 55 | for c in pytplot.data_quants[old_name].coords: 56 | if c == 'time' or c == 'spec_bins': 57 | continue 58 | data_dict[c] = pytplot.data_quants[old_name].coords[c].values 59 | pytplot.store_data(new_name, data=data_dict) 60 | pytplot.data_quants[new_name].attrs = attr_dict 61 | 62 | return 63 | -------------------------------------------------------------------------------- /pytplot/tplot_math/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | from .add_across import add_across 7 | from .add import add 8 | from .avg_res_data import avg_res_data 9 | from .clip import clip 10 | from .crop import crop 11 | from .deflag import deflag 12 | from .degap import degap 13 | from .derive import derive 14 | from .divide import divide 15 | from .flatten import flatten 16 | from .interp_nan import interp_nan 17 | from .tinterp import tinterp 18 | from .join_vec import join_vec 19 | from .multiply import multiply 20 | from .resample import resample 21 | from .spec_mult import spec_mult 22 | from .split_vec import split_vec 23 | from .subtract import subtract 24 | from .pwr_spec import pwr_spec -------------------------------------------------------------------------------- /pytplot/tplot_math/add.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | def add(tvar1,tvar2,new_tvar=None): 10 | """ 11 | Adds two tplot variables together. Will interpolate if the two are not on the same time cadence. 12 | 13 | Parameters: 14 | tvar1 : str 15 | Name of first tplot variable. 16 | tvar2 : int/float 17 | Name of second tplot variable 18 | new_tvar : str 19 | Name of new tvar for added data. If not set, then the data in tvar1 is replaced. 20 | 21 | Returns: 22 | None 23 | 24 | Examples: 25 | >>> pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 26 | >>> pytplot.store_data('c', data={'x':[0,4,8,12,16,19,21], 'y':[1,4,1,7,1,9,1]}) 27 | >>> pytplot.add('a','c','a+c') 28 | """ 29 | 30 | # interpolate tvars 31 | tv2 = pytplot.tplot_math.tinterp(tvar1, tvar2) 32 | 33 | # separate and subtract data 34 | data1 = pytplot.data_quants[tvar1].values 35 | data2 = pytplot.data_quants[tv2].values 36 | data = data1 + data2 37 | 38 | # store subtracted data 39 | if new_tvar is None: 40 | pytplot.data_quants[tvar1].values = data 41 | return tvar1 42 | 43 | if 'spec_bins' in pytplot.data_quants[tvar1].coords: 44 | pytplot.store_data(new_tvar, data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data, 45 | 'v': pytplot.data_quants[tvar1].coords['spec_bins'].values}) 46 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 47 | else: 48 | pytplot.store_data(new_tvar, data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data}) 49 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 50 | 51 | return new_tvar -------------------------------------------------------------------------------- /pytplot/tplot_math/add_across.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | import copy 9 | 10 | def add_across(tvar,column_range=None,new_tvar=None): 11 | """ 12 | Adds across columns in the tplot variable 13 | 14 | .. note:: 15 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 16 | 17 | Parameters: 18 | tvar : str 19 | Name of tplot variable. 20 | column_range: list of ints 21 | The columns to add together. For example, if [1,4] is given here, columns 1, 2, 3, and 4 will be added together. 22 | If not set, then every column is added. 23 | new_tvar : str 24 | Name of new tvar for averaged data. If not set, then the variable is replaced 25 | 26 | Returns: 27 | None 28 | 29 | Examples: 30 | >>> #Add across every column in the data 31 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 32 | >>> pytplot.add_across('d',new_tvar='d_aa') 33 | >>> print(pytplot.data_quants['d_aa'].data) 34 | 35 | >>> #Add across specific columns in the data 36 | >>> pytplot.store_data('b', data={'x':[2,5,8,11,14,17,20], 'y':[[1,1,1,1,1,1],[2,2,5,4,1,1],[100,100,3,50,1,1],[4,4,8,58,1,1],[5,5,9,21,1,1],[6,6,2,2,1,1],[7,7,1,6,1,1]]}) 37 | >>> pytplot.add_across('b',column_range=[[1,2],[3,4]],new_tvar='b_aap') 38 | >>> print(pytplot.data_quants['b_aap'].data) 39 | """ 40 | # separate and add data 41 | 42 | if 'spec_bins' in pytplot.data_quants[tvar].coords: 43 | d, s = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvar) 44 | else: 45 | d = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvar, no_spec_bins=True) 46 | s = None 47 | 48 | time = d.index.copy() 49 | data1 = d.copy() 50 | if s is not None: 51 | data2 = s.copy() 52 | else: 53 | data2=None 54 | data = [] 55 | spec_data = [] 56 | 57 | if column_range is None: 58 | column_range = [0, len(d.columns)-1] 59 | 60 | #grab column data 61 | if len(column_range)==2 and isinstance(column_range[0],int): 62 | range_start = column_range[0] 63 | range_end = column_range[1] 64 | add_col = list(range(range_start,range_end+1)) 65 | datasum = data1[add_col].sum(axis=1) 66 | data = data + [list(datasum)] 67 | else: 68 | for i in column_range: 69 | #if not a list 70 | if type(i) == int: 71 | data = data + [list(data1[i])] 72 | #sum across listed column range 73 | else: 74 | range_start = i[0] 75 | range_end = i[1] 76 | add_col = list(range(range_start,range_end+1)) 77 | datasum = data1[add_col].sum(axis=1) 78 | data = data + [list(datasum)] 79 | 80 | if data2 is not None: 81 | if len(column_range) == 2 and isinstance(column_range[0], int): 82 | range_start = column_range[0] 83 | range_end = column_range[1] 84 | add_col = list(range(range_start, range_end + 1)) 85 | datasum = data2[add_col].mean(axis=1) 86 | spec_data = spec_data + [list(datasum)] 87 | else: 88 | for i in column_range: 89 | # if not a list 90 | if type(i) == int: 91 | spec_data = spec_data + [list(data2[i])] 92 | # sum across listed column range 93 | else: 94 | range_start = i[0] 95 | range_end = i[1] 96 | add_col = list(range(range_start, range_end + 1)) 97 | datasum = data2[add_col].mean(axis=1) 98 | spec_data = spec_data + [list(datasum)] 99 | 100 | #store added data 101 | if s is None: 102 | pytplot.store_data(new_tvar,data={'x':time, 'y':np.transpose(data)}) 103 | else: 104 | pytplot.store_data(new_tvar, data={'x': time, 'y':np.transpose(data), 'v': np.transpose(spec_data)}) 105 | 106 | #pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 107 | 108 | return 109 | 110 | -------------------------------------------------------------------------------- /pytplot/tplot_math/avg_res_data.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import numpy as np 3 | import copy 4 | 5 | def avg_res_data(tvar,res,new_tvar=None): 6 | """ 7 | Averages the variable over a specified period of time. 8 | 9 | Parameters: 10 | tvar1 : str 11 | Name of tplot variable. 12 | res : int/float 13 | The new data resolution 14 | new_tvar : str 15 | Name of new tvar for averaged data. If not set, then the data in tvar is replaced. 16 | 17 | Returns: 18 | None 19 | 20 | Examples: 21 | >>> #Average the data over every two seconds 22 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 23 | >>> pytplot.avg_res_data('d',2,'d2res') 24 | >>> print(pytplot.data_quants['d'].values) 25 | """ 26 | 27 | tvar_new = pytplot.data_quants[tvar].coarsen(time=res, boundary='trim').mean() 28 | tvar_new.name = pytplot.data_quants[tvar].name 29 | tvar_new.attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 30 | 31 | if new_tvar is None: 32 | pytplot.data_quants[tvar] = tvar_new 33 | else: 34 | if 'spec_bins' in pytplot.data_quants[tvar].coords: 35 | pytplot.store_data(new_tvar, data={'x': tvar_new.coords['time'].values, 'y': tvar_new.values, 36 | 'v': tvar_new.coords['spec_bins'].values}) 37 | else: 38 | pytplot.store_data(new_tvar, data={'x': tvar_new.coords['time'].values, 'y': tvar_new.values}) 39 | 40 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 41 | 42 | return 43 | 44 | -------------------------------------------------------------------------------- /pytplot/tplot_math/clip.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | import copy 9 | 10 | def clip(tvar,ymin,ymax,new_tvar=None): 11 | """ 12 | Change out-of-bounds data to NaN. 13 | 14 | Parameters: 15 | tvar1 : str 16 | Name of tvar to use for data clipping. 17 | ymin : int/float 18 | Minimum value to keep (inclusive) 19 | ymax : int/float 20 | Maximum value to keep (inclusive) 21 | newtvar : str 22 | Name of new tvar for clipped data storage. If not specified, tvar will be replaced 23 | 24 | Returns: 25 | None 26 | 27 | Examples: 28 | >>> Make any values below 2 and above 6 equal to NaN. 29 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1],[2,2],[100,100],[4,4],[5,5],[6,6],[7,7]]}) 30 | >>> pytplot.clip('d',2,6,'e') 31 | """ 32 | 33 | a = copy.deepcopy(pytplot.data_quants[tvar].where(pytplot.data_quants[tvar] >= ymin)) 34 | a = copy.deepcopy(a.where(a <= ymax)) 35 | 36 | if new_tvar is None: 37 | a.name = tvar 38 | pytplot.data_quants[tvar] = a 39 | else: 40 | if 'spec_bins' in a.coords: 41 | pytplot.store_data(new_tvar, data={'x': a.coords['time'], 'y': a.values, 'v': a.coords['spec_bins']}) 42 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 43 | else: 44 | pytplot.store_data(new_tvar, data={'x': a.coords['time'], 'y': a.values}) 45 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 46 | 47 | return 48 | -------------------------------------------------------------------------------- /pytplot/tplot_math/crop.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | def crop(tvar1,tvar2, replace=True): 10 | """ 11 | Crops both tplot variable so that their times are the same. This is done automatically by other processing routines if times do not match up. 12 | 13 | Parameters: 14 | tvar1 : str 15 | Name of the first tplot variable 16 | tvar2 : str 17 | Name of the second tplot variable 18 | replace : bool, optional 19 | If true, the data in the original tplot variables are replaced. Otherwise, new variables are created. 20 | 21 | Returns: 22 | None 23 | 24 | 25 | Examples: 26 | >>> pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 27 | >>> pytplot.store_data('b', data={'x':[2,5,8,11,14,17,20], 'y':[[1,1,1,1,1,1],[2,2,5,4,1,1],[100,100,3,50,1,1],[4,4,8,58,1,1],[5,5,9,21,1,1],[6,6,2,2,1,1],[7,7,1,6,1,1]]}) 28 | >>> pytplot.crop('a','b') 29 | """ 30 | 31 | # grab time and data arrays 32 | tv1 = pytplot.data_quants[tvar1].copy() 33 | tv2 = pytplot.data_quants[tvar2].copy() 34 | # find first and last time indices 35 | t0_1 = tv1.coords['time'][0] 36 | t0_2 = tv2.coords['time'][0] 37 | tx_1 = tv1.coords['time'][-1] 38 | tx_2 = tv2.coords['time'][-1] 39 | # find cut locations 40 | cut1 = max([t0_1, t0_2]) 41 | cut2 = min([tx_1, tx_2]) 42 | # trim data 43 | tv1 = tv1.sel(time=slice(cut1, cut2)) 44 | tv1.attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 45 | tv2 = tv2.sel(time=slice(cut1, cut2)) 46 | tv2.attrs = copy.deepcopy(pytplot.data_quants[tvar2].attrs) 47 | # Replace the variables if specified 48 | if replace: 49 | pytplot.data_quants[tvar1] = tv1 50 | pytplot.data_quants[tvar1].name = tvar1 51 | pytplot.data_quants[tvar2] = tv2 52 | pytplot.data_quants[tvar2].name = tvar2 53 | return 54 | else: 55 | pytplot.data_quants[tvar1 + '_cropped'] = copy.deepcopy(tv1) 56 | pytplot.data_quants[tvar1 + '_cropped'].attrs = copy.deepcopy(tv1.attrs) 57 | pytplot.data_quants[tvar1 + '_cropped'].name = tvar1+ '_cropped' 58 | pytplot.data_quants[tvar2 + '_cropped'] = copy.deepcopy(tv2) 59 | pytplot.data_quants[tvar2 + '_cropped'].attrs = copy.deepcopy(tv2.attrs) 60 | pytplot.data_quants[tvar2 + '_cropped'].name = tvar2 + '_cropped' 61 | return tvar2 + '_cropped' -------------------------------------------------------------------------------- /pytplot/tplot_math/deflag.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | 10 | def deflag(tvar,flag,new_tvar=None): 11 | """ 12 | Change specified 'flagged' data to NaN. 13 | 14 | Parameters: 15 | tvar1 : str 16 | Name of tplot variable to use for data clipping. 17 | flag : int,list 18 | Flagged data will be converted to NaNs. 19 | newtvar : str 20 | Name of new tvar for deflagged data storage. If not specified, then the data in tvar1 will be replaced. 21 | 22 | Returns: 23 | None 24 | 25 | Examples: 26 | >>> # Remove any instances of [100,90,7,2,57] from 'd', store in 'e'. 27 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1],[2,2],[100,4],[4,90],[5,5],[6,6],[7,7]]}) 28 | >>> pytplot.deflag('d',[100,90,7,2,57],'e') 29 | """ 30 | 31 | a = copy.deepcopy(pytplot.data_quants[tvar].where(pytplot.data_quants[tvar]!=flag)) 32 | 33 | 34 | if new_tvar is None: 35 | a.name = tvar 36 | pytplot.data_quants[tvar] = a 37 | else: 38 | if 'spec_bins' in a.coords: 39 | pytplot.store_data(new_tvar, data={'x': a.coords['time'], 'y': a.values, 'v': a.coords['spec_bins']}) 40 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 41 | else: 42 | pytplot.store_data(new_tvar, data={'x': a.coords['time'], 'y': a.values}) 43 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 44 | 45 | return 46 | -------------------------------------------------------------------------------- /pytplot/tplot_math/degap.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | import pandas as pd 9 | import copy 10 | 11 | def degap(tvar,dt,margin,func='nan',new_tvar = None): 12 | ''' 13 | Fills gaps in the data either with NaNs or the last number. 14 | 15 | Parameters: 16 | tvar : str 17 | Name of tplot variable to modify 18 | dt : int/float 19 | Step size of the data in seconds 20 | margin : int/float, optional 21 | The maximum deviation from the step size allowed before degapping occurs. In other words, if you'd like to fill in data every 4 seconds 22 | but occasionally the data is 4.1 seconds apart, set the margin to .1 so that a data point is not inserted there. 23 | func : str, optional 24 | Either 'nan' or 'ffill', which overrides normal interpolation with NaN 25 | substitution or forward-filled values. 26 | new_tvar : str, optional 27 | The new tplot variable name to store the data into. If None, then the data is overwritten. 28 | 29 | Returns: 30 | None 31 | 32 | Examples: 33 | >>> # TODO 34 | ''' 35 | 36 | gap_size = np.diff(pytplot.data_quants[tvar].coords['time']) 37 | gap_index_locations = np.where(gap_size > dt+margin) 38 | new_tvar_index = pytplot.data_quants[tvar].coords['time'] 39 | values_to_add = np.array([]) 40 | for i in gap_index_locations[0]: 41 | values_to_add = np.append(values_to_add, np.arange(new_tvar_index[i], new_tvar_index[i+1], dt)) 42 | 43 | new_index = np.sort(np.unique(np.concatenate((values_to_add, new_tvar_index)))) 44 | 45 | if func == 'nan': 46 | method = None 47 | if func == 'ffill': 48 | method = 'ffill' 49 | 50 | a = pytplot.data_quants[tvar].reindex({'time': new_index}, method=method) 51 | 52 | if new_tvar is None: 53 | a.name = tvar 54 | a.attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 55 | pytplot.data_quants[tvar] = copy.deepcopy(a) 56 | else: 57 | if 'spec_bins' in a.coords: 58 | pytplot.store_data(new_tvar, data={'x': a.coords['time'], 'y': a.values, 'v': a.coords['spec_bins']}) 59 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 60 | else: 61 | pytplot.store_data(new_tvar, data={'x': a.coords['time'], 'y': a.values}) 62 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 63 | 64 | return 65 | -------------------------------------------------------------------------------- /pytplot/tplot_math/derive.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | import copy 9 | 10 | def derive(tvar,new_tvar=None): 11 | """ 12 | Takes the derivative of the tplot variable. 13 | 14 | Parameters: 15 | tvar : str 16 | Name of tplot variable. 17 | new_tvar : str 18 | Name of new tplot variable. If not set, then the data in tvar is replaced. 19 | 20 | Returns: 21 | None 22 | 23 | Examples: 24 | >>> pytplot.store_data('b', data={'x':[2,5,8,11,14,17,20], 'y':[[1,1,1,1,1,1],[2,2,5,4,1,1],[100,100,3,50,1,1],[4,4,8,58,1,1],[5,5,9,21,1,1],[6,6,2,2,1,1],[7,7,1,6,1,1]]}) 25 | >>> pytplot.derive('b','dbdt') 26 | >>> print(pytplot.data_quants['dbdt'].values) 27 | """ 28 | 29 | a = pytplot.data_quants[tvar].differentiate('time') 30 | if new_tvar is None: 31 | a.name = tvar 32 | a.attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 33 | pytplot.data_quants[tvar] = a 34 | else: 35 | data = {'x':a.coords['time'], 'y':a.values} 36 | for coord in a.coords: 37 | if coord != 'time' and coord != 'spec_bins': 38 | data[coord] = a.coords[coord].values 39 | 40 | pytplot.store_data(new_tvar, data=data) 41 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 42 | 43 | return 44 | -------------------------------------------------------------------------------- /pytplot/tplot_math/divide.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | import copy 9 | 10 | def divide(tvar1,tvar2,new_tvar=None): 11 | """ 12 | Divides two tplot variables. Will interpolate if the two are not on the same time cadence. 13 | 14 | Parameters: 15 | tvar1 : str 16 | Name of first tplot variable. 17 | tvar2 : int/float 18 | Name of second tplot variable 19 | new_tvar : str 20 | Name of new tvar for divided data. If not set, then the data in tvar1 is replaced. 21 | 22 | Returns: 23 | None 24 | 25 | Examples: 26 | >>> pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 27 | >>> pytplot.store_data('c', data={'x':[0,4,8,12,16,19,21], 'y':[1,4,1,7,1,9,1]}) 28 | >>> pytplot.divide('a','c','a_over_c') 29 | """ 30 | # interpolate tvars 31 | tv2 = pytplot.tplot_math.tinterp(tvar1, tvar2) 32 | # separate and divide data 33 | data1 = pytplot.data_quants[tvar1].values 34 | data2 = pytplot.data_quants[tv2].values 35 | data = data1 / data2 36 | # store divided data 37 | if new_tvar is None: 38 | pytplot.data_quants[tvar1].values = data 39 | return tvar1 40 | if 'spec_bins' in pytplot.data_quants[tvar1].coords: 41 | pytplot.store_data(new_tvar, data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data, 42 | 'v': pytplot.data_quants[tvar1].coords['spec_bins'].values}) 43 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 44 | else: 45 | pytplot.store_data(new_tvar, data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data}) 46 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 47 | 48 | return new_tvar 49 | -------------------------------------------------------------------------------- /pytplot/tplot_math/examples.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | 9 | pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 10 | pytplot.store_data('b', data={'x':[2,5,8,11,14,17,20], 'y':[[1,1,1,1,1,1],[2,2,5,4,1,1],[100,100,3,50,1,1],[4,4,8,58,1,1],[5,5,9,21,1,1],[6,6,2,2,1,1],[7,7,1,6,1,1]]}) 11 | pytplot.store_data('c', data={'x':[0,4,8,12,16,19,21], 'y':[1,4,1,7,1,9,1]}) 12 | pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 13 | pytplot.store_data('e', data={'x':[2,5,8,11,14,17,21], 'y':[[np.nan,1,1],[np.nan,2,3],[4,np.nan,47],[4,np.nan,5],[5,5,99],[6,6,25],[7,np.nan,-5]]}) 14 | pytplot.store_data('g', data={'x':[0,4,8,12,16,19,21], 'y':[[8,1,1],[100,2,3],[4,2,47],[4,39,5],[5,5,99],[6,6,25],[7,-2,-5]]}) 15 | pytplot.store_data('h', data={'x':[0,4,8,12,16,19,21], 'y':[[8,1,1],[100,2,3],[4,2,47],[4,39,5],[5,5,99],[6,6,25],[7,-2,-5]],'v':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 16 | 17 | print('add_across_partial') 18 | pytplot.add_across_partial('b',[[1,2],[3,4]],'b_aap') 19 | print(pytplot.data_quants['b_aap'].data) 20 | 21 | print('add_across') 22 | pytplot.add_across('d','d_aa') 23 | print(pytplot.data_quants['d_aa'].data) 24 | 25 | print('add') 26 | pytplot.add('a','c','a+c') 27 | print(pytplot.data_quants['a+c'].data) 28 | 29 | print('avg_res_data') 30 | pytplot.avg_res_data('d',2,'d2res') 31 | print(pytplot.data_quants['d'].data) 32 | print(pytplot.data_quants['d2res'].data) 33 | pytplot.avg_res_data('h', 7, 'h7res') 34 | print(pytplot.data_quants['h'].data) 35 | print(pytplot.data_quants['h'].spec_bins) 36 | print(pytplot.data_quants['h7res'].data) 37 | print(pytplot.data_quants['h7res'].spec_bins) 38 | 39 | print('clip') 40 | pytplot.clip('d',2,6,'d_clip') 41 | print(pytplot.data_quants['d_clip'].data) 42 | pytplot.clip('h',2,6,'h_clip') 43 | print(pytplot.data_quants['h_clip'].data) 44 | print(pytplot.data_quants['h_clip'].spec_bins) 45 | 46 | print('crop') 47 | pytplot.crop('a','b') 48 | 49 | print('deflag') 50 | pytplot.deflag('d',[100,90,7,2,57],'d_deflag') 51 | print(pytplot.data_quants['d'].data) 52 | print(pytplot.data_quants['d_deflag'].data) 53 | pytplot.deflag('h',[100,90,7,2,57],'h_deflag') 54 | print(pytplot.data_quants['h_deflag'].data) 55 | print(pytplot.data_quants['h_deflag'].spec_bins) 56 | 57 | 58 | print('derive') 59 | pytplot.derive('b','dbdt') 60 | print(pytplot.data_quants['dbdt'].data) 61 | pytplot.derive('h','dhdt') 62 | print(pytplot.data_quants['dhdt'].data) 63 | print(pytplot.data_quants['dhdt'].spec_bins) 64 | 65 | print('divide') 66 | pytplot.divide('a','b','a/b') 67 | print(pytplot.data_quants['a/b'].data) 68 | 69 | print('flatten_full') 70 | pytplot.flatten_full('d','d_ff') 71 | print(pytplot.data_quants['d_ff'].data) 72 | 73 | print('flatten') 74 | pytplot.flatten('d',8,14,'d_flatten') 75 | print(pytplot.data_quants['d_flatten'].data) 76 | 77 | print('interp_nan') 78 | pytplot.interp_nan('e','e_nonan',s_limit=5) 79 | print(pytplot.data_quants['e_nonan'].data) 80 | 81 | print('interpolate') 82 | pytplot.tinterp('a','c',interp='cubic') 83 | print(pytplot.data_quants['c_interp'].data) 84 | 85 | print('join_vec') 86 | pytplot.join_vec(['d','e','g'],'deg') 87 | print(pytplot.data_quants['deg'].data) 88 | 89 | print('multiply') 90 | pytplot.multiply('a','c','ac',interp='linear') 91 | print(pytplot.data_quants['ac'].data) 92 | 93 | 94 | print('resample') 95 | # pytplot.resample('d',[3,4,5,6,7,18],'d_resampled') 96 | # print(pytplot.data_quants['d_resampled'].data) 97 | pytplot.resample('h',[3,4,5,6,7,18],'h_resampled') 98 | print(pytplot.data_quants['h_resampled'].data) 99 | print(pytplot.data_quants['h_resampled'].spec_bins) 100 | 101 | print('spec_mult') 102 | pytplot.spec_mult('h','h_specmult') 103 | print(pytplot.data_quants['h_specmult'].data) 104 | 105 | print('split_vec') 106 | pytplot.split_vec('b',['b1','b2','b3'],[0,[1,3],4]) 107 | print(pytplot.data_quants['b1'].data) 108 | print(pytplot.data_quants['b2'].data) 109 | print(pytplot.data_quants['b3'].data) 110 | defaultlist = pytplot.split_vec('b') 111 | print(pytplot.data_quants['b'].data) 112 | print(pytplot.data_quants['data_3'].data) 113 | print(defaultlist) 114 | 115 | print('subtract') 116 | pytplot.subtract('a','c','a-c') 117 | print(pytplot.data_quants['a-c'].data) -------------------------------------------------------------------------------- /pytplot/tplot_math/flatten.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | def flatten(tvar,range=None,new_tvar=None): 10 | """ 11 | Divides the column by an average over specified time 12 | 13 | .. note:: 14 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 15 | 16 | Parameters: 17 | tvar : str 18 | Name of first tplot variable. 19 | range : [int, int], optional 20 | The time range to average over. The default is the whole range. 21 | new_tvar : str 22 | Name of new tvar for added data. If not set, then a name is made up. 23 | 24 | Returns: 25 | None 26 | 27 | Examples: 28 | >>> # Divide each column by the average of the data between times 8 and 14 29 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 30 | >>> pytplot.flatten('d',[8,14],'d_flatten') 31 | >>> print(pytplot.data_quants['d_flatten'].values) 32 | """ 33 | 34 | 35 | if new_tvar is None: 36 | new_tvar = tvar + "_flattened" 37 | 38 | if 'spec_bins' in pytplot.data_quants[tvar].coords: 39 | df, s = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvar) 40 | else: 41 | df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvar, no_spec_bins=True) 42 | s=None 43 | 44 | if range is None: 45 | pass 46 | 47 | time = df.index 48 | #if time given not an index, choose closest time 49 | if range is None: 50 | df_index = list(df.columns) 51 | # divide by column average 52 | for i in df_index: 53 | df[i] = df[i] / df[i].mean() 54 | else: 55 | if range[0] not in time: 56 | tdiff = abs(time - range[0]) 57 | start_t = time[tdiff.argmin()] 58 | if range[1] not in time: 59 | tdiff = abs(time - range[1]) 60 | end_t = time[tdiff.argmin()] 61 | df_index = list(df.columns) 62 | 63 | #divide by specified time average 64 | for i in df_index: 65 | df[i] = df[i]/((df.loc[start_t:end_t])[i]).mean() 66 | 67 | if s is not None: 68 | pytplot.store_data(new_tvar,data = {'x':df.index,'y':df.values, 'v': s.values}) 69 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 70 | else: 71 | pytplot.store_data(new_tvar, data={'x': df.index, 'y': df.values}) 72 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 73 | return -------------------------------------------------------------------------------- /pytplot/tplot_math/interp_nan.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | def interp_nan(tvar, new_tvar=None, s_limit=None): 10 | """ 11 | Interpolates the tplot variable through NaNs in the data. This is basically just a wrapper for xarray's interpolate_na function. 12 | 13 | .. note:: 14 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 15 | 16 | Parameters: 17 | tvar : str 18 | Name of tplot variable. 19 | s_limit : int or float, optional 20 | The maximum size of the gap in seconds to not interpolate over. I.e. if there are too many NaNs in a row, leave them there. 21 | new_tvar : str 22 | Name of new tvar for added data. If not set, then the original tvar is replaced. 23 | 24 | Returns: 25 | None 26 | 27 | Examples: 28 | >>> # Interpolate through the np.NaN values 29 | >>> pytplot.store_data('e', data={'x':[2,5,8,11,14,17,21], 'y':[[np.nan,1,1],[np.nan,2,3],[4,np.nan,47],[4,np.nan,5],[5,5,99],[6,6,25],[7,np.nan,-5]]}) 30 | >>> pytplot.interp_nan('e','e_nonan',s_limit=5) 31 | >>> print(pytplot.data_quants['e_nonan'].values) 32 | """ 33 | 34 | x = pytplot.data_quants[tvar].interpolate_na(dim='time', limit=s_limit) 35 | x.attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 36 | 37 | if new_tvar is None: 38 | pytplot.data_quants[tvar] = x 39 | x.name = tvar 40 | else: 41 | pytplot.data_quants[new_tvar] = x 42 | pytplot.data_quants[new_tvar].name = new_tvar 43 | 44 | 45 | -------------------------------------------------------------------------------- /pytplot/tplot_math/join_vec.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import pandas as pd 8 | import copy 9 | import xarray as xr 10 | 11 | #JOIN TVARS 12 | #join TVars into single TVar with multiple columns 13 | def join_vec(tvars,new_tvar=None, merge=False): 14 | """ 15 | Joins 1D tplot variables into one tplot variable. 16 | 17 | .. note:: 18 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 19 | 20 | Parameters: 21 | tvars : list of str 22 | Name of tplot variables to join together 23 | new_tvar : str, optional 24 | The name of the new tplot variable. If not specified, a name will be assigned. 25 | merge : bool, optional 26 | Whether or not to merge the created variable into an older variable 27 | 28 | Returns: 29 | None 30 | 31 | Examples: 32 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 33 | >>> pytplot.store_data('e', data={'x':[2,5,8,11,14,17,21], 'y':[[np.nan,1,1],[np.nan,2,3],[4,np.nan,47],[4,np.nan,5],[5,5,99],[6,6,25],[7,np.nan,-5]]}) 34 | >>> pytplot.store_data('g', data={'x':[0,4,8,12,16,19,21], 'y':[[8,1,1],[100,2,3],[4,2,47],[4,39,5],[5,5,99],[6,6,25],[7,-2,-5]]}) 35 | >>> pytplot.join_vec(['d','e','g'],'deg') 36 | >>> print(pytplot.data_quants['deg'].values) 37 | """ 38 | 39 | if not isinstance(tvars, list): 40 | tvars = [tvars] 41 | if new_tvar is None: 42 | new_tvar = '-'.join(tvars)+'_joined' 43 | 44 | to_merge=False 45 | if new_tvar in pytplot.data_quants.keys() and merge: 46 | prev_data_quant = pytplot.data_quants[new_tvar] 47 | to_merge = True 48 | 49 | for i,val in enumerate(tvars): 50 | if i == 0: 51 | if 'spec_bins' in pytplot.data_quants[tvars[i]].coords: 52 | df, s = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvars[i]) 53 | else: 54 | df = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvars[i], no_spec_bins=True) 55 | s = None 56 | else: 57 | if 'spec_bins' in pytplot.data_quants[tvars[i]].coords: 58 | d = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvars[i], no_spec_bins=True) 59 | else: 60 | d = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvars[i], no_spec_bins=True) 61 | df = pd.concat([df,d],axis=1) 62 | 63 | if s is None: 64 | pytplot.store_data(new_tvar,data={'x': df.index,'y': df.values}) 65 | else: 66 | pytplot.store_data(new_tvar, data={'x': df.index, 'y': df.values, 'v': s.values}) 67 | 68 | if to_merge is True: 69 | cur_data_quant = pytplot.data_quants[new_tvar] 70 | plot_options = copy.deepcopy(pytplot.data_quants[new_tvar].attrs) 71 | pytplot.data_quants[new_tvar] = xr.concat([prev_data_quant, cur_data_quant], dim='time').sortby('time') 72 | pytplot.data_quants[new_tvar].attrs = plot_options 73 | 74 | return new_tvar 75 | -------------------------------------------------------------------------------- /pytplot/tplot_math/multiply.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | def multiply(tvar1,tvar2,new_tvar=None): 10 | """ 11 | Multiplies two tplot variables. Will interpolate if the two are not on the same time cadence. 12 | 13 | Parameters: 14 | tvar1 : str 15 | Name of first tplot variable. 16 | tvar2 : int/float 17 | Name of second tplot variable 18 | new_tvar : str 19 | Name of new tplot variable. If not set, then the data in tvar1 is replaced. 20 | 21 | Returns: 22 | None 23 | 24 | Examples: 25 | >>> pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 26 | >>> pytplot.store_data('c', data={'x':[0,4,8,12,16,19,21], 'y':[1,4,1,7,1,9,1]}) 27 | >>> pytplot.multiply('a','c','a_x_c') 28 | """ 29 | 30 | 31 | # interpolate tvars 32 | tv2 = pytplot.tplot_math.tinterp(tvar1, tvar2) 33 | # separate and multiply data 34 | data1 = pytplot.data_quants[tvar1].values 35 | data2 = pytplot.data_quants[tv2].values 36 | data = data1 * data2 37 | 38 | if new_tvar is None: 39 | pytplot.data_quants[tvar1].values = data 40 | return tvar1 41 | 42 | if 'spec_bins' in pytplot.data_quants[tvar1].coords: 43 | pytplot.store_data(new_tvar, data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data, 'v':pytplot.data_quants[tvar1].coords['spec_bins'].values}) 44 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 45 | else: 46 | pytplot.store_data(new_tvar,data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data}) 47 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 48 | return new_tvar 49 | -------------------------------------------------------------------------------- /pytplot/tplot_math/pwr_spec.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytplot 3 | from scipy import signal 4 | 5 | 6 | # First pass at the power spectrum function. This is still missing several features of the IDL power spectrum routine, such as 7 | # bin, nohanning, notperhertz, and tm_sensativity. The IDL routine is located in dpwrspc.pro. 8 | 9 | # There is also the issue of this not quite having the same units as the plot I use as my reference. 10 | # https://agupubs.onlinelibrary.wiley.com/doi/full/10.1002/2015GL065366#grl53372-bib-0016 11 | # Interestingly enough, the output is the same if units of seconds are used in the periodogram instead of Hertz. 12 | # Perhaps they calculated it differently? 13 | 14 | def pwr_spec(tvar, nbp=256, nsp=128, name=None): 15 | """ 16 | Calculates the power spectrum of a line, and adds a tplot variable for this new spectrogram 17 | 18 | Parameters: 19 | tvar : str 20 | Name of tvar to use 21 | nbp : int, optional 22 | The number of points to use when calculating the FFT 23 | nsp : int, optional 24 | The number of points to shift over to calculate the next FFT 25 | name : str, optional 26 | The name of the new tplot variable created, 27 | 28 | Returns: 29 | None 30 | 31 | Examples: 32 | >>> pytplot.cdf_to_tplot("/path/to/pytplot/testfiles/mvn_euv_l2_bands_20170619_v09_r03.cdf") 33 | >>> pytplot.tplot_math.split_vec('data') 34 | >>> pytplot.pwr_spec('data_0') 35 | >>> pytplot.tplot('data_0_pwrspec') 36 | """ 37 | 38 | x = pytplot.data_quants[tvar].coords['time'] 39 | y = pytplot.data_quants[tvar].values.squeeze() 40 | 41 | if len(y.shape) > 1: 42 | print("Can only perform action for a single line") 43 | 44 | l = len(x) 45 | x_new = [] 46 | f_new = [] 47 | pxx_new = [] 48 | shift_lsp = np.arange(0, l-1, nsp) 49 | for i in shift_lsp: 50 | 51 | x_n = x[i:i+nbp] 52 | y_n = y[i:i+nbp] 53 | if len(x_n) < nbp: 54 | continue 55 | 56 | median_diff_between_points = np.median(np.diff(x_n)) 57 | 58 | w = signal.get_window("hanning", nbp) 59 | f,pxx = signal.periodogram(y_n, fs=(1/median_diff_between_points), window=w, detrend='linear') 60 | f = f[1:-1] 61 | pxx = pxx[1:-1] 62 | x_new.append((x_n[-1] + x_n[0]) / 2) 63 | f_new.append(f) 64 | pxx_new.append(pxx) 65 | 66 | if name is None: 67 | name = tvar + "_pwrspec" 68 | 69 | pytplot.store_data(name, data={'x': x_new, 'y': pxx_new, 'v': f_new}) 70 | pytplot.options(name, 'spec', 1) 71 | pytplot.options(name, 'zlog', 1) 72 | pytplot.options(name, 'ylog', 1) 73 | 74 | return 75 | -------------------------------------------------------------------------------- /pytplot/tplot_math/resample.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import numpy as np 8 | from scipy import interpolate 9 | from scipy.interpolate import interp1d 10 | import copy 11 | 12 | def resample(tvar,times,new_tvar=None): 13 | """ 14 | Linearly interpolates data to user-specified values. To interpolate one tplot variable to another, use tinterp. 15 | 16 | .. note:: 17 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 18 | 19 | Parameters: 20 | tvar : str 21 | Name of tvar whose data will be interpolated to specified times. 22 | times : int/list 23 | Desired times for interpolation. 24 | new_tvar : str 25 | Name of new tvar in which to store interpolated data. If none is specified, tvar will be overwritten 26 | 27 | Returns: 28 | None 29 | 30 | Examples: 31 | >>> # Interpolate data for 'd' to values [3,4,5,6,7,18]. 32 | >>> pytplot.store_data('d', data={'x':[2,5,8,11,14,17,21], 'y':[[1,1],[2,2],[100,100],[4,4],[5,5],[6,6],[7,7]]}) 33 | >>> pytplot.tplot_resample('d',[3,4,5,6,7,18],'d_resampled') 34 | """ 35 | 36 | x = pytplot.data_quants[tvar].interp(time=times) 37 | 38 | if new_tvar is None: 39 | x.attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 40 | pytplot.data_quants[tvar] = x 41 | x.name = tvar 42 | else: 43 | pytplot.data_quants[new_tvar] = copy.deepcopy(x) 44 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 45 | pytplot.data_quants[new_tvar].name = new_tvar 46 | 47 | return 48 | -------------------------------------------------------------------------------- /pytplot/tplot_math/spec_mult.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import pandas as pd 3 | import copy 4 | 5 | def spec_mult(tvar,new_tvar=None): 6 | """ 7 | Multiplies the data by the stored spectrogram bins and created a new tplot variable 8 | 9 | .. note:: 10 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 11 | 12 | Parameters: 13 | tvar : str 14 | Name of tplot variable 15 | times : int/list 16 | Desired times for interpolation. 17 | new_tvar : str 18 | Name of new tvar in which to store interpolated data. If none is specified, a name will be created. 19 | 20 | Returns: 21 | None 22 | 23 | Examples: 24 | >>> pytplot.store_data('h', data={'x':[0,4,8,12,16,19,21], 'y':[[8,1,1],[100,2,3],[4,2,47],[4,39,5],[5,5,99],[6,6,25],[7,-2,-5]],'v':[[1,1,50],[2,2,3],[100,4,47],[4,90,5],[5,5,99],[6,6,25],[7,7,-5]]}) 25 | >>> pytplot.spec_mult('h','h_specmult') 26 | >>> print(pytplot.data_quants['h_specmult'].data) 27 | """ 28 | 29 | if new_tvar is None: 30 | new_tvar = tvar+'_specmult' 31 | if 'spec_bins' not in pytplot.data_quants[tvar].coords: 32 | print("Specified variable must have spec bins stored. Returning...") 33 | return 34 | d, s = pytplot.tplot_utilities.convert_tplotxarray_to_pandas_dataframe(tvar) 35 | dataframe = d.values 36 | specframe = s.values 37 | new_df = pd.DataFrame(dataframe*specframe, columns=d.columns, index=d.index) 38 | pytplot.store_data(new_tvar,data={'x': new_df.index,'y': new_df.values}) 39 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar].attrs) 40 | return 41 | -------------------------------------------------------------------------------- /pytplot/tplot_math/split_vec.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import numpy as np 3 | 4 | def split_vec(tvar, new_name=None, columns='all', suffix=None): 5 | """ 6 | Splits up 2D data into many 1D tplot variables. 7 | 8 | .. note:: 9 | This analysis routine assumes the data is no more than 2 dimensions. If there are more, they may become flattened! 10 | 11 | Parameters: 12 | tvar : str 13 | Name of tplot variable to split up 14 | newtvars : int/list, optional 15 | The names of the new tplot variables. This must be the same length as the number of variables created. 16 | columns : list of ints, optional 17 | The specific column numbers to grab from the data. The default is to split all columns. 18 | 19 | Returns: 20 | None 21 | 22 | Examples: 23 | >>> pytplot.store_data('b', data={'x':[2,5,8,11,14,17,20], 'y':[[1,1,1,1,1,1],[2,2,5,4,1,1],[100,100,3,50,1,1],[4,4,8,58,1,1],[5,5,9,21,1,1],[6,6,2,2,1,1],[7,7,1,6,1,1]]}) 24 | >>> pytplot.tplot_math.split_vec('b',['b1','b2','b3'],[0,[1,3],4]) 25 | >>> print(pytplot.data_quants['b2'].values) 26 | """ 27 | 28 | # Make sure the tvar is found 29 | if tvar not in pytplot.data_quants: 30 | print(f"Error: {tvar} not found in memory.") 31 | return 32 | 33 | # Give a default to the new name 34 | if new_name is None: 35 | new_name = tvar 36 | 37 | # Gather data from the tvar 38 | alldata = pytplot.get_data(tvar) 39 | time = alldata[0] 40 | data = alldata[1] 41 | dim = data.shape 42 | 43 | # If already size one, simply return 44 | if len(dim) == 1: 45 | return [tvar] 46 | 47 | vec_length = dim[1] 48 | 49 | # Determine what the suffix list will be 50 | if suffix is not None: 51 | if vec_length > len(suffix): 52 | print(f"split_vec error: number of columns ({vec_length}) is greater than the number of suffix entered") 53 | else: 54 | if vec_length == 3: 55 | suffix = ["_x", "_y", "_z"] 56 | else: 57 | suffix = [] 58 | for i in range(vec_length): 59 | suffix.append("_"+str(i)) 60 | 61 | 62 | created_variables = [] 63 | 64 | #grab column data 65 | if columns == 'all': 66 | columns = range(vec_length) 67 | 68 | for i in columns: 69 | 70 | #if not a list 71 | if isinstance(i,list): 72 | range_start = i[0] 73 | range_end = i[1] 74 | else: 75 | range_start = i 76 | range_end = i 77 | split_col = list(range(range_start,range_end+1)) 78 | split_name = new_name + suffix[i] 79 | created_variables = created_variables + [split_name] 80 | 81 | data_for_tplot = {'x':time, 'y':data[:,split_col].squeeze()} 82 | 83 | if not pytplot.store_data(split_name,data=data_for_tplot): 84 | raise Exception(f"Failed to store {split_name} in pytplot.") 85 | 86 | 87 | return created_variables 88 | -------------------------------------------------------------------------------- /pytplot/tplot_math/subtract.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | def subtract(tvar1,tvar2,new_tvar=None): 10 | """ 11 | Subtracts two tplot variables. Will interpolate if the two are not on the same time cadence. 12 | 13 | Parameters: 14 | tvar1 : str 15 | Name of first tplot variable. 16 | tvar2 : int/float 17 | Name of second tplot variable 18 | new_tvar : str 19 | Name of new tvar for added data. If not set, then the data in tvar1 is replaced. 20 | 21 | Returns: 22 | None 23 | 24 | Examples: 25 | >>> pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 26 | >>> pytplot.store_data('c', data={'x':[0,4,8,12,16,19,21], 'y':[1,4,1,7,1,9,1]}) 27 | >>> pytplot.subtract('a','c','a-c') 28 | """ 29 | 30 | #interpolate tvars 31 | tv2 = pytplot.tplot_math.tinterp(tvar1,tvar2) 32 | 33 | #separate and subtract data 34 | data1 = pytplot.data_quants[tvar1].values 35 | data2 = pytplot.data_quants[tv2].values 36 | data = data1 - data2 37 | 38 | #store subtracted data 39 | if new_tvar is None: 40 | pytplot.data_quants[tvar1].values = data 41 | return tvar1 42 | 43 | if 'spec_bins' in pytplot.data_quants[tvar1].coords: 44 | pytplot.store_data(new_tvar, data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data, 'v':pytplot.data_quants[tvar1].coords['spec_bins'].values}) 45 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 46 | else: 47 | pytplot.store_data(new_tvar,data={'x': pytplot.data_quants[tvar1].coords['time'].values, 'y': data}) 48 | pytplot.data_quants[new_tvar].attrs = copy.deepcopy(pytplot.data_quants[tvar1].attrs) 49 | 50 | return new_tvar 51 | -------------------------------------------------------------------------------- /pytplot/tplot_math/tinterp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/Pytplot 5 | 6 | import pytplot 7 | import copy 8 | 9 | 10 | def tinterp(tvar1,tvar2,replace=False): 11 | """ 12 | Interpolates one tplot variable to another one's time cadence. This is done automatically by other processing routines. 13 | 14 | Parameters: 15 | tvar1 : str 16 | Name of first tplot variable whose times will be used to interpolate tvar2's data. 17 | tvar2 : str 18 | Name of second tplot variable whose data will be interpolated. 19 | replace : bool, optional 20 | If true, the data in the original tplot variable is replaced. Otherwise, a variable is created. 21 | 22 | Returns: 23 | new_var2, the name of the new tplot variable 24 | 25 | Examples: 26 | >>> pytplot.store_data('a', data={'x':[0,4,8,12,16], 'y':[1,2,3,4,5]}) 27 | >>> pytplot.store_data('c', data={'x':[0,4,8,12,16,19,21], 'y':[1,4,1,7,1,9,1]}) 28 | >>> pytplot.tinterp('a','c') 29 | >>> print(pytplot.data_quants['c_interp'].data) 30 | """ 31 | new_tvar2 = pytplot.data_quants[tvar2].interp_like(pytplot.data_quants[tvar1]) 32 | 33 | if replace: 34 | pytplot.data_quants[tvar2] = new_tvar2 35 | return 36 | else: 37 | pytplot.data_quants[tvar1 + '_tinterp'] = copy.deepcopy(new_tvar2) 38 | pytplot.data_quants[tvar1 + '_tinterp'].attrs = copy.deepcopy(new_tvar2.attrs) 39 | pytplot.data_quants[tvar1 + '_tinterp'].name = tvar1 + '_tinterp' 40 | 41 | return tvar1 + '_tinterp' 42 | -------------------------------------------------------------------------------- /pytplot/tplot_names.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | 8 | def tplot_names(quiet=False): 9 | """ 10 | This function will print out and return a list of all current Tplot Variables stored in the memory. 11 | 12 | Parameters: 13 | quiet : bool 14 | If True, does not print out the variables (only returns the list variables) 15 | 16 | Returns: 17 | list : list of str 18 | A list of all Tplot Variables stored in the memory 19 | 20 | Examples: 21 | >>> import pytplot 22 | >>> x_data = [1,2,3,4,5] 23 | >>> y_data = [1,2,3,4,5] 24 | >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) 25 | >>> tnames = pyplot.tplot_names() 26 | 0 : Variable 1 27 | 28 | """ 29 | 30 | index = 0 31 | return_names=[] 32 | 33 | # TODO: Print out links as well? 34 | 35 | for key, _ in pytplot.data_quants.items(): 36 | if isinstance(pytplot.data_quants[key], dict): 37 | # non-record varying variables are stored as dictionaries 38 | if isinstance(key, str): 39 | names_to_print = key 40 | 41 | if quiet != True: 42 | print(index, ":", names_to_print) 43 | 44 | return_names.append(names_to_print) 45 | index += 1 46 | continue 47 | 48 | if len(pytplot.data_quants[key].attrs['plot_options']['overplots']) != 0: 49 | names_to_print = pytplot.data_quants[key].name + " data from: " 50 | for oplot_name in pytplot.data_quants[key].attrs['plot_options']['overplots']: 51 | names_to_print = names_to_print + " " + oplot_name 52 | 53 | else: 54 | if isinstance(key, str): 55 | names_to_print = pytplot.data_quants[key].name 56 | 57 | if quiet != True: 58 | print(index, ":", names_to_print) 59 | 60 | index += 1 61 | 62 | return_names.append(names_to_print) 63 | return return_names -------------------------------------------------------------------------------- /pytplot/tplot_options.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | from . import tplot_utilities 8 | 9 | def tplot_options(option, value): 10 | """ 11 | This function allows the user to set GLOBAL options for the generated plots. 12 | 13 | Parameters: 14 | option : str 15 | The name of the option. See section below 16 | value : str/int/float/list 17 | The value of the option. See section below. 18 | 19 | Options: 20 | ================ ========== ===== 21 | Options Value type Notes 22 | ================ ========== ===== 23 | title str Title of the the entire output 24 | title_size int Font size of the output 25 | wsize [int, int] [height, width], pixel size of the plot window 26 | title_align int Offset position in pixels of the title 27 | var_label srt Name of the tplot variable to be used as another x axis 28 | alt_range [flt, flt] The min and max altitude to be plotted on all alt plots 29 | map_x_range [int, int] The min and max longitude to be plotted on all map plots 30 | map_y_range [int, int] The min and max latitude to be plotted on all map plots 31 | x_range [flt, flt] The min and max x_range (usually time) to be plotted on all Spec/1D plots 32 | data_gap int Number of seconds with consecutive nan values allowed before no interp should occur 33 | roi [str, str] Times between which there's a region of interest for a user 34 | crosshair bool Option allowing crosshairs and crosshair legend 35 | vertical_spacing int The space in pixels between two plots 36 | show_all_axes bool Whether or not to just use one axis at the bottom of the plot 37 | black_background bool Whether or not to make plot backgrounds black w/ white text 38 | axis_font_size int The font size of the axis ticks. Default is 10. 39 | axis_tick_num [tuples] A list of tuples that determines how many ticks appear. See pyqtgraph textFillLimits 40 | yaxis_width int The number of pixels wide of the y axis 41 | y_axis_zoom bool Set True if the mouse wheel should zoom in on the y axis as well as the x on plots. 42 | ================ ========== ===== 43 | 44 | Returns: 45 | None 46 | 47 | Examples: 48 | >>> # Set the plot title 49 | >>> import pytplot 50 | >>> pytplot.tplot_options('title', 'SWEA Data for Orbit 1563') 51 | 52 | >>> # Set the window size 53 | >>> pytplot.tplot_options('wsize', [1000,500]) 54 | 55 | 56 | """ 57 | 58 | option = option.lower() 59 | 60 | temp = tplot_utilities.set_tplot_options(option, value, pytplot.tplot_opt_glob) 61 | pytplot.tplot_opt_glob = temp 62 | 63 | return 64 | -------------------------------------------------------------------------------- /pytplot/tplot_rename.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | from collections import OrderedDict 8 | 9 | def tplot_rename(old_name, new_name): 10 | """ 11 | This function will rename tplot variables that are already stored in memory. 12 | 13 | Parameters: 14 | old_name : str 15 | Old name of the Tplot Variable 16 | new_name : str 17 | New name of the Tplot Variable 18 | 19 | Returns: 20 | None 21 | 22 | Examples: 23 | >>> # Rename Variable 1 to Variable 2 24 | >>> import pytplot 25 | >>> pytplot.tplot_rename("Variable1", "Variable2") 26 | 27 | """ 28 | 29 | #if old name input is a number, convert to corresponding name 30 | if isinstance(old_name, int): 31 | if isinstance(pytplot.data_quants[old_name], dict): 32 | old_name = pytplot.data_quants[old_name]['name'] 33 | else: 34 | old_name = pytplot.data_quants[old_name].name 35 | 36 | # check if old name is in current dictionary 37 | if old_name not in pytplot.data_quants.keys(): 38 | print("That name is currently not in pytplot") 39 | return 40 | 41 | #remake dictionary with new name in old name's slot 42 | d = pytplot.data_quants 43 | d2 = OrderedDict([(new_name, v) if k == old_name else (k, v) for k, v in d.items()]) 44 | new_data_quants = d2 45 | for key in d2: 46 | if isinstance(new_data_quants[key], dict): 47 | # the variable is non-record varying 48 | new_data_quants[key]['name'] = key 49 | else: 50 | new_data_quants[key].name = key 51 | 52 | pytplot.data_quants = new_data_quants 53 | return 54 | -------------------------------------------------------------------------------- /pytplot/xlim.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | from . import tplot_utilities 8 | from bokeh.models import Range1d 9 | 10 | def xlim(min, max): 11 | """ 12 | This function will set the x axis range for all time series plots 13 | 14 | Parameters: 15 | min : flt 16 | The time to start all time series plots. Can be given in seconds since epoch, or as a string 17 | in the format "YYYY-MM-DD HH:MM:SS" 18 | max : flt 19 | The time to end all time series plots. Can be given in seconds since epoch, or as a string 20 | in the format "YYYY-MM-DD HH:MM:SS" 21 | 22 | Returns: 23 | None 24 | 25 | Examples: 26 | >>> # Set the timespan to be 2017-07-17 00:00:00 plus 1 day 27 | >>> import pytplot 28 | >>> pytplot.xlim(1500249600, 1500249600 + 86400) 29 | 30 | >>> # The same as above, but using different inputs 31 | >>> pytplot.xlim("2017-07-17 00:00:00", "2017-07-18 00:00:00") 32 | 33 | """ 34 | if not isinstance(min, (int, float, complex)): 35 | min = tplot_utilities.str_to_int(min) 36 | if not isinstance(max, (int, float, complex)): 37 | max = tplot_utilities.str_to_int(max) 38 | if 'x_range' in pytplot.tplot_opt_glob: 39 | pytplot.lim_info['xlast'] = pytplot.tplot_opt_glob['x_range'] 40 | else: 41 | pytplot.lim_info['xfull'] = Range1d(min, max) 42 | pytplot.lim_info['xlast'] = Range1d(min, max) 43 | pytplot.tplot_opt_glob['x_range'] = [min, max] 44 | return 45 | -------------------------------------------------------------------------------- /pytplot/ylim.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | 8 | def ylim(name, min, max): 9 | """ 10 | This function will set the y axis range displayed for a specific tplot variable. 11 | 12 | Parameters: 13 | name : str 14 | The name of the tplot variable that you wish to set y limits for. 15 | min : flt 16 | The start of the y axis. 17 | max : flt 18 | The end of the y axis. 19 | 20 | Returns: 21 | None 22 | 23 | Examples: 24 | >>> # Change the y range of Variable1 25 | >>> import pytplot 26 | >>> x_data = [1,2,3,4,5] 27 | >>> y_data = [1,2,3,4,5] 28 | >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) 29 | >>> pytplot.ylim('Variable1', 2, 4) 30 | 31 | """ 32 | if name not in pytplot.data_quants.keys(): 33 | print("That name is currently not in pytplot.") 34 | return 35 | 36 | pytplot.data_quants[name].attrs['plot_options']['yaxis_opt']['y_range'] = [min, max] 37 | 38 | return -------------------------------------------------------------------------------- /pytplot/zlim.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Regents of the University of Colorado. All Rights Reserved. 2 | # Released under the MIT license. 3 | # This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics. 4 | # Verify current version before use at: https://github.com/MAVENSDC/PyTplot 5 | 6 | import pytplot 7 | 8 | def zlim(name, min, max): 9 | """ 10 | This function will set the z axis range displayed for a specific tplot variable. 11 | This is only used for spec plots, where the z axis represents the magnitude of the values 12 | in each bin. 13 | 14 | Parameters: 15 | name : str 16 | The name of the tplot variable that you wish to set z limits for. 17 | min : flt 18 | The start of the z axis. 19 | max : flt 20 | The end of the z axis. 21 | 22 | Returns: 23 | None 24 | 25 | Examples: 26 | >>> # Change the z range of Variable1 27 | >>> import pytplot 28 | >>> x_data = [1,2,3] 29 | >>> y_data = [ [1,2,3] , [4,5,6], [7,8,9] ] 30 | >>> v_data = [1,2,3] 31 | >>> pytplot.store_data("Variable3", data={'x':x_data, 'y':y_data, 'v':v_data}) 32 | >>> pytplot.zlim('Variable1', 2, 3) 33 | 34 | """ 35 | if name not in pytplot.data_quants.keys(): 36 | print("That name is currently not in pytplot.") 37 | return 38 | 39 | pytplot.data_quants[name].attrs['plot_options']['zaxis_opt']['z_range'] = [min, max] 40 | 41 | return -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy >= 1.20.0 2 | bokeh >= 1.1,<3.0 3 | pyqtgraph >= 0.11.1 4 | pandas 5 | matplotlib 6 | scipy 7 | cdflib 8 | xarray 9 | pyqt5 >= 5.15.2 10 | pyqtwebengine >= 5.15.2 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = pytplot 3 | version = 1.7.28 4 | author = MAVEN SDC 5 | author_email = mavensdc@lasp.colorado.edu 6 | description = A python version of IDL tplot libraries 7 | url = https://github.com/MAVENSDC/pytplot 8 | keywords = 9 | tplot 10 | maven 11 | lasp 12 | IDL 13 | spedas 14 | classifiers = 15 | Development Status :: 5 - Production/Stable 16 | Environment :: Console 17 | Intended Audience :: Science/Research 18 | Operating System :: OS Independent 19 | Programming Language :: Python :: 3.7 20 | Topic :: Utilities 21 | license_file = LICENSE 22 | long_description = file: README.md 23 | long_description_content_type = text/markdown 24 | 25 | [options] 26 | python_requires = >= 3.6 27 | setup_requires = 28 | setuptools >= 38.6 29 | pip >= 10 30 | include_package_data = True 31 | packages = find: 32 | install_requires = 33 | numpy >= 1.20.0 34 | bokeh >= 1.1 35 | pyqtgraph >= 0.11.1 36 | pandas 37 | matplotlib 38 | scipy 39 | cdflib 40 | xarray 41 | pyqt5 >= 5.15.2 42 | pyqtwebengine >= 5.15.2 43 | 44 | [options.extras_require] 45 | tests = 46 | pytest 47 | pytest-cov 48 | coveralls 49 | flake8 50 | mypy 51 | astropy 52 | 53 | [options.entry_points] 54 | console_scripts = 55 | 56 | [flake8] 57 | max-line-length = 132 58 | exclude = .git,__pycache__,.eggs/,doc/,docs/,build/,dist/,archive/ 59 | 60 | [coverage:run] 61 | cover_pylib = false 62 | omit = 63 | /home/travis/virtualenv/* 64 | */site-packages/* 65 | */bin/* 66 | 67 | [coverage:report] 68 | exclude_lines = 69 | pragma: no cover 70 | def __repr__ 71 | except RuntimeError 72 | except NotImplementedError 73 | except ImportError 74 | except FileNotFoundError 75 | except CalledProcessError 76 | logging.warning 77 | logging.error 78 | logging.critical 79 | if __name__ == .__main__.: 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #Uploading new versions to pypi: 2 | # 3 | #python setup.py sdist 4 | #twine upload dist/* 5 | #Username MAVENSDC 6 | 7 | 8 | ################################# 9 | #Uploading new versions to conda: 10 | ################################## 11 | 12 | #Note: You'll need to make sure cdflib is up to date on conda as well 13 | 14 | #Run: conda skeleton pypi pytplot 15 | #This should generate a meta.yaml file 16 | #Edit the YAML file so that the requirements/about/extra look like this: 17 | 18 | 19 | ''' 20 | requirements: 21 | build: 22 | - setuptools 23 | - twine 24 | - python 25 | - pip 26 | run: 27 | - python 28 | - numpy 29 | - bokeh 30 | - pandas 31 | - matplotlib 32 | - scipy 33 | - xarray 34 | - pyqtgraph 35 | - cdflib 36 | 37 | about: 38 | home: "https://github.com/MAVENSDC/pytplot" 39 | license: "MIT" 40 | summary: "Pytplot is an effort to replicate the functionality IDL tplot library in python" 41 | doc_url: "https://github.com/MAVENSDC/pytplot" 42 | dev_url: "https://github.com/MAVENSDC/pyptlot" 43 | 44 | extra: 45 | recipe-maintainers: 46 | - MAVENSDC 47 | ''' 48 | 49 | 50 | 51 | #conda-build pytplot 52 | #conda-build --python 3.6 pytplot 53 | #conda-build --python 3.5 pytplot 54 | #This should put stuff in C:/Anaconda/conda-bld 55 | #conda convert -f --platform all /path/to/created/bundles/file.tar.bz2 -o /path/to/place/converted/files 56 | #anaconda upload /path/to/created/or/converted/bundles/file.tar.bz2 57 | #Username MAVENSDC 58 | 59 | from setuptools import setup 60 | setup() 61 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_alt_plot.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import os 3 | import numpy as np 4 | 5 | current_directory = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | def test_altitude_plot(): 8 | 9 | pytplot.netcdf_to_tplot(current_directory + "/testfiles/g15_xrs_2s_20170619_20170619.nc", time='time_tag') 10 | pytplot.store_data('altitude', data={'x': pytplot.data_quants['A_COUNT'].coords['time'].values, 'y': np.arange(0, len(pytplot.data_quants['A_COUNT'].coords['time'].values), step=1)}) 11 | pytplot.link('A_COUNT', 'altitude') 12 | pytplot.xlim('2017-06-19 02:00:00', '2017-06-19 04:00:00') 13 | pytplot.ylim("A_COUNT", 17000, 18000) 14 | pytplot.timebar('2017-06-19 03:00:00', "A_COUNT", color=(100, 255, 0), thick=3) 15 | pytplot.timebar('2017-06-19 03:30:00', "A_COUNT", color='g') 16 | pytplot.options("A_COUNT", 'alt', 1) 17 | pytplot.tplot(2, testing=True) 18 | pytplot.tplot(2, testing=True, bokeh=True) -------------------------------------------------------------------------------- /tests/test_cdf_to_tplot.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import os 3 | 4 | current_directory = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | def test_cdf_euv_read(): 7 | 8 | pytplot.cdf_to_tplot(current_directory + "/testfiles/mvn_euv_l2_bands_20170619_v09_r03.cdf") 9 | pytplot.tplot('data', testing=True) 10 | pytplot.tplot('data', testing=True, bokeh=True) 11 | 12 | def test_cdf_swe_read(): 13 | pytplot.cdf_to_tplot(current_directory + "/testfiles/mvn_swe_l2_svyspec_20170619_v04_r04.cdf") 14 | pytplot.options('diff_en_fluxes', 'colormap', 'magma') 15 | pytplot.options('diff_en_fluxes', 'ztitle', 'FLUX') 16 | pytplot.options('diff_en_fluxes', 'ytitle', 'Energy') 17 | pytplot.options("diff_en_fluxes", "spec", 1) 18 | pytplot.options("diff_en_fluxes", "crosshair_y", "banana") 19 | pytplot.options("diff_en_fluxes", "crosshair_z", "tomato") 20 | pytplot.options("diff_en_fluxes", 'panel_size', 1) 21 | pytplot.options('diff_en_fluxes', 'ylog', 1) 22 | pytplot.options('diff_en_fluxes', 'zlog', 1) 23 | pytplot.tplot('diff_en_fluxes', testing=True) 24 | pytplot.tplot('data', testing=True, bokeh=True) -------------------------------------------------------------------------------- /tests/test_maps.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import os 3 | import numpy as np 4 | 5 | current_directory = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | def test_map_plot(): 8 | pytplot.netcdf_to_tplot(current_directory + "/testfiles/g15_xrs_2s_20170619_20170619.nc", time='time_tag') 9 | 10 | pytplot.store_data('lat', data={'x': pytplot.data_quants['A_COUNT'].coords['time'].values, 'y': np.arange(0, 90, step=(90 / len(pytplot.data_quants['A_COUNT'].coords['time'].values)))}) 11 | pytplot.link('A_COUNT', 'lat', link_type='lat') 12 | pytplot.store_data('lon', data={'x': pytplot.data_quants['A_COUNT'].coords['time'].values, 'y': np.arange(0, 360,step=(360 / len(pytplot.data_quants['A_COUNT'].coords['time'].values)))}) 13 | pytplot.link('A_COUNT', 'lon', link_type='lon') 14 | pytplot.xlim('2017-06-19 02:00:00', '2017-06-19 04:00:00') 15 | pytplot.ylim("A_COUNT", 17000, 18000) 16 | pytplot.timebar('2017-06-19 03:00:00', "A_COUNT", color=(100, 255, 0), thick=3) 17 | pytplot.timebar('2017-06-19 03:30:00', "A_COUNT", color='g') 18 | pytplot.options("A_COUNT", 'map', 1) 19 | pytplot.tplot(2, testing=True) 20 | pytplot.tplot(2, testing=True, bokeh=True) -------------------------------------------------------------------------------- /tests/test_netcdf_to_tplot.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import os 3 | 4 | current_directory = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | def test_goes_read(): 7 | 8 | pytplot.netcdf_to_tplot(current_directory + "/testfiles/g15_xrs_2s_20170619_20170619.nc", time='time_tag') 9 | pytplot.xlim('2017-06-19 02:00:00', '2017-06-19 04:00:00') 10 | pytplot.ylim("B_COUNT", 17000, 18000) 11 | pytplot.timebar('2017-06-19 03:00:00', "B_COUNT", color=(100, 255, 0), thick=3) 12 | pytplot.timebar('2017-06-19 03:30:00', "B_COUNT", color='g') 13 | pytplot.options("B_COUNT", 'ylog', 1) 14 | pytplot.store_data("BCOUNTFLUX", data=["B_COUNT", "B_FLUX"]) 15 | pytplot.tplot([1, 2, 3, 4, 5, 7], var_label=6, testing=True) -------------------------------------------------------------------------------- /tests/test_qt_import.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | 3 | def test_qt_import(): 4 | assert pytplot.using_graphics == True -------------------------------------------------------------------------------- /tests/test_tplot_math.py: -------------------------------------------------------------------------------- 1 | import pytplot 2 | import os 3 | 4 | current_directory = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | def test_math(): 7 | 8 | pytplot.cdf_to_tplot(os.path.dirname(os.path.realpath(__file__)) + "/testfiles/mvn_euv_l2_bands_20170619_v09_r03.cdf") 9 | pytplot.tplot_names() 10 | 11 | pytplot.tplot_math.split_vec('mvn_euv_calib_bands') 12 | 13 | pytplot.tplot('mvn_euv_calib_bands_x', testing=True) 14 | 15 | pytplot.tplot_math.subtract('mvn_euv_calib_bands_x', 'mvn_euv_calib_bands_y', new_tvar='s') 16 | 17 | pytplot.tplot('s', testing=True) 18 | 19 | pytplot.tplot_math.add('s', 'mvn_euv_calib_bands_x', new_tvar='a') 20 | 21 | pytplot.tplot(['mvn_euv_calib_bands_x', 'a'], testing=True) 22 | 23 | pytplot.tplot_math.subtract('mvn_euv_calib_bands_x', 'mvn_euv_calib_bands_z', new_tvar='m') 24 | 25 | pytplot.tplot('m', testing=True) 26 | 27 | pytplot.tplot_math.divide('m', 'mvn_euv_calib_bands_z', new_tvar='d') 28 | 29 | pytplot.tplot('d', testing=True) 30 | 31 | pytplot.add_across('mvn_euv_calib_bands', new_tvar='data_summed') 32 | 33 | pytplot.tplot('mvn_euv_calib_bands', testing=True) 34 | 35 | pytplot.avg_res_data('data_summed', res=120) 36 | 37 | pytplot.tplot('data_summed', testing=True) 38 | 39 | pytplot.deflag('mvn_euv_calib_bands', 0, new_tvar='deflagged') 40 | 41 | pytplot.tplot('deflagged', testing=True) 42 | 43 | pytplot.flatten('mvn_euv_calib_bands') 44 | 45 | pytplot.tplot('data_flattened', testing=True) 46 | 47 | pytplot.join_vec(['mvn_euv_calib_bands_x', 'mvn_euv_calib_bands_y', 'mvn_euv_calib_bands_z'], new_tvar='data2') 48 | 49 | pytplot.tplot('data2', testing=True) 50 | 51 | pytplot.pwr_spec('mvn_euv_calib_bands_x') 52 | 53 | pytplot.tplot('mvn_euv_calib_bands_x_pwrspec', testing=True) 54 | 55 | pytplot.derive('mvn_euv_calib_bands_x') 56 | 57 | pytplot.store_data("data3", data=['mvn_euv_calib_bands_x', 'mvn_euv_calib_bands_y', 'mvn_euv_calib_bands_z']) 58 | 59 | pytplot.tplot('data3', testing=True) 60 | 61 | pytplot.cdf_to_tplot(os.path.dirname(os.path.realpath(__file__))+ "/testfiles/mvn_swe_l2_svyspec_20170619_v04_r04.cdf") 62 | 63 | pytplot.resample('mvn_euv_calib_bands_y', pytplot.data_quants['diff_en_fluxes'].coords['time'].values, new_tvar='data_3_resampled') 64 | 65 | pytplot.tplot('data_3_resampled', testing=True) 66 | pytplot.options('diff_en_fluxes', 'spec', 1) 67 | pytplot.spec_mult('diff_en_fluxes') 68 | 69 | pytplot.add_across('diff_en_fluxes_specmult', new_tvar='tot_en_flux', column_range=[[0, 10], [10, 20], [20, 30]]) 70 | 71 | pytplot.options('diff_en_fluxes', 'ylog', 1) 72 | pytplot.options('diff_en_fluxes', 'zlog', 1) 73 | pytplot.options('tot_en_flux', 'ylog', 1) 74 | pytplot.ylim('tot_en_flux', 1, 100) 75 | pytplot.tplot(['diff_en_fluxes', 'tot_en_flux'], testing=True) 76 | 77 | pytplot.split_vec('tot_en_flux') 78 | 79 | pytplot.add('tot_en_flux_x', 'mvn_euv_calib_bands_y', new_tvar='weird_data') 80 | 81 | pytplot.tplot('weird_data', testing=True) 82 | -------------------------------------------------------------------------------- /tests/testfiles/g15_xrs_2s_20170619_20170619.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/tests/testfiles/g15_xrs_2s_20170619_20170619.nc -------------------------------------------------------------------------------- /tests/testfiles/mvn_euv_l2_bands_20170619_v09_r03.cdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/tests/testfiles/mvn_euv_l2_bands_20170619_v09_r03.cdf -------------------------------------------------------------------------------- /tests/testfiles/mvn_swe_l2_svyspec_20170619_v04_r04.cdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAVENSDC/PyTplot/f7fb3042898a52f8289b9d8c60ffae38949cad04/tests/testfiles/mvn_swe_l2_svyspec_20170619_v04_r04.cdf --------------------------------------------------------------------------------