Skip to content

Commit a4f29ff

Browse files
authored
Merge pull request #41 from ECCCO-mission/jmbhughes-patch
Add tqdm and row inversion mode
2 parents 4ae2535 + a37dbe7 commit a4f29ff

File tree

10 files changed

+76
-24
lines changed

10 files changed

+76
-24
lines changed

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
# Changelog
22

3+
Follows [Keep A Changelog format](https://keepachangelog.com/en/1.1.0/)
4+
5+
## 0.0.3
6+
7+
### Added
8+
9+
- Uses tqdm for progress bar tracking
10+
- Adds better row mode instead of starting chunked and instantly switching to row
11+
12+
### Changed
13+
14+
- Prints elapsed seconds as integer instead of float
15+
- Expands documentation for missing parameters
16+
317
## 0.0.2
418

5-
### New features
19+
### Added
620

721
- Writes scores to a text file
822

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ for examples MaGIXS, CubIXSS, or ECCCO observations.
1616

1717
## Install
1818

19-
`pip install overlappogram` or clone the repository and install manually
19+
`pip install overlappogram` or clone the repository and install manually.
2020

2121
## How to Use
2222

@@ -25,7 +25,7 @@ for examples MaGIXS, CubIXSS, or ECCCO observations.
2525
`unfold ./path/to/config.toml`
2626

2727
The `config.toml` file should be structured similar to the [example_config.toml](example_config.toml).
28-
We provide more description of the config file [in the documentation](https://eccco-mission.github.io/overlappogram/configuration.html).
28+
We provide more description of the config file [in the documentation](https://eccco-mission.github.io/overlappogram/configuration.html).
2929

3030
## Getting Help
3131

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
project = "overlappogram"
1010
copyright = "2024, J. Marcus Hughes, Dyana Beabout"
1111
author = "J. Marcus Hughes, Dyana Beabout"
12-
release = "0.0.2"
12+
release = "0.0.3"
1313

1414
# -- General configuration ---------------------------------------------------
1515
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

docs/configuration.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Configuration
44
==============
55

6-
Each run of `unfold` requires a configuration file in the `TOML format <https://toml.io/en/>`. The configuration file
6+
Each run of `unfold` requires a configuration file in the `TOML format <https://toml.io/en/>`_. The configuration file
77
is divided into sections:
88

99
- **paths**: provides the input file paths.
@@ -15,7 +15,7 @@ is divided into sections:
1515
.. note::
1616
All sections and parameters are expected in the configuration file. There are no optional parameters with defaults.
1717

18-
`We provide an example configuration file here. <https://github.com/ECCCO-mission/overlappogram/blob/main/example_config.toml>`
18+
`We provide an example configuration file here. <https://github.com/ECCCO-mission/overlappogram/blob/main/example_config.toml>`_
1919

2020
**paths** section
2121
------------------
@@ -47,12 +47,12 @@ There are six configurables for this section:
4747
- *field_angle_range*: a list of two integers defining the range of field angles to use in inversion. Units are arc seconds.
4848
- *response_dependency_name*: for now, only "logt" is supported.
4949
- *response_dependency_list*: a list of floats defining the logarithm of the temperature used in the response dependency.
50-
- *smooth_over*: ?
50+
- *smooth_over*: the method of smoothing, currently only supports "dependence"
5151

5252
**model** section
5353
-------------------
5454

55-
This section defines the parameters used by `Scikit-Learn's ElasticNet <https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.ElasticNet.html>`.
55+
This section defines the parameters used by `Scikit-Learn's ElasticNet <https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.ElasticNet.html>`_.
5656
You can find more exhaustive descriptions of the parameters at that link in the `sklearn` documentation.
5757

5858
There are six configurables for this section:

docs/overview.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,15 @@ Optimization modes
3535

3636
There are three possible optimization modes: "row,", "chunked," and "hybrid."
3737

38+
Row mode
39+
+++++++++
3840
"row" is the simplest optimization mode and is recommended for newcomers. In this optimization mode, the overlappogram
3941
is divided into rows. Each row gets its own ElasticNet model to use when carrying out the inversion. Thus, each row is
4042
inverted independently.
4143

44+
45+
Chunked mode
46+
+++++++++++++
4247
"chunked" is the next simplest optimization row. The image is divided into a number of *chunks*
4348
or sets of contiguous, non-overlapping rows.
4449
The number of chunks is set by the *num_threads* parameter in the **execution** section of the configuration file.
@@ -53,6 +58,8 @@ parameter in the **model** section of the configuration to true when used the "c
5358
We do not yet understand when this happens and doesn't happen.
5459
Thus, it is recommended to avoid chunked optimization unless you are confident.
5560

61+
Hybrid mode
62+
++++++++++++
5663
"hybrid" is a combination of the "chunked" and "row" optimization modes. The optimization begins in chunked mode but
5764
switches to the row mode to optimize CPU performance. When inverting an overlappogram, some rows are harder to invert
5865
than others and thus take more time. These rows tend to be adjacent and thus in the same chunk. Consequently, we noticed

example_config.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ overwrite = true
1212

1313
[inversion]
1414
solution_fov_width = 2
15-
detector_row_range = [300, 400] #[0, 792]
15+
detector_row_range = [0, 50]
1616
field_angle_range = [-1227, 1227]
1717
response_dependency_name = "logt"
1818
response_dependency_list = [5.7, 5.8, 5.9, 6.0 , 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8]
1919
smooth_over = 'dependence'
2020

2121
[model]
22-
alphas = [3E-5] #[0.2, 0.1, 0.01, 0.005]
22+
alphas = [3E-5, 4E-5, 0.1] #[0.2, 0.1, 0.01, 0.005]
2323
rhos = [0.1]
2424
warm_start = false
2525
tol = 1E-2

overlappogram/cli.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
@click.command()
2222
@click.argument("config")
2323
def unfold(config):
24-
"""Unfold an overlappogram given a configuration toml file.""" # TODO improve message
24+
"""Unfold an overlappogram given a configuration toml file.
25+
26+
See https://eccco-mission.github.io/overlappogram/configuration.html for the configuration file format.
27+
"""
2528

2629
with open(config) as f:
2730
config = toml.load(f)
@@ -42,6 +45,8 @@ def unfold(config):
4245

4346
for alpha in config["model"]["alphas"]:
4447
for rho in config["model"]["rhos"]:
48+
print(80*"-")
49+
print(f"Beginning inversion for alpha={alpha}, rho={rho}.")
4550
start = time.time()
4651
em_cube, prediction, scores, unconverged_rows = inversion.invert(
4752
overlappogram,
@@ -54,9 +59,9 @@ def unfold(config):
5459
)
5560
end = time.time()
5661
print(
57-
f"Inversion Time for alpha={alpha}, rho={rho}:",
58-
end - start,
59-
f"; {len(unconverged_rows)} unconverged rows",
62+
f"Inversion for alpha={alpha}, rho={rho} took",
63+
int(end - start),
64+
f"seconds; {len(unconverged_rows)} unconverged rows",
6065
)
6166

6267
postfix = (

overlappogram/inversion.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from ndcube import NDCube
1010
from sklearn.exceptions import ConvergenceWarning
1111
from sklearn.linear_model import ElasticNet
12+
from tqdm import tqdm
1213

1314
from overlappogram.response import prepare_response_function
1415

@@ -60,6 +61,8 @@ def __init__(
6061
response_dependency_list=response_dependency_list,
6162
)
6263

64+
self._progress_bar = None # initialized in invert call
65+
6366
@property
6467
def is_inverted(self) -> bool:
6568
return not any(
@@ -107,7 +110,8 @@ def _progress_indicator(self, future):
107110
with self._thread_count_lock:
108111
if not future.cancelled():
109112
self._completed_row_count += 1
110-
print(f"{self._completed_row_count / self.total_row_count * 100:3.0f}% complete", end="\r")
113+
#print(f"{self._completed_row_count / self.total_row_count * 100:3.0f}% complete", end="\r")
114+
self._progress_bar.update(1)
111115

112116
def _switch_to_row_inversion(self, model_config, alpha, rho, num_row_threads=50):
113117
self._mode = InversionMode.ROW
@@ -161,6 +165,29 @@ def _collect_results(self, mode_switch_thread_count, model_config, alpha, rho):
161165
self._switch_to_row_inversion(model_config, alpha, rho)
162166
break
163167

168+
def _start_row_inversion(self, model_config, alpha, rho, num_threads):
169+
self.executors = [concurrent.futures.ThreadPoolExecutor(max_workers=num_threads)]
170+
171+
self.futures = {}
172+
self._models = []
173+
for i, row_index in enumerate(range(self._detector_row_range[0], self._detector_row_range[1])):
174+
enet_model = ElasticNet(
175+
alpha=alpha,
176+
l1_ratio=rho,
177+
tol=model_config["tol"],
178+
max_iter=model_config["max_iter"],
179+
precompute=False, # setting this to true slows down performance dramatically
180+
positive=True,
181+
copy_X=False,
182+
fit_intercept=False,
183+
selection=model_config["selection"],
184+
warm_start=False, # warm start doesn't make sense in the row version
185+
)
186+
self._models.append(enet_model)
187+
future = self.executors[-1].submit(self._invert_image_row, row_index, i)
188+
future.add_done_callback(self._progress_indicator)
189+
self.futures[future] = (row_index, i)
190+
164191
def _start_chunk_inversion(self, model_config, alpha, rho, num_threads):
165192
starts = np.arange(
166193
self._detector_row_range[0],
@@ -255,6 +282,8 @@ def invert(
255282

256283
self._mode = mode
257284

285+
self._progress_bar = tqdm(total=self.total_row_count, unit='row', delay=1, leave=False)
286+
258287
self._models = []
259288
self._completed_row_count = 0
260289

@@ -269,16 +298,16 @@ def invert(
269298
# mode never switches since count=0
270299
self._collect_results(0, model_config, alpha, rho)
271300
elif self._mode == InversionMode.ROW:
272-
self._start_chunk_inversion(model_config, alpha, rho, num_threads)
273-
# TODO: it would be better to have a mode to start in row but right now we fake it with a fast mode switch
274-
self._collect_results(np.inf, model_config, alpha, rho) # immediately switch mode
301+
self._start_row_inversion(model_config, alpha, rho, num_threads)
275302
self._collect_results(np.inf, model_config, alpha, rho)
276303
else:
277304
raise ValueError("Invalid InversionMode.")
278305

279306
for executor in self.executors:
280307
executor.shutdown()
281308

309+
self._progress_bar.close()
310+
282311
return (
283312
np.transpose(self._em_data, (2, 0, 1)),
284313
self._inversion_prediction,

overlappogram/spectral.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,8 @@ def create_spectrally_pure_images(image_list: list, gnt_path: str, rsp_dep_list:
4747
for index in range(len(image_list)):
4848
# Create spectrally pure data cube.
4949
for em_data in image_list:
50-
# with fits.open(image_list[index]) as em_hdul:
51-
# em_data_cube = em_hdul[0].data.astype(np.float64)
5250
em_data_cube = em_data.astype(np.float64)
5351
em_data_cube = np.transpose(em_data_cube, axes=(1, 2, 0))
54-
# em_dep_list = em_hdul[1].data['logt']
55-
# print(em_dep_list)
5652
if index == 0:
5753
image_height, num_slits, num_logts = np.shape(em_data_cube)
5854
gnt_data_cube = np.zeros((image_height, num_slits, num_gnts), dtype=np.float64)

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ requires = ["setuptools",
55

66
[project]
77
name = "overlappogram"
8-
version = "0.0.2"
8+
version = "0.0.3"
99
dependencies = ["numpy",
1010
"astropy",
1111
"scikit-learn",
@@ -16,7 +16,8 @@ dependencies = ["numpy",
1616
"scipy",
1717
"ndcube",
1818
"toml",
19-
"click"
19+
"click",
20+
"tqdm"
2021
]
2122
requires-python = ">=3.9"
2223
authors = [

0 commit comments

Comments
 (0)