mossview 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mossview-1.0.0/LICENSE +21 -0
- mossview-1.0.0/MANIFEST.in +1 -0
- mossview-1.0.0/PKG-INFO +94 -0
- mossview-1.0.0/README.md +75 -0
- mossview-1.0.0/pyproject.toml +50 -0
- mossview-1.0.0/setup.cfg +4 -0
- mossview-1.0.0/src/mossview/cli.py +400 -0
- mossview-1.0.0/src/mossview/gui.py +0 -0
- mossview-1.0.0/src/mossview/legado/main.py +76 -0
- mossview-1.0.0/src/mossview.egg-info/PKG-INFO +94 -0
- mossview-1.0.0/src/mossview.egg-info/SOURCES.txt +12 -0
- mossview-1.0.0/src/mossview.egg-info/dependency_links.txt +1 -0
- mossview-1.0.0/src/mossview.egg-info/requires.txt +2 -0
- mossview-1.0.0/src/mossview.egg-info/top_level.txt +1 -0
mossview-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jose Luiz Bruiani Barco
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prune legado
|
mossview-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mossview
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Um sistema para leitura, processamento e visualização de dados Mössbauer
|
|
5
|
+
Author-email: Seu Nome <seu.email@exemplo.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/seu-usuario/mossview
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: numpy>=1.20.0
|
|
17
|
+
Requires-Dist: matplotlib>=3.3.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# MossView
|
|
21
|
+
|
|
22
|
+
MossView is a command-line tool for reading Mössbauer spectra and plotting relative transmission. The main application is [`core-app/mossview.py`](core-app/mossview.py). It reads counts from a `.mos` file and, when it finds an associated `.job` file, uses its metadata for the plot title and velocity scale.
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Python 3.8 or later, as declared in `pyproject.toml`.
|
|
27
|
+
- Dependencies listed in [`requirements.txt`](requirements.txt): NumPy and Matplotlib, with their transitive dependencies pinned there.
|
|
28
|
+
|
|
29
|
+
Install the dependencies in a virtual environment if desired:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m venv .venv
|
|
33
|
+
# Windows PowerShell
|
|
34
|
+
.venv\Scripts\Activate.ps1
|
|
35
|
+
# macOS or Linux
|
|
36
|
+
source .venv/bin/activate
|
|
37
|
+
python -m pip install -r requirements.txt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
Run from the repository root and pass a `.mos` or `.job` file:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python core-app/mossview.py data-control/b025a-26.mos
|
|
46
|
+
python core-app/mossview.py data-control/b025adi.job
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
When given a `.job`, MossView tries to locate the `.mos` file named near the beginning of the job file, then falls back to a file with the same base name. When given a `.mos`, it looks for a `.job` that references it or has the same base name. Paths are checked as provided, then relative to the script directory.
|
|
50
|
+
|
|
51
|
+
The program prints progress and saves a PNG beside the `.mos` file with the same base name. It also opens an interactive Matplotlib window; close the window to end the process.
|
|
52
|
+
|
|
53
|
+
## Processing flow
|
|
54
|
+
|
|
55
|
+
1. Resolves the input path and finds the associated `.mos` and `.job` files.
|
|
56
|
+
2. Reads recognized metadata from the `.job`'s `&DATA ... &END` block: `VMAX` (absolute value), `ND`, and `TRIANG`. The plot uses `VMAX`, defaulting to 10 mm/s when unavailable. `ND` and `TRIANG` are read but do not affect current processing.
|
|
57
|
+
3. Reads counts from the `.mos`. The parser accepts XML containing a `<data>` element, as well as text containing numbers. It can read the XML `time` attribute, but does not currently use it in the plot.
|
|
58
|
+
4. Builds a linear velocity scale from `-VMAX` to `+VMAX`, with one point per count.
|
|
59
|
+
5. Calculates relative transmission as `count / maximum_count × 100` and plots Poisson error bars as `√count / maximum_count × 100`.
|
|
60
|
+
6. Saves and displays the plot.
|
|
61
|
+
|
|
62
|
+
## `&PARAM` block validation
|
|
63
|
+
|
|
64
|
+
When a `.job` file is available, the program examines assignments only inside the `&PARAM ... &END` block. It compares selected parameters with final values found in tables in the same file. It also checks `ARX` values above 0.5 mm/s when no applicable reference is available. If it finds discrepancies, it writes a copy with the `-updated.job` suffix, reports the changes, and uses that copy. The original `.job` is not overwritten by this process.
|
|
65
|
+
|
|
66
|
+
This correction is a heuristic for numeric discrepancies and `ARX` values. It is not a complete NORMOS format validator or a guarantee of physical correctness. Review the `-updated.job` file before using it with another program.
|
|
67
|
+
|
|
68
|
+
## Repository components
|
|
69
|
+
|
|
70
|
+
| Path | Purpose |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `core-app/mossview.py` | Main application: file reading, association, limited validation, and plotting. |
|
|
73
|
+
| `data-control/` | Local sample `.mos`, `.job`, and image files. This directory is ignored by Git. |
|
|
74
|
+
| `legado/main.py` | Independent plotting simulator for damped sine waves and Lissajous curves; it is not called by the current MossView application. |
|
|
75
|
+
| `legado/executables/` | Historical DOS/MBPLOT-related executables, separate from the main application. |
|
|
76
|
+
| `website/` | Minimal website content; it does not provide a working viewer interface. |
|
|
77
|
+
| `pyproject.toml` | Package metadata and Semantic Release configuration. |
|
|
78
|
+
| `.github/workflows/` | Release and PyPI publishing automation. |
|
|
79
|
+
|
|
80
|
+
## Current scope and limitations
|
|
81
|
+
|
|
82
|
+
- The available interface is a CLI with an interactive plot; there is no dedicated GUI or web application.
|
|
83
|
+
- The velocity axis is always linear and symmetric. The program does not use `ND` or `TRIANG` to reconstruct other acquisition geometries.
|
|
84
|
+
- The permissive text parser extracts numeric tokens but does not support every possible `.mos` format.
|
|
85
|
+
- `pyproject.toml` sets `packages = []` and defines no console command. Run the script directly as shown above; the publishing configuration alone does not provide an installed CLI.
|
|
86
|
+
- `legado/README.md` describes a historical system and its executables, not the current implementation.
|
|
87
|
+
|
|
88
|
+
## Development and releases
|
|
89
|
+
|
|
90
|
+
Automatic versioning is configured for the `main` and `beta` branches with Python Semantic Release. The publishing workflow builds and uploads the package to PyPI when a GitHub release is published. Packaging configuration and workflows are still evolving; to run the tool locally, use the commands in the Usage section.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
Distributed under the MIT License. See [`LICENSE`](LICENSE).
|
mossview-1.0.0/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# MossView
|
|
2
|
+
|
|
3
|
+
MossView is a command-line tool for reading Mössbauer spectra and plotting relative transmission. The main application is [`core-app/mossview.py`](core-app/mossview.py). It reads counts from a `.mos` file and, when it finds an associated `.job` file, uses its metadata for the plot title and velocity scale.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Python 3.8 or later, as declared in `pyproject.toml`.
|
|
8
|
+
- Dependencies listed in [`requirements.txt`](requirements.txt): NumPy and Matplotlib, with their transitive dependencies pinned there.
|
|
9
|
+
|
|
10
|
+
Install the dependencies in a virtual environment if desired:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
# Windows PowerShell
|
|
15
|
+
.venv\Scripts\Activate.ps1
|
|
16
|
+
# macOS or Linux
|
|
17
|
+
source .venv/bin/activate
|
|
18
|
+
python -m pip install -r requirements.txt
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Run from the repository root and pass a `.mos` or `.job` file:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python core-app/mossview.py data-control/b025a-26.mos
|
|
27
|
+
python core-app/mossview.py data-control/b025adi.job
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
When given a `.job`, MossView tries to locate the `.mos` file named near the beginning of the job file, then falls back to a file with the same base name. When given a `.mos`, it looks for a `.job` that references it or has the same base name. Paths are checked as provided, then relative to the script directory.
|
|
31
|
+
|
|
32
|
+
The program prints progress and saves a PNG beside the `.mos` file with the same base name. It also opens an interactive Matplotlib window; close the window to end the process.
|
|
33
|
+
|
|
34
|
+
## Processing flow
|
|
35
|
+
|
|
36
|
+
1. Resolves the input path and finds the associated `.mos` and `.job` files.
|
|
37
|
+
2. Reads recognized metadata from the `.job`'s `&DATA ... &END` block: `VMAX` (absolute value), `ND`, and `TRIANG`. The plot uses `VMAX`, defaulting to 10 mm/s when unavailable. `ND` and `TRIANG` are read but do not affect current processing.
|
|
38
|
+
3. Reads counts from the `.mos`. The parser accepts XML containing a `<data>` element, as well as text containing numbers. It can read the XML `time` attribute, but does not currently use it in the plot.
|
|
39
|
+
4. Builds a linear velocity scale from `-VMAX` to `+VMAX`, with one point per count.
|
|
40
|
+
5. Calculates relative transmission as `count / maximum_count × 100` and plots Poisson error bars as `√count / maximum_count × 100`.
|
|
41
|
+
6. Saves and displays the plot.
|
|
42
|
+
|
|
43
|
+
## `&PARAM` block validation
|
|
44
|
+
|
|
45
|
+
When a `.job` file is available, the program examines assignments only inside the `&PARAM ... &END` block. It compares selected parameters with final values found in tables in the same file. It also checks `ARX` values above 0.5 mm/s when no applicable reference is available. If it finds discrepancies, it writes a copy with the `-updated.job` suffix, reports the changes, and uses that copy. The original `.job` is not overwritten by this process.
|
|
46
|
+
|
|
47
|
+
This correction is a heuristic for numeric discrepancies and `ARX` values. It is not a complete NORMOS format validator or a guarantee of physical correctness. Review the `-updated.job` file before using it with another program.
|
|
48
|
+
|
|
49
|
+
## Repository components
|
|
50
|
+
|
|
51
|
+
| Path | Purpose |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `core-app/mossview.py` | Main application: file reading, association, limited validation, and plotting. |
|
|
54
|
+
| `data-control/` | Local sample `.mos`, `.job`, and image files. This directory is ignored by Git. |
|
|
55
|
+
| `legado/main.py` | Independent plotting simulator for damped sine waves and Lissajous curves; it is not called by the current MossView application. |
|
|
56
|
+
| `legado/executables/` | Historical DOS/MBPLOT-related executables, separate from the main application. |
|
|
57
|
+
| `website/` | Minimal website content; it does not provide a working viewer interface. |
|
|
58
|
+
| `pyproject.toml` | Package metadata and Semantic Release configuration. |
|
|
59
|
+
| `.github/workflows/` | Release and PyPI publishing automation. |
|
|
60
|
+
|
|
61
|
+
## Current scope and limitations
|
|
62
|
+
|
|
63
|
+
- The available interface is a CLI with an interactive plot; there is no dedicated GUI or web application.
|
|
64
|
+
- The velocity axis is always linear and symmetric. The program does not use `ND` or `TRIANG` to reconstruct other acquisition geometries.
|
|
65
|
+
- The permissive text parser extracts numeric tokens but does not support every possible `.mos` format.
|
|
66
|
+
- `pyproject.toml` sets `packages = []` and defines no console command. Run the script directly as shown above; the publishing configuration alone does not provide an installed CLI.
|
|
67
|
+
- `legado/README.md` describes a historical system and its executables, not the current implementation.
|
|
68
|
+
|
|
69
|
+
## Development and releases
|
|
70
|
+
|
|
71
|
+
Automatic versioning is configured for the `main` and `beta` branches with Python Semantic Release. The publishing workflow builds and uploads the package to PyPI when a GitHub release is published. Packaging configuration and workflows are still evolving; to run the tool locally, use the commands in the Usage section.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
Distributed under the MIT License. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mossview" # Nome do pacote no PyPI
|
|
7
|
+
version = "1.0.0" # Versão inicial do software
|
|
8
|
+
description = "Um sistema para leitura, processamento e visualização de dados Mössbauer"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Seu Nome", email = "seu.email@exemplo.com" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"numpy>=1.20.0",
|
|
24
|
+
"matplotlib>=3.3.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/seu-usuario/mossview"
|
|
29
|
+
|
|
30
|
+
# Configuração correta para o padrão src/
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
include = ["mossview*"]
|
|
34
|
+
|
|
35
|
+
# Configuração de automação de release e versionamento
|
|
36
|
+
[tool.semantic_release]
|
|
37
|
+
version_toml = [
|
|
38
|
+
"pyproject.toml:project.version",
|
|
39
|
+
]
|
|
40
|
+
commit_parser = "conventional"
|
|
41
|
+
build_command = "pip install build && python -m build"
|
|
42
|
+
|
|
43
|
+
[tool.semantic_release.branches.main]
|
|
44
|
+
match = "^main$"
|
|
45
|
+
prerelease = false
|
|
46
|
+
|
|
47
|
+
[tool.semantic_release.branches.beta]
|
|
48
|
+
match = "^beta$"
|
|
49
|
+
prerelease = true
|
|
50
|
+
prerelease_token = "beta"
|
mossview-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
MossView - Mössbauer spectrum viewer (.mos and .job)
|
|
5
|
+
with validation and filtering restricted to parameters in the &PARAM block.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import xml.etree.ElementTree as ET
|
|
14
|
+
import numpy as np
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_file_path(input_path_str: str) -> Path:
|
|
19
|
+
"""
|
|
20
|
+
Resolve the input file path using these rules:
|
|
21
|
+
1. If the absolute path exists, return it.
|
|
22
|
+
2. If the path exists in the current working directory (CWD), return it.
|
|
23
|
+
3. If the path exists in the Python script directory, return it.
|
|
24
|
+
"""
|
|
25
|
+
path = Path(input_path_str)
|
|
26
|
+
|
|
27
|
+
if path.is_absolute() and path.exists():
|
|
28
|
+
return path.resolve()
|
|
29
|
+
|
|
30
|
+
if path.exists():
|
|
31
|
+
return path.resolve()
|
|
32
|
+
|
|
33
|
+
script_dir = Path(__file__).parent.resolve()
|
|
34
|
+
script_relative_path = script_dir / input_path_str
|
|
35
|
+
if script_relative_path.exists():
|
|
36
|
+
return script_relative_path.resolve()
|
|
37
|
+
|
|
38
|
+
return path.resolve()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_job_file(job_path: Path) -> dict:
|
|
42
|
+
"""
|
|
43
|
+
Read a NORMOS .JOB file and extract display metadata and raw content
|
|
44
|
+
for analysis of the &PARAM block.
|
|
45
|
+
"""
|
|
46
|
+
info = {
|
|
47
|
+
'mos_filename': None,
|
|
48
|
+
'title': '',
|
|
49
|
+
'vmax': 10.0,
|
|
50
|
+
'nd': None,
|
|
51
|
+
'triang': True,
|
|
52
|
+
'raw_content': ''
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
with open(job_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
info['raw_content'] = content
|
|
59
|
+
lines = [line.strip() for line in content.splitlines() if line.strip()]
|
|
60
|
+
|
|
61
|
+
for line in lines[:6]:
|
|
62
|
+
if line.lower().endswith('.mos'):
|
|
63
|
+
info['mos_filename'] = line
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
title_lines = []
|
|
67
|
+
for line in lines:
|
|
68
|
+
if line.startswith('*'):
|
|
69
|
+
cleaned = line.strip('*').strip()
|
|
70
|
+
if cleaned:
|
|
71
|
+
title_lines.append(cleaned)
|
|
72
|
+
if title_lines:
|
|
73
|
+
info['title'] = " | ".join(title_lines)
|
|
74
|
+
|
|
75
|
+
data_match = re.search(r'&DATA(.*?)&END', content, re.DOTALL | re.IGNORECASE)
|
|
76
|
+
if data_match:
|
|
77
|
+
data_block = data_match.group(1)
|
|
78
|
+
|
|
79
|
+
vmax_m = re.search(r'VMAX\s*=\s*([+-]?\d+\.?\d*)', data_block, re.IGNORECASE)
|
|
80
|
+
if vmax_m:
|
|
81
|
+
info['vmax'] = abs(float(vmax_m.group(1)))
|
|
82
|
+
|
|
83
|
+
nd_m = re.search(r'ND\s*=\s*(\d+)', data_block, re.IGNORECASE)
|
|
84
|
+
if nd_m:
|
|
85
|
+
info['nd'] = int(nd_m.group(1))
|
|
86
|
+
|
|
87
|
+
triang_m = re.search(r'TRIANG\s*=\s*\.?(TRUE|FALSE)\.?', data_block, re.IGNORECASE)
|
|
88
|
+
if triang_m:
|
|
89
|
+
info['triang'] = (triang_m.group(1).upper() == 'TRUE')
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"[WARNING] Error reading metadata from .JOB file: {e}")
|
|
93
|
+
|
|
94
|
+
return info
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def extract_reference_values(content: str) -> dict:
|
|
98
|
+
"""
|
|
99
|
+
Extract reference/final values from report tables in the .JOB/.RES file.
|
|
100
|
+
"""
|
|
101
|
+
refs = {}
|
|
102
|
+
|
|
103
|
+
# 1. Parameter table: "Index Name Initial Final Error"
|
|
104
|
+
pattern_index = re.compile(
|
|
105
|
+
r'^\s*\d+\s+([A-Z]+)\s+[+-]?\d+\.?\d*(?:[eE][+-]?\d+)?\s+([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)',
|
|
106
|
+
re.MULTILINE
|
|
107
|
+
)
|
|
108
|
+
for match in pattern_index.finditer(content):
|
|
109
|
+
param_name = match.group(1).upper()
|
|
110
|
+
try:
|
|
111
|
+
refs[param_name] = float(match.group(2))
|
|
112
|
+
except ValueError:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
# 2. "Sub. #Lines Area (mm/s)" table for mapping subspectrum areas (ARX)
|
|
116
|
+
if "Sub. #Lines" in content:
|
|
117
|
+
sub_block = content.split("Sub. #Lines")[1]
|
|
118
|
+
pattern_area = re.compile(r'^\s*(\d+)\s+\d+\s+([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)', re.MULTILINE)
|
|
119
|
+
for match in pattern_area.finditer(sub_block):
|
|
120
|
+
sub_idx = match.group(1)
|
|
121
|
+
try:
|
|
122
|
+
refs[f"ARX({sub_idx})"] = float(match.group(2))
|
|
123
|
+
except ValueError:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
return refs
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def validate_and_filter_param_block(job_path: Path, content: str) -> tuple[str, Path, list[str]]:
|
|
130
|
+
"""
|
|
131
|
+
Validate and edit ONLY data inside the &PARAM ... &END block.
|
|
132
|
+
Check numeric discrepancies (such as decimal-place shifts in ARX)
|
|
133
|
+
against report data or physical limits.
|
|
134
|
+
"""
|
|
135
|
+
param_match = re.search(r'(&PARAM\b.*?&END)', content, re.DOTALL | re.IGNORECASE)
|
|
136
|
+
if not param_match:
|
|
137
|
+
return content, job_path, []
|
|
138
|
+
|
|
139
|
+
original_param_block = param_match.group(1)
|
|
140
|
+
warnings = []
|
|
141
|
+
refs = extract_reference_values(content)
|
|
142
|
+
|
|
143
|
+
# Regular expression to capture KEY(INDEX)=VALUE assignments
|
|
144
|
+
param_kv_pattern = re.compile(r'([A-Z0-9_]+\(\d+\))\s*=\s*([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)', re.IGNORECASE)
|
|
145
|
+
|
|
146
|
+
def replace_param(match):
|
|
147
|
+
key = match.group(1).upper()
|
|
148
|
+
raw_val = match.group(2)
|
|
149
|
+
try:
|
|
150
|
+
val = float(raw_val)
|
|
151
|
+
except ValueError:
|
|
152
|
+
return match.group(0)
|
|
153
|
+
|
|
154
|
+
base_name = re.sub(r'\(\d+\)', '', key)
|
|
155
|
+
target_val = refs.get(key) or refs.get(base_name)
|
|
156
|
+
|
|
157
|
+
if target_val is not None:
|
|
158
|
+
# Check for a significant deviation from the expected value (e.g. a decimal-place error)
|
|
159
|
+
if abs(val - target_val) > 1e-4:
|
|
160
|
+
formatted_target = f"{target_val:.5f}".rstrip('0').rstrip('.') if abs(target_val) < 100 else f"{target_val}"
|
|
161
|
+
warnings.append(
|
|
162
|
+
f"Parameter {key}={val} is outside the expected range. Corrected to {key}={formatted_target} (expected: {target_val})."
|
|
163
|
+
)
|
|
164
|
+
return match.group(0).replace(raw_val, formatted_target)
|
|
165
|
+
|
|
166
|
+
# Fallback safeguard if ARX exceeds the typical limit (> 0.5 mm/s) without an available table
|
|
167
|
+
if base_name == "ARX" and val > 0.5:
|
|
168
|
+
corrected_val = val / 10.0
|
|
169
|
+
formatted_target = f"{corrected_val:.5f}".rstrip('0').rstrip('.')
|
|
170
|
+
warnings.append(
|
|
171
|
+
f"Parameter {key}={val} exceeds the expected spectrum limit. Corrected to {key}={formatted_target}."
|
|
172
|
+
)
|
|
173
|
+
return match.group(0).replace(raw_val, formatted_target)
|
|
174
|
+
|
|
175
|
+
return match.group(0)
|
|
176
|
+
|
|
177
|
+
new_param_block = param_kv_pattern.sub(replace_param, original_param_block)
|
|
178
|
+
|
|
179
|
+
if warnings and job_path:
|
|
180
|
+
new_content = content.replace(original_param_block, new_param_block)
|
|
181
|
+
stem = job_path.stem
|
|
182
|
+
updated_job_path = job_path if stem.endswith("-updated") else job_path.parent / f"{stem}-updated.job"
|
|
183
|
+
|
|
184
|
+
with open(updated_job_path, 'w', encoding='utf-8') as f:
|
|
185
|
+
f.write(new_content)
|
|
186
|
+
|
|
187
|
+
return new_content, updated_job_path, warnings
|
|
188
|
+
|
|
189
|
+
return content, job_path, warnings
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def parse_mos_file(mos_path: Path) -> tuple[np.ndarray, dict]:
|
|
193
|
+
"""
|
|
194
|
+
Read counts from a .MOS file in XML (WisSoft) or plain text/ASCII format.
|
|
195
|
+
"""
|
|
196
|
+
counts = []
|
|
197
|
+
meta = {}
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
tree = ET.parse(mos_path)
|
|
201
|
+
root = tree.getroot()
|
|
202
|
+
data_elem = root.find('data')
|
|
203
|
+
if data_elem is not None and data_elem.text:
|
|
204
|
+
text_data = data_elem.text.strip()
|
|
205
|
+
counts = [float(val) for val in text_data.split()]
|
|
206
|
+
if 'time' in data_elem.attrib:
|
|
207
|
+
meta['time'] = float(data_elem.attrib['time'])
|
|
208
|
+
return np.array(counts, dtype=float), meta
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
with open(mos_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
213
|
+
content = f.read()
|
|
214
|
+
|
|
215
|
+
data_match = re.search(r'<data[^>]*>(.*?)</data>', content, re.DOTALL | re.IGNORECASE)
|
|
216
|
+
if data_match:
|
|
217
|
+
text_data = data_match.group(1).strip()
|
|
218
|
+
tokens = text_data.split()
|
|
219
|
+
counts = [float(v) for v in tokens if v.replace('.', '', 1).lstrip('-').isdigit()]
|
|
220
|
+
return np.array(counts, dtype=float), meta
|
|
221
|
+
|
|
222
|
+
tokens = content.split()
|
|
223
|
+
for t in tokens:
|
|
224
|
+
try:
|
|
225
|
+
counts.append(float(t))
|
|
226
|
+
except ValueError:
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
if not counts:
|
|
230
|
+
raise ValueError(f"Could not extract numeric data from file {mos_path.name}")
|
|
231
|
+
|
|
232
|
+
return np.array(counts, dtype=float), meta
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def locate_associated_files(target_path: Path):
|
|
236
|
+
"""
|
|
237
|
+
Identify the .MOS and .JOB files from the path provided by the user.
|
|
238
|
+
"""
|
|
239
|
+
job_path = None
|
|
240
|
+
mos_path = None
|
|
241
|
+
job_info = {}
|
|
242
|
+
|
|
243
|
+
if target_path.suffix.lower() == '.job':
|
|
244
|
+
job_path = target_path
|
|
245
|
+
job_info = parse_job_file(job_path)
|
|
246
|
+
|
|
247
|
+
if job_info.get('mos_filename'):
|
|
248
|
+
candidate = job_path.parent / job_info['mos_filename']
|
|
249
|
+
if candidate.exists():
|
|
250
|
+
mos_path = candidate
|
|
251
|
+
else:
|
|
252
|
+
for f in job_path.parent.iterdir():
|
|
253
|
+
if f.name.lower() == job_info['mos_filename'].lower():
|
|
254
|
+
mos_path = f
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
if not mos_path:
|
|
258
|
+
candidate = job_path.with_suffix('.MOS')
|
|
259
|
+
if candidate.exists():
|
|
260
|
+
mos_path = candidate
|
|
261
|
+
else:
|
|
262
|
+
candidate_lower = job_path.with_suffix('.mos')
|
|
263
|
+
if candidate_lower.exists():
|
|
264
|
+
mos_path = candidate_lower
|
|
265
|
+
|
|
266
|
+
elif target_path.suffix.lower() == '.mos':
|
|
267
|
+
mos_path = target_path
|
|
268
|
+
|
|
269
|
+
possible_jobs = list(mos_path.parent.glob('*.JOB')) + list(mos_path.parent.glob('*.job'))
|
|
270
|
+
for j_file in possible_jobs:
|
|
271
|
+
j_info = parse_job_file(j_file)
|
|
272
|
+
if j_info.get('mos_filename') and j_info['mos_filename'].lower() == mos_path.name.lower():
|
|
273
|
+
job_path = j_file
|
|
274
|
+
job_info = j_info
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
if not job_path:
|
|
278
|
+
candidate = mos_path.with_suffix('.JOB')
|
|
279
|
+
if candidate.exists():
|
|
280
|
+
job_path = candidate
|
|
281
|
+
job_info = parse_job_file(job_path)
|
|
282
|
+
|
|
283
|
+
return mos_path, job_path, job_info
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def calculate_velocity_scale(num_channels: int, vmax: float) -> np.ndarray:
|
|
287
|
+
"""
|
|
288
|
+
Calculate the velocity scale (in mm/s) for a linear Mössbauer scan.
|
|
289
|
+
"""
|
|
290
|
+
return np.linspace(-abs(vmax), abs(vmax), num_channels)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def plot_mossbauer(velocity: np.ndarray, counts: np.ndarray, title: str, job_name: str, mos_name: str, output_image_path: Path):
|
|
294
|
+
"""
|
|
295
|
+
Generate a Mössbauer spectrum plot in relative transmission (%) and raw counts.
|
|
296
|
+
"""
|
|
297
|
+
baseline = np.max(counts)
|
|
298
|
+
rel_transmission = (counts / baseline) * 100.0
|
|
299
|
+
errors_rel = (np.sqrt(counts) / baseline) * 100.0
|
|
300
|
+
|
|
301
|
+
plt.style.use('seaborn-v0_8-whitegrid' if 'seaborn-v0_8-whitegrid' in plt.style.available else 'default')
|
|
302
|
+
fig, ax1 = plt.subplots(figsize=(10, 6), dpi=120)
|
|
303
|
+
|
|
304
|
+
ax1.errorbar(velocity, rel_transmission, yerr=errors_rel, fmt='o', color='#004080',
|
|
305
|
+
ecolor='#80b3ff', elinewidth=0.8, markersize=3, label='Experimental Data', alpha=0.85)
|
|
306
|
+
|
|
307
|
+
ax1.axhline(100.0, color='crimson', linestyle='--', linewidth=1.0, label='Baseline (100%)')
|
|
308
|
+
|
|
309
|
+
ax1.set_xlabel('Velocity (mm/s)', fontsize=12, fontweight='bold')
|
|
310
|
+
ax1.set_ylabel('Relative Transmission (%)', fontsize=12, fontweight='bold', color='#004080')
|
|
311
|
+
ax1.tick_params(axis='y', labelcolor='#004080')
|
|
312
|
+
|
|
313
|
+
ax2 = ax1.twinx()
|
|
314
|
+
ax2.set_ylabel('Total Counts', fontsize=11, color='#555555')
|
|
315
|
+
min_counts = np.min(counts)
|
|
316
|
+
ax2.set_ylim((min_counts / baseline) * 100, 100.5)
|
|
317
|
+
ax2.set_yticks(np.linspace((min_counts / baseline) * 100, 100, 6))
|
|
318
|
+
ax2.set_yticklabels([f"{int(val * baseline / 100):,}" for val in ax2.get_yticks()])
|
|
319
|
+
ax2.grid(False)
|
|
320
|
+
|
|
321
|
+
display_title = title if title else f"Mossbauer Spectrum - {mos_name}"
|
|
322
|
+
plt.title(display_title, fontsize=13, fontweight='bold', pad=15)
|
|
323
|
+
|
|
324
|
+
info_text = (f"File: {mos_name}\n"
|
|
325
|
+
f"JOB: {job_name if job_name else 'None'}\n"
|
|
326
|
+
f"Channels: {len(counts)}\n"
|
|
327
|
+
f"Vmax: {np.max(np.abs(velocity)):.4f} mm/s\n"
|
|
328
|
+
f"Max Count: {int(baseline):,}")
|
|
329
|
+
|
|
330
|
+
ax1.text(0.02, 0.05, info_text, transform=ax1.transAxes, fontsize=9,
|
|
331
|
+
verticalalignment='bottom', bbox=dict(boxstyle='round,pad=0.5', facecolor='white', alpha=0.85, edgecolor='#ccc'))
|
|
332
|
+
|
|
333
|
+
ax1.legend(loc='upper right', frameon=True)
|
|
334
|
+
fig.tight_layout()
|
|
335
|
+
|
|
336
|
+
plt.savefig(output_image_path, dpi=300)
|
|
337
|
+
print(f"[SUCCESS] Plot saved to: {output_image_path}")
|
|
338
|
+
|
|
339
|
+
plt.show()
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def main():
|
|
343
|
+
parser = argparse.ArgumentParser(
|
|
344
|
+
description="MossView - Mössbauer spectrum reader and viewer (.MOS and .JOB)"
|
|
345
|
+
)
|
|
346
|
+
parser.add_argument("file", help="File name or full path (.job or .mos)")
|
|
347
|
+
args = parser.parse_args()
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
resolved_path = resolve_file_path(args.file)
|
|
351
|
+
except Exception as e:
|
|
352
|
+
print(f"[ERROR] Failed to process the specified path: {e}")
|
|
353
|
+
sys.exit(1)
|
|
354
|
+
|
|
355
|
+
if not resolved_path.exists():
|
|
356
|
+
print(f"[ERROR] File not found:\n -> {resolved_path}")
|
|
357
|
+
sys.exit(1)
|
|
358
|
+
|
|
359
|
+
print(f"[OK] File found: {resolved_path}")
|
|
360
|
+
|
|
361
|
+
mos_path, job_path, job_info = locate_associated_files(resolved_path)
|
|
362
|
+
|
|
363
|
+
if not mos_path or not mos_path.exists():
|
|
364
|
+
print(f"[ERROR] Could not find the .MOS data file associated with {resolved_path.name}")
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
# 1. Read and validate the &PARAM block in the .JOB file
|
|
368
|
+
if job_path and job_path.exists():
|
|
369
|
+
raw_content = job_info.get('raw_content', '')
|
|
370
|
+
_, updated_job_path, warnings = validate_and_filter_param_block(job_path, raw_content)
|
|
371
|
+
|
|
372
|
+
if warnings:
|
|
373
|
+
print("\n[WARNING] Values outside the expected range were found in the &PARAM block:")
|
|
374
|
+
for w in warnings:
|
|
375
|
+
print(f" -> {w}")
|
|
376
|
+
print(f"[OK] Updated file with corrected &PARAM block saved to: {updated_job_path}\n")
|
|
377
|
+
job_path = updated_job_path
|
|
378
|
+
job_info = parse_job_file(job_path)
|
|
379
|
+
|
|
380
|
+
# 2. Read the .MOS data file
|
|
381
|
+
try:
|
|
382
|
+
counts, meta = parse_mos_file(mos_path)
|
|
383
|
+
print(f"[OK] .MOS spectrum loaded: {len(counts)} channels read.")
|
|
384
|
+
except Exception as e:
|
|
385
|
+
print(f"[ERROR] Failed to read .MOS file: {e}")
|
|
386
|
+
sys.exit(1)
|
|
387
|
+
|
|
388
|
+
vmax = job_info.get('vmax', 10.0)
|
|
389
|
+
title = job_info.get('title', '')
|
|
390
|
+
velocity = calculate_velocity_scale(len(counts), vmax)
|
|
391
|
+
|
|
392
|
+
job_name = job_path.name if job_path else "None"
|
|
393
|
+
mos_name = mos_path.name
|
|
394
|
+
output_png = mos_path.with_suffix('.png')
|
|
395
|
+
|
|
396
|
+
plot_mossbauer(velocity, counts, title, job_name, mos_name, output_png)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
if __name__ == "__main__":
|
|
400
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
|
|
4
|
+
class MBPlotSimulator:
|
|
5
|
+
"""
|
|
6
|
+
Simulador da lógica de amostragem, transformação de coordenadas
|
|
7
|
+
e geração de gráficos do sistema clássico MBPLOT.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, screen_width=800, screen_height=600):
|
|
10
|
+
self.screen_width = screen_width
|
|
11
|
+
self.screen_height = screen_height
|
|
12
|
+
|
|
13
|
+
def world_to_screen(self, x_world, y_world, x_min, x_max, y_min, y_max):
|
|
14
|
+
"""
|
|
15
|
+
Converte coordenadas de mundo (matemáticas) para coordenadas de tela (pixels).
|
|
16
|
+
"""
|
|
17
|
+
x_screen = ((x_world - x_min) / (x_max - x_min)) * self.screen_width
|
|
18
|
+
y_screen = self.screen_height - ((y_world - y_min) / (y_max - y_min)) * self.screen_height
|
|
19
|
+
return x_screen, y_screen
|
|
20
|
+
|
|
21
|
+
def generate_damped_sine(self, x_min=0.0, x_max=10.0, num_points=500, alpha=0.5, omega=2.0 * np.pi):
|
|
22
|
+
"""
|
|
23
|
+
Gera a função de onda amortecida: f(x) = exp(-alpha * x) * sin(omega * x)
|
|
24
|
+
"""
|
|
25
|
+
x = np.linspace(x_min, x_max, num_points)
|
|
26
|
+
y = np.exp(-alpha * x) * np.sin(omega * x)
|
|
27
|
+
return x, y
|
|
28
|
+
|
|
29
|
+
def generate_lissajous(self, a=3, b=4, delta=np.pi/2, num_points=1000):
|
|
30
|
+
"""
|
|
31
|
+
Gera curvas paramétricas de Lissajous: x(t) = sin(a*t + delta), y(t) = sin(b*t)
|
|
32
|
+
"""
|
|
33
|
+
t = np.linspace(0, 2 * np.pi, num_points)
|
|
34
|
+
x = np.sin(a * t + delta)
|
|
35
|
+
y = np.sin(b * t)
|
|
36
|
+
return x, y
|
|
37
|
+
|
|
38
|
+
def plot_function(self, x, y, title="MBPLOT - Visualização de Função"):
|
|
39
|
+
"""
|
|
40
|
+
Plota o gráfico com estilo retrô similar ao ambiente gráfico DOS.
|
|
41
|
+
"""
|
|
42
|
+
x_min, x_max = np.min(x), np.max(x)
|
|
43
|
+
y_min, y_max = np.min(y), np.max(y)
|
|
44
|
+
|
|
45
|
+
# Mapeamento para resolução de tela simulada
|
|
46
|
+
x_pixel, y_pixel = self.world_to_screen(x, y, x_min, x_max, y_min, y_max)
|
|
47
|
+
|
|
48
|
+
plt.figure(figsize=(9, 6), facecolor='black')
|
|
49
|
+
ax = plt.axes()
|
|
50
|
+
ax.set_facecolor('black')
|
|
51
|
+
|
|
52
|
+
# Desenhar linha estilo monitor VGA clássico (Verde / Ciano)
|
|
53
|
+
plt.plot(x_pixel, y_pixel, color='#00FF66', linewidth=1.5, label='Sinal Processado')
|
|
54
|
+
|
|
55
|
+
plt.title(title, color='white', fontsize=12, fontweight='bold')
|
|
56
|
+
plt.xlabel("Pixel X (Viewport)", color='white')
|
|
57
|
+
plt.ylabel("Pixel Y (Viewport Invertido)", color='white')
|
|
58
|
+
plt.tick_params(colors='white')
|
|
59
|
+
plt.grid(True, color='#333333', linestyle='--')
|
|
60
|
+
plt.xlim(0, self.screen_width)
|
|
61
|
+
plt.ylim(self.screen_height, 0) # Invertido igual coordenada de tela
|
|
62
|
+
plt.legend(facecolor='black', edgecolor='white', labelcolor='white')
|
|
63
|
+
|
|
64
|
+
plt.tight_layout()
|
|
65
|
+
plt.show()
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
simulator = MBPlotSimulator(screen_width=640, screen_height=480)
|
|
69
|
+
|
|
70
|
+
# 1. Plotar Onda Senoidal Amortecida
|
|
71
|
+
x_func, y_func = simulator.generate_damped_sine(x_min=0, x_max=5, num_points=400, alpha=0.6, omega=4.0*np.pi)
|
|
72
|
+
simulator.plot_function(x_func, y_func, title="MBPLOT Simulação: Onda Amortecida f(x) = e^(-a*x) * sin(w*x)")
|
|
73
|
+
|
|
74
|
+
# 2. Plotar Figura de Lissajous
|
|
75
|
+
x_liss, y_liss = simulator.generate_lissajous(a=5, b=6, delta=np.pi/4)
|
|
76
|
+
simulator.plot_function(x_liss, y_liss, title="MBPLOT Simulação: Curva Paramétrica de Lissajous")
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mossview
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Um sistema para leitura, processamento e visualização de dados Mössbauer
|
|
5
|
+
Author-email: Seu Nome <seu.email@exemplo.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/seu-usuario/mossview
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: numpy>=1.20.0
|
|
17
|
+
Requires-Dist: matplotlib>=3.3.0
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# MossView
|
|
21
|
+
|
|
22
|
+
MossView is a command-line tool for reading Mössbauer spectra and plotting relative transmission. The main application is [`core-app/mossview.py`](core-app/mossview.py). It reads counts from a `.mos` file and, when it finds an associated `.job` file, uses its metadata for the plot title and velocity scale.
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Python 3.8 or later, as declared in `pyproject.toml`.
|
|
27
|
+
- Dependencies listed in [`requirements.txt`](requirements.txt): NumPy and Matplotlib, with their transitive dependencies pinned there.
|
|
28
|
+
|
|
29
|
+
Install the dependencies in a virtual environment if desired:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m venv .venv
|
|
33
|
+
# Windows PowerShell
|
|
34
|
+
.venv\Scripts\Activate.ps1
|
|
35
|
+
# macOS or Linux
|
|
36
|
+
source .venv/bin/activate
|
|
37
|
+
python -m pip install -r requirements.txt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
Run from the repository root and pass a `.mos` or `.job` file:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python core-app/mossview.py data-control/b025a-26.mos
|
|
46
|
+
python core-app/mossview.py data-control/b025adi.job
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
When given a `.job`, MossView tries to locate the `.mos` file named near the beginning of the job file, then falls back to a file with the same base name. When given a `.mos`, it looks for a `.job` that references it or has the same base name. Paths are checked as provided, then relative to the script directory.
|
|
50
|
+
|
|
51
|
+
The program prints progress and saves a PNG beside the `.mos` file with the same base name. It also opens an interactive Matplotlib window; close the window to end the process.
|
|
52
|
+
|
|
53
|
+
## Processing flow
|
|
54
|
+
|
|
55
|
+
1. Resolves the input path and finds the associated `.mos` and `.job` files.
|
|
56
|
+
2. Reads recognized metadata from the `.job`'s `&DATA ... &END` block: `VMAX` (absolute value), `ND`, and `TRIANG`. The plot uses `VMAX`, defaulting to 10 mm/s when unavailable. `ND` and `TRIANG` are read but do not affect current processing.
|
|
57
|
+
3. Reads counts from the `.mos`. The parser accepts XML containing a `<data>` element, as well as text containing numbers. It can read the XML `time` attribute, but does not currently use it in the plot.
|
|
58
|
+
4. Builds a linear velocity scale from `-VMAX` to `+VMAX`, with one point per count.
|
|
59
|
+
5. Calculates relative transmission as `count / maximum_count × 100` and plots Poisson error bars as `√count / maximum_count × 100`.
|
|
60
|
+
6. Saves and displays the plot.
|
|
61
|
+
|
|
62
|
+
## `&PARAM` block validation
|
|
63
|
+
|
|
64
|
+
When a `.job` file is available, the program examines assignments only inside the `&PARAM ... &END` block. It compares selected parameters with final values found in tables in the same file. It also checks `ARX` values above 0.5 mm/s when no applicable reference is available. If it finds discrepancies, it writes a copy with the `-updated.job` suffix, reports the changes, and uses that copy. The original `.job` is not overwritten by this process.
|
|
65
|
+
|
|
66
|
+
This correction is a heuristic for numeric discrepancies and `ARX` values. It is not a complete NORMOS format validator or a guarantee of physical correctness. Review the `-updated.job` file before using it with another program.
|
|
67
|
+
|
|
68
|
+
## Repository components
|
|
69
|
+
|
|
70
|
+
| Path | Purpose |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `core-app/mossview.py` | Main application: file reading, association, limited validation, and plotting. |
|
|
73
|
+
| `data-control/` | Local sample `.mos`, `.job`, and image files. This directory is ignored by Git. |
|
|
74
|
+
| `legado/main.py` | Independent plotting simulator for damped sine waves and Lissajous curves; it is not called by the current MossView application. |
|
|
75
|
+
| `legado/executables/` | Historical DOS/MBPLOT-related executables, separate from the main application. |
|
|
76
|
+
| `website/` | Minimal website content; it does not provide a working viewer interface. |
|
|
77
|
+
| `pyproject.toml` | Package metadata and Semantic Release configuration. |
|
|
78
|
+
| `.github/workflows/` | Release and PyPI publishing automation. |
|
|
79
|
+
|
|
80
|
+
## Current scope and limitations
|
|
81
|
+
|
|
82
|
+
- The available interface is a CLI with an interactive plot; there is no dedicated GUI or web application.
|
|
83
|
+
- The velocity axis is always linear and symmetric. The program does not use `ND` or `TRIANG` to reconstruct other acquisition geometries.
|
|
84
|
+
- The permissive text parser extracts numeric tokens but does not support every possible `.mos` format.
|
|
85
|
+
- `pyproject.toml` sets `packages = []` and defines no console command. Run the script directly as shown above; the publishing configuration alone does not provide an installed CLI.
|
|
86
|
+
- `legado/README.md` describes a historical system and its executables, not the current implementation.
|
|
87
|
+
|
|
88
|
+
## Development and releases
|
|
89
|
+
|
|
90
|
+
Automatic versioning is configured for the `main` and `beta` branches with Python Semantic Release. The publishing workflow builds and uploads the package to PyPI when a GitHub release is published. Packaging configuration and workflows are still evolving; to run the tool locally, use the commands in the Usage section.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
Distributed under the MIT License. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
src/mossview/cli.py
|
|
6
|
+
src/mossview/gui.py
|
|
7
|
+
src/mossview.egg-info/PKG-INFO
|
|
8
|
+
src/mossview.egg-info/SOURCES.txt
|
|
9
|
+
src/mossview.egg-info/dependency_links.txt
|
|
10
|
+
src/mossview.egg-info/requires.txt
|
|
11
|
+
src/mossview.egg-info/top_level.txt
|
|
12
|
+
src/mossview/legado/main.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mossview
|