timewise 0.4.10__tar.gz → 0.5.2__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.
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: timewise
3
- Version: 0.4.10
3
+ Version: 0.5.2
4
4
  Summary: A small package to download infrared data from the WISE satellite
5
- Home-page: https://github.com/JannisNe/timewise
6
5
  License: MIT
7
6
  Author: Jannis Necker
8
7
  Author-email: jannis.necker@gmail.com
@@ -13,25 +12,31 @@ Classifier: Programming Language :: Python :: 3.8
13
12
  Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
- Requires-Dist: astropy (>=5.1,<6.0)
15
+ Provides-Extra: dev
16
+ Provides-Extra: docs
17
+ Requires-Dist: astropy (>=5.1,<6.0.0)
17
18
  Requires-Dist: backoff (>=2.1.2,<3.0.0)
19
+ Requires-Dist: coveralls (>=3.3.1,<4.0.0) ; extra == "dev"
18
20
  Requires-Dist: jupyter[jupyter] (>=1.0.0,<2.0.0)
19
21
  Requires-Dist: jupyterlab[jupyter] (>=4.0.6,<5.0.0)
20
22
  Requires-Dist: matplotlib (>=3.5.3,<4.0.0)
23
+ Requires-Dist: myst-parser (>=1,<3) ; extra == "docs"
21
24
  Requires-Dist: numpy (>=1.23.2,<2.0.0)
22
25
  Requires-Dist: pandas (>=1.4.3,<3.0.0)
23
26
  Requires-Dist: pydantic (>=1.9.0,<2.0.0)
27
+ Requires-Dist: pytest (>=7.2.2,<8.0.0) ; extra == "dev"
24
28
  Requires-Dist: pyvo (>=1.4.1,<2.0.0)
25
29
  Requires-Dist: requests (>=2.28.1,<3.0.0)
26
- Requires-Dist: scikit-image (>=0.19.3,<0.21.0)
30
+ Requires-Dist: scikit-image (>=0.19.3,<0.22.0)
27
31
  Requires-Dist: scikit-learn (>=1.3.0,<2.0.0)
28
- Requires-Dist: seaborn (>=0.11.2,<0.13.0)
32
+ Requires-Dist: seaborn (>=0.11.2,<0.14.0)
33
+ Requires-Dist: sphinx-rtd-theme (>=1.3.0,<2.0.0) ; extra == "docs"
29
34
  Requires-Dist: tqdm (>=4.64.0,<5.0.0)
30
35
  Requires-Dist: urllib3 (==1.26.15)
31
36
  Requires-Dist: virtualenv (>=20.16.3,<21.0.0)
32
37
  Project-URL: Bug Tracker, https://github.com/JannisNe/timewise/issues
33
38
  Project-URL: Documentation, https://timewise.readthedocs.io/en/latest
34
- Project-URL: Repository, https://github.com/JannisNe/timewise
39
+ Project-URL: Homepage, https://github.com/JannisNe/timewise
35
40
  Description-Content-Type: text/markdown
36
41
 
37
42
  [![CI](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml/badge.svg)](https://github.com/JannisNe/timewise/actions/workflows/continous_integration.yml)
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=2.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [project]
6
+ name = "timewise"
7
+ version = "0.5.2"
8
+ description = "A small package to download infrared data from the WISE satellite"
9
+ authors = [
10
+ { name = "Jannis Necker", email = "jannis.necker@gmail.com" },
11
+ ]
12
+ license = { text = "MIT" }
13
+ readme = "README.md"
14
+ requires-python = ">=3.8,<3.12"
15
+ dependencies = [
16
+ "tqdm>=4.64.0,<5.0.0",
17
+ "requests>=2.28.1,<3.0.0",
18
+ "pandas>=1.4.3,<3.0.0",
19
+ "numpy>=1.23.2,<2.0.0",
20
+ "pyvo>=1.4.1,<2.0.0",
21
+ "astropy>=5.1,<6.0.0",
22
+ "matplotlib>=3.5.3,<4.0.0",
23
+ "scikit-image>=0.19.3,<0.22.0",
24
+ "backoff>=2.1.2,<3.0.0",
25
+ "virtualenv>=20.16.3,<21.0.0",
26
+ "seaborn>=0.11.2,<0.14.0",
27
+ "urllib3==1.26.15",
28
+ "pydantic>=1.9.0,<2.0.0",
29
+ "scikit-learn>=1.3.0,<2.0.0",
30
+ "jupyterlab[jupyter]>=4.0.6,<5.0.0",
31
+ "jupyter[jupyter]>=1.0.0,<2.0.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ timewise = "timewise.cli:timewise_cli"
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/JannisNe/timewise"
39
+ Documentation = "https://timewise.readthedocs.io/en/latest"
40
+ "Bug Tracker" = "https://github.com/JannisNe/timewise/issues"
41
+
42
+ [project.optional-dependencies]
43
+ dev = [
44
+ "coveralls>=3.3.1,<4.0.0",
45
+ "pytest>=7.2.2,<8.0.0",
46
+ ]
47
+ docs = [
48
+ "myst-parser>=1,<3",
49
+ "sphinx-rtd-theme>=1.3.0,<2.0.0",
50
+ ]
@@ -1,5 +1,3 @@
1
1
  from timewise.wise_data_by_visit import WiseDataByVisit
2
2
  from timewise.wise_bigdata_desy_cluster import WISEDataDESYCluster
3
3
  from timewise.parent_sample_base import ParentSampleBase
4
-
5
- __version__ = "0.4.10"
@@ -1,5 +1,4 @@
1
1
  import gc
2
- import os
3
2
  import pickle
4
3
  import threading
5
4
  import time
@@ -30,7 +29,7 @@ class BigParentSampleBase(ParentSampleBase):
30
29
  self._keep_df_in_memory = keep_file_in_memory
31
30
  self._time_when_df_was_used_last = time.time()
32
31
  self._df = None
33
- self._cache_file = os.path.join(self.cache_dir, "cache.pkl")
32
+ self._cache_file = self.cache_dir / "cache.pkl"
34
33
  self._lock_cache_file = False
35
34
 
36
35
  self._clean_thread = threading.Thread(target=self._periodically_drop_df_to_disk, daemon=True, name='ParentSampleCleanThread').start()
@@ -50,7 +49,7 @@ class BigParentSampleBase(ParentSampleBase):
50
49
 
51
50
  if isinstance(self._df, type(None)):
52
51
 
53
- if os.path.isfile(self._cache_file):
52
+ if self._cache_file.is_file():
54
53
  logger.debug(f'loading from {self._cache_file}')
55
54
  self._wait_for_unlock_cache_file()
56
55
  self._lock_cache_file = True
@@ -97,9 +96,9 @@ class BigParentSampleBase(ParentSampleBase):
97
96
  logger.debug('stopped clean thread')
98
97
 
99
98
  def __del__(self):
100
- if hasattr(self, "_cache_file") and os.path.isfile(self._cache_file):
99
+ if hasattr(self, "_cache_file") and self._cache_file.is_file():
101
100
  logger.debug(f'removing {self._cache_file}')
102
- os.remove(self._cache_file)
101
+ self._cache_file.unlink()
103
102
 
104
103
  if hasattr(self, "clean_thread"):
105
104
  logger.debug(f'stopping clean thread')
@@ -1,11 +1,11 @@
1
1
  import logging
2
2
  import yaml
3
3
  import json
4
- import os
5
4
  import inspect
6
5
  from pydantic import BaseModel, validator
7
6
  import pandas as pd
8
7
  import importlib
8
+ from pathlib import Path
9
9
 
10
10
  from timewise.parent_sample_base import ParentSampleBase
11
11
  from timewise.wise_data_base import WISEDataBase
@@ -80,7 +80,7 @@ class TimewiseConfigLoader(BaseModel):
80
80
  @validator("filename")
81
81
  def validate_file(cls, v: str):
82
82
  if v is not None:
83
- if not os.path.isfile(v):
83
+ if not Path(v).is_file():
84
84
  raise ValueError(f"No file {v}!")
85
85
  return v
86
86
 
@@ -0,0 +1,52 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+
5
+
6
+ # Setting up the Logger
7
+ main_logger = logging.getLogger('timewise')
8
+ logger_format = logging.Formatter(
9
+ '%(levelname)s:%(name)s:%(funcName)s - [%(threadName)s] - %(asctime)s: \n\t%(message)s', "%H:%M:%S"
10
+ )
11
+ stream_handler = logging.StreamHandler()
12
+ stream_handler.setFormatter(logger_format)
13
+ main_logger.addHandler(stream_handler)
14
+ main_logger.propagate = False # do not propagate to root logger
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def get_directories() -> dict[str, Path | None]:
20
+ # Setting up data directory
21
+ DATA_DIR_KEY = 'TIMEWISE_DATA'
22
+ if DATA_DIR_KEY in os.environ:
23
+ data_dir = Path(os.environ[DATA_DIR_KEY]).expanduser()
24
+ else:
25
+ logger.warning(f'{DATA_DIR_KEY} not set! Using home directory.')
26
+ data_dir = Path('~/').expanduser()
27
+
28
+ BIGDATA_DIR_KEY = 'TIMEWISE_BIGDATA'
29
+ if BIGDATA_DIR_KEY in os.environ:
30
+ bigdata_dir = Path(os.environ[BIGDATA_DIR_KEY]).expanduser()
31
+ logger.info(f"Using bigdata directory {bigdata_dir}")
32
+ else:
33
+ bigdata_dir = None
34
+ logger.info(f"No bigdata directory set.")
35
+
36
+ output_dir = data_dir / 'output'
37
+ plots_dir = output_dir / 'plots'
38
+ cache_dir = data_dir / 'cache'
39
+
40
+ return {
41
+ 'data_dir': data_dir,
42
+ 'bigdata_dir': bigdata_dir,
43
+ 'output_dir': output_dir,
44
+ 'plots_dir': plots_dir,
45
+ 'cache_dir': cache_dir
46
+ }
47
+
48
+
49
+ def backoff_hndlr(details):
50
+ logger.info("Backing off {wait:0.1f} seconds after {tries} tries "
51
+ "calling function {target} with args {args} and kwargs "
52
+ "{kwargs}".format(**details))
@@ -3,7 +3,7 @@ import pandas as pd
3
3
  import numpy as np
4
4
  import logging
5
5
 
6
- from timewise.general import cache_dir, plots_dir
6
+ from timewise.general import get_directories
7
7
  from timewise.utils import plot_sdss_cutout, plot_panstarrs_cutout
8
8
 
9
9
 
@@ -26,14 +26,14 @@ class ParentSampleBase(abc.ABC):
26
26
 
27
27
  def __init__(self, base_name):
28
28
  # set up directories
29
- self.cache_dir = os.path.join(cache_dir, base_name)
30
- self.plots_dir = os.path.join(plots_dir, base_name)
29
+ d = get_directories()
30
+ self.cache_dir = d["cache_dir"] / base_name
31
+ self.plots_dir = d["plots_dir"] / base_name
31
32
 
32
33
  for d in [self.cache_dir, self.plots_dir]:
33
- if not os.path.isdir(d):
34
- os.makedirs(d)
34
+ d.parent.mkdir(parents=True, exist_ok=True)
35
35
 
36
- self.local_sample_copy = os.path.join(self.cache_dir, 'sample.csv')
36
+ self.local_sample_copy = self.cache_dir / 'sample.csv'
37
37
 
38
38
  def plot_cutout(self, ind, arcsec=20, interactive=False, **kwargs):
39
39
  """
@@ -54,9 +54,10 @@ class ParentSampleBase(abc.ABC):
54
54
 
55
55
  fn = kwargs.pop(
56
56
  "fn",
57
- [os.path.join(self.plots_dir, f"{i}_{r[self.default_keymap['id']]}.pdf")
57
+ [self.plots_dir / f"{i}_{r[self.default_keymap['id']]}.pdf"
58
58
  for i, r in sel.iterrows()]
59
59
  )
60
+ self.plots_dir.mkdir(parents=True, exist_ok=True)
60
61
 
61
62
  logger.debug(f"\nRA: {ra}\nDEC: {dec}\nTITLE: {title}\nFN: {fn}")
62
63
  ou = list()
@@ -84,4 +85,5 @@ class ParentSampleBase(abc.ABC):
84
85
 
85
86
  def save_local(self):
86
87
  logger.debug(f"saving under {self.local_sample_copy}")
88
+ self.local_sample_copy.parent.mkdir(parents=True, exist_ok=True)
87
89
  self.df.to_csv(self.local_sample_copy)
@@ -12,14 +12,20 @@ from functools import cache
12
12
  from astropy.table import Table
13
13
  from PIL import Image
14
14
  from io import BytesIO
15
+ import hashlib
16
+ from threading import Thread
17
+ from queue import Queue
18
+ import sys
15
19
 
16
-
17
- from timewise.general import cache_dir, backoff_hndlr
20
+ from timewise.general import backoff_hndlr, get_directories
18
21
 
19
22
 
20
23
  logger = logging.getLogger(__name__)
21
24
  mirong_url = 'http://staff.ustc.edu.cn/~jnac/data_public/wisevar.txt'
22
- local_copy = os.path.join(cache_dir, 'mirong_sample.csv')
25
+
26
+
27
+ def get_mirong_path():
28
+ return get_directories()['cache_dir'] / 'mirong_sample.csv'
23
29
 
24
30
 
25
31
  @cache
@@ -29,7 +35,8 @@ def get_2d_gaussian_correction(cl):
29
35
 
30
36
  def get_mirong_sample():
31
37
 
32
- if not os.path.isfile(local_copy):
38
+ mirong_path = get_mirong_path()
39
+ if not mirong_path.is_file():
33
40
 
34
41
  logger.info(f'getting MIRONG sample from {mirong_url}')
35
42
  r = requests.get(mirong_url)
@@ -44,16 +51,16 @@ def get_mirong_sample():
44
51
  mirong_sample = pd.DataFrame(lll[1:-1], columns=lll[0])
45
52
  mirong_sample['ra'] = mirong_sample['RA']
46
53
  mirong_sample['dec'] = mirong_sample['DEC']
47
- logger.debug(f'saving to {local_copy}')
48
-
49
- mirong_sample.to_csv(local_copy, index=False)
50
54
  logger.info(f'found {len(mirong_sample)} objects in MIRONG Sample')
55
+
51
56
  mirong_sample.drop(columns=['ra', 'dec'], inplace=True)
52
- mirong_sample.to_csv(local_copy, index=False)
57
+ logger.debug(f'saving to {mirong_path}')
58
+ mirong_path.parent.mkdir(parents=True, exist_ok=True)
59
+ mirong_sample.to_csv(mirong_path, index=False)
53
60
 
54
61
  else:
55
- logger.debug(f'loading {local_copy}')
56
- mirong_sample = pd.read_csv(local_copy)
62
+ logger.debug(f'loading {mirong_path}')
63
+ mirong_sample = pd.read_csv(mirong_path)
57
64
 
58
65
  return mirong_sample
59
66
 
@@ -150,6 +157,24 @@ class PanSTARRSQueryError(Exception):
150
157
  pass
151
158
 
152
159
 
160
+ def load_cache_or_download(url):
161
+ logger.debug(f"loading or downloading {url}")
162
+ h = hashlib.md5(url.encode()).hexdigest()
163
+ cache_dir = get_directories()['cache_dir']
164
+ cache_file = cache_dir / (h + ".cache")
165
+ logger.debug(f"cache file is {cache_file}")
166
+ if not cache_file.is_file():
167
+ logger.debug(f"downloading {url}")
168
+ r = requests.get(url)
169
+ with open(cache_file, 'wb') as f:
170
+ f.write(r.content)
171
+ return r.content
172
+ else:
173
+ logger.debug(f"loading {cache_file}")
174
+ with open(cache_file, 'rb') as f:
175
+ return f.read()
176
+
177
+
153
178
  def annotate_not_available(ax):
154
179
  xlim = ax.get_xlim()
155
180
  ylim = ax.get_ylim()
@@ -170,7 +195,8 @@ def getimages(ra, dec, filters="grizy"):
170
195
 
171
196
  service = "https://ps1images.stsci.edu/cgi-bin/ps1filenames.py"
172
197
  url = f"{service}?ra={ra}&dec={dec}&filters={filters}"
173
- table = Table.read(url, format='ascii')
198
+ content = load_cache_or_download(url)
199
+ table = Table.read(content.decode(), format='ascii')
174
200
  return table
175
201
 
176
202
 
@@ -231,8 +257,8 @@ def getcolorim(ra, dec, size=240, output_size=None, filters="grizy", format="jpg
231
257
  if format not in ("jpg", "png"):
232
258
  raise ValueError("format must be jpg or png")
233
259
  url = geturl(ra, dec, size=size, filters=filters, output_size=output_size, format=format, color=True)
234
- r = requests.get(url)
235
- im = Image.open(BytesIO(r.content))
260
+ content = load_cache_or_download(url)
261
+ im = Image.open(BytesIO(content))
236
262
  return im
237
263
 
238
264
 
@@ -253,8 +279,8 @@ def getgrayim(ra, dec, size=240, output_size=None, filter="g", format="jpg"):
253
279
  if filter not in list("grizy"):
254
280
  raise ValueError("filter must be one of grizy")
255
281
  url = geturl(ra, dec, size=size, filters=filter, output_size=output_size, format=format)
256
- r = requests.get(url[0])
257
- im = Image.open(BytesIO(r.content))
282
+ content = load_cache_or_download(url[0])
283
+ im = Image.open(BytesIO(content))
258
284
  return im
259
285
 
260
286
 
@@ -446,3 +472,47 @@ class StableTAPService(vo.dal.TAPService):
446
472
  #######################################################
447
473
  # END CUSTOM TAP Service #
448
474
  ###########################################################################################################
475
+
476
+
477
+ ###########################################################################################################
478
+ # START CUSTOM TAP Service #
479
+ #######################################################
480
+
481
+
482
+ class ErrorQueue(Queue):
483
+ """Queue subclass whose join() re-raises exceptions from worker threads."""
484
+
485
+ def __init__(self, *args, **kwargs):
486
+ super().__init__(*args, **kwargs)
487
+ self.error_queue = Queue()
488
+
489
+ def report_error(self, exc_info):
490
+ """Called by workers to push an exception into the error queue."""
491
+ self.error_queue.put(exc_info)
492
+ # Also decrement unfinished_tasks, so join() won't block forever
493
+ with self.all_tasks_done:
494
+ self.unfinished_tasks = max(0, self.unfinished_tasks - 1)
495
+ self.all_tasks_done.notify_all()
496
+
497
+ def join(self):
498
+ """Wait until all tasks are done, or raise if a worker failed."""
499
+ with self.all_tasks_done:
500
+ while self.unfinished_tasks:
501
+ if not self.error_queue.empty():
502
+ exc_info = self.error_queue.get()
503
+ raise exc_info[1].with_traceback(exc_info[2])
504
+ self.all_tasks_done.wait()
505
+
506
+
507
+ class ExceptionSafeThread(Thread):
508
+ """Thread subclass that reports uncaught exceptions to the ErrorQueue."""
509
+
510
+ def __init__(self, error_queue: ErrorQueue, *args, **kwargs):
511
+ super().__init__(*args, **kwargs)
512
+ self.error_queue = error_queue
513
+
514
+ def run(self):
515
+ try:
516
+ super().run()
517
+ except Exception:
518
+ self.error_queue.report_error(sys.exc_info())