isgri 0.6.1__py3-none-any.whl → 0.7.1__py3-none-any.whl

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.
isgri/cli/query.py CHANGED
@@ -1,164 +1,210 @@
1
- import click
2
- from pathlib import Path
3
- from ..catalog import ScwQuery
4
- from ..config import Config
5
- from ..__version__ import __version__
6
-
7
-
8
- def parse_time(time_str):
9
- """
10
- Parse time string as IJD float or ISO date string.
11
-
12
- Parameters
13
- ----------
14
- time_str : str or None
15
- Time as "YYYY-MM-DD" or IJD number
16
-
17
- Returns
18
- -------
19
- float or str or None
20
- Parsed time value
21
- """
22
- if time_str is None:
23
- return None
24
-
25
- try:
26
- return float(time_str)
27
- except ValueError:
28
- return time_str
29
-
30
-
31
- def parse_coord(coord):
32
- """
33
- Parse RA and Dec strings as float degrees or sexagesimal strings.
34
-
35
- Parameters
36
- ----------
37
- coord : str or None
38
- Coordinate as float degrees or sexagesimal string
39
- Returns
40
- -------
41
- float or str or None
42
- Parsed coordinate value
43
- """
44
- if coord is None:
45
- return None
46
-
47
- try:
48
- return float(coord)
49
- except ValueError:
50
- return coord
51
-
52
-
53
- def query_direct(
54
- catalog_path, tstart, tstop, ra, dec, radius, fov, max_chi, chi_type, revolution, output, list_swids, count
55
- ):
56
- try:
57
- q = ScwQuery(catalog_path)
58
- initial_count = len(q.catalog)
59
- # Parse times (handle both IJD and ISO)
60
- tstart = parse_time(tstart)
61
- tstop = parse_time(tstop)
62
-
63
- # Apply filters
64
- if tstart or tstop:
65
- q = q.time(tstart=tstart, tstop=tstop)
66
-
67
- if ra is not None and dec is not None:
68
- ra = parse_coord(ra)
69
- dec = parse_coord(dec)
70
- if radius is not None:
71
- q = q.position(ra=ra, dec=dec, radius=radius)
72
- else:
73
- q = q.position(ra=ra, dec=dec, fov_mode=fov)
74
-
75
- if max_chi is not None:
76
- q = q.quality(max_chi=max_chi, chi_type=chi_type)
77
-
78
- if revolution:
79
- q = q.revolution(revolution)
80
-
81
- if count:
82
- click.echo(q.count())
83
-
84
- elif output:
85
- q.write(output, overwrite=True, swid_only=list_swids)
86
- click.echo(f"Saved {q.count()} SCWs to {output}")
87
-
88
- else:
89
- results = q.get()
90
- click.echo(f"Found {len(results)}/{initial_count} SCWs")
91
- if len(results) > 0:
92
- display_cols = ["SWID", "TSTART", "TSTOP", "RA_SCX", "DEC_SCX"]
93
- chi_col = f"{chi_type}_CHI" if chi_type != "RAW" else "CHI"
94
- if chi_col in results.colnames:
95
- display_cols.append(chi_col)
96
- click.echo(results[display_cols][:10])
97
- if len(results) > 10:
98
- click.echo(f"... and {len(results) - 10} more")
99
-
100
- except Exception as e:
101
- click.echo(f"Error: {e}", err=True)
102
- raise click.Abort()
103
-
104
-
105
- def query_interactive(catalog_path):
106
- """Run interactive query session."""
107
- click.echo("=== Interactive Query Mode ===\n")
108
-
109
- q = ScwQuery(catalog_path)
110
- click.echo(f"Loaded {len(q.catalog)} SCWs")
111
- click.echo("Type 'help' for commands\n")
112
-
113
- while True:
114
- try:
115
- cmd = click.prompt("query>", default="").strip().lower()
116
-
117
- if cmd in ("exit", "quit", "q"):
118
- break
119
- elif cmd == "help":
120
- click.echo("Commands: time, pos, quality, show, reset, save, exit")
121
- elif cmd == "time":
122
- tstart = click.prompt("Start", default="", show_default=False)
123
- tstop = click.prompt("Stop", default="", show_default=False)
124
- tstart = parse_time(tstart) if tstart else None
125
- tstop = parse_time(tstop) if tstop else None
126
- q = q.time(tstart=tstart or None, tstop=tstop or None)
127
- click.echo(f"→ {q.count()} SCWs")
128
- elif cmd == "pos":
129
- ra = click.prompt("RA")
130
- dec = click.prompt("Dec")
131
- mode = click.prompt("Mode", type=click.Choice(["fov", "radius"]), default="fov")
132
- if mode == "radius":
133
- radius = click.prompt("Radius (deg)", type=float, default=10.0)
134
- q = q.position(ra=parse_coord(ra), dec=parse_coord(dec), radius=radius)
135
- else:
136
- fov_mode = click.prompt("FOV mode", type=click.Choice(["full", "any"]), default="any")
137
- q = q.position(ra=parse_coord(ra), dec=parse_coord(dec), fov_mode=fov_mode)
138
- click.echo(f"→ {q.count()} SCWs")
139
-
140
- elif cmd == "quality":
141
- max_chi = click.prompt("Max chi-squared", type=float)
142
- chi_type = click.prompt("Chi type", type=click.Choice(["RAW", "CUT", "GTI"]), default="CUT")
143
- q = q.quality(max_chi=max_chi, chi_type=chi_type)
144
- click.echo(f" {q.count()} SCWs")
145
-
146
- elif cmd == "show":
147
- results = q.get()
148
- click.echo(f"\n{len(results)} SCWs:")
149
- click.echo(results[["SWID", "TSTART", "TSTOP"]])
150
- elif cmd == "reset":
151
- q = q.reset()
152
- click.echo(f"→ {len(q.catalog)} SCWs")
153
- elif cmd == "save":
154
- only_scws = click.confirm("Save only SWID list?", default=False)
155
- path = click.prompt("File")
156
- q.write(path, overwrite=True, swid_only=only_scws)
157
- click.echo(f"✓ Saved")
158
- else:
159
- click.echo(f"Unknown: {cmd}")
160
-
161
- except KeyboardInterrupt:
162
- click.echo("\nUse 'exit' to quit")
163
- except Exception as e:
164
- click.echo(f"Error: {e}", err=True)
1
+ import click
2
+ from pathlib import Path
3
+ from ..catalog import ScwQuery
4
+ from ..config import Config
5
+ from ..__version__ import __version__
6
+
7
+
8
+ def parse_time(time_str):
9
+ """
10
+ Parse time string as IJD float or ISO date string.
11
+
12
+ Parameters
13
+ ----------
14
+ time_str : str or None
15
+ Time as "YYYY-MM-DD" or IJD number
16
+
17
+ Returns
18
+ -------
19
+ float or str or None
20
+ Parsed time value
21
+ """
22
+ if time_str is None:
23
+ return None
24
+
25
+ try:
26
+ return float(time_str)
27
+ except ValueError:
28
+ return time_str
29
+
30
+
31
+ def parse_coord(coord):
32
+ """
33
+ Parse RA and Dec strings as float degrees or sexagesimal strings.
34
+
35
+ Parameters
36
+ ----------
37
+ coord : str or None
38
+ Coordinate as float degrees or sexagesimal string
39
+ Returns
40
+ -------
41
+ float or str or None
42
+ Parsed coordinate value
43
+ """
44
+ if coord is None:
45
+ return None
46
+
47
+ try:
48
+ return float(coord)
49
+ except ValueError:
50
+ return coord
51
+
52
+
53
+ def match_command(user_input):
54
+ """Match user input to command, allowing partial matches."""
55
+ commands = {
56
+ "time": ["time", "t"],
57
+ "pos": ["pos", "position", "p"],
58
+ "quality": ["quality", "qual", "q"],
59
+ "revolution": ["revolution", "rev", "r"],
60
+ "show": ["show", "s", "display"],
61
+ "reset": ["reset", "clear"],
62
+ "save": ["save", "write"],
63
+ "help": ["help", "h", "?"],
64
+ "exit": ["exit", "quit", "q"],
65
+ }
66
+
67
+ user_input = user_input.lower().strip()
68
+
69
+ for cmd, aliases in commands.items():
70
+ if user_input in aliases:
71
+ return cmd
72
+ for alias in aliases:
73
+ if alias.startswith(user_input) and len(user_input) >= 2:
74
+ return cmd
75
+
76
+ return None
77
+
78
+
79
+ def query_direct(
80
+ catalog_path, tstart, tstop, ra, dec, radius, fov, max_chi, chi_type, revolution, output, list_swids, count
81
+ ):
82
+ try:
83
+ q = ScwQuery(catalog_path)
84
+ initial_count = len(q.catalog)
85
+
86
+ tstart = parse_time(tstart)
87
+ tstop = parse_time(tstop)
88
+
89
+ if tstart or tstop:
90
+ q = q.time(tstart=tstart, tstop=tstop)
91
+
92
+ if ra is not None and dec is not None:
93
+ ra = parse_coord(ra)
94
+ dec = parse_coord(dec)
95
+ if radius is not None:
96
+ q = q.position(ra=ra, dec=dec, radius=radius)
97
+ else:
98
+ q = q.position(ra=ra, dec=dec, fov_mode=fov)
99
+
100
+ if max_chi is not None:
101
+ q = q.quality(max_chi=max_chi, chi_type=chi_type)
102
+
103
+ if revolution:
104
+ rev_list = [int(r.strip()) for r in revolution.split(",")]
105
+ q = q.revolution(rev_list)
106
+
107
+ if count:
108
+ click.echo(q.count())
109
+
110
+ elif output:
111
+ q.write(output, overwrite=True, swid_only=list_swids)
112
+ click.echo(f"Saved {q.count()} SCWs to {output}")
113
+
114
+ else:
115
+ results = q.get()
116
+ click.echo(f"Found {len(results)}/{initial_count} SCWs")
117
+ if len(results) > 0:
118
+ display_cols = ["SWID", "TSTART", "TSTOP", "RA_SCX", "DEC_SCX", "CHI", "CUT_CHI", "GTI_CHI"]
119
+ click.echo(results[display_cols][:10])
120
+ if len(results) > 10:
121
+ click.echo(f"... and {len(results) - 10} more")
122
+
123
+ except Exception as e:
124
+ click.echo(f"Error: {e}", err=True)
125
+ raise click.Abort()
126
+
127
+
128
+ def query_interactive(catalog_path):
129
+ click.echo("=== Interactive Query Mode ===\n")
130
+
131
+ q = ScwQuery(catalog_path)
132
+ click.echo(f"Loaded {len(q.catalog)} SCWs")
133
+ click.echo("Type 'help' for available commands\n")
134
+
135
+ while True:
136
+ try:
137
+ user_input = click.prompt("query>", default="").strip()
138
+ cmd = match_command(user_input)
139
+
140
+ if cmd == "exit":
141
+ break
142
+ elif cmd == "help":
143
+ click.echo("\nAvailable commands:")
144
+ click.echo(" time - Filter by time range")
145
+ click.echo(" pos - Filter by position (FOV or radius)")
146
+ click.echo(" quality - Filter by chi-squared quality")
147
+ click.echo(" revolution - Filter by revolution number(s)")
148
+ click.echo(" show - Display current results")
149
+ click.echo(" reset - Clear all filters")
150
+ click.echo(" save - Save results to file")
151
+ click.echo(" exit - Exit interactive mode")
152
+ click.echo("\nExamples:")
153
+ click.echo(" time: Start=2020-01-01, Stop=2020-12-31 or IJD")
154
+ click.echo(" pos: RA=83.63, Dec=22.01 (degrees or sexagesimal)")
155
+ click.echo(" revolution: 1234 (single) or 1234, 1235, 1236 (multiple)")
156
+ click.echo("\nTip: You can use abbreviations (t, p, q, rev, etc.)")
157
+ click.echo()
158
+ elif cmd == "time":
159
+ tstart = click.prompt("Start", default="", show_default=False)
160
+ tstop = click.prompt("Stop", default="", show_default=False)
161
+ tstart = parse_time(tstart) if tstart else None
162
+ tstop = parse_time(tstop) if tstop else None
163
+ q = q.time(tstart=tstart or None, tstop=tstop or None)
164
+ click.echo(f" {q.count()} SCWs")
165
+ elif cmd == "pos":
166
+ ra = click.prompt("RA")
167
+ dec = click.prompt("Dec")
168
+ mode = click.prompt("Mode", type=click.Choice(["fov", "radius"]), default="fov")
169
+ if mode == "radius":
170
+ radius = click.prompt("Radius (deg)", type=float, default=10.0)
171
+ q = q.position(ra=parse_coord(ra), dec=parse_coord(dec), radius=radius)
172
+ else:
173
+ fov_mode = click.prompt("FOV mode", type=click.Choice(["full", "any"]), default="any")
174
+ q = q.position(ra=parse_coord(ra), dec=parse_coord(dec), fov_mode=fov_mode)
175
+ click.echo(f"→ {q.count()} SCWs")
176
+ elif cmd == "quality":
177
+ max_chi = click.prompt("Max chi-squared", type=float)
178
+ chi_type = click.prompt("Chi type", type=click.Choice(["CHI", "CUT", "GTI"]), default="CUT")
179
+ q = q.quality(max_chi=max_chi, chi_type=chi_type)
180
+ click.echo(f"→ {q.count()} SCWs")
181
+ elif cmd == "revolution":
182
+ rev_str = click.prompt("Revolution(s)", default="", show_default=False)
183
+ if rev_str:
184
+ rev_list = [int(r.strip()) for r in rev_str.split(",")]
185
+ q = q.revolution(rev_list)
186
+ click.echo(f"→ {q.count()} SCWs")
187
+ elif cmd == "show":
188
+ results = q.get()
189
+ click.echo(f"\n{len(results)} SCWs:")
190
+ display_cols = ["SWID", "TSTART", "TSTOP", "RA_SCX", "DEC_SCX", "CHI", "CUT_CHI", "GTI_CHI"]
191
+ click.echo(results[display_cols][:10])
192
+ if len(results) > 10:
193
+ click.echo(f"... and {len(results) - 10} more")
194
+ elif cmd == "reset":
195
+ q = q.reset()
196
+ click.echo(f"→ {len(q.catalog)} SCWs")
197
+ elif cmd == "save":
198
+ swid_only = click.confirm("Save only SWID list?", default=False)
199
+ path = click.prompt("File")
200
+ q.write(path, overwrite=True, swid_only=swid_only)
201
+ click.echo(f"✓ Saved")
202
+ elif cmd is None:
203
+ click.echo(f"Unknown command: {user_input}. Type 'help' for available commands.")
204
+ else:
205
+ click.echo(f"Unknown command: {user_input}. Type 'help' for available commands.")
206
+
207
+ except KeyboardInterrupt:
208
+ click.echo("\nUse 'exit' to quit")
209
+ except Exception as e:
210
+ click.echo(f"Error: {e}", err=True)
isgri/config.py CHANGED
@@ -32,6 +32,8 @@ class Config:
32
32
  Path to INTEGRAL archive directory
33
33
  catalog_path : Path or None
34
34
  Path to catalog FITS file (validated on access)
35
+ pif_path : Path or None
36
+ Path to PIF file
35
37
  """
36
38
 
37
39
  DEFAULT_PATH = Path(user_config_dir("isgri")) / "config.toml"
@@ -98,12 +100,24 @@ class Config:
98
100
  If configured path doesn't exist
99
101
  """
100
102
  path_str = self.config.get("catalog_path")
101
- if not path_str:
102
- return None
103
- path = Path(path_str)
104
- if not path.exists():
105
- raise FileNotFoundError(f"Catalog path does not exist: {path}")
106
- return path
103
+ if path_str:
104
+ return Path(path_str)
105
+ return None
106
+
107
+ @property
108
+ def pif_path(self) -> Optional[Path]:
109
+ """
110
+ Get PIF path from config.
111
+
112
+ Returns
113
+ -------
114
+ Path or None
115
+ Path to PIF file
116
+ """
117
+ path_str = self.config.get("pif_path")
118
+ if path_str:
119
+ return Path(path_str)
120
+ return None
107
121
 
108
122
  def save(self):
109
123
  """Save current config to file."""
@@ -111,7 +125,9 @@ class Config:
111
125
  with open(self.path, "wb") as f:
112
126
  tomli_w.dump(self._config or {}, f)
113
127
 
114
- def create_new(self, archive_path: Optional[Path] = None, catalog_path: Optional[Path] = None):
128
+ def create_new(
129
+ self, archive_path: Optional[Path] = None, catalog_path: Optional[Path] = None, pif_path: Optional[Path] = None
130
+ ):
115
131
  """
116
132
  Create new config file with given paths.
117
133
 
@@ -121,15 +137,21 @@ class Config:
121
137
  Path to archive directory
122
138
  catalog_path : Path, optional
123
139
  Path to catalog FITS file
140
+ pif_path : Path, optional
141
+ Path to PIF file
124
142
  """
125
143
  self._config = {}
126
144
  if archive_path:
127
145
  self._config["archive_path"] = str(archive_path)
128
146
  if catalog_path:
129
147
  self._config["catalog_path"] = str(catalog_path)
148
+ if pif_path:
149
+ self._config["pif_path"] = str(pif_path)
130
150
  self.save()
131
151
 
132
- def set(self, archive_path: Optional[Path] = None, catalog_path: Optional[Path] = None):
152
+ def set(
153
+ self, archive_path: Optional[Path] = None, catalog_path: Optional[Path] = None, pif_path: Optional[Path] = None
154
+ ):
133
155
  """
134
156
  Update config paths and save.
135
157
 
@@ -139,13 +161,19 @@ class Config:
139
161
  New archive directory path
140
162
  catalog_path : Path, optional
141
163
  New catalog path
164
+ pif_path : Path, optional
165
+ New PIF path
142
166
  """
143
167
  if archive_path:
144
168
  self.config["archive_path"] = str(archive_path)
145
169
  if catalog_path:
146
170
  self.config["catalog_path"] = str(catalog_path)
171
+ if pif_path:
172
+ self.config["pif_path"] = str(pif_path)
147
173
 
148
174
  self.save()
149
175
 
150
176
  def __repr__(self):
151
- return f"Config(path={self.path}, archive={self.archive_path}, catalog={self.catalog_path})"
177
+ return (
178
+ f"Config(path={self.path}, archive={self.archive_path}, catalog={self.catalog_path}, pif={self.pif_path})"
179
+ )