diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..4af5eb2 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: # more options at https://docs.codecov.com/docs/commit-status + default: + target: auto # use the coverage from the base commit, fail if coverage is lower + threshold: 0% # allow the coverage to drop by + +comment: + layout: " diff, flags, files" + behavior: default + require_changes: false + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/.codespell/ignore_lines.txt b/.codespell/ignore_lines.txt new file mode 100644 index 0000000..07fa7c8 --- /dev/null +++ b/.codespell/ignore_lines.txt @@ -0,0 +1,2 @@ +;; Please include filenames and explanations for each ignored line. +;; See https://docs.openverse.org/meta/codespell.html for docs. diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt new file mode 100644 index 0000000..04b4fcf --- /dev/null +++ b/.codespell/ignore_words.txt @@ -0,0 +1,8 @@ +;; Please include explanations for each ignored word (lowercase). +;; See https://docs.openverse.org/meta/codespell.html for docs. + +;; abbreviation for "materials" often used in a journal title +mater + +;; Frobenius norm used in np.linalg.norm +fro diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..88077af --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + docs/source/conf.py +max-line-length = 79 +# Ignore some style 'errors' produced while formatting by 'black' +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +extend-ignore = E203 diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md new file mode 100644 index 0000000..b3454de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_feature.md @@ -0,0 +1,16 @@ +--- +name: Bug Report or Feature Request +about: Report a bug or suggest a new feature! +title: "" +labels: "" +assignees: "" +--- + +### Problem + + + +### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 0000000..56bcd01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,46 @@ +--- +name: Release +about: Checklist and communication channel for PyPI and GitHub release +title: "Ready for PyPI/GitHub release" +labels: "release" +assignees: "" +--- + +### PyPI/GitHub rc-release preparation checklist: + +- [ ] All PRs/issues attached to the release are merged. +- [ ] All the badges on the README are passing. +- [ ] License information is verified as correct. If you are unsure, please comment below. +- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are + missing), tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation, and the website are updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. +- [ ] Grammar and writing quality are checked (no typos). +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. + +Please tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: + +### PyPI/GitHub full-release preparation checklist: + +- [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) +- [ ] License information on PyPI is correct. +- [ ] Docs are deployed successfully to `https:///`. +- [ ] Successfully run all tests, tutorial examples or do functional testing. + +Please let the maintainer know that all checks are done and the package is ready for full release. + +### conda-forge release preparation checklist: + + + +- [ ] Ensure that the full release has appeared on PyPI successfully. +- [ ] New package dependencies listed in `conda.txt` and `tests.txt` are added to `meta.yaml` in the feedstock. +- [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. +- [ ] Tag the maintainer for conda-forge release. + +### Post-release checklist + + + +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..1099d86 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml new file mode 100644 index 0000000..a8c2f60 --- /dev/null +++ b/.github/workflows/build-wheel-release-upload.yml @@ -0,0 +1,230 @@ +name: Build Wheels and Release + +on: + workflow_dispatch: + push: + tags: + - "*" # Trigger on all tags initially, but tag and release privilege are verified in _build-wheel-release-upload.yml + +env: + PYTHON_VERSIONS: '["3.11","3.12","3.13"]' + +permissions: + contents: write + actions: read + packages: write + +concurrency: + group: build-wheels-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash {0} + +jobs: + get-python-versions: + runs-on: ubuntu-latest + outputs: + py_ver: ${{ steps.set.outputs.py_ver }} + py_last: ${{ steps.set.outputs.py_last }} + steps: + - id: set + run: | + echo py_ver=$PYTHON_VERSIONS >> $GITHUB_OUTPUT + echo "py_last=${{ fromJson(env.PYTHON_VERSIONS)[0] }}" >> $GITHUB_OUTPUT + + check-tag-on-main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository with full history + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT_TOKEN }} + fetch-depth: 0 + + - name: Verify tag + run: | + git fetch origin main + TAG_COMMIT=$(git rev-parse ${{ github.ref_name }}) + if git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then + echo "Tag ${{ github.ref_name }} ($TAG_COMMIT) is contained in main" + else + echo "::error::Tag ${{ github.ref_name }} ($TAG_COMMIT) is not found in main. Please release from the main branch." + exit 1 + fi + + check-tag-privilege: + # No third party actions used + uses: scikit-package/release-scripts/.github/workflows/_check-tag-privilege.yml@v0 + with: + maintainer_github_username: sbillinge + + build-sdist: + needs: [check-tag-privilege, get-python-versions, check-tag-on-main] + runs-on: ubuntu-latest + + steps: + - name: Checkout + # GitHub officially-maintained actions + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + # GitHub officially-maintained actions + uses: actions/setup-python@v5 + with: + python-version: ${{ needs.get-python-versions.outputs.py_last }} + + - name: Build docs and remove fonts that blow up the wheel size + run: | + python -m pip install Sphinx sphinx-rtd-theme sphinx-copybutton m2r + cd docs + make html + cd build/html/_static/css/fonts + find . -type f ! -name '*.ttf' -delete + cd ../.. + rm -rf fonts + cd .. + rm -rf _sources + cd .. + rm -rf doctrees + cd ../.. + + - name: Make sdist + run: | + python -m pip install --upgrade pip build cython setuptools setuptools-git-versioning + python -m build --sdist --no-isolation + + - name: Strip source codes + run: | + set -euo pipefail + tar xf dist/diffpy_srxplanargui-*.tar.gz + SRC_DIR=$(find . -maxdepth 1 -type d -name 'diffpy_srxplanargui-*' | head -n1) + find "$SRC_DIR" -type f -name '*.c' -print0 \ + | xargs -0 perl -i.bak -0777 -pe 's{/\*.*?\*/}{}gs' + find "$SRC_DIR" -type f -name '*.c.bak' -delete + tar czf dist/"${SRC_DIR#./}".tar.gz "$SRC_DIR" + rm -rf "$SRC_DIR" + + - name: Upload sdist + # GitHub officially-maintained actions + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/ + retention-days: 1 + + - name: Upload INSTRUCTIONS.txt + uses: actions/upload-artifact@v4 + with: + name: instructions + path: INSTRUCTIONS.txt + + build-wheels: + needs: [build-sdist, get-python-versions] + + name: Build wheels ${{ matrix.python }}-${{ matrix.buildplat }} + runs-on: ${{ matrix.buildplat }} + strategy: + fail-fast: false + matrix: + buildplat: + - ubuntu-latest + - macos-14 + - windows-latest + python: ${{ fromJSON(needs.get-python-versions.outputs.py_ver) }} + steps: + - name: Download sdist + # GitHub officially-maintained actions + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist/ + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheels + run: | + # setuptools-git-versioning only look into .git and PKG-INFO in the top directory + which python; python --version + which pip; pip --version + python -m pip install --upgrade pip build cython setuptools setuptools-git-versioning + tar xf dist/diffpy_srxplanargui-*.tar.gz + cd diffpy_srxplanargui-* + python -m pip wheel . --no-deps --no-build-isolation --wheel-dir ./../dist + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.python }}-${{ matrix.buildplat }} + path: dist/*.whl + + test-wheels: + needs: [build-wheels, get-python-versions] + + name: Test wheels ${{ matrix.python }}-${{ matrix.buildplat }} + runs-on: ${{ matrix.buildplat }} + strategy: + fail-fast: false + matrix: + buildplat: + - ubuntu-latest + - macos-14 + - windows-latest + python: ${{ fromJson(needs.get-python-versions.outputs.py_ver) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download wheels + uses: actions/download-artifact@v4 + with: + name: wheels-${{ matrix.python }}-${{ matrix.buildplat }} + path: dist/ + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install wheels + run: | + python -m pip install --upgrade pip pytest setuptools-git-versioning + python -m pip install dist/*.whl + + - name: Assert no source files in installed package + run: | + # For debugging + # touch $(python -c "import sysconfig, os; print(os.path.join(sysconfig.get_paths()['purelib'], 'diffpy/srxplanargui', 'fake.py'))") + python - << 'EOF' + import os, glob, sys, sysconfig + sp = sysconfig.get_paths()['purelib'] + pkg = os.path.join(sp, 'diffpy', 'srxplanargui') + patterns = [os.path.join(pkg, '*.py'), + os.path.join(pkg, '*.c')] + bad = [] + for p in patterns: + bad.extend(glob.glob(p)) + + if bad: + print("Found leftover source files in installed package:") + for f in bad: + print(" -", f) + sys.exit(1) + + print("No .py or .c files present in diffpy/srxplanargui/") + EOF + + - name: Run tests + run: python -m pytest + + release: + needs: [test-wheels] + uses: ./.github/workflows/release-github.yml + secrets: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml new file mode 100644 index 0000000..33bc2b7 --- /dev/null +++ b/.github/workflows/check-news-item.yml @@ -0,0 +1,12 @@ +name: Check for News + +on: + pull_request_target: + branches: + - main + +jobs: + check-news-item: + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 + with: + project: diffpy.srxplanargui diff --git a/.github/workflows/matrix-no-codecov-on-merge-to-main.yml b/.github/workflows/matrix-no-codecov-on-merge-to-main.yml new file mode 100644 index 0000000..774b926 --- /dev/null +++ b/.github/workflows/matrix-no-codecov-on-merge-to-main.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: + - main + release: + types: + - prereleased + - published + workflow_dispatch: + +jobs: + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-no-codecov-on-merge-to-main.yml@v0 + with: + project: diffpy.srxplanargui + c_extension: false + headless: false diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml new file mode 100644 index 0000000..10df627 --- /dev/null +++ b/.github/workflows/release-github.yml @@ -0,0 +1,163 @@ +name: Release on GitHub + +on: + workflow_call: + secrets: + PAT_TOKEN: + description: "GitHub Personal Access Token" + required: true + +env: + TAG: ${{ github.ref_name }} + +defaults: + run: + shell: bash {0} + +jobs: + prepare-release: + if: ${{ ! contains(github.ref, 'rc') }} + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + ref: main + + - name: Update CHANGELOG + run: | + wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/update-changelog.py + python update-changelog.py "$TAG" + rm update-changelog.py + + - name: Commit updated CHANGELOG.rst + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + if ! git diff --cached --quiet; then + git commit -m "update changelog for $TAG" + git push origin main + else + echo "No CHANGELOG.rst changes" + fi + + - name: New tag + run: | + git fetch --tags + git tag -d "$TAG" 2>/dev/null || true + git push origin ":$TAG" 2>/dev/null || true + git tag "$TAG" + git push origin "$TAG" + + - name: Get CHANGELOG.txt + run: | + wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/get-latest-changelog.py + python get-latest-changelog.py "$TAG" + rm get-latest-changelog.py + + - name: Upload changelog.txt + uses: actions/upload-artifact@v4 + with: + name: changelog + path: CHANGELOG.txt + + release: + needs: [prepare-release] + if: always() + runs-on: ubuntu-latest + env: + REPO_FULL: ${{ github.repository }} + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + steps: + - name: Check prepare release + run: | + if [[ ${{ needs.prepare-release.result }} != 'success' && "$TAG" != *rc* ]]; then + echo "::error::Skipping release job because prepare-release failed" + exit 78 + fi + echo "Continuing with release job" + + - name: Download built wheels + uses: actions/download-artifact@v4 + with: + path: dist/ + + - name: Download changelog + if: ${{ needs.prepare-release.result == 'success' }} + uses: actions/download-artifact@v4 + with: + name: changelog + path: . + + - name: Download instructions + uses: actions/download-artifact@v4 + with: + name: instructions + path: . + + - name: Zip wheels and instructions into dist/srxplanargui-$TAG-wheels.zip + run: | + mkdir -p dist + find dist -type f -name '*.whl' | zip -j dist/srxplanargui-"$TAG"-wheels.zip -@ + zip -j dist/pdfgetx-"$TAG"-wheels.zip INSTRUCTIONS.txt + + - name: Prepare release metadata + id: meta + run: | + if [[ "$TAG" == *rc* ]]; then + PRERELEASE=true + TITLE="Pre-release $TAG" + BODY_RAW="Changelog: https://github.com/$REPO_FULL/commits/$TAG" + else + PRERELEASE=false + TITLE="Release $TAG" + BODY_RAW=$( payload.json + + echo "Release metadata:" + cat payload.json + + - name: Create GitHub Release + id: create_release + run: | + set -euo pipefail + + HTTP_STATUS=$( + curl --silent --output resp.json --write-out "%{http_code}" \ + -X POST "https://api.github.com/repos/$REPO_FULL/releases" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $PAT_TOKEN" \ + --data @payload.json + ) + if [[ "$HTTP_STATUS" -ne 201 ]]; then + echo "::error::Failed to create release (status $HTTP_STATUS)" + exit 1 + fi + + UPLOAD_URL=$(jq -r .upload_url resp.json | sed 's/{.*}//') + echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT + + - name: Upload pdfgetx-$TAG-wheels.zip + if: steps.create_release.outputs.upload_url != '' + run: | + FILE=dist/pdfgetx-$TAG-wheels.zip + echo "Uploading asset: $FILE" + curl --silent --fail --data-binary @"$FILE" \ + -H "Content-Type: application/zip" \ + -H "Authorization: Bearer $PAT_TOKEN" \ + "${{ steps.create_release.outputs.upload_url }}?name=$(basename "$FILE")" diff --git a/.github/workflows/tests-on-pr-no-codecov.yml b/.github/workflows/tests-on-pr-no-codecov.yml new file mode 100644 index 0000000..aa33e97 --- /dev/null +++ b/.github/workflows/tests-on-pr-no-codecov.yml @@ -0,0 +1,16 @@ +name: Tests on PR + +on: + pull_request: + workflow_dispatch: + +jobs: + tests-on-pr: + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr-no-codecov.yml@v0 + with: + project: diffpy.srxplanargui + c_extension: false + headless: false + run: | + conda install pre-commit + pre-commit run --all-files diff --git a/.gitignore b/.gitignore index c296211..099e294 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,93 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] +*$py.class # C extensions *.so -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -temp -develop-eggs +# Distribution / packaging +.Python +env/ +build/ +_build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +venv/ +*.egg-info/ .installed.cfg -lib -lib64 -tags +*.egg +bin/ +temp/ +tags/ errors.err +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + # Installer logs pip-log.txt +pip-delete-this-directory.txt MANIFEST # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache nosetests.xml +coverage.xml +*,cover +.hypothesis/ # Translations *.mo +*.pot # Mr Developer .mr.developer.cfg .project .pydevproject -.settings -# version information -setup.cfg -/dpx/srxplanargui/version.cfg +# Django stuff: +*.log + +# Sphinx documentation +docs/build/ +docs/source/generated/ + +# pytest +.pytest_cache/ + +# PyBuilder +target/ + +# Editor files +# mac +.DS_Store +*~ + +# vim +*.swp +*.swo + +# pycharm +.idea/ + +# VSCode +.vscode/ + +# Ipython Notebook +.ipynb_checkpoints diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..86f162b --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,5 @@ +[settings] +# Keep import statement below line_length character limit +line_length = 79 +multi_line_output = 3 +include_trailing_comma = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0e4a84d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,66 @@ +default_language_version: + python: python3 +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: "pre-commit-autoupdate" + autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-added-large-files + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + name: Prevent Commit to Main Branch + args: ["--branch", "main"] + stages: [pre-commit] + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + additional_dependencies: + - tomli + # prettier - multi formatter for .json, .yml, and .md files + - repo: https://github.com/pre-commit/mirrors-prettier + rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + hooks: + - id: prettier + additional_dependencies: + - "prettier@^3.2.4" + # docformatter - PEP 257 compliant docstring formatter + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..aaa8889 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "latest" + +python: + install: + - requirements: requirements/docs.txt + +sphinx: + configuration: docs/source/conf.py diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..51563e5 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,12 @@ +Authors +======= + +Xiaohao Yang + +Billinge Group members + +Contributors +------------ + +For a list of contributors, visit +https://github.com/diffpy/diffpy.srxplanargui/graphs/contributors diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 1ea282b..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,7 +0,0 @@ -This code is developed by: - - Xiaohao Yang - -This code was developed as part of the xPDFsuite project to create software -and tools for general researchers to use PDF in their work. For more -information on the DiffPy project email sb2896@columbia.edu diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..f29d3b5 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,5 @@ +============= +Release notes +============= + +.. current developments diff --git a/CODE-OF-CONDUCT.rst b/CODE-OF-CONDUCT.rst new file mode 100644 index 0000000..e8199ca --- /dev/null +++ b/CODE-OF-CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socioeconomic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSENOTICE.txt b/LICENSE.rst similarity index 82% rename from LICENSENOTICE.txt rename to LICENSE.rst index 1c51b7d..1f80da0 100644 --- a/LICENSENOTICE.txt +++ b/LICENSE.rst @@ -1,3 +1,6 @@ +Copyright (c) 2009-2025, The Trustees of Columbia University in the City of New York. +All rights reserved. + Use of this software is subject to and permitted only under a separate, written Use License granted by Columbia University. If you or your employer is not a party to such an agreement, then your use of this software is @@ -5,7 +8,4 @@ prohibited. If you don’t know whether or not your anticipated use is under a license, you must contact Prof. Simon Billinge at sb2896@columbia.edu. Use of this software without a license is prohibited. -Copyright 2009-2016, Trustees of Columbia University in the City of New York. - - For more information please email Prof. Simon Billinge at sb2896@columbia.edu diff --git a/MANIFEST.in b/MANIFEST.in index 982d898..f1a78ee 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,12 @@ -recursive-include dpx * -prune doc -exclude MANIFEST.in +graft src +graft tests +graft requirements + +include AUTHORS.rst LICENSE*.rst README.rst + +# Exclude all bytecode files and __pycache__ directories +global-exclude *.py[cod] # Exclude all .pyc, .pyo, and .pyd files. +global-exclude .DS_Store # Exclude Mac filesystem artifacts. +global-exclude __pycache__ # Exclude Python cache directories. +global-exclude .git* # Exclude git files and directories. +global-exclude .idea # Exclude PyCharm project settings. diff --git a/README.rst b/README.rst index 40653f7..b831934 100644 --- a/README.rst +++ b/README.rst @@ -1,45 +1,97 @@ -dpx.srxplanargui -======================================================================== +|Icon| |title|_ +=============== + +.. |title| replace:: diffpy.srxplanargui +.. _title: https://diffpy.github.io/diffpy.srxplanargui + +.. |Icon| image:: https://avatars.githubusercontent.com/diffpy + :target: https://diffpy.github.io/diffpy.srxplanargui + :height: 100px + +|PythonVersion| |PR| + +|Black| |Tracking| + +.. |Black| image:: https://img.shields.io/badge/code_style-black-black + :target: https://github.com/psf/black + +.. |Codecov| image:: https://codecov.io/gh/diffpy/diffpy.srxplanargui/branch/main/graph/badge.svg + :target: https://codecov.io/gh/diffpy/diffpy.srxplanargui + +.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.srxplanargui/pulls + +.. |PyPI| image:: https://img.shields.io/pypi/v/diffpy.srxplanargui + :target: https://pypi.org/project/diffpy.srxplanargui/ + +.. |PythonVersion| image:: https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13-blue + +.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue + :target: https://github.com/diffpy/diffpy.srxplanargui/issues + +xPDFsuite, a software for PDF transformation and visualization. GUI for diffpy.srxplanar -REQUIREMENTS ------------------------------------------------------------------------- +Citation +-------- + +If you use diffpy.srxplanargui in a scientific publication, we would like you to cite this package as + + Yang, X., Juhas, P., Farrow, C. L., & Billinge, S. J. (2014). + xPDFsuite: an end-to-end software solution for high throughput pair distribution function transformation, + visualization and analysis. arXiv preprint arXiv:1402.3163. + +Installation +------------ + +The preferred method is to be installed with `xpdfsuite` package or the wheel file. + +This package also provides command-line utilities. To check the software has been installed correctly, type :: + + diffpy.srxplanargui --version + +You can also type the following command to verify the installation. :: + + python -c "import diffpy.srxplanargui; print(diffpy.srxplanargui.__version__)" + + +To view the basic usage and available commands, type :: + + diffpy.srxplanargui -h + +Support +---------------------- -The dpx.srxplanargui requires Python 2.7 and the following software: +If you see a bug or want to request a feature, please `report it as an issue `_ and/or `submit a fix as a PR `_. -* ``diffpy.srxplanar`` -* ``dpx.confutils`` -* ``numpy`` -* ``scipy`` -* ``traits`` -* ``traitsui`` -* ``chaco`` +Feel free to fork the project. To install diffpy.srxplanargui +in a development mode, with its sources being directly used by Python +rather than copied to a package directory, use the following in the root +directory :: -The image integration (diffpy.srxplanargui) requires + pip install -e . -* ``fabio`` -* ``pyfai`` -* ``matplotlib`` +To ensure code quality and to prevent accidental commits into the default branch, please set up the use of our pre-commit +hooks. -INSTALLATION ------------------------------------------------------------------------- +1. Install pre-commit in your working environment by running ``conda install pre-commit``. -We are going to release conda package for all platform. For general user -please use the installation file and install software. For developor, -you can install dpx.srxplanargui using +2. Initialize pre-commit (one time only) ``pre-commit install``. - python setup.py install - -Note: the dependency is not specified in the setup.py. You need to install -them yourself. You can use Anaconda or other python enviroment. +Thereafter your code will be linted by black and isort and checked against flake8 before you can commit. +If it fails by black or isort, just rerun and it should pass (black and isort will modify the files so should +pass after they are modified). If the flake8 test fails please see the error messages and fix them manually before +trying to commit again. +Improvements and fixes are always appreciated. -CONTACTS ------------------------------------------------------------------------- +Contact +------- -For more information on diffpy.Structure please visit the project web-page +For more information on diffpy.srxplanargui please visit the project `web-page `_ or email Simon Billinge at sb2896@columbia.edu. -http://www.diffpy.org/ +Acknowledgements +---------------- -or email Prof. Simon Billinge at sb2896@columbia.edu. +``diffpy.srxplanargui`` is built and maintained with `scikit-package `_. diff --git a/docs/source/_static/.placeholder b/docs/source/_static/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/docs/source/api/diffpy.srxplanargui.rst b/docs/source/api/diffpy.srxplanargui.rst new file mode 100644 index 0000000..7dab4cd --- /dev/null +++ b/docs/source/api/diffpy.srxplanargui.rst @@ -0,0 +1,30 @@ +:tocdepth: -1 + +|title| +======= + +.. |title| replace:: diffpy.srxplanargui package + +.. automodule:: diffpy.srxplanargui + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + diffpy.srxplanargui.example_package + +Submodules +---------- + +|module| +-------- + +.. |module| replace:: diffpy.srxplanargui.example_submodule module + +.. automodule:: diffpy.srxplanargui.example_submodule + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..4d8de40 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# diffpy.srxplanargui documentation build configuration file, created by # noqa: E501 +# sphinx-quickstart on Thu Jan 30 15:49:41 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import time +from importlib.metadata import version +from pathlib import Path + +# Attempt to import the version dynamically from GitHub tag. +try: + fullversion = version("diffpy.srxplanargui") +except Exception: + fullversion = "No version found. The correct version will appear in the released version." # noqa: E501 + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use Path().resolve() to make it absolute, like shown here. # noqa: E501 +# sys.path.insert(0, str(Path(".").resolve())) +sys.path.insert(0, str(Path("../..").resolve())) +sys.path.insert(0, str(Path("../../src").resolve())) + +# abbreviations +ab_authors = "Rundong Hua, Simon Billinge, Billinge Group members" + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", + "sphinx_copybutton", + "m2r", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "diffpy.srxplanargui" +copyright = "%Y, The Trustees of Columbia University in the City of New York" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# The short X.Y version. +version = "".join(fullversion.split(".post")[:1]) +# The full version, including alpha/beta/rc tags. +release = fullversion + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +today = time.strftime("%B %d, %Y", time.localtime()) +year = today.split()[-1] +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' +# substitute YEAR in the copyright string +copyright = copyright.replace("%Y", year) + +# For sphinx_copybutton extension. +# Do not copy "$" for shell commands in code-blocks. +copybutton_prompt_text = r"^\$ " +copybutton_prompt_is_regexp = True + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ["build"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +modindex_common_prefix = ["diffpy.srxplanargui"] + +# Display all warnings for missing links. +nitpicky = True + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +html_context = { + "display_github": True, + "github_user": "diffpy", + "github_repo": "diffpy.srxplanargui", + "github_version": "main", + "conf_py_path": "/docs/source/", +} + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + "navigation_with_keys": "true", +} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +basename = "diffpy.srxplanargui".replace(" ", "").replace(".", "") +htmlhelp_basename = basename + "doc" + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + "index", + "diffpy.srxplanargui.tex", + "diffpy.srxplanargui Documentation", + ab_authors, + "manual", + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + "index", + "diffpy.srxplanargui", + "diffpy.srxplanargui Documentation", + ab_authors, + 1, + ) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + "index", + "diffpy.srxplanargui", + "diffpy.srxplanargui Documentation", + ab_authors, + "diffpy.srxplanargui", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +# intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..4f15775 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,56 @@ +####### +|title| +####### + +.. |title| replace:: diffpy.srxplanargui documentation + +``diffpy.srxplanargui`` - xPDFsuite, a software for PDF transformation and visualization. + +| Software version |release| +| Last updated |today|. + +=============== +Getting started +=============== + +Welcome to the ``diffpy.srxplanargui`` documentation! + +To get started, please visit the :ref:`Getting started ` page. + +======= +Authors +======= + +``diffpy.srxplanargui`` is developed by Rundong Hua, Simon Billinge, Billinge Group members. The maintainer for this project is Simon Billinge. For a detailed list of contributors see +https://github.com/diffpy/diffpy.srxplanargui/graphs/contributors. + +============ +Installation +============ + +See the `README `_ +file included with the distribution. + +================ +Acknowledgements +================ + +``diffpy.srxplanargui`` is built and maintained with `scikit-package `_. + +================= +Table of contents +================= +.. toctree:: + :maxdepth: 2 + + getting-started + Package API + release + license + +======= +Indices +======= + +* :ref:`genindex` +* :ref:`search` diff --git a/docs/source/license.rst b/docs/source/license.rst new file mode 100644 index 0000000..5e751f7 --- /dev/null +++ b/docs/source/license.rst @@ -0,0 +1,38 @@ +:tocdepth: -1 + +.. index:: license + +License +####### + +OPEN SOURCE LICENSE AGREEMENT +============================= +BSD 3-Clause License + +Copyright (c) 2025, The Trustees of Columbia University in the City of New York. +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/source/release.rst b/docs/source/release.rst new file mode 100644 index 0000000..27cd0cc --- /dev/null +++ b/docs/source/release.rst @@ -0,0 +1,5 @@ +:tocdepth: -1 + +.. index:: release notes + +.. include:: ../../CHANGELOG.rst diff --git a/dpx/__init__.py b/dpx/__init__.py deleted file mode 100755 index 079d0fe..0000000 --- a/dpx/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -""" -Blank namespace package. -""" - -__import__('pkg_resources').declare_namespace(__name__) - -# End of file diff --git a/dpx/srxplanargui/__init__.py b/dpx/srxplanargui/__init__.py deleted file mode 100755 index 1985b91..0000000 --- a/dpx/srxplanargui/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - - -# obtain version information -from dpx.srxplanargui.version import __version__ - -# End of file diff --git a/dpx/srxplanargui/calibration.py b/dpx/srxplanargui/calibration.py deleted file mode 100644 index 0e7d600..0000000 --- a/dpx/srxplanargui/calibration.py +++ /dev/null @@ -1,341 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -import numpy as np -import os -import sys -import re - -from traits.etsconfig.api import ETSConfig -ETSConfig.toolkit = 'qt4' - -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on - -from traitsui.api import \ - Item, Group, View, Handler, Controller, spring, Action, \ - HGroup, VGroup, Tabbed, \ - RangeEditor, CheckListEditor, TextEditor, EnumEditor, ButtonEditor, \ - ArrayEditor, TitleEditor, TableEditor, HistoryEditor, InstanceEditor, ImageEditor -from traitsui.menu import ToolBar, OKButton, CancelButton, Menu, MenuBar, OKCancelButtons -from pyface.api import ImageResource, SplashScreen - -from dpx.srxplanargui.srxconfig import SrXconfig -from diffpy.srxplanar.srxplanar import SrXplanar -from diffpy.srxplanar.selfcalibrate import selfCalibrate -from diffpy.srxplanar.srxplanarconfig import checkMax - -from dpx.confutils.tools import module_exists_lower -if module_exists_lower('pyfai'): - import pyFAI - missingpyFAI = False -else: - missingpyFAI = True - -# determine option name for calibrant used in pyFAI-calib -# The current option is "-c", but it was "-S" in 0.9.3. -pyFAIcalib_opt_calibrant = '-c' -if not missingpyFAI: - from pkg_resources import parse_version - if parse_version(pyFAI.version) <= parse_version('0.9.3'): - pyFAIcalib_opt_calibrant = '-S' - del parse_version - - -class CalibrationHandler(Handler): - - def closed(self, info, is_ok): - ''' - notify main gui to delete current plot in plots list - ''' - if is_ok: - info.object.calibration() - return True - - -class Calibration(HasTraits): - image = File - dspacefile = File - srx = Instance(SrXplanar) - srxconfig = Instance(SrXconfig) - pythonbin = File - pyFAIdir = Directory - caliscript = File - missingpyFAI = Bool(False) - - xpixelsize = DelegatesTo('srxconfig') - ypixelsize = DelegatesTo('srxconfig') - wavelength = DelegatesTo('srxconfig') - xbeamcenter = DelegatesTo('srxconfig') - ybeamcenter = DelegatesTo('srxconfig') - xdimension = DelegatesTo('srxconfig') - ydimension = DelegatesTo('srxconfig') - distance = DelegatesTo('srxconfig') - rotationd = DelegatesTo('srxconfig') - tiltd = DelegatesTo('srxconfig') - - def __init__(self, *args, **kwargs): - super(Calibration, self).__init__(*args, **kwargs) - self.locatePyFAI() - self.missingpyFAI = missingpyFAI - return - - def locatePyFAI(self): - pythonbin = sys.executable - if sys.platform == 'win32': - pyFAIdir = os.path.join(sys.exec_prefix, 'Scripts') - elif sys.platform == 'linux2': - pyFAIdir = os.path.join(sys.exec_prefix, 'bin') - else: - pyFAIdir = os.path.join(sys.exec_prefix, 'bin') - self.pythonbin = pythonbin - self.pyFAIdir = pyFAIdir - return - - @on_trait_change('pyFAIdir') - def _pyFAIdirChanged(self): - if sys.platform == 'win32': - caliscript = os.path.join(self.pyFAIdir, 'pyFAI-calib.py') - intescript = os.path.join(self.pyFAIdir, 'pyFAI-waxs.py') - elif sys.platform == 'linux2': - caliscript = os.path.join(self.pyFAIdir, 'pyFAI-calib') - intescript = os.path.join(self.pyFAIdir, 'pyFAI-waxs') - else: - caliscript = os.path.join(self.pyFAIdir, 'pyFAI-calib') - intescript = os.path.join(self.pyFAIdir, 'pyFAI-waxs') - self.caliscript = caliscript - self.intescript = intescript - return - - def callPyFAICalibration(self, image=None, dspacefile=None): - if image == None: - image = self.image - else: - self.image = image - if dspacefile == None: - dspacefile = self.dspacefile - else: - self.dspacefile = dspacefile - - flag = False - if os.path.exists(image) and os.path.isfile(image): - if os.path.exists(dspacefile) and os.path.isfile(dspacefile): - flag = True - - if flag: - image = os.path.abspath(image) - dspacefile = os.path.abspath(dspacefile) - - # remove .npt and .azim - for f in [os.path.splitext(image)[0] + '.npt', - os.path.splitext(image)[0] + '.azim']: - if os.path.exists(f): - os.remove(f) - - ps = [self.xpixelsize * 1000, self.ypixelsize * 1000] - - calicmd = [self.pythonbin, self.caliscript] - calicmd.extend(['-w', str(self.wavelength)]) - calicmd.extend([pyFAIcalib_opt_calibrant, str(dspacefile)]) - calicmd.extend(['-p', str(ps[0]) + ',' + str(ps[1])]) - calicmd.extend([str(image)]) - - import subprocess - try: - os.environ.pop('QT_API') - except: - pass - subprocess.call(calicmd) - - # integrate image - ponifile = os.path.splitext(str(image))[0] + '.poni' - intecmd = [ - self.pythonbin, self.intescript, '-p', ponifile, str(image)] - subprocess.call(intecmd) - self.parsePyFAIoutput(image) - print 'Calibration finished!' - return - - def parsePyFAIoutput(self, image=None): - if image == None: - image = self.image - - filename = os.path.splitext(image)[0] + '.xy' - if os.path.exists(filename): - f = open(filename, 'r') - lines = f.readlines() - f.close() - else: - raise ValueError('pyFAI results file does not exist.') - for line in lines: - if re.search('# Distance Sample-beamCenter', line): - distance = findFloat(line)[0] - elif re.search('# Center', line): - x, y = findFloat(line) - elif re.search('# Tilt', line): - tiltd, rotationd = findFloat(line) - - self.distance = distance - self.xbeamcenter = x # - 0.5 - self.ybeamcenter = y # - y - 0.5 - self.tiltd = tiltd - self.rotationd = rotationd # + 180 - self.srxconfig.flipvertical = False - self.srxconfig.fliphorizontal = False - return - - def selfCalibration(self, image=None): - # self.addfiles.selected[0].fullname - if image == None: - image = self.image - - if os.path.exists(image) and os.path.isfile(image): - for mode, showresults in zip(['x', 'y', 'x', 'y'], [False, False, False, True]): - selfCalibrate(self.srx, image, mode=mode, cropedges=self.slice, - showresults=showresults, xywidth=self.xywidth) - return - - slice = Enum(['auto', 'x', 'y', 'box']) - - calibrationmode = Enum(['self', 'calibrant']) - - def calibration(self, image=None, dspacefile=None): - if self.calibrationmode == 'calibrant': - self.callPyFAICalibration(image, dspacefile) - elif self.calibrationmode == 'self': - self.selfCalibration(image) - else: - raise ValueError('calibration mode error') - return - - xywidth = Int(6) - qmincali = Float(0.5) - qmaxcali = Float(10.0) - - @on_trait_change('srxconfig.[xpixelsize, ypixelsize, distance, wavelength, xdimension, ydimension]') - def _qmaxChanged(self): - tthmax, qmax = checkMax(self.srxconfig) - self.qmincali = min(1.25, qmax / 10) - self.qmaxcali = qmax / 2 - return - - inst1 = Str( - 'Please install pyFAI and FabIO to use the calibration function (refer to help).') - inst2 = Str( - '(http://github.com/kif/pyFAI, https://forge.epn-campus.eu/projects/azimuthal/files)') - main_View = \ - View( - Item('calibrationmode', style='custom', label='Calibration mode'), - Item('image', label='Image file'), - - Group( - Item('inst1', style='readonly'), - Item('inst2', style='readonly'), - visible_when='missingpyFAI and calibrationmode=="calibrant"', - show_border=True, - show_labels=False, - ), - Group( - Item('dspacefile', label='D-space file'), - Item('pyFAIdir', label='pyFAI dir.'), - show_border=True, - visible_when='calibrationmode=="calibrant"', - enabled_when='not missingpyFAI', - label='Please specify the d-space file and the location of pyFAI executable' - ), - HGroup( - Item('xpixelsize', label='Pixel size x (mm)'), - Item('ypixelsize', label='Pixel size y (mm)'), - visible_when='calibrationmode=="calibrant"', - enabled_when='not missingpyFAI', - show_border=True, - label='Please specify the size of pixel' - ), - HGroup( - Item('wavelength', label='Wavelength (A)'), - visible_when='calibrationmode=="calibrant"', - enabled_when='not missingpyFAI', - show_border=True, - label='Please specify the wavelength' - ), - HGroup( - Item('wavelength', visible_when='integrationspace == "qspace"', - label='Wavelength(Angstrom)'), - Item('distance', label='Distance(mm)'), - label='Please specify the wavelength and distance between sample and detector:', - show_border=True, - visible_when='calibrationmode=="self"' - ), - HGroup( - VGroup( - Item('xbeamcenter', label='x beamcenter (pixel)'), - Item('rotationd', label='Rotation (degree)'), - ), - VGroup( - Item('ybeamcenter', label='y beamcenter (pixel)'), - Item('tiltd', label='Tilt rotation (degree)') - ), - show_border=True, - label='Plasee specify the initial value of following parameters:', - visible_when='calibrationmode=="self"' - ), - HGroup( - VGroup( - Item('xdimension', label='x dimension (pixel)'), - Item('xpixelsize', label='Pixel size x (mm)'), - ), - VGroup( - Item('ydimension', label='y dimension (pixel)'), - Item('ypixelsize', label='Pixel size y (mm)') - ), - show_border=True, - label='Plasee specify the dimension of detector and size of pixel:', - visible_when='calibrationmode=="self"' - ), - HGroup( - VGroup( - Item('xywidth', label='(x,y) center searching range, +/-'), - Item('slice', label='Refining using slab along'), - ), - VGroup( - Item('qmincali', label='Qmin in calibration'), - Item('qmaxcali', label='Qmax in calibration') - ), - show_border=True, - label='Others', - visible_when='calibrationmode=="self"', - ), - - title='Calibration', - width=600, - height=450, - resizable=True, - buttons=[OKButton, CancelButton], - handler=CalibrationHandler(), - icon=ImageResource('icon.png'), - ) - - -def findFloat(line): - temp = re.findall('[-+]?\d*\.\d+|[-+]?\d+', line) - return map(float, temp) - -if __name__ == '__main__': - srxconfig = SrXconfig() - cali = Calibration(srxconfig=srxconfig) - # cali.callPyFAICalibration('ceo2.tif', 'ceo2.d') - # cali.parsePyFAIoutput() - cali.configure_traits(view='main_View') diff --git a/dpx/srxplanargui/help.py b/dpx/srxplanargui/help.py deleted file mode 100644 index 5b4825a..0000000 --- a/dpx/srxplanargui/help.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -'''provide help for SrXgui -''' - -import numpy as np -import os -import sys -from traits.etsconfig.api import ETSConfig -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on - -from traitsui.api import \ - Item, Group, View, Handler, Controller, spring, Action, \ - HGroup, VGroup, Tabbed, \ - RangeEditor, CheckListEditor, TextEditor, EnumEditor, ButtonEditor, \ - ArrayEditor, TitleEditor, TableEditor, HistoryEditor, InstanceEditor, \ - ImageEditor -from traitsui.menu import ToolBar, OKButton, CancelButton, Menu - -from pyface.api import ImageResource - -class HelpHandler(Handler): - - def _qsnext(self, info): - info.object.qsindex += 1 - return - - def _qsprevious(self, info): - info.object.qsindex -= 1 - return - - def _cpReftext(self, info): - info.object.cpReftext() - return - - -class SrXguiHelp(HasTraits): - - if sys.platform.startswith('win'): - if ETSConfig.toolkit == 'qt4': - hheight = 510 - hwidth = 960 - else: - hheight = 556 - hwidth = 980 - else: - hheight = 524 - hwidth = 964 - - ####################### - # quick start - ####################### - - imgs = [ImageResource('%02d.png' % i) for i in range(1, 25)] - - qslen = Int(len(imgs) - 1) - - next_action = \ - Action(name='Next', - action='_qsnext', - enabled_when='object.qsindex 2: - mask = points_in_polygon(self.pts, points) - mask = mask.reshape(self.staticmask.shape) - if remove: - self.staticmask = np.logical_and(self.staticmask, np.logical_not(mask)) - else: - self.staticmask = np.logical_or(self.staticmask, mask) - self.refreshMask(staticmask=self.staticmask) - return - - def addPointMask(self, ndx, remove=None): - ''' - :param ndx: (x,y) float - ''' - x, y = ndx - r = self.pts - np.array((x, y)) - r = np.sum(r ** 2, axis=1) - mask = r < ((self.pointmaskradius + 1) ** 2) - mask = mask.reshape(self.staticmask.shape) - if remove: - self.staticmask = np.logical_and(self.staticmask, np.logical_not(mask)) - else: - self.staticmask = np.logical_or(self.staticmask, mask) - self.refreshMask(self.staticmask) - return - - def clearMask(self): - self.staticmask = self.staticmask * 0 - self.refreshMask(self.staticmask) - return - - def invertMask(self): - self.staticmask = np.logical_not(self.staticmask) - self.refreshMask(self.staticmask) - return - - def refreshMask(self, staticmask=None, draw=True): - self.staticmask = self.srx.mask.staticMask() if staticmask == None else staticmask - self.dynamicmask = self.srx.mask.dynamicMask(self.imageorg, dymask=self.staticmask) - self.dynamicmask = np.logical_or(self.dynamicmask, self.srx.mask.edgeMask()) - self.mask = np.logical_or(self.staticmask, self.dynamicmask) - if draw: - self.refreshImage() - return - - maskaboveint = Int(10e10) - maskbelowint = Int(1) - - def maskabove(self): - mask = self.imageorg > self.maskaboveint - self.staticmask = np.logical_or(self.staticmask, mask) - self.refreshMask(self.staticmask) - return - - def maskbelow(self): - mask = self.imageorg < self.maskbelowint - self.staticmask = np.logical_or(self.staticmask, mask) - self.refreshMask(self.staticmask) - return - - def _appendTools(self): - ''' - append xy position, zoom, pan tools to plot - - :param plot: the plot object to append on - ''' - plot = self.plot - img_plot = self.img_plot - - # tools - self.pan = PanTool(plot) - self.zoom = ZoomTool(component=plot, tool_mode="box", always_on=False) - self.lstool = MasklineDrawer(self.plot, imageplot=self) - self.xyseltool = MaskPointInspector(img_plot, imageplot=self) - # self.lstool.imageplot = self - - img_plot.tools.append(self.xyseltool) - overlay = ImageInspectorOverlay(component=img_plot, image_inspector=self.xyseltool, - bgcolor="white", border_visible=True) - img_plot.overlays.append(overlay) - - plot.tools.append(self.pan) - plot.overlays.append(self.zoom) - return - - def _enableMaskEditing(self): - ''' - enable mask tool and disable pan tool - ''' - self.maskediting = True - for i in range(self.plot.tools.count(self.pan)): - self.plot.tools.remove(self.pan) - self.plot.overlays.append(self.lstool) - self.titlebak = self.plot.title - self.plot.title = 'Click: add a vertex; +Click: remove a vertex; \n : finish the selection' - return - - def _disableMaskEditing(self): - ''' - disable mask tool and enable pan tool - ''' - self.plot.overlays.remove(self.lstool) - self.plot.tools.append(self.pan) - self.plot.title = self.titlebak - self.maskediting = False - return - - def _enablePointMaskEditing(self): - self.maskediting = True - for i in range(self.plot.tools.count(self.pan)): - self.plot.tools.remove(self.pan) - self.titlebak = self.plot.title - self.plot.title = 'Click: add a point; : exit the point selection' - return - - def _disablePointMaskEditing(self): - self.plot.tools.append(self.pan) - self.plot.title = self.titlebak - self.maskediting = False - return - - def refreshImage(self, mask=None, draw=True): - ''' - recalculate the image using self.mask or mask and refresh display - ''' - mask = self.mask if mask == None else mask - image = self.applyScale() - image = image * np.logical_not(mask) + image.max() * mask - self.pd.set_data("imagedata", image) - if draw: - self.plot.invalidate_draw() - return - - scalemode = Enum('linear', ['linear', 'log'], desc='Scale the image') - scalepowder = Float(0.5, desc='gamma value to control the contrast') - - def applyScale(self, image=None): - ''' - apply the scale to increase/decrease contrast - ''' - if self.scalemode == 'linear': - if image == None: - image = self.imageorg - intmax = self.imageorgmax - else: - image = image - intmax = image.max() - elif self.scalemode == 'log': - if image == None: - image = self.imageorglog - intmax = self.imageorglogmax - else: - image = np.log(image) - image[image < 0] = 0 - intmax = image.max() - else: - image = image - intmax = image.max() - - image = intmax * ((image / intmax) ** self.scalepowder) - return image - - splb = Float(0.0) - spub = Float(1.0) - def _scalemode_changed(self): - if self.scalemode == 'linear': - self.scalepowder = 0.5 - self.splb = 0.0 - self.spub = 1.0 - elif self.scalemode == 'log': - self.scalepowder = 1.0 - self.splb = 0.0 - self.spub = 4.0 - self.refreshImage() - return - - def _scalepowder_changed(self, old, new): - if np.round(old, 1) != np.round(new, 1): - self.refreshImage() - return - - def _add_notifications(self): - self.on_trait_change(self.refreshMaskFile, 'srxconfig.maskfile') - return - - def _del_notifications(self): - self.on_trait_change(self.refreshMaskFile, 'srxconfig.maskfile', remove=True) - return - - addpolygon_bb = Button('Add polygon mask') - removepolygon_bb = Button('Remove polygon mask') - addpoint_bb = Button('Add point mask') - clearmask_bb = Button('Clear mask', desc='Clear mask') - invertmask_bb = Button('Invert mask', desc='Invert mask') - advancedmask_bb = Button('Dynamic mask', desc='The dynamic mask is dynamically generated for each image.') - maskabove_bb = Button('Mask intensity above') - maskbelow_bb = Button('Mask intensity below') - loadmaskfile_bb = Button('Load mask') - savemaskfile_bb = Button('Save mask') - - def _addpolygon_bb_fired(self): - self.removepolygonmask = False - self._enableMaskEditing() - return - def _removepolygon_bb_fired(self): - self.removepolygonmask = True - self._enableMaskEditing() - return - def _addpoint_bb_fired(self): - self._enablePointMaskEditing() - self.xyseltool.enablemaskselect = True - return - def _clearmask_bb_fired(self): - self.clearMask() - return - def _invertmask_bb_fired(self): - self.invertMask() - return - def _advancedmask_bb_fired(self): - self.edit_traits('advancedmask_view') - # if not hasattr(self, 'advhint'): - # self.advhint = AdvHint() - # self.advhint.edit_traits('advhint_view') - return - def _maskabove_bb_fired(self): - self.maskabove() - return - def _maskbelow_bb_fired(self): - self.maskbelow() - return - def _loadmaskfile_bb_fired(self): - self.edit_traits('loadmaskfile_view') - return - def _savemaskfile_bb_fired(self): - if self.maskfile == '': - self.maskfile = os.path.join(self.srxconfig.savedirectory, 'mask.npy') - else: - self.maskfile = os.path.splitext(self.maskfile)[0] + '.npy' - self.edit_traits('savemaskfile_view') - return - - def __init__(self, **kwargs): - ''' - init the object and create notification - ''' - HasTraits.__init__(self, **kwargs) - self.createPlot() - # self._loadMaskPar() - self._add_notifications() - return - - hinttext = Str('Zoom: ; Reset: ; Pan: ; Toggle XY coordinates:

') - traits_view = View( - Group( - Item('plot', editor=ComponentEditor(size=(550, 550)), - show_label=False), - HGroup( - spring, - Item('scalemode', label='Scale mode'), - Item('scalepowder', label='Gamma', - editor=RangeEditor(auto_set=False, low_name='splb', high_name='spub', format='%.1f')), - spring, - ), - VGroup( - HGroup( - Item('addpolygon_bb', enabled_when='not maskediting'), - Item('removepolygon_bb', enabled_when='not maskediting'), - spring, - Item('maskabove_bb', enabled_when='not maskediting'), - Item('maskaboveint', enabled_when='not maskediting'), - show_labels=False, - ), - HGroup( - Item('addpoint_bb', enabled_when='not maskediting'), - Item('pointmaskradius', label='Size:', show_label=True), - spring, - Item('maskbelow_bb', enabled_when='not maskediting'), - Item('maskbelowint', enabled_when='not maskediting'), - show_labels=False, - ), - HGroup( - Item('clearmask_bb', enabled_when='not maskediting'), - Item('invertmask_bb', enabled_when='not maskediting'), - Item('advancedmask_bb', enabled_when='not maskediting'), - spring, - Item('loadmaskfile_bb'), - Item('savemaskfile_bb'), - show_labels=False, - ), - show_labels=False, - show_border=True, - label='Mask' - ), - orientation="vertical"), - resizable=True, - title='2D image', - statusbar=['hinttext'], - width=600, - height=700, - icon=ImageResource('icon.png'), - ) - - savemaskfile_action = \ - Action(name='OK ', - action='_save') - loadmaskfile_action = \ - Action(name='OK ', - action='_load') - applydymask_action = \ - Action(name='Apply ', - action='_applyDymask') - - savemaskfile_view = \ - View(Item('maskfile'), - buttons=[savemaskfile_action, CancelButton], - title='Save mask file', - width=500, - resizable=True, - handler=SaveLoadMaskHandler(), - icon=ImageResource('icon.png'), - ) - - loadmaskfile_view = \ - View(Item('maskfile'), - buttons=[loadmaskfile_action, CancelButton], - title='Load mask file', - width=500, - resizable=True, - handler=SaveLoadMaskHandler(), - icon=ImageResource('icon.png'), - ) - - - advancedmask_view = \ - View( - Group( - VGroup( - Item('cropedges', label='Mask edges', editor=ArrayEditor(width=-50)), - label='Edge mask', - show_border=True, - ), - VGroup( - Item('darkpixelmask', label='Enable'), - Item('darkpixelr', label='Threshold', enabled_when='darkpixelmask'), - label='Dark pixel mask', - show_border=True, - ), - VGroup( - Item('brightpixelmask', label='Enable'), - Item('brightpixelsize', label='Testing size', enabled_when='brightpixelmask'), - Item('brightpixelr', label='Threshold', enabled_when='brightpixelmask'), - label='Bright pixel mask', - show_border=True, - ), - VGroup( - Item('avgmask', label='Enable'), - Item('avgmaskhigh', label='High', enabled_when='avgmask'), - Item('avgmasklow', label='Low', enabled_when='avgmask'), - label='Average mask', - show_border=True, - ), - ), - - title='Dynamic mask', - width=320, - handler=AdvMaskHandler(), - resizable=True, - buttons=[applydymask_action, OKButton, CancelButton], - icon=ImageResource('icon.png'), - ) - -class MasklineDrawer(LineSegmentTool): - """ - """ - - imageplot = Any - - def _finalize_selection(self): - self.imageplot._disableMaskEditing() - self.imageplot.mergeMask(self.points) - return - - def __init__(self, *args, **kwargs): - LineSegmentTool.__init__(self, *args, **kwargs) - self.line.line_color = "red" - self.line.vertex_color = "white" - return - -class MaskPointInspector(ImageInspectorTool): - - exitmask_key = KeySpec('Enter') - imageplot = Any - enablemaskselect = Bool(False) - - def normal_key_pressed(self, event): - if self.inspector_key.match(event): - self.visible = not self.visible - event.handled = True - if self.exitmask_key.match(event): - self.enablemaskselect = False - self.imageplot._disablePointMaskEditing() - return - - def normal_left_down(self, event): - if self.enablemaskselect: - ndx = self.component.map_index((event.x, event.y)) - self.imageplot.addPointMask(ndx) - return - -class AdvHint(HasTraits): - - advhinttext = str( -'''Notes: Advanced Masks are generated during the integration and refreshed for each image. -You can preview the masks here or apply the current masks to the static mask permanently. - -Edge mask: mask the pixels around the image edge. (left, right, top, bottom) -Dark pixel mask: mask the pixels too dark compared to their local environment -Bright pixel mask: mask the pixels too bright compared to their local environment -Average mask: Mask the pixels too bright or too dark compared to the average intensity - at the similar diffraction angle. Currect calibration information is required.''') - - advhint_view = \ - View( - Group( - Item('advhinttext', style='readonly', show_label=False), - show_border=True, - ), - - title='Advanced mask hints', - width=640, - resizable=False, - buttons=[OKButton], - icon=ImageResource('icon.png'), - ) diff --git a/dpx/srxplanargui/images/01.png b/dpx/srxplanargui/images/01.png deleted file mode 100644 index 9985ac5..0000000 Binary files a/dpx/srxplanargui/images/01.png and /dev/null differ diff --git a/dpx/srxplanargui/images/02.png b/dpx/srxplanargui/images/02.png deleted file mode 100644 index 1a14316..0000000 Binary files a/dpx/srxplanargui/images/02.png and /dev/null differ diff --git a/dpx/srxplanargui/images/03.png b/dpx/srxplanargui/images/03.png deleted file mode 100644 index 709656f..0000000 Binary files a/dpx/srxplanargui/images/03.png and /dev/null differ diff --git a/dpx/srxplanargui/images/04.png b/dpx/srxplanargui/images/04.png deleted file mode 100644 index 904aba9..0000000 Binary files a/dpx/srxplanargui/images/04.png and /dev/null differ diff --git a/dpx/srxplanargui/images/05.png b/dpx/srxplanargui/images/05.png deleted file mode 100644 index 370163a..0000000 Binary files a/dpx/srxplanargui/images/05.png and /dev/null differ diff --git a/dpx/srxplanargui/images/06.png b/dpx/srxplanargui/images/06.png deleted file mode 100644 index baeb539..0000000 Binary files a/dpx/srxplanargui/images/06.png and /dev/null differ diff --git a/dpx/srxplanargui/images/07.png b/dpx/srxplanargui/images/07.png deleted file mode 100644 index 26445e9..0000000 Binary files a/dpx/srxplanargui/images/07.png and /dev/null differ diff --git a/dpx/srxplanargui/images/08.png b/dpx/srxplanargui/images/08.png deleted file mode 100644 index 3abbed2..0000000 Binary files a/dpx/srxplanargui/images/08.png and /dev/null differ diff --git a/dpx/srxplanargui/images/09.png b/dpx/srxplanargui/images/09.png deleted file mode 100644 index 71d4b83..0000000 Binary files a/dpx/srxplanargui/images/09.png and /dev/null differ diff --git a/dpx/srxplanargui/images/10.png b/dpx/srxplanargui/images/10.png deleted file mode 100644 index 3a51222..0000000 Binary files a/dpx/srxplanargui/images/10.png and /dev/null differ diff --git a/dpx/srxplanargui/images/11.png b/dpx/srxplanargui/images/11.png deleted file mode 100644 index c0248da..0000000 Binary files a/dpx/srxplanargui/images/11.png and /dev/null differ diff --git a/dpx/srxplanargui/images/12.png b/dpx/srxplanargui/images/12.png deleted file mode 100644 index 49e16b5..0000000 Binary files a/dpx/srxplanargui/images/12.png and /dev/null differ diff --git a/dpx/srxplanargui/images/13.png b/dpx/srxplanargui/images/13.png deleted file mode 100644 index 033b3a0..0000000 Binary files a/dpx/srxplanargui/images/13.png and /dev/null differ diff --git a/dpx/srxplanargui/images/14.png b/dpx/srxplanargui/images/14.png deleted file mode 100644 index 50dbed2..0000000 Binary files a/dpx/srxplanargui/images/14.png and /dev/null differ diff --git a/dpx/srxplanargui/images/15.png b/dpx/srxplanargui/images/15.png deleted file mode 100644 index 5c590f9..0000000 Binary files a/dpx/srxplanargui/images/15.png and /dev/null differ diff --git a/dpx/srxplanargui/images/16.png b/dpx/srxplanargui/images/16.png deleted file mode 100644 index cacf764..0000000 Binary files a/dpx/srxplanargui/images/16.png and /dev/null differ diff --git a/dpx/srxplanargui/images/17.png b/dpx/srxplanargui/images/17.png deleted file mode 100644 index f7c58df..0000000 Binary files a/dpx/srxplanargui/images/17.png and /dev/null differ diff --git a/dpx/srxplanargui/images/18.png b/dpx/srxplanargui/images/18.png deleted file mode 100644 index d3c305b..0000000 Binary files a/dpx/srxplanargui/images/18.png and /dev/null differ diff --git a/dpx/srxplanargui/images/19.png b/dpx/srxplanargui/images/19.png deleted file mode 100644 index 5f1ade1..0000000 Binary files a/dpx/srxplanargui/images/19.png and /dev/null differ diff --git a/dpx/srxplanargui/images/20.png b/dpx/srxplanargui/images/20.png deleted file mode 100644 index d21b241..0000000 Binary files a/dpx/srxplanargui/images/20.png and /dev/null differ diff --git a/dpx/srxplanargui/images/21.png b/dpx/srxplanargui/images/21.png deleted file mode 100644 index d22a274..0000000 Binary files a/dpx/srxplanargui/images/21.png and /dev/null differ diff --git a/dpx/srxplanargui/images/22.png b/dpx/srxplanargui/images/22.png deleted file mode 100644 index 30b90d9..0000000 Binary files a/dpx/srxplanargui/images/22.png and /dev/null differ diff --git a/dpx/srxplanargui/images/24.png b/dpx/srxplanargui/images/24.png deleted file mode 100644 index ef335a8..0000000 Binary files a/dpx/srxplanargui/images/24.png and /dev/null differ diff --git a/dpx/srxplanargui/live.py b/dpx/srxplanargui/live.py deleted file mode 100644 index eea8ab6..0000000 --- a/dpx/srxplanargui/live.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -'''provide UI for srxplanar -''' - -import numpy as np -import re -import os -import sys -from functools import partial -import threading -import time - -from traits.etsconfig.api import ETSConfig -ETSConfig.toolkit = 'qt4' - -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on - -from traitsui.api import \ - Item, Group, View, Handler, Controller, spring, Action, \ - HGroup, VGroup, Tabbed, \ - RangeEditor, CheckListEditor, TextEditor, EnumEditor, ButtonEditor, \ - ArrayEditor, TitleEditor, TableEditor, HistoryEditor, InstanceEditor, ImageEditor -from traitsui.menu import ToolBar, OKButton, CancelButton, Menu, MenuBar, OKCancelButtons -from pyface.api import ImageResource, GUI, SplashScreen - -from dpx.srxplanargui.selectfiles import AddFiles -from dpx.srxplanargui.srxconfig import SrXconfig -from dpx.srxplanargui.srxgui import SrXgui, SrXguiHandler, SaveHandler, LoadHandler -from diffpy.srxplanar.srxplanar import SrXplanar -from dpx.srxplanargui.help import SrXguiHelp -from dpx.srxplanargui.calibration import Calibration - -from dpx.confutils.tools import checkFileVal - -class SrXguiLive(SrXgui): - - getxgui = Any - - def __init__(self, configfile=None, args=None, **kwargs): - - # init the object, createt the notifications - - self.splash = SplashScreen(image=ImageResource('01.png'), show_log_messages=False) - self.splash.open() - - super(SrXgui, self).__init__(**kwargs) - configfile = self.detectConfigfile(configfile) - if not os.path.exists(configfile): - configfile = self.detectConfigfile('default') - self.configfile = configfile - - if not kwargs.has_key('srxconfig'): - self.srxconfig = SrXconfig(filename=configfile, args=args, **kwargs) - - self.addfiles = AddFiles(srxconfig=self.srxconfig) - self.srx = SrXplanar(self.srxconfig) - self.addfiles.srx = self.srx - self.help = SrXguiHelp() - self.calibration = Calibration(srx=self.srx, srxconfig=self.srxconfig) - self.splash.close() - return - - @on_trait_change('srxconfig.savedirectory') - def _changedir(self): - newdir = self.srxconfig.savedirectory - if os.path.exists(newdir): - self.getxgui.getxconfig.inputdir = os.path.abspath(newdir) - self.getxgui.getxconfig.savedir = os.path.abspath(newdir) - else: - self.getxgui.getxconfig.inputdir = os.path.abspath(os.path.curdir) - self.getxgui.getxconfig.savedir = os.path.abspath(os.path.curdir) - return - - def processSelected(self, summation=False): - if self.addfiles.selected: - self.srx.updateConfig() - filelist = [f.fullname for f in self.addfiles.selected] - self.srx.prepareCalculation(filelist) - rvlist = self.srx.integrateFilelist(filelist, summation=summation) - newchifilelist = [rv['filename'] for rv in rvlist] - GUI.invoke_later(self.addNewImagesToGetXgui, newchifilelist) - return - - def addNewImagesToGetXgui(self, filelist): - ''' - add new images to getxgui, if images are already there, refresh them - - :param filelist: list of full path of new images - ''' - self.addfiles.refreshdatalist = True - newdatacontainers = self.getxgui.selectfiles.addFiles(filelist) - self.getxgui.createNewPlot(newdatacontainers) - return - - helpbutton_action = \ - Action(name='Help ', - action='_helpView') - saveconfig_action = \ - Action(name='Save Config', - action='_saveconfigView', - enabled_when='not capturing') - loadconfig_action = \ - Action(name='Load Config', - action='_loadconfigView', - enabled_when='not capturing') - - traits_view = \ - View( - HGroup( - Item('addfiles', editor=InstanceEditor(view='traits_view'), - style='custom', label='Files', width=0.4), - VGroup( - Group(Item('srxconfig', editor=InstanceEditor(view='main_view'), - style='custom', label='Basic', show_label=False), - springy=True, - ), - HGroup(spring, - Item('selfcalibratebb', enabled_when='not capturing'), - Item('integratbb', enabled_when='not capturing'), - spring, - show_labels=False, - ), - ), - layout='split', - springy=True, - dock='tab', - show_labels=False - ), - resizable=True, - title='SrXgui', - width=700, - height=650, - kind='live', - icon=ImageResource('icon.png'), - handler=SrXguiHandler(), - buttons=[helpbutton_action, saveconfig_action, loadconfig_action, OKButton], - ) - -def main(): - gui = SrXguiLive() - gui.configure_traits(view='traits_view') - return - -if __name__ == '__main__': - sys.exit(main()) diff --git a/dpx/srxplanargui/srxconfig.py b/dpx/srxplanargui/srxconfig.py deleted file mode 100644 index 6cce0fb..0000000 --- a/dpx/srxplanargui/srxconfig.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.srxplanar by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSENOTICE.txt for license information. -# -############################################################################## - -import numpy as np -import ConfigParser -import re, os, sys -from functools import partial -import argparse - -from traits.etsconfig.api import ETSConfig -ETSConfig.toolkit = 'qt4' - -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on - -from traitsui.api import \ - Item, Group, View, Handler, Controller, spring, Action, \ - HGroup, VGroup, Tabbed, \ - RangeEditor, CheckListEditor, TextEditor, EnumEditor, ButtonEditor, \ - ArrayEditor, TitleEditor, TableEditor, HistoryEditor, InstanceEditor, BooleanEditor -from traitsui.menu import ToolBar, OKButton, CancelButton, Menu, MenuBar, OKCancelButtons -from pyface.api import ImageResource - -from dpx.confutils.configtraits import ConfigBaseTraits -from dpx.confutils.tools import _configPropertyRad, _configPropertyR, _configPropertyRW -from diffpy.srxplanar.srxplanarconfig import _description, _epilog, _optdatalist, \ - _defaultdata, checkMax - -class SrXconfig(ConfigBaseTraits): - ''' - config class, based on ConfigBase class in diffpy.confutils - ''' - - # Text to display before the argument help - _description = _description - - # Text to display after the argument help - _epilog = _epilog - - _optdatalist = _optdatalist - - _defaultdata = {'configfile': [], - 'headertitle': 'SrXgui configration' - } - - rotation = Property(depends_on='rotationd', fget=lambda self: np.radians(self.rotationd)) - tilt = Property(depends_on='tiltd', fget=lambda self: np.radians(self.tiltd)) - tthstep = Property(depends_on='tthstepd', fget=lambda self: np.radians(self.tthstepd)) - tthmax = Property(depends_on='tthmaxd', fget=lambda self: np.radians(self.tthmaxd)) - - tthorqmax = Property(depends_on='integrationspace, tthmaxd, qmax', - fget=lambda self: self.tthmax if self.integrationspace == 'twotheta' else self.qmax) - tthorqstep = Property(depends_on='integrationspace, tthmaxd, qmax', - fget=lambda self: self.tthstep if self.integrationspace == 'twotheta' else self.qstep) - - def _preUpdateSelf(self, **kwargs): - ''' - additional process called in self._updateSelf, this method is called - before self._copySelftoConfig(), i.e. before copy options value to - self.config (config file) - - check the tthmaxd and qmax, and set tthorqmax, tthorqstep according to integration space - - :param kwargs: optional kwargs - ''' - self.tthmaxd, self.qmax = checkMax(self) - '''addmask = [b for b in self.addmask if not (b in ['brightpixel', 'darkpixel'])] - if len(addmask) > 0: - self.maskfile = addmask[0]''' - return - - def _opendirectory_changed(self): - if os.path.exists(self.opendirectory): - self.savedirectory = os.path.abspath(self.opendirectory) - else: - self.opendirectory = os.path.abspath(os.curdir) - self.savedirectory = os.path.abspath(os.curdir) - return - - def _savedirectory_changed(self): - if not os.path.exists(self.savedirectory): - self.savedirectory = os.path.abspath(os.curdir) - return - - directory_group = \ - Group(Item('opendirectory', label='Input dir.', help='directory of 2D images'), - Item('savedirectory', label='Output dir.', help='directory of saved files'), - show_border=True, - label='Files', - ) - mask_group = \ - Group(Item('maskfile', label='Mask file'), - show_border=True, - label='Masks', - ) - - geometry_visible = Bool(False) - geometry_group = \ - Group(Item('integrationspace', label='Integration grid'), - Item('wavelength', visible_when='integrationspace == "qspace"', label='Wavelength',), - Item('xbeamcenter', label='X beamcenter'), - Item('ybeamcenter', label='Y beamcenter'), - Item('distance', label='Distance'), - Item('rotationd', label='Rotation'), - Item('tiltd', label='Tilt rotation'), - Item('tthstepd', label='Integration step', visible_when='integrationspace == "twotheta"'), - Item('qstep', label='Integration step', visible_when='integrationspace == "qspace"'), - - show_border=True, - # label='Geometry parameters', - visible_when='geometry_visible', - ) - - correction_visible = Bool(False) - correction_group = \ - Group(Item('uncertaintyenable', label='Uncertainty'), - Item('sacorrectionenable', label='Solid angle corr.'), - Item('polcorrectionenable', label='Polarization corr.'), - Item('polcorrectf', label='Polarization factor'), - - # Item('brightpixelmask', label='Bright pixel mask'), - # Item('darkpixelmask', label='Dark pixel mask'), - # Item('avgmask', label='Average mask'), - # Item('cropedges', label='Crop edges', editor=ArrayEditor(width=-50)), - - show_border=True, - # label='Corrections' - visible_when='correction_visible' - ) - - detector_visible = Bool(False) - detector_group = \ - Group(Item('fliphorizontal', label='Flip horizontally'), - Item('flipvertical', label='Flip vertically'), - Item('xdimension', label='x dimension'), - Item('ydimension', label='y dimension'), - Item('xpixelsize', label='x pixel size'), - Item('ypixelsize', label='x pixel size'), - - show_border=True, - # label='Detector parameters' - visible_when='detector_visible' - ), - - - main_view = \ - View( - Group( - directory_group, - mask_group, - Group( - Group(Item('geometry_visible', label='Geometry parameters'), - geometry_group,), - Group(Item('correction_visible', label='Corrections'), - correction_group,), - Group(Item('detector_visible', label='Detector parameters'), - detector_group,), - # label = 'Basic' - show_border=True, - ), - ), - - resizable=True, - scrollable=True, - # handler = handler, - icon=ImageResource('icon.png'), - ) - - -SrXconfig.initConfigClass() - -if __name__ == '__main__': - a = SrXconfig() - # a.updateConfig() - a.configure_traits(view='main_view') diff --git a/dpx/srxplanargui/srxgui.py b/dpx/srxplanargui/srxgui.py deleted file mode 100644 index 5808087..0000000 --- a/dpx/srxplanargui/srxgui.py +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -'''provide UI for srxplanar -''' - -import numpy as np -import os -import sys - -from traits.etsconfig.api import ETSConfig -ETSConfig.toolkit = 'qt4' - -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on - -from traitsui.api import \ - Item, Group, View, Handler, Controller, spring, Action, \ - HGroup, VGroup, Tabbed, \ - RangeEditor, CheckListEditor, TextEditor, EnumEditor, ButtonEditor, \ - ArrayEditor, TitleEditor, TableEditor, HistoryEditor, InstanceEditor, ImageEditor -from traitsui.menu import ToolBar, OKButton, CancelButton, Menu, MenuBar, OKCancelButtons -from pyface.api import ImageResource, SplashScreen - -from dpx.srxplanargui.selectfiles import AddFiles -from dpx.srxplanargui.srxconfig import SrXconfig -from diffpy.srxplanar.srxplanar import SrXplanar -from dpx.srxplanargui.help import SrXguiHelp -from dpx.srxplanargui.calibration import Calibration - -class SrXguiHandler(Handler): - - def closed(self, info, is_ok): - ''' - notify main gui to delete current plot in plots list - ''' - configfile = info.object.detectConfigfile('default') - info.object.saveConfig(configfile) - return True - - def _saveconfigView(self, info): - info.object._saveconfigView() - return - - def _loadconfigView(self, info): - info.object._loadconfigView() - return - - def _helpView(self, info): - info.object._helpbb_changed() - return - -class SaveHandler(Handler): - - def closed(self, info, is_ok): - if is_ok: - info.object.saveConfig(info.object.configfile) - return True - -class LoadHandler(Handler): - def closed(self, info, is_ok): - if is_ok: - info.object.loadConfig(info.object.configfile) - return - -class SrXgui(HasTraits): - - addfiles = Instance(AddFiles) - srxconfig = Instance(SrXconfig) - help = Instance(SrXguiHelp) - splash = Any - calibration = Instance(Calibration) - - def __init__(self, configfile=None, args=None, **kwargs): - ''' - init the object, createt the notifications - ''' - super(SrXgui, self).__init__(**kwargs) - configfile = self.detectConfigfile(configfile) - if not os.path.exists(configfile): - configfile = self.detectConfigfile('default') - self.configfile = configfile - - if not kwargs.has_key('srxconfig'): - self.srxconfig = SrXconfig(filename=configfile, args=args, **kwargs) - - self.addfiles = AddFiles(srxconfig=self.srxconfig) - self.srx = SrXplanar(self.srxconfig) - self.addfiles.srx = self.srx - self.help = SrXguiHelp() - self.calibration = Calibration(srx=self.srx, srxconfig=self.srxconfig) - - # self.loadConfig(configfile) - self.splash.close() - return - - def saveConfig(self, filename=None): - ''' - save config - ''' - if filename == 'default': - filename = self.detectConfigfile(filename) - self.srxconfig.writeConfig(filename, mode='full') - self.configfile = filename - return - - def loadConfig(self, filename=None): - ''' - load config - ''' - configfile = self.detectConfigfile(filename) - if os.path.exists(configfile): - self.srxconfig.updateConfig(filename=configfile) - self.configfile = configfile - return - - def processSelected(self, summation=False): - if self.addfiles.selected: - self.srx.updateConfig() - filelist = [f.fullname for f in self.addfiles.selected] - self.srx.prepareCalculation(filelist) - self.srx.integrateFilelist(filelist, summation=summation) - return - - def detectConfigfile(self, filename): - ''' - current directory > home directory, if none, then return the curdir+filename - if 'default', then return home+filename - ''' - if filename == None: - configfile = os.path.join(os.path.curdir, 'srxconfig.cfg') - elif filename == 'default': - configfile = os.path.join(os.path.expanduser('~'), 'srxconfig.cfg') - else: - if os.path.abspath(filename): - if os.path.exists(filename): - configfile = filename - else: - filename = os.path.split(filename)[1] - configfile = os.path.join(os.path.curdir, filename) - else: - configfile = os.path.join(os.path.curdir, filename) - return configfile - - - ########################################################### - def _saveconfigView(self): - self.edit_traits(view='saveconfig_view') - return - def _loadconfigView(self): - self.edit_traits(view='loadconfig_view') - return - - configfile = File() - helpbutton_action = \ - Action(name='Help ', - action='_helpView') - saveconfig_action = \ - Action(name='Save Config', - action='_saveconfigView') - loadconfig_action = \ - Action(name='Load Config', - action='_loadconfigView') - - saveconfig_view = \ - View(Item('configfile'), - - resizable=True, - title='Save config', - width=500, - buttons=[OKButton, CancelButton], - handler=SaveHandler(), - icon=ImageResource('icon.png'), - ) - loadconfig_view = \ - View(Item('configfile'), - - resizable=True, - title='Load config', - width=500, - buttons=[OKButton, CancelButton], - handler=LoadHandler(), - icon=ImageResource('icon.png'), - ) - ############################################################# - - def _integratbb_changed(self): - self.processSelected(False) - return - - def _integratessbb_changed(self): - self.processSelected(True) - return - - def _helpbb_changed(self): - self.help.edit_traits(view='quickstart_view') - return - - def _selfcalibratebb_changed(self): - image = None - if self.addfiles.selected != None: - if len(self.addfiles.selected) == 1: - image = self.addfiles.selected[0].fullname - - if image != None: - self.calibration.image = image - self.calibration.edit_traits(view='main_View') - return - - integratbb = Button('Integrate') - integratessbb = Button('Sum and Integrate') - selfcalibratebb = Button('Calibrate') - helpbb = Button('Help') - - traits_view = \ - View( - HGroup( - Item('addfiles', editor=InstanceEditor(view='traits_view'), - style='custom', label='Files', width=0.4), - VGroup( - Group(Item('srxconfig', editor=InstanceEditor(view='main_view'), - style='custom', label='Basic', show_label=False), - springy=True, - ), - HGroup(spring, - Item('selfcalibratebb'), - Item('integratbb'), - spring, - show_labels=False, - ), - ), - - layout='split', - springy=True, - dock='tab', - show_labels=False - ), - resizable=True, - title='SrXgui', - width=700, - height=650, - kind='live', - buttons=[helpbutton_action, saveconfig_action, loadconfig_action, OKButton], - icon=ImageResource('icon.png'), - handler=SrXguiHandler(), - ) - -if __name__ == '__main__': - sys.exit() diff --git a/dpx/srxplanargui/version.py b/dpx/srxplanargui/version.py deleted file mode 100644 index eda8c87..0000000 --- a/dpx/srxplanargui/version.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.srxplanar by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSENOTICE.txt for license information. -# -############################################################################## - -"""Definition of __version__ and __date__ for this package. -""" - -# obtain version information -from pkg_resources import get_distribution -_pkgname = __name__.rsplit('.', 1)[0] -__version__ = get_distribution(_pkgname).version - -# we assume that tag_date was used and __version__ ends in YYYYMMDD -__date__ = __version__[-8:-4] + '-' + \ - __version__[-4:-2] + '-' + __version__[-2:] - -# End of file diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 0000000..790d30b --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/news/doc.rst b/news/doc.rst new file mode 100644 index 0000000..7677ce5 --- /dev/null +++ b/news/doc.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Change "diffpy.srxplanargui" from python 2 to python 3 architecture. +* Support ``scikit-package`` Level 5 standard (https://scikit-package.github.io/scikit-package/). + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e9510cc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,88 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning>=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.srxplanargui" +dynamic=['version', 'dependencies'] +authors = [ + { name="Simon Billinge", email="sb2896@columbia.edu" }, +] +maintainers = [ + { name="Simon Billinge", email="sb2896@columbia.edu" }, +] +description = "xPDFsuite, a software for PDF transformation and visualization." +keywords = ['diffpy', 'pdf', 'data interpretation'] +readme = "README.rst" +requires-python = ">=3.11, <3.14" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.srxplanargui/" +Issues = "https://github.com/diffpy/diffpy.srxplanargui/issues/" + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +where = ["src"] # list of folders that contain the packages (["."] by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[project.scripts] +diffpy-srxplanargui = "diffpy.srxplanargui_app:main" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements/pip.txt"]} + +[tool.codespell] +exclude-file = ".codespell/ignore_lines.txt" +ignore-words = ".codespell/ignore_words.txt" +skip = "*.cif,*.dat" + +[tool.docformatter] +recursive = true +wrap-summaries = 72 +wrap-descriptions = 72 + +[tool.black] +line-length = 79 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.rst + | \.txt + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' diff --git a/requirements/conda.txt b/requirements/conda.txt new file mode 100644 index 0000000..c944f3b --- /dev/null +++ b/requirements/conda.txt @@ -0,0 +1,7 @@ +numpy +fabio +pyface +scipy +traits +pyfai +matplotlib diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 0000000..5f34c6e --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,5 @@ +sphinx +sphinx_rtd_theme +sphinx-copybutton +doctr +m2r diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 0000000..fadf68c --- /dev/null +++ b/requirements/pip.txt @@ -0,0 +1,10 @@ +numpy +chaco +pyface +scipy +traits +traitsui +fabio +pyfai +matplotlib +diffpy.srxplanar diff --git a/requirements/tests.txt b/requirements/tests.txt new file mode 100644 index 0000000..a727786 --- /dev/null +++ b/requirements/tests.txt @@ -0,0 +1,6 @@ +flake8 +pytest +codecov +coverage +pytest-cov +pytest-env diff --git a/setup.py b/setup.py deleted file mode 100755 index 1d46919..0000000 --- a/setup.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python - -# Installation script for dpx.pdfgetxgui - -"""dpx.srxplanargui - a software for 2D powder diffraction image integration - and error propagation - -Packages: dpx.srxplanargui -Scripts: srxgui -""" - -import os -from setuptools import setup, find_packages -from setuptools.command.build_py import build_py -import py_compile - - -class custom_build_pyc(build_py): - - def byte_compile(self, files): - for file in files: - if file.endswith('.py'): - py_compile.compile(file) - os.unlink(file) - -# Use this version when git data are not available, like in git zip archive. -# Update when tagging a new release. -FALLBACK_VERSION = '1.0' - -# versioncfgfile holds version data for git commit hash and date. -# It must reside in the same directory as version.py. -MYDIR = os.path.dirname(os.path.abspath(__file__)) -versioncfgfile = os.path.join(MYDIR, 'dpx', 'srxplanargui', 'version.cfg') -gitarchivecfgfile = versioncfgfile.replace('version.cfg', 'gitarchive.cfg') - - -def gitinfo(): - from subprocess import Popen, PIPE - kw = dict(stdout=PIPE, cwd=MYDIR) - proc = Popen(['git', 'describe', '--match=v[[:digit:]]*'], **kw) - desc = proc.stdout.read() - proc = Popen(['git', 'log', '-1', '--format=%H %at %ai'], **kw) - glog = proc.stdout.read() - rv = {} - rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v') - rv['commit'], rv['timestamp'], rv['date'] = glog.strip().split(None, 2) - return rv - - -def getversioncfg(): - import re - from ConfigParser import RawConfigParser - vd0 = dict(version=FALLBACK_VERSION, commit='', date='', timestamp=0) - # first fetch data from gitarchivecfgfile, ignore if it is unexpanded - g = vd0.copy() - cp0 = RawConfigParser(vd0) - cp0.read(gitarchivecfgfile) - if '$Format:' not in cp0.get('DEFAULT', 'commit'): - g = cp0.defaults() - mx = re.search(r'\btag: v(\d[^,]*)', g.pop('refnames')) - if mx: - g['version'] = mx.group(1) - # then try to obtain version data from git. - gitdir = os.path.join(MYDIR, '.git') - if os.path.exists(gitdir) or 'GIT_DIR' in os.environ: - try: - g = gitinfo() - except OSError: - pass - # finally, check and update the active version file - cp = RawConfigParser() - cp.read(versioncfgfile) - d = cp.defaults() - rewrite = not d or (g['commit'] and ( - g['version'] != d.get('version') or g['commit'] != d.get('commit'))) - if rewrite: - cp.set('DEFAULT', 'version', g['version']) - cp.set('DEFAULT', 'commit', g['commit']) - cp.set('DEFAULT', 'date', g['date']) - cp.set('DEFAULT', 'timestamp', g['timestamp']) - cp.write(open(versioncfgfile, 'w')) - return cp - -versiondata = getversioncfg() - - -def dirglob(d, *patterns): - from glob import glob - rv = [] - for p in patterns: - rv += glob(os.path.join(d, p)) - return rv - -# define distribution -setup_args = dict( - name='dpx.srxplanargui', - cmdclass=dict(build_py=custom_build_pyc), - version=versiondata.get('DEFAULT', 'version'), - namespace_packages=['dpx'], - packages=find_packages(), - include_package_data=True, - zip_safe=False, - entry_points={ - # define console_scripts here, see setuptools docs for details. - 'console_scripts': ['srxgui = dpx.srxplanargui.srxguiapp:main', - ], - }, - - author='Simon J.L. Billinge', - author_email='sb2896@columbia.edu', - description='xPDFsuite, a software for PDF transformation and visualization', - maintainer='Xiaohao Yang', - maintainer_email='sodestiny1@gmail.com', - license='see LICENSENOTICE.txt', - url='', - keywords='2D powder diffraction image integration uncertainty propagation', - classifiers=[ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Environment :: MacOS X', - 'Environment :: Win32 (MS Windows)', - 'Environment :: X11 Applications', - 'Intended Audience :: Science/Research', - 'Operating System :: MacOS', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Topic :: Scientific/Engineering :: Physics', - ], -) - -if __name__ == '__main__': - setup(**setup_args) - -# End of file diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py new file mode 100644 index 0000000..ffe8c8b --- /dev/null +++ b/src/diffpy/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2012-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Xiaohao Yang and Billinge Group members. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srxplanargui/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## diff --git a/src/diffpy/srxplanargui/__init__.py b/src/diffpy/srxplanargui/__init__.py new file mode 100644 index 0000000..19cf2a0 --- /dev/null +++ b/src/diffpy/srxplanargui/__init__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2012-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Xiaohao Yang, Billinge Group members. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srxplanargui/graphs/contributors +# +# See LICENSE.rst for license information. +# +############################################################################## +"""XPDFsuite, a software for PDF transformation and visualization.""" + +# package version +from diffpy.srxplanargui.version import __version__ # noqa + +# silence the pyflakes syntax checker +assert __version__ or True + +# End of file diff --git a/src/diffpy/srxplanargui/calibration.py b/src/diffpy/srxplanargui/calibration.py new file mode 100644 index 0000000..b2bca01 --- /dev/null +++ b/src/diffpy/srxplanargui/calibration.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srxplanargui by Simon J. L. Billinge group +# (c) 2012-2025 Trustees of the Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Xiaohao Yang +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE.txt for license information. +# +############################################################################## + +import os +import re +import sys + +from pyface.api import ImageResource +from traits.api import ( + Bool, + DelegatesTo, + Directory, + Enum, + File, + Float, + HasTraits, + Instance, + Int, + Str, + on_trait_change, +) +from traits.etsconfig.api import ETSConfig +from traitsui.api import Group, Handler, HGroup, Item, VGroup, View +from traitsui.menu import CancelButton, OKButton + +from diffpy.srxconfutils.tools import module_exists_lower +from diffpy.srxplanar.selfcalibrate import selfCalibrate +from diffpy.srxplanar.srxplanar import SrXplanar +from diffpy.srxplanar.srxplanarconfig import checkMax +from diffpy.srxplanargui.srxconfig import SrXconfig + +ETSConfig.toolkit = "qt" + +if module_exists_lower("pyfai"): + import pyFAI + + missingpyFAI = False +else: + missingpyFAI = True + +# determine option name for calibrant used in pyFAI-calib +# The current option is "-c", but it was "-S" in 0.9.3. +pyFAIcalib_opt_calibrant = "-c" +if not missingpyFAI: + from pkg_resources import parse_version + + if parse_version(pyFAI.version) <= parse_version("0.9.3"): + pyFAIcalib_opt_calibrant = "-S" + del parse_version + + +class CalibrationHandler(Handler): + + def closed(self, info, is_ok): + """Notify main gui to delete current plot in plots list.""" + if is_ok: + info.object.calibration() + return True + + +class Calibration(HasTraits): + image = File + dspacefile = File + srx = Instance(SrXplanar) + srxconfig = Instance(SrXconfig) + pythonbin = File + pyFAIdir = Directory + caliscript = File + missingpyFAI = Bool(False) + + xpixelsize = DelegatesTo("srxconfig") + ypixelsize = DelegatesTo("srxconfig") + wavelength = DelegatesTo("srxconfig") + xbeamcenter = DelegatesTo("srxconfig") + ybeamcenter = DelegatesTo("srxconfig") + xdimension = DelegatesTo("srxconfig") + ydimension = DelegatesTo("srxconfig") + distance = DelegatesTo("srxconfig") + rotationd = DelegatesTo("srxconfig") + tiltd = DelegatesTo("srxconfig") + configmode = DelegatesTo("srxconfig") + xpixelsizetem = DelegatesTo("srxconfig") + ypixelsizetem = DelegatesTo("srxconfig") + + def __init__(self, *args, **kwargs): + super(Calibration, self).__init__(*args, **kwargs) + self.locatePyFAI() + self.missingpyFAI = missingpyFAI + return + + def locatePyFAI(self): + pythonbin = sys.executable + if sys.platform == "win32": + pyFAIdir = os.path.join(sys.exec_prefix, "Scripts") + elif sys.platform.startswith("linux"): + pyFAIdir = os.path.join(sys.exec_prefix, "bin") + else: + pyFAIdir = os.path.join(sys.exec_prefix, "bin") + self.pythonbin = pythonbin + self.pyFAIdir = pyFAIdir + return + + @on_trait_change("pyFAIdir") + def _pyFAIdirChanged(self): + if sys.platform == "win32": + caliscript = os.path.join(self.pyFAIdir, "pyFAI-calib.py") + intescript = os.path.join(self.pyFAIdir, "pyFAI-waxs.py") + elif sys.platform.startswith("linux"): + caliscript = os.path.join(self.pyFAIdir, "pyFAI-calib") + intescript = os.path.join(self.pyFAIdir, "pyFAI-waxs") + else: + caliscript = os.path.join(self.pyFAIdir, "pyFAI-calib") + intescript = os.path.join(self.pyFAIdir, "pyFAI-waxs") + self.caliscript = caliscript + self.intescript = intescript + return + + def callPyFAICalibration(self, image=None, dspacefile=None): + if image is None: + image = self.image + else: + self.image = image + if dspacefile is None: + dspacefile = self.dspacefile + else: + self.dspacefile = dspacefile + + flag = False + if os.path.exists(image) and os.path.isfile(image): + if os.path.exists(dspacefile) and os.path.isfile(dspacefile): + flag = True + + if flag: + image = os.path.abspath(image) + dspacefile = os.path.abspath(dspacefile) + + # remove .npt and .azim + for f in [ + os.path.splitext(image)[0] + ".npt", + os.path.splitext(image)[0] + ".azim", + ]: + if os.path.exists(f): + os.remove(f) + + ps = [self.xpixelsize * 1000, self.ypixelsize * 1000] + + calicmd = [self.pythonbin, self.caliscript] + calicmd.extend(["-w", str(self.wavelength)]) + calicmd.extend([pyFAIcalib_opt_calibrant, str(dspacefile)]) + calicmd.extend(["-p", str(ps[0]) + "," + str(ps[1])]) + calicmd.extend([str(image)]) + + import subprocess + + try: + os.environ.pop("QT_API") + except KeyError: + pass + subprocess.call(calicmd) + + # integrate image + ponifile = os.path.splitext(str(image))[0] + ".poni" + intecmd = [ + self.pythonbin, + self.intescript, + "-p", + ponifile, + str(image), + ] + subprocess.call(intecmd) + self.parsePyFAIoutput(image) + print("Calibration finished!") + return + + def parsePyFAIoutput(self, image=None): + if image is None: + image = self.image + + filename = os.path.splitext(image)[0] + ".xy" + if os.path.exists(filename): + f = open(filename, "r") + lines = f.readlines() + f.close() + else: + raise ValueError("pyFAI results file does not exist.") + for line in lines: + if re.search("# Distance Sample-beamCenter", line): + distance = findFloat(line)[0] + elif re.search("# Center", line): + x, y = findFloat(line) + elif re.search("# Tilt", line): + tiltd, rotationd = findFloat(line) + + self.distance = distance + self.xbeamcenter = x # - 0.5 + self.ybeamcenter = y # - y - 0.5 + self.tiltd = tiltd + self.rotationd = rotationd # + 180 + self.srxconfig.flipvertical = False + self.srxconfig.fliphorizontal = False + return + + def selfCalibration(self, image=None): + # self.addfiles.selected[0].fullname + if image is None: + image = self.image + + if os.path.exists(image) and os.path.isfile(image): + for mode, showresults in zip( + ["x", "y", "x", "y"], [False, False, False, True] + ): + selfCalibrate( + self.srx, + image, + mode=mode, + cropedges=self.slice, + showresults=showresults, + xywidth=self.xywidth, + ) + return + + slice = Enum(["auto", "x", "y", "box", "full"]) + calibrationmode = Enum(["self", "calibrant"]) + + def calibration(self, image=None, dspacefile=None): + if self.calibrationmode == "calibrant": + self.callPyFAICalibration(image, dspacefile) + elif self.calibrationmode == "self": + self.selfCalibration(image) + else: + raise ValueError("calibration mode error") + return + + xywidth = Int(6) + qmincali = Float(0.5) + qmaxcali = Float(10.0) + + @on_trait_change( + "srxconfig.[xpixelsize, ypixelsize, distance," + " wavelength, xdimension, ydimension]" + ) + def _qmaxChanged(self): + tthmax, qmax = checkMax(self.srxconfig) + self.qmincali = min(1.25, qmax / 10) + self.qmaxcali = qmax / 2 + return + + inst1 = Str( + "Please install pyFAI and FabIO to use" + " the calibration function (refer to help)." + ) + inst2 = Str( + "(http://github.com/kif/pyFAI," + " https://forge.epn-campus.eu/projects/azimuthal/files)" + ) + main_View = View( + # Item('calibrationmode', style='custom', label='Calibration mode'), + Item("image", label="Image file"), + Group( + Item("inst1", style="readonly"), + Item("inst2", style="readonly"), + visible_when='missingpyFAI and calibrationmode=="calibrant"', + show_border=True, + show_labels=False, + ), + Group( + Item("dspacefile", label="D-space file"), + Item("pyFAIdir", label="pyFAI dir."), + show_border=True, + visible_when='calibrationmode=="calibrant"', + enabled_when="not missingpyFAI", + label="Please specify the d-space file and" + + " the location of pyFAI executable", + ), + HGroup( + Item( + "xpixelsize", + label="Pixel size x (mm)", + visible_when='configmode == "normal"', + ), + Item( + "xpixelsizetem", + label="Pixel size x (A^-1)", + visible_when='configmode == "TEM"', + ), + Item( + "ypixelsize", + label="Pixel size y (mm)", + visible_when='configmode == "normal"', + ), + Item( + "ypixelsizetem", + label="Pixel size y (A^-1)", + visible_when='configmode == "TEM"', + ), + visible_when='calibrationmode=="calibrant"', + enabled_when="not missingpyFAI", + show_border=True, + label="Please specify the size of pixel", + ), + HGroup( + Item("wavelength", label="Wavelength (A)"), + visible_when='calibrationmode=="calibrant"', + enabled_when="not missingpyFAI", + show_border=True, + label="Please specify the wavelength", + ), + HGroup( + Item( + "wavelength", + visible_when='integrationspace == "qspace"', + label="Wavelength(Angstrom)", + ), + Item( + "distance", + label="Distance(mm)", + visible_when='configmode == "normal"', + ), + Item( + "distance", + label="Camera Length(mm)", + visible_when='configmode == "TEM"', + ), + label="Please specify the wavelength and" + + " distance between sample and detector:", + show_border=True, + visible_when='calibrationmode=="self"', + ), + HGroup( + VGroup( + Item("xbeamcenter", label="x beamcenter (pixel)"), + Item("rotationd", label="Rotation (degree)"), + ), + VGroup( + Item("ybeamcenter", label="y beamcenter (pixel)"), + Item("tiltd", label="Tilt rotation (degree)"), + ), + show_border=True, + label="Plasee specify the initial value of following parameters:", + visible_when='calibrationmode=="self"', + ), + HGroup( + VGroup( + Item("xdimension", label="x dimension (pixel)"), + Item( + "xpixelsize", + label="Pixel size x (mm)", + visible_when='configmode == "normal"', + ), + Item( + "xpixelsizetem", + label="Pixel size x (A^-1)", + visible_when='configmode == "TEM"', + ), + ), + VGroup( + Item("ydimension", label="y dimension (pixel)"), + Item( + "ypixelsize", + label="Pixel size y (mm)", + visible_when='configmode == "normal"', + ), + Item( + "ypixelsizetem", + label="Pixel size y (A^-1)", + visible_when='configmode == "TEM"', + ), + ), + show_border=True, + label="Please specify the dimension of detector" + + " and size of pixel:", + visible_when='calibrationmode=="self"', + ), + HGroup( + VGroup( + Item("xywidth", label="(x,y) center searching range, +/-"), + Item("slice", label="Refining using slab along"), + ), + VGroup( + Item("qmincali", label="Qmin in calibration"), + Item("qmaxcali", label="Qmax in calibration"), + ), + show_border=True, + label="Others", + visible_when='calibrationmode=="self"', + ), + title="Calibration", + width=600, + height=450, + resizable=True, + buttons=[OKButton, CancelButton], + handler=CalibrationHandler(), + icon=ImageResource("icon.png"), + ) + + +def findFloat(line): + pattern = r"[-+]?(?:\d+\.\d*|\.\d+|\d+)" + return [float(x) for x in re.findall(pattern, line)] + + +if __name__ == "__main__": + srxconfig = SrXconfig() + cali = Calibration(srxconfig=srxconfig) + # cali.callPyFAICalibration('ceo2.tif', 'ceo2.d') + # cali.parsePyFAIoutput() + cali.configure_traits(view="main_View") diff --git a/dpx/srxplanargui/datacontainer.py b/src/diffpy/srxplanargui/datacontainer.py similarity index 55% rename from dpx/srxplanargui/datacontainer.py rename to src/diffpy/srxplanargui/datacontainer.py index d5476b9..e64af8f 100644 --- a/dpx/srxplanargui/datacontainer.py +++ b/src/diffpy/srxplanargui/datacontainer.py @@ -1,8 +1,8 @@ #!/usr/bin/env python ############################################################################## # -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University +# diffpy.srxplanargui by Simon J. L. Billinge group +# (c) 2012-2025 Trustees of the Columbia University # in the City of New York. All rights reserved. # # File coded by: Xiaohao Yang @@ -13,23 +13,21 @@ ############################################################################## import os -import numpy as np -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on +from traits.api import File, HasTraits, Property, property_depends_on -class DataContainer (HasTraits): + +class DataContainer(HasTraits): # The full path and file name of the file: fullname = File # The base file name of the source file: basename = Property # Str - @property_depends_on('fullname') - def _get_basename (self): + @property_depends_on("fullname") + def _get_basename(self): return os.path.basename(self.fullname) -if __name__ == '__main__': + +if __name__ == "__main__": test = DataContainer() diff --git a/dpx/srxplanargui/gitarchive.cfg b/src/diffpy/srxplanargui/gitarchive.cfg similarity index 76% rename from dpx/srxplanargui/gitarchive.cfg rename to src/diffpy/srxplanargui/gitarchive.cfg index 2f8fb70..43b349f 100644 --- a/dpx/srxplanargui/gitarchive.cfg +++ b/src/diffpy/srxplanargui/gitarchive.cfg @@ -2,4 +2,4 @@ commit = $Format:%H$ date = $Format:%ai$ timestamp = $Format:%at$ -refnames = $Format:%D$ \ No newline at end of file +refnames = $Format:%D$ diff --git a/src/diffpy/srxplanargui/help.py b/src/diffpy/srxplanargui/help.py new file mode 100644 index 0000000..076a0c0 --- /dev/null +++ b/src/diffpy/srxplanargui/help.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srxplanargui by Simon J. L. Billinge group +# (c) 2012-2025 Trustees of the Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Xiaohao Yang +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE.txt for license information. +# +############################################################################## +"""Provide help for SrXgui.""" + +import sys + +from pyface.api import ImageResource +from traits.api import HasTraits, Int, Property, property_depends_on +from traits.etsconfig.api import ETSConfig +from traitsui.api import Action, Handler, ImageEditor, Item, View +from traitsui.menu import OKButton + + +class HelpHandler(Handler): + + def _qsnext(self, info): + info.object.qsindex += 1 + return + + def _qsprevious(self, info): + info.object.qsindex -= 1 + return + + def _cpReftext(self, info): + info.object.cpReftext() + return + + +class SrXguiHelp(HasTraits): + + if sys.platform.startswith("win"): + if ETSConfig.toolkit == "qt": + hheight = 510 + hwidth = 960 + else: + hheight = 556 + hwidth = 980 + else: + hheight = 524 + hwidth = 964 + + ####################### + # quick start + ####################### + + imgs = [ImageResource("%02d.png" % i) for i in range(1, 23)] + + qslen = Int(len(imgs) - 1) + + next_action = Action( + name="Next", + action="_qsnext", + enabled_when="object.qsindex 2: + mask = points_in_polygon(self.pts, points) + mask = mask.reshape(self.staticmask.shape) + if remove: + self.staticmask = np.logical_and( + self.staticmask, np.logical_not(mask) + ) + else: + self.staticmask = np.logical_or(self.staticmask, mask) + self.refreshMask(staticmask=self.staticmask) + return + + def addPointMask(self, ndx, remove=None): + """Param ndx -- (x,y) float.""" + x, y = ndx + r = self.pts - np.array((x, y)) + r = np.sum(r**2, axis=1) + mask = r < ((self.pointmaskradius + 1) ** 2) + mask = mask.reshape(self.staticmask.shape) + if remove: + self.staticmask = np.logical_and( + self.staticmask, np.logical_not(mask) + ) + else: + self.staticmask = np.logical_or(self.staticmask, mask) + self.refreshMask(self.staticmask) + return + + def clearMask(self): + self.staticmask = self.staticmask * 0 + self.refreshMask(self.staticmask) + return + + def invertMask(self): + self.staticmask = np.logical_not(self.staticmask) + self.refreshMask(self.staticmask) + return + + def refreshMask(self, staticmask=None, draw=True): + self.staticmask = ( + self.srx.mask.staticMask() if staticmask is None else staticmask + ) + self.dynamicmask = self.srx.mask.dynamicMask( + self.imageorg, dymask=self.staticmask + ) + self.dynamicmask = np.logical_or( + self.dynamicmask, self.srx.mask.edgeMask() + ) + self.mask = np.logical_or(self.staticmask, self.dynamicmask) + if draw: + self.refreshImage() + return + + maskaboveint = Int(10e10) + maskbelowint = Int(1) + + def maskabove(self): + mask = self.imageorg > self.maskaboveint + self.staticmask = np.logical_or(self.staticmask, mask) + self.refreshMask(self.staticmask) + return + + def maskbelow(self): + mask = self.imageorg < self.maskbelowint + self.staticmask = np.logical_or(self.staticmask, mask) + self.refreshMask(self.staticmask) + return + + def _appendTools(self): + """Append xy position, zoom, pan tools to plot. + + :param plot: the plot object to append on + """ + plot = self.plot + img_plot = self.img_plot + + # tools + self.pan = PanTool(plot) + self.zoom = ZoomTool(component=plot, tool_mode="box", always_on=False) + self.lstool = MasklineDrawer(self.plot, imageplot=self) + self.xyseltool = MaskPointInspector(img_plot, imageplot=self) + # self.lstool.imageplot = self + + img_plot.tools.append(self.xyseltool) + overlay = ImageInspectorOverlay( + component=img_plot, + image_inspector=self.xyseltool, + bgcolor="white", + border_visible=True, + ) + img_plot.overlays.append(overlay) + + plot.tools.append(self.pan) + plot.overlays.append(self.zoom) + return + + def _enableMaskEditing(self): + """Enable mask tool and disable pan tool.""" + self.maskediting = True + for i in range(self.plot.tools.count(self.pan)): + self.plot.tools.remove(self.pan) + self.plot.overlays.append(self.lstool) + self.titlebak = self.plot.title + self.plot.title = ( + "Click: add a vertex; " + "+Click: remove a vertex; \n " + ": finish the selection" + ) + return + + def _disableMaskEditing(self): + """Disable mask tool and enable pan tool.""" + self.plot.overlays.remove(self.lstool) + self.plot.tools.append(self.pan) + self.plot.title = self.titlebak + self.maskediting = False + return + + def _enablePointMaskEditing(self): + self.maskediting = True + for i in range(self.plot.tools.count(self.pan)): + self.plot.tools.remove(self.pan) + self.titlebak = self.plot.title + self.plot.title = ( + "Click: add a point; : exit the point selection" + ) + return + + def _disablePointMaskEditing(self): + self.plot.tools.append(self.pan) + self.plot.title = self.titlebak + self.maskediting = False + return + + def refreshImage(self, mask=None, draw=True): + """Recalculate the image using self.mask or mask and refresh + display.""" + mask = self.mask if mask is None else mask + image = self.applyScale() + image = image * np.logical_not(mask) + image.max() * mask + self.pd.set_data("imagedata", image) + if draw: + self.plot.invalidate_draw() + return + + scalemode = Enum("linear", ["linear", "log"], desc="Scale the image") + scalepowder = Float(0.5, desc="gamma value to control the contrast") + + def applyScale(self, image=None): + """Apply the scale to increase/decrease contrast.""" + if self.scalemode == "linear": + if image is None: + image = self.imageorg + intmax = self.imageorgmax + else: + image = image + intmax = image.max() + elif self.scalemode == "log": + if image is None: + image = self.imageorglog + intmax = self.imageorglogmax + else: + image = np.log(image) + image[image < 0] = 0 + intmax = image.max() + else: + image = image + intmax = image.max() + + image = intmax * ((image / intmax) ** self.scalepowder) + return image + + splb = Float(0.0) + spub = Float(1.0) + + def _scalemode_changed(self): + if self.scalemode == "linear": + self.scalepowder = 0.5 + self.splb = 0.0 + self.spub = 1.0 + elif self.scalemode == "log": + self.scalepowder = 1.0 + self.splb = 0.0 + self.spub = 4.0 + self.refreshImage() + return + + def _scalepowder_changed(self, old, new): + if np.round(old, 1) != np.round(new, 1): + self.refreshImage() + return + + def _add_notifications(self): + self.on_trait_change(self.refreshMaskFile, "srxconfig.maskfile") + return + + def _del_notifications(self): + self.on_trait_change( + self.refreshMaskFile, "srxconfig.maskfile", remove=True + ) + return + + addpolygon_bb = Button("Add polygon mask") + removepolygon_bb = Button("Remove polygon mask") + addpoint_bb = Button("Add point mask") + clearmask_bb = Button("Clear mask", desc="Clear mask") + invertmask_bb = Button("Invert mask", desc="Invert mask") + advancedmask_bb = Button( + "Dynamic mask", + desc="The dynamic mask is dynamically generated for each image.", + ) + maskabove_bb = Button("Mask intensity above") + maskbelow_bb = Button("Mask intensity below") + loadmaskfile_bb = Button("Load mask") + savemaskfile_bb = Button("Save mask") + + def _addpolygon_bb_fired(self): + self.removepolygonmask = False + self._enableMaskEditing() + return + + def _removepolygon_bb_fired(self): + self.removepolygonmask = True + self._enableMaskEditing() + return + + def _addpoint_bb_fired(self): + self._enablePointMaskEditing() + self.xyseltool.enablemaskselect = True + return + + def _clearmask_bb_fired(self): + self.clearMask() + return + + def _invertmask_bb_fired(self): + self.invertMask() + return + + def _advancedmask_bb_fired(self): + self.edit_traits("advancedmask_view") + # if not hasattr(self, 'advhint'): + # self.advhint = AdvHint() + # self.advhint.edit_traits('advhint_view') + return + + def _maskabove_bb_fired(self): + self.maskabove() + return + + def _maskbelow_bb_fired(self): + self.maskbelow() + return + + def _loadmaskfile_bb_fired(self): + self.edit_traits("loadmaskfile_view") + return + + def _savemaskfile_bb_fired(self): + if self.maskfile == "": + self.maskfile = os.path.join( + self.srxconfig.savedirectory, "mask.npy" + ) + else: + self.maskfile = os.path.splitext(self.maskfile)[0] + ".npy" + self.edit_traits("savemaskfile_view") + return + + def __init__(self, **kwargs): + """Init the object and create notification.""" + HasTraits.__init__(self, **kwargs) + self.createPlot() + # self._loadMaskPar() + self._add_notifications() + return + + hinttext = Str( + "Zoom: ; Reset: ;" + " Pan: ; Toggle XY coordinates:

" + ) + traits_view = View( + Group( + Item( + "plot", + editor=ComponentEditor(size=(550, 550)), + show_label=False, + ), + HGroup( + spring, + Item("scalemode", label="Scale mode"), + Item( + "scalepowder", + label="Gamma", + editor=RangeEditor( + auto_set=False, + low_name="splb", + high_name="spub", + format="%.1f", + ), + ), + spring, + ), + VGroup( + HGroup( + Item("addpolygon_bb", enabled_when="not maskediting"), + Item("removepolygon_bb", enabled_when="not maskediting"), + spring, + Item("maskabove_bb", enabled_when="not maskediting"), + Item("maskaboveint", enabled_when="not maskediting"), + show_labels=False, + ), + HGroup( + Item("addpoint_bb", enabled_when="not maskediting"), + Item("pointmaskradius", label="Size:", show_label=True), + spring, + Item("maskbelow_bb", enabled_when="not maskediting"), + Item("maskbelowint", enabled_when="not maskediting"), + show_labels=False, + ), + HGroup( + Item("clearmask_bb", enabled_when="not maskediting"), + Item("invertmask_bb", enabled_when="not maskediting"), + Item("advancedmask_bb", enabled_when="not maskediting"), + spring, + Item("loadmaskfile_bb"), + Item("savemaskfile_bb"), + show_labels=False, + ), + show_labels=False, + show_border=True, + label="Mask", + ), + orientation="vertical", + ), + resizable=True, + title="2D image", + statusbar=["hinttext"], + width=600, + height=700, + icon=ImageResource("icon.png"), + ) + + savemaskfile_action = Action(name="OK ", action="_save") + loadmaskfile_action = Action(name="OK ", action="_load") + applydymask_action = Action(name="Apply ", action="_applyDymask") + + savemaskfile_view = View( + Item("maskfile"), + buttons=[savemaskfile_action, CancelButton], + title="Save mask file", + width=500, + resizable=True, + handler=SaveLoadMaskHandler(), + icon=ImageResource("icon.png"), + ) + + loadmaskfile_view = View( + Item("maskfile"), + buttons=[loadmaskfile_action, CancelButton], + title="Load mask file", + width=500, + resizable=True, + handler=SaveLoadMaskHandler(), + icon=ImageResource("icon.png"), + ) + + advancedmask_view = View( + Group( + VGroup( + Item( + "cropedges", + label="Mask edges", + editor=ArrayEditor(width=-50), + ), + label="Edge mask", + show_border=True, + ), + VGroup( + Item("darkpixelmask", label="Enable"), + Item( + "darkpixelr", + label="Threshold", + enabled_when="darkpixelmask", + ), + label="Dark pixel mask", + show_border=True, + ), + VGroup( + Item("brightpixelmask", label="Enable"), + Item( + "brightpixelsize", + label="Testing size", + enabled_when="brightpixelmask", + ), + Item( + "brightpixelr", + label="Threshold", + enabled_when="brightpixelmask", + ), + label="Bright pixel mask", + show_border=True, + ), + VGroup( + Item("avgmask", label="Enable"), + Item("avgmaskhigh", label="High", enabled_when="avgmask"), + Item("avgmasklow", label="Low", enabled_when="avgmask"), + label="Average mask", + show_border=True, + ), + ), + title="Dynamic mask", + width=320, + handler=AdvMaskHandler(), + resizable=True, + buttons=[applydymask_action, OKButton, CancelButton], + icon=ImageResource("icon.png"), + ) + + +class MasklineDrawer(LineSegmentTool): + """""" + + imageplot = Any + + def _finalize_selection(self): + self.imageplot._disableMaskEditing() + self.imageplot.mergeMask(self.points) + return + + def __init__(self, *args, **kwargs): + LineSegmentTool.__init__(self, *args, **kwargs) + self.line.line_color = "red" + self.line.vertex_color = "white" + return + + +class MaskPointInspector(ImageInspectorTool): + exitmask_key = KeySpec("Enter") + imageplot = Any + enablemaskselect = Bool(False) + + def normal_key_pressed(self, event): + if self.inspector_key.match(event): + self.visible = not self.visible + event.handled = True + if self.exitmask_key.match(event): + self.enablemaskselect = False + self.imageplot._disablePointMaskEditing() + return + + def normal_left_down(self, event): + if self.enablemaskselect: + ndx = self.component.map_index((event.x, event.y)) + self.imageplot.addPointMask(ndx) + return + + +class AdvHint(HasTraits): + advhinttext = str( + """Notes: Advanced Masks are generated during the integration + and refreshed for each image. + You can preview the masks here or apply the current masks + to the static mask permanently. + +Edge mask: mask the pixels around the image edge. +(left, right, top, bottom) + +Dark pixel mask: mask the pixels too dark +compared to their local environment + +Bright pixel mask: mask the pixels too bright +compared to their local environment +Average mask: Mask the pixels too bright or too dark +compared to the average intensity at the similar diffraction angle. +Correct calibration information is required.""" + ) + + advhint_view = View( + Group( + Item("advhinttext", style="readonly", show_label=False), + show_border=True, + ), + title="Advanced mask hints", + width=640, + resizable=False, + buttons=[OKButton], + icon=ImageResource("icon.png"), + ) diff --git a/src/diffpy/srxplanargui/images/01.png b/src/diffpy/srxplanargui/images/01.png new file mode 100644 index 0000000..38a360b Binary files /dev/null and b/src/diffpy/srxplanargui/images/01.png differ diff --git a/src/diffpy/srxplanargui/images/02.png b/src/diffpy/srxplanargui/images/02.png new file mode 100644 index 0000000..8c39885 Binary files /dev/null and b/src/diffpy/srxplanargui/images/02.png differ diff --git a/src/diffpy/srxplanargui/images/03.png b/src/diffpy/srxplanargui/images/03.png new file mode 100644 index 0000000..d23cc9d Binary files /dev/null and b/src/diffpy/srxplanargui/images/03.png differ diff --git a/src/diffpy/srxplanargui/images/04.png b/src/diffpy/srxplanargui/images/04.png new file mode 100644 index 0000000..0d84cbc Binary files /dev/null and b/src/diffpy/srxplanargui/images/04.png differ diff --git a/src/diffpy/srxplanargui/images/05.png b/src/diffpy/srxplanargui/images/05.png new file mode 100644 index 0000000..0874af2 Binary files /dev/null and b/src/diffpy/srxplanargui/images/05.png differ diff --git a/src/diffpy/srxplanargui/images/06.png b/src/diffpy/srxplanargui/images/06.png new file mode 100644 index 0000000..b465f08 Binary files /dev/null and b/src/diffpy/srxplanargui/images/06.png differ diff --git a/src/diffpy/srxplanargui/images/07.png b/src/diffpy/srxplanargui/images/07.png new file mode 100644 index 0000000..aafa077 Binary files /dev/null and b/src/diffpy/srxplanargui/images/07.png differ diff --git a/src/diffpy/srxplanargui/images/08.png b/src/diffpy/srxplanargui/images/08.png new file mode 100644 index 0000000..5f5363f Binary files /dev/null and b/src/diffpy/srxplanargui/images/08.png differ diff --git a/src/diffpy/srxplanargui/images/09.png b/src/diffpy/srxplanargui/images/09.png new file mode 100644 index 0000000..57954bc Binary files /dev/null and b/src/diffpy/srxplanargui/images/09.png differ diff --git a/src/diffpy/srxplanargui/images/10.png b/src/diffpy/srxplanargui/images/10.png new file mode 100644 index 0000000..c51c8bc Binary files /dev/null and b/src/diffpy/srxplanargui/images/10.png differ diff --git a/src/diffpy/srxplanargui/images/11.png b/src/diffpy/srxplanargui/images/11.png new file mode 100644 index 0000000..94742b8 Binary files /dev/null and b/src/diffpy/srxplanargui/images/11.png differ diff --git a/src/diffpy/srxplanargui/images/12.png b/src/diffpy/srxplanargui/images/12.png new file mode 100644 index 0000000..2b5ab13 Binary files /dev/null and b/src/diffpy/srxplanargui/images/12.png differ diff --git a/src/diffpy/srxplanargui/images/13.png b/src/diffpy/srxplanargui/images/13.png new file mode 100644 index 0000000..b578e7c Binary files /dev/null and b/src/diffpy/srxplanargui/images/13.png differ diff --git a/src/diffpy/srxplanargui/images/14.png b/src/diffpy/srxplanargui/images/14.png new file mode 100644 index 0000000..c518ff9 Binary files /dev/null and b/src/diffpy/srxplanargui/images/14.png differ diff --git a/src/diffpy/srxplanargui/images/15.png b/src/diffpy/srxplanargui/images/15.png new file mode 100644 index 0000000..7a13b9a Binary files /dev/null and b/src/diffpy/srxplanargui/images/15.png differ diff --git a/src/diffpy/srxplanargui/images/16.png b/src/diffpy/srxplanargui/images/16.png new file mode 100644 index 0000000..c8cf22e Binary files /dev/null and b/src/diffpy/srxplanargui/images/16.png differ diff --git a/src/diffpy/srxplanargui/images/17.png b/src/diffpy/srxplanargui/images/17.png new file mode 100644 index 0000000..8f8a47a Binary files /dev/null and b/src/diffpy/srxplanargui/images/17.png differ diff --git a/src/diffpy/srxplanargui/images/18.png b/src/diffpy/srxplanargui/images/18.png new file mode 100644 index 0000000..1da1629 Binary files /dev/null and b/src/diffpy/srxplanargui/images/18.png differ diff --git a/src/diffpy/srxplanargui/images/19.png b/src/diffpy/srxplanargui/images/19.png new file mode 100644 index 0000000..24fc4c4 Binary files /dev/null and b/src/diffpy/srxplanargui/images/19.png differ diff --git a/src/diffpy/srxplanargui/images/20.png b/src/diffpy/srxplanargui/images/20.png new file mode 100644 index 0000000..97409e4 Binary files /dev/null and b/src/diffpy/srxplanargui/images/20.png differ diff --git a/dpx/srxplanargui/images/23.png b/src/diffpy/srxplanargui/images/21.png similarity index 100% rename from dpx/srxplanargui/images/23.png rename to src/diffpy/srxplanargui/images/21.png diff --git a/src/diffpy/srxplanargui/images/22.png b/src/diffpy/srxplanargui/images/22.png new file mode 100644 index 0000000..78704c1 Binary files /dev/null and b/src/diffpy/srxplanargui/images/22.png differ diff --git a/dpx/srxplanargui/images/gitarchive.cfg b/src/diffpy/srxplanargui/images/gitarchive.cfg similarity index 76% rename from dpx/srxplanargui/images/gitarchive.cfg rename to src/diffpy/srxplanargui/images/gitarchive.cfg index 2f8fb70..43b349f 100644 --- a/dpx/srxplanargui/images/gitarchive.cfg +++ b/src/diffpy/srxplanargui/images/gitarchive.cfg @@ -2,4 +2,4 @@ commit = $Format:%H$ date = $Format:%ai$ timestamp = $Format:%at$ -refnames = $Format:%D$ \ No newline at end of file +refnames = $Format:%D$ diff --git a/dpx/srxplanargui/images/icon.ico b/src/diffpy/srxplanargui/images/icon.ico similarity index 100% rename from dpx/srxplanargui/images/icon.ico rename to src/diffpy/srxplanargui/images/icon.ico diff --git a/dpx/srxplanargui/images/icon.png b/src/diffpy/srxplanargui/images/icon.png similarity index 100% rename from dpx/srxplanargui/images/icon.png rename to src/diffpy/srxplanargui/images/icon.png diff --git a/src/diffpy/srxplanargui/live.py b/src/diffpy/srxplanargui/live.py new file mode 100644 index 0000000..9c05fc5 --- /dev/null +++ b/src/diffpy/srxplanargui/live.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srxplanargui by Simon J. L. Billinge group +# (c) 2012-2025 Trustees of the Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Xiaohao Yang +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE.txt for license information. +# +############################################################################## +"""Provide UI for srxplanar.""" + +import os +import sys + +from pyface.api import GUI, ImageResource, SplashScreen +from traits.api import Any, on_trait_change +from traits.etsconfig.api import ETSConfig +from traitsui.api import ( + Action, + Group, + HGroup, + InstanceEditor, + Item, + VGroup, + View, + spring, +) +from traitsui.menu import OKButton + +from diffpy.srxplanar.srxplanar import SrXplanar +from diffpy.srxplanargui.calibration import Calibration +from diffpy.srxplanargui.help import SrXguiHelp +from diffpy.srxplanargui.selectfiles import AddFiles +from diffpy.srxplanargui.srxconfig import SrXconfig +from diffpy.srxplanargui.srxgui import SrXgui, SrXguiHandler + +ETSConfig.toolkit = "qt" + + +class SrXguiLive(SrXgui): + + getxgui = Any + + def __init__(self, configfile=None, args=None, **kwargs): + + # init the object, createt the notifications + + self.splash = SplashScreen( + image=ImageResource("01.png"), show_log_messages=False + ) + self.splash.open() + + super(SrXgui, self).__init__(**kwargs) + configfile = self.detectConfigfile(configfile) + if not os.path.exists(configfile): + configfile = self.detectConfigfile("default") + self.configfile = configfile + + if not kwargs.has_key("srxconfig"): + self.srxconfig = SrXconfig( + filename=configfile, args=args, **kwargs + ) + + self.addfiles = AddFiles(srxconfig=self.srxconfig) + self.srx = SrXplanar(self.srxconfig) + self.addfiles.srx = self.srx + self.help = SrXguiHelp() + self.calibration = Calibration(srx=self.srx, srxconfig=self.srxconfig) + self.splash.close() + return + + @on_trait_change("srxconfig.savedirectory") + def _changedir(self): + newdir = self.srxconfig.savedirectory + if os.path.exists(newdir): + self.getxgui.getxconfig.inputdir = os.path.abspath(newdir) + self.getxgui.getxconfig.savedir = os.path.abspath(newdir) + else: + self.getxgui.getxconfig.inputdir = os.path.abspath(os.path.curdir) + self.getxgui.getxconfig.savedir = os.path.abspath(os.path.curdir) + return + + def processSelected(self, summation=False): + if self.addfiles.selected: + self.srx.updateConfig() + filelist = [f.fullname for f in self.addfiles.selected] + self.srx.prepareCalculation(filelist) + rvlist = self.srx.integrateFilelist(filelist, summation=summation) + newchifilelist = [rv["filename"] for rv in rvlist] + GUI.invoke_later(self.addNewImagesToGetXgui, newchifilelist) + return + + def addNewImagesToGetXgui(self, filelist): + """Add new images to getxgui, if images are already there, + refresh them. + + :param filelist: list of full path of new images + """ + self.addfiles.refreshdatalist = True + newdatacontainers = self.getxgui.selectfiles.addFiles(filelist) + self.getxgui.createNewPlot(newdatacontainers) + return + + helpbutton_action = Action(name="Help ", action="_helpView") + saveconfig_action = Action( + name="Save Config", + action="_saveconfigView", + enabled_when="not capturing", + ) + loadconfig_action = Action( + name="Load Config", + action="_loadconfigView", + enabled_when="not capturing", + ) + + traits_view = View( + HGroup( + Item( + "addfiles", + editor=InstanceEditor(view="traits_view"), + style="custom", + label="Files", + width=0.4, + ), + VGroup( + Group( + Item( + "srxconfig", + editor=InstanceEditor(view="main_view"), + style="custom", + label="Basic", + show_label=False, + ), + springy=True, + ), + HGroup( + spring, + Item("selfcalibratebb", enabled_when="not capturing"), + Item("integratbb", enabled_when="not capturing"), + spring, + show_labels=False, + ), + ), + layout="split", + springy=True, + dock="tab", + show_labels=False, + ), + resizable=True, + title="SrXgui", + width=700, + height=650, + kind="live", + icon=ImageResource("icon.png"), + handler=SrXguiHandler(), + buttons=[ + helpbutton_action, + saveconfig_action, + loadconfig_action, + OKButton, + ], + ) + + +def main(): + gui = SrXguiLive() + gui.configure_traits(view="traits_view") + return + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/dpx/srxplanargui/selectfiles.py b/src/diffpy/srxplanargui/selectfiles.py similarity index 53% rename from dpx/srxplanargui/selectfiles.py rename to src/diffpy/srxplanargui/selectfiles.py index bb63137..7b3d1e8 100644 --- a/dpx/srxplanargui/selectfiles.py +++ b/src/diffpy/srxplanargui/selectfiles.py @@ -1,7 +1,7 @@ #!/usr/bin/env python ############################################################################## # -# dpx.pdfgetxgui by Simon J. L. Billinge group +# diffpy.srxplanargui by Simon J. L. Billinge group # (c) 2012 Trustees of the Columbia University # in the City of New York. All rights reserved. # @@ -12,55 +12,67 @@ # ############################################################################## + import os -import sys -import fnmatch -import functools -import re -from collections import OrderedDict -from traits.etsconfig.api import ETSConfig - -from traitsui.qt4.table_editor import TableEditor as TableEditorBE -from traits.api import \ - Dict, List, Enum, Bool, File, Float, Int, Array, Str, Range, Directory, CFloat, CInt, \ - HasTraits, Property, Instance, Event, Button, Any, \ - on_trait_change, DelegatesTo, cached_property, property_depends_on - -from traitsui.api import \ - Item, Group, View, Handler, spring, Action, \ - HGroup, VGroup, Tabbed, \ - RangeEditor, CheckListEditor, TextEditor, EnumEditor, ButtonEditor, \ - ArrayEditor, TitleEditor, TableEditor, HistoryEditor -from traitsui.menu import ToolBar, OKButton, CancelButton, Menu, OKCancelButtons -from traitsui.table_column import ObjectColumn + from pyface.api import ImageResource +from traits.api import ( + Any, + Bool, + Button, + Directory, + Enum, + Event, + HasTraits, + Instance, + Property, + Str, + on_trait_change, + property_depends_on, +) +from traitsui.api import ( + Group, + Handler, + HGroup, + Item, + TableEditor, + TextEditor, + TitleEditor, + VGroup, + View, + spring, +) +from traitsui.editors.table_editor import TableEditor as TableEditorBE +from traitsui.menu import CancelButton, OKButton +from traitsui.table_column import ObjectColumn try: from diffpy.pdfgetx.functs import sortKeyNumericString -except: +except ImportError: from diffpy.pdfgete.functs import sortKeyNumericString -from dpx.srxplanargui.datacontainer import DataContainer -from dpx.srxplanargui.srxconfig import SrXconfig -from dpx.srxplanargui.imageplot import ImagePlot + from diffpy.srxplanar.loadimage import openImage, saveImage +from diffpy.srxplanargui.datacontainer import DataContainer +from diffpy.srxplanargui.imageplot import ImagePlot +from diffpy.srxplanargui.srxconfig import SrXconfig -#-- The Live Search table editor definition ------------------------------ +# -- The Live Search table editor definition ------------------------------ class AddFilesHandler(Handler): def object_selectallbb_changed(self, info): - ''' - select all files - ''' + """Select all files.""" # FIXME try: editor = [ - aa for aa in info.ui._editors if isinstance(aa, TableEditorBE)][0] - info.object.selected = [info.object.datafiles[i] - for i in editor.filtered_indices] + aa for aa in info.ui._editors if isinstance(aa, TableEditorBE) + ][0] + info.object.selected = [ + info.object.datafiles[i] for i in editor.filtered_indices + ] editor.refresh() - except: + except (AttributeError, IndexError): pass return @@ -81,16 +93,17 @@ class AddFiles(HasTraits): srxconfig = Instance(SrXconfig) - # The currenty inputdir directory being searched: + # The currently inputdir directory being searched: # inputdir = DelegatesTo('srxconfig') inputdir = Directory() # , entries = 10 ) def _inputdir_default(self): return self.srxconfig.opendirectory + # Should sub directories be included in the search: recursive = Bool(False) # The file types to include in the search: - filetype = Enum('tif', 'npy', 'all') + filetype = Enum("tif", "npy", "all") # The current search string: search = Str # Is the search case sensitive? @@ -105,28 +118,27 @@ def _inputdir_default(self): # Summary of current number of files: summary = Property # Str # some meta data - _filetypedict = {'tif': ['.tif', '.tiff', '.tif.bz2'], - 'npy': ['.npy'], - 'all': 'all', - } + _filetypedict = { + "tif": [".tif", ".tiff", ".tif.bz2"], + "npy": [".npy"], + "all": "all", + } - #-- Property Implementations --------------------------------------------- + # -- Property Implementations --------------------------------------------- - @property_depends_on('search, casesensitive') + @property_depends_on("search, casesensitive") def _get_filter(self): - '''get filename filter - ''' + """Get filename filter.""" return _createFileNameFilter(self.search, self.casesensitive) refreshdatalist = Event - @property_depends_on('inputdir, recursive, filetype, refreshdatalist') + @property_depends_on("inputdir, recursive, filetype, refreshdatalist") def _get_datafiles(self): - ''' - create a datacontainer list, all files under inputdir is filtered using filetype - ''' + """Create a datacontainer list, all files under inputdir is + filtered using filetype.""" inputdir = self.inputdir - if inputdir == '': + if inputdir == "": inputdir = os.getcwd() if not os.path.exists(inputdir): self.srxconfig.opendirectory = os.getcwd() @@ -137,46 +149,50 @@ def _get_datafiles(self): rv = [] for dirpath, dirnames, filenames in os.walk(inputdir): for filename in filenames: - if (os.path.splitext(filename)[1] in filetypes)or (filetypes == 'all'): + if (os.path.splitext(filename)[1] in filetypes) or ( + filetypes == "all" + ): rv.append(os.path.join(dirpath, filename)) else: - rv = [os.path.join(inputdir, filename) - for filename in os.listdir(inputdir) - if (os.path.splitext(filename)[1] in filetypes) or (filetypes == 'all')] + rv = [ + os.path.join(inputdir, filename) + for filename in os.listdir(inputdir) + if (os.path.splitext(filename)[1] in filetypes) + or (filetypes == "all") + ] rv.sort(key=sortKeyNumericString) rvlist = [DataContainer(fullname=fn) for fn in rv] return rvlist - @property_depends_on('datafiles, search, casesensitive, selected') + @property_depends_on("datafiles, search, casesensitive, selected") def _get_summary(self): - ''' - get summary of file - ''' + """Get summary of file.""" if self.selected and self.datafiles: - rv = '%d files selected in a total of %d files.' % ( - len(self.selected), len(self.datafiles)) + rv = "%d files selected in a total of %d files." % ( + len(self.selected), + len(self.datafiles), + ) else: - rv = '0 files selected in a total of 0 files.' + rv = "0 files selected in a total of 0 files." return rv - @on_trait_change('srxconfig.opendirectory') + @on_trait_change("srxconfig.opendirectory") def _changeInputdir(self): - ''' - change inputdir of getxconfig - ''' + """Change inputdir of getxconfig.""" self.inputdir = self.srxconfig.opendirectory return def _plotbb_fired(self): try: imagefile = self.selected[0].fullname - except: + except IndexError: imagefile = None - if imagefile != None: + if imagefile is not None: if os.path.exists(imagefile): imageplot = ImagePlot( - imagefile=imagefile, srx=self.srx, srxconfig=self.srxconfig) + imagefile=imagefile, srx=self.srx, srxconfig=self.srxconfig + ) # imageplot.createPlot() imageplot.edit_traits() return @@ -188,9 +204,10 @@ def _refreshbb_fired(self): sumname = Str def _sumbb_fired(self): - self.sumname = os.path.splitext( - self.selected[0].fullname)[0] + '_sum.tif' - self.edit_traits(view='saveimage_view') + self.sumname = ( + os.path.splitext(self.selected[0].fullname)[0] + "_sum.tif" + ) + self.edit_traits(view="saveimage_view") return def _sumImgs(self): @@ -206,67 +223,73 @@ def _sumImgs(self): saveimage_view = View( Group( - Item('sumname', springy=True, label='File name'), + Item("sumname", springy=True, label="File name"), ), buttons=[OKButton, CancelButton], - title='Save image', + title="Save image", width=500, # height = 400, resizable=True, handler=SaveImageHandler(), - icon=ImageResource('icon.png'), + icon=ImageResource("icon.png"), ) - #-- Traits UI Views ------------------------------------------------------ + # -- Traits UI Views ------------------------------------------------------ tableeditor = TableEditor( columns=[ - ObjectColumn(name='basename', - label='Name', - # width=0.70, - editable=False, - ), + ObjectColumn( + name="basename", + label="Name", + # width=0.70, + editable=False, + ), ], auto_size=True, # show_toolbar = True, deletable=True, # reorderable = True, edit_on_first_click=False, - filter_name='filter', - selection_mode='rows', - selected='selected', - dclick='dclick', - label_bg_color='(244, 243, 238)', - cell_bg_color='(234, 233, 228)', + filter_name="filter", + selection_mode="rows", + selected="selected", + dclick="dclick", + label_bg_color="(244, 243, 238)", + cell_bg_color="(234, 233, 228)", ) - selectallbb = Button('Select all') - refreshbb = Button('Refresh') - plotbb = Button('Mask') - sumbb = Button('Sum') + selectallbb = Button("Select all") + refreshbb = Button("Refresh") + plotbb = Button("Mask") + sumbb = Button("Sum") traits_view = View( VGroup( VGroup( HGroup( - Item('search', id='search', springy=True, - editor=TextEditor(auto_set=False)), + Item( + "search", + id="search", + springy=True, + editor=TextEditor(auto_set=False), + ), ), - HGroup(spring, - Item('selectallbb', show_label=False), - Item('refreshbb', show_label=False), - spring, - Item('filetype', label='Type'), - ), - Item('datafiles', id='datafiles', editor=tableeditor), - Item('summary', editor=TitleEditor()), - - HGroup(spring, - Item('plotbb', show_label=False), - Item('sumbb', show_label=False), - spring, - ), - dock='horizontal', - show_labels=False + HGroup( + spring, + Item("selectallbb", show_label=False), + Item("refreshbb", show_label=False), + spring, + Item("filetype", label="Type"), + ), + Item("datafiles", id="datafiles", editor=tableeditor), + Item("summary", editor=TitleEditor()), + HGroup( + spring, + Item("plotbb", show_label=False), + Item("sumbb", show_label=False), + spring, + ), + dock="horizontal", + show_labels=False, ), ), # title = 'Add files', @@ -278,31 +301,35 @@ def _sumImgs(self): def _createFileNameFilter(pattern, casesensitive): - '''Build function that returns True for matching files. + """Build function that returns True for matching files. pattern -- string pattern to be matched casesensitive -- flag for case-sensitive file matching Return callable object. - ''' + """ try: from diffpy.pdfgetx.multipattern import MultiPattern - except: + except ImportError: from diffpy.pdfgete.multipattern import MultiPattern # MultiPattern always matches for an empty pattern, thus there # is no need to handle empty search string in a special way. patterns = pattern.split() - if casesensitive: - mp = MultiPattern(patterns) - rv = lambda x: mp.match(x.basename) - else: + if not casesensitive: patterns = [p.lower() for p in patterns] - mp = MultiPattern(patterns) - rv = lambda x: mp.match(x.basename.lower()) + + mp = MultiPattern(patterns) + + def rv(x): + name = x.basename + if not casesensitive: + name = name.lower() + return mp.match(name) + return rv # Run the demo (if invoked from the command line): -if __name__ == '__main__': +if __name__ == "__main__": addfiles = AddFiles() addfiles.configure_traits() diff --git a/src/diffpy/srxplanargui/srxconfig.py b/src/diffpy/srxplanargui/srxconfig.py new file mode 100644 index 0000000..a83a517 --- /dev/null +++ b/src/diffpy/srxplanargui/srxconfig.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srxplanar by DANSE Diffraction group +# Simon J. L. Billinge +# (c) 2010-2025 Trustees of the Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Xiaohao Yang +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSENOTICE.txt for license information. +# +############################################################################## + + +import os + +import numpy as np +from pyface.api import ImageResource +from traits.api import Bool, Enum, Property, on_trait_change +from traits.etsconfig.api import ETSConfig +from traitsui.api import Group, Item, View + +from diffpy.srxconfutils.configtraits import ConfigBaseTraits +from diffpy.srxplanar.srxplanarconfig import ( + _description, + _epilog, + _optdatalist, + checkMax, +) + +ETSConfig.toolkit = "qt" + + +_optdatalist.append( + [ + "xpixelsizetem", + { + "sec": "Beamline", + "h": "detector pixel size in x axis, in A^-1", + "d": 0.02, + }, + ] +) +_optdatalist.append( + [ + "ypixelsizetem", + { + "sec": "Beamline", + "h": "detector pixel size in y axis, in A^-1", + "d": 0.02, + }, + ] +) + +for i in _optdatalist: + if i[0] == "polcorrectionenable": + i[1] = { + "sec": "Others", + "args": "n", + "config": "n", + "header": "n", + "s": "polarcorr", + "h": "enable polarization correction", + "n": "?", + "co": False, + "d": False, + } + elif i[0] == "polcorrectf": + i[1] = { + "sec": "Others", + "args": "n", + "config": "n", + "header": "n", + "s": "polarf", + "h": "polarization correction factor", + "d": 0.99, + } + elif i[0] == "xpixelsize": + i[1] = { + "sec": "Beamline", + "args": "n", + "config": "n", + "header": "n", + "s": "xp", + "h": "detector pixel size in x axis, in mm", + "d": 0.2, + } + elif i[0] == "ypixelsize": + i[1] = { + "sec": "Beamline", + "args": "n", + "config": "n", + "header": "n", + "s": "yp", + "h": "detector pixel size in y axis, in mm", + "d": 0.2, + } + + +class SrXconfig(ConfigBaseTraits): + """Config class, based on ConfigBase class in diffpy.confutils.""" + + # Text to display before the argument help + _description = _description + + # Text to display after the argument help + _epilog = _epilog + + _optdatalist = _optdatalist + + _defaultdata = {"configfile": [], "headertitle": "SrXgui configuration"} + + rotation = Property( + depends_on="rotationd", fget=lambda self: np.radians(self.rotationd) + ) + tilt = Property( + depends_on="tiltd", fget=lambda self: np.radians(self.tiltd) + ) + tthstep = Property( + depends_on="tthstepd", fget=lambda self: np.radians(self.tthstepd) + ) + tthmax = Property( + depends_on="tthmaxd", fget=lambda self: np.radians(self.tthmaxd) + ) + + tthorqmax = Property( + depends_on="integrationspace, tthmaxd, qmax", + fget=lambda self: ( + self.tthmax if self.integrationspace == "twotheta" else self.qmax + ), + ) + tthorqstep = Property( + depends_on="integrationspace, tthmaxd, qmax", + fget=lambda self: ( + self.tthstep if self.integrationspace == "twotheta" else self.qstep + ), + ) + + def _preUpdateSelf(self, **kwargs): + """Additional process called in self._updateSelf, this method is + called before self._copySelftoConfig(), i.e. before copy options + value to self.config (config file) + + check the tthmaxd and qmax, and set tthorqmax, tthorqstep + according to integration space + + :param kwargs: optional kwargs + """ + self.tthmaxd, self.qmax = checkMax(self) + """Addmask = [b for b in self.addmask if not (b in + ['brightpixel', 'darkpixel'])] if len(addmask) > 0: + + self.maskfile = addmask[0] + """ + return + + def _opendirectory_changed(self): + if os.path.exists(self.opendirectory): + self.savedirectory = os.path.abspath(self.opendirectory) + else: + self.opendirectory = os.path.abspath(os.curdir) + self.savedirectory = os.path.abspath(os.curdir) + return + + def _savedirectory_changed(self): + if not os.path.exists(self.savedirectory): + self.savedirectory = os.path.abspath(os.curdir) + return + + configmode = Enum(["TEM", "normal"]) + + @on_trait_change("distance, wavelength, xpixelsizetem, ypixelsizetem") + def _refreshPSsize(self, obj, name, new): + self.updateConfig( + xpixelsize=self.xpixelsizetem * self.wavelength * self.distance, + ypixelsize=self.ypixelsizetem * self.wavelength * self.distance, + ) + return + + directory_group = Group( + Item( + "opendirectory", label="Input dir.", help="directory of 2D images" + ), + Item( + "savedirectory", + label="Output dir.", + help="directory of saved files", + ), + show_border=True, + label="Files", + ) + mask_group = Group( + Item("maskfile", label="Mask file"), + show_border=True, + label="Masks", + ) + + geometry_visible = Bool(False) + geometry_group = Group( + Item("integrationspace", label="Integration grid"), + Item( + "wavelength", + visible_when='integrationspace == "qspace"', + label="Wavelength", + ), + Item("xbeamcenter", label="X beamcenter"), + Item("ybeamcenter", label="Y beamcenter"), + Item( + "distance", + label="Camera length", + visible_when='configmode == "TEM"', + ), + Item( + "distance", label="Distance", visible_when='configmode == "normal"' + ), + Item("rotationd", label="Rotation"), + Item("tiltd", label="Tilt rotation"), + Item( + "tthstepd", + label="Integration step", + visible_when='integrationspace == "twotheta"', + ), + Item( + "qstep", + label="Integration step", + visible_when='integrationspace == "qspace"', + ), + show_border=True, + # label='Geometry parameters', + visible_when="geometry_visible", + ) + + correction_visible = Bool(False) + correction_group = Group( + Item("uncertaintyenable", label="Uncertainty"), + Item("sacorrectionenable", label="solid angle corr."), + # Item('polcorrectionenable', label='polarization corr.'), + # Item('polcorrectf', label='polarization factor'), + # Item('brightpixelmask', label='Bright pixel mask'), + # Item('darkpixelmask', label='Dark pixel mask'), + # Item('avgmask', label='Average mask'), + # Item('cropedges', label='Crop edges', editor=ArrayEditor(width=-50)), + show_border=True, + # label='Corrections' + visible_when="correction_visible", + ) + + detector_visible = Bool(False) + detector_group = ( + Group( + Item("fliphorizontal", label="Flip horizontally"), + Item("flipvertical", label="Flip vertically"), + Item("xdimension", label="x dimension"), + Item("ydimension", label="y dimension"), + Item( + "xpixelsizetem", + label="x pixel size (A^-1)", + tooltip="x pixel size, in A^-1", + visible_when='configmode == "TEM"', + ), + Item( + "ypixelsizetem", + label="y pixel size (A^-1)", + tooltip="y pixel size, in A^-1", + visible_when='configmode == "TEM"', + ), + show_border=True, + # label='Detector parameters' + visible_when="detector_visible", + ), + ) + + main_view = View( + Group( + directory_group, + mask_group, + Group( + # Item('configmode'), + Group( + Item("geometry_visible", label="Geometry parameters"), + geometry_group, + ), + Group( + Item("correction_visible", label="Corrections"), + correction_group, + ), + Group( + Item("detector_visible", label="Detector parameters"), + detector_group, + ), + # label = 'Basic' + show_border=True, + ), + ), + resizable=True, + scrollable=True, + # handler = handler, + icon=ImageResource("icon.png"), + ) + + +SrXconfig.initConfigClass() + +if __name__ == "__main__": + a = SrXconfig() + # a.updateConfig() + a.configure_traits(view="main_view") diff --git a/src/diffpy/srxplanargui/srxgui.py b/src/diffpy/srxplanargui/srxgui.py new file mode 100644 index 0000000..76ecaa0 --- /dev/null +++ b/src/diffpy/srxplanargui/srxgui.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python +############################################################################## +# +# diffpy.srxplanargui by Simon J. L. Billinge group +# (c) 2012-2025 Trustees of the Columbia University +# in the City of New York. All rights reserved. +# +# File coded by: Xiaohao Yang +# +# See AUTHORS.txt for a list of people who contributed. +# See LICENSE.txt for license information. +# +############################################################################## +"""Provide UI for srxplanar.""" + +import os +import sys + +from pyface.api import ImageResource +from traits.api import Any, Button, File, HasTraits, Instance +from traits.etsconfig.api import ETSConfig +from traitsui.api import ( + Action, + Group, + Handler, + HGroup, + InstanceEditor, + Item, + VGroup, + View, + spring, +) +from traitsui.menu import CancelButton, OKButton + +from diffpy.srxplanar.srxplanar import SrXplanar +from diffpy.srxplanargui.calibration import Calibration +from diffpy.srxplanargui.help import SrXguiHelp +from diffpy.srxplanargui.selectfiles import AddFiles +from diffpy.srxplanargui.srxconfig import SrXconfig + +ETSConfig.toolkit = "qt" + + +class SrXguiHandler(Handler): + + def closed(self, info, is_ok): + """Notify main gui to delete current plot in plots list.""" + configfile = info.object.detectConfigfile("default") + info.object.saveConfig(configfile) + return True + + def _saveconfigView(self, info): + info.object._saveconfigView() + return + + def _loadconfigView(self, info): + info.object._loadconfigView() + return + + def _helpView(self, info): + info.object._helpbb_changed() + return + + +class SaveHandler(Handler): + + def closed(self, info, is_ok): + if is_ok: + info.object.saveConfig(info.object.configfile) + return True + + +class LoadHandler(Handler): + def closed(self, info, is_ok): + if is_ok: + info.object.loadConfig(info.object.configfile) + return + + +class SrXgui(HasTraits): + + addfiles = Instance(AddFiles) + srxconfig = Instance(SrXconfig) + help = Instance(SrXguiHelp) + splash = Any + calibration = Instance(Calibration) + + def __init__(self, configfile=None, args=None, **kwargs): + """Init the object, createt the notifications.""" + super(SrXgui, self).__init__(**kwargs) + configfile = self.detectConfigfile(configfile) + if not os.path.exists(configfile): + configfile = self.detectConfigfile("default") + self.configfile = configfile + + if not kwargs.has_key("srxconfig"): + self.srxconfig = SrXconfig( + filename=configfile, args=args, **kwargs + ) + + self.addfiles = AddFiles(srxconfig=self.srxconfig) + self.srx = SrXplanar(self.srxconfig) + self.addfiles.srx = self.srx + self.help = SrXguiHelp() + self.calibration = Calibration(srx=self.srx, srxconfig=self.srxconfig) + + # self.loadConfig(configfile) + self.splash.close() + return + + def saveConfig(self, filename=None): + """Save config.""" + if filename == "default": + filename = self.detectConfigfile(filename) + self.srxconfig.writeConfig(filename, mode="full") + self.configfile = filename + return + + def loadConfig(self, filename=None): + """Load config.""" + configfile = self.detectConfigfile(filename) + if os.path.exists(configfile): + self.srxconfig.updateConfig(filename=configfile) + self.configfile = configfile + return + + def processSelected(self, summation=False): + if self.addfiles.selected: + self.srx.updateConfig() + filelist = [f.fullname for f in self.addfiles.selected] + self.srx.prepareCalculation(filelist) + self.srx.integrateFilelist(filelist, summation=summation) + return + + def detectConfigfile(self, filename): + """Current directory > home directory, if none, then return the + curdir+filename if 'default', then return home+filename.""" + if filename is None: + configfile = os.path.join(os.path.curdir, "srxconfig.cfg") + elif filename == "default": + configfile = os.path.join(os.path.expanduser("~"), "srxconfig.cfg") + else: + if os.path.abspath(filename): + if os.path.exists(filename): + configfile = filename + else: + filename = os.path.split(filename)[1] + configfile = os.path.join(os.path.curdir, filename) + else: + configfile = os.path.join(os.path.curdir, filename) + return configfile + + ########################################################### + def _saveconfigView(self): + self.edit_traits(view="saveconfig_view") + return + + def _loadconfigView(self): + self.edit_traits(view="loadconfig_view") + return + + configfile = File() + helpbutton_action = Action(name="Help ", action="_helpView") + saveconfig_action = Action(name="Save Config", action="_saveconfigView") + loadconfig_action = Action(name="Load Config", action="_loadconfigView") + + saveconfig_view = View( + Item("configfile"), + resizable=True, + title="Save config", + width=500, + buttons=[OKButton, CancelButton], + handler=SaveHandler(), + icon=ImageResource("icon.png"), + ) + loadconfig_view = View( + Item("configfile"), + resizable=True, + title="Load config", + width=500, + buttons=[OKButton, CancelButton], + handler=LoadHandler(), + icon=ImageResource("icon.png"), + ) + ############################################################# + + def _integratbb_changed(self): + self.processSelected(False) + return + + def _integratessbb_changed(self): + self.processSelected(True) + return + + def _helpbb_changed(self): + self.help.edit_traits(view="quickstart_view") + return + + def _selfcalibratebb_changed(self): + image = None + if self.addfiles.selected is not None: + if len(self.addfiles.selected) == 1: + image = self.addfiles.selected[0].fullname + + if image is not None: + self.calibration.image = image + self.calibration.edit_traits(view="main_View") + return + + integratbb = Button("Integrate") + integratessbb = Button("Sum and Integrate") + selfcalibratebb = Button("Calibrate") + helpbb = Button("Help") + + traits_view = View( + HGroup( + Item( + "addfiles", + editor=InstanceEditor(view="traits_view"), + style="custom", + label="Files", + width=0.4, + ), + VGroup( + Group( + Item( + "srxconfig", + editor=InstanceEditor(view="main_view"), + style="custom", + label="Basic", + show_label=False, + ), + springy=True, + ), + HGroup( + spring, + Item("selfcalibratebb"), + Item("integratbb"), + spring, + show_labels=False, + ), + ), + layout="split", + springy=True, + dock="tab", + show_labels=False, + ), + resizable=True, + title="SrXgui", + width=700, + height=650, + kind="live", + buttons=[ + helpbutton_action, + saveconfig_action, + loadconfig_action, + OKButton, + ], + icon=ImageResource("icon.png"), + handler=SrXguiHandler(), + ) + + +if __name__ == "__main__": + sys.exit() diff --git a/dpx/srxplanargui/srxguiapp.py b/src/diffpy/srxplanargui/srxguiapp.py similarity index 55% rename from dpx/srxplanargui/srxguiapp.py rename to src/diffpy/srxplanargui/srxguiapp.py index ae7981f..c9c2fe5 100644 --- a/dpx/srxplanargui/srxguiapp.py +++ b/src/diffpy/srxplanargui/srxguiapp.py @@ -1,8 +1,8 @@ #!/usr/bin/env python ############################################################################## # -# dpx.pdfgetxgui by Simon J. L. Billinge group -# (c) 2012 Trustees of the Columbia University +# diffpy.srxplanargui by Simon J. L. Billinge group +# (c) 2012-2025 Trustees of the Columbia University # in the City of New York. All rights reserved. # # File coded by: Xiaohao Yang @@ -11,42 +11,46 @@ # See LICENSE.txt for license information. # ############################################################################## +"""Provide UI for srxplanar.""" -'''provide UI for srxplanar -''' - -import numpy as np +import logging import os import sys - import warnings + +from pyface.api import ImageResource, SplashScreen +from traits.etsconfig.api import ETSConfig + +from diffpy.srxplanargui.srxgui import SrXgui + warnings.filterwarnings("ignore") -import logging -logging.disable('CRITICAL') + + +logging.disable("CRITICAL") # break if help passed to the args sysargv = sys.argv[1:] -if ('--help' in sysargv) or('-h' in sysargv): - from dpx.srxplanargui.srxconfig import SrXconfig +if ("--help" in sysargv) or ("-h" in sysargv): + from diffpy.srxplanargui.srxconfig import SrXconfig + SrXconfig(args=sysargv) -from traits.etsconfig.api import ETSConfig -os.environ['QT_API'] = 'pyside' -ETSConfig.toolkit = 'qt4' -from pyface.qt import QtGui, QtCore -from pyface.api import ImageResource, SplashScreen +os.environ["QT_API"] = "pyside" +ETSConfig.toolkit = "qt" + + # open splash screen -splash = SplashScreen(image=ImageResource('01.png'), show_log_messages=False) -if not any([aa == '-h' or aa == '--help' for aa in sysargv]): +splash = SplashScreen(image=ImageResource("01.png"), show_log_messages=False) +if not any([aa == "-h" or aa == "--help" for aa in sysargv]): splash.open() -from dpx.srxplanargui.srxgui import SrXgui def main(): gui = SrXgui(splash=splash) - gui.configure_traits(view='traits_view') + gui.configure_traits(view="traits_view") return -if __name__ == '__main__': + +if __name__ == "__main__": sys.exit(main()) diff --git a/src/diffpy/srxplanargui/srxplanargui_app.py b/src/diffpy/srxplanargui/srxplanargui_app.py new file mode 100644 index 0000000..653c323 --- /dev/null +++ b/src/diffpy/srxplanargui/srxplanargui_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.srxplanargui.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.srxplanargui", + description=( + "xPDFsuite, a software for PDF transformation" + " and visualization.\n\n For more information, visit: " + "https://github.com/diffpy/diffpy.srxplanargui/" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--version", + action="store_true", + help="Show the program's version number and exit", + ) + + args = parser.parse_args() + + if args.version: + print(f"diffpy.srxplanargui {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/srxplanargui/version.py b/src/diffpy/srxplanargui/version.py new file mode 100644 index 0000000..d278817 --- /dev/null +++ b/src/diffpy/srxplanargui/version.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +############################################################################## +# +# (c) 2012-2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. +# +# File coded by: Xiaohao Yang, Billinge Group members. +# +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srxplanargui/graphs/contributors # noqa: E501 +# +# See LICENSE.rst for license information. +# +############################################################################## +"""Definition of __version__.""" + +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] + +# obtain version information +from importlib.metadata import PackageNotFoundError, version + +try: + __version__ = version("diffpy.srxplanargui") +except PackageNotFoundError: + __version__ = "unknown" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e3b6313 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,19 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..008dab2 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,10 @@ +"""Unit tests for __version__.py.""" + +import diffpy.srxplanargui # noqa + + +def test_package_version(): + """Ensure the package version is defined and not set to the initial + placeholder.""" + assert hasattr(diffpy.srxplanargui, "__version__") + assert diffpy.srxplanargui.__version__ != "0.0.0"