adss 1.0__py3-none-any.whl → 1.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.
adss/client.py ADDED
@@ -0,0 +1,671 @@
1
+ """
2
+ Main client class for the Astronomy TAP Client.
3
+ """
4
+ from typing import Optional, Dict, List, Union, BinaryIO, Any
5
+ import pandas as pd
6
+ import urllib.parse
7
+
8
+ from .auth import Auth
9
+ from .endpoints.queries import QueriesEndpoint
10
+ from .endpoints.users import UsersEndpoint
11
+ from .endpoints.metadata import MetadataEndpoint
12
+ #from .endpoints.admin import AdminEndpoint
13
+ from .endpoints.images import ImagesEndpoint, LuptonImagesEndpoint, StampImagesEndpoint, TrilogyImagesEndpoint
14
+ from .models.user import User, Role, SchemaPermission, TablePermission
15
+ from .models.query import Query, QueryResult
16
+ from .models.metadata import Column, Table, Schema, DatabaseMetadata
17
+ from .exceptions import AuthenticationError
18
+
19
+
20
+ class ADSSClient:
21
+ """
22
+ Client for interacting with the Astronomy TAP Service API.
23
+
24
+ This is the main entry point for the client library, providing
25
+ access to all API functionality through a single interface.
26
+ """
27
+
28
+ def __init__(self, base_url: str, username: Optional[str] = None, password: Optional[str] = None, verify_ssl: bool = True, **kwargs):
29
+ """
30
+ Initialize the TAP Client.
31
+
32
+ Args:
33
+ base_url: The base URL of the API server
34
+ username: Optional username for immediate authentication
35
+ password: Optional password for immediate authentication
36
+ verify_ssl: Whether to verify SSL certificates for HTTPS requests. Set to False to disable SSL verification.
37
+ **kwargs: Additional keyword arguments to pass to the request
38
+ """
39
+ # Ensure base URL is properly formatted
40
+ parsed_url = urllib.parse.urlparse(base_url)
41
+ if not parsed_url.scheme:
42
+ base_url = "http://" + base_url
43
+ self.base_url = base_url.rstrip('/')
44
+
45
+ # Initialize authentication with SSL verification setting
46
+ self.auth = Auth(self.base_url, verify_ssl=verify_ssl)
47
+
48
+ if not verify_ssl:
49
+ # ignore warnings
50
+ import urllib3
51
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
52
+
53
+ # Initialize endpoints
54
+ self.queries = QueriesEndpoint(self.base_url, self.auth)
55
+ self.users = UsersEndpoint(self.base_url, self.auth)
56
+ self.metadata = MetadataEndpoint(self.base_url, self.auth)
57
+ #self.admin = AdminEndpoint(self.base_url, self.auth)
58
+ self.images = ImagesEndpoint(self.base_url, self.auth)
59
+ self.lupton_images = LuptonImagesEndpoint(self.base_url, self.auth)
60
+ self.stamp_images = StampImagesEndpoint(self.base_url, self.auth)
61
+ self.trilogy_images = TrilogyImagesEndpoint(self.base_url, self.auth)
62
+
63
+ # Authenticate if credentials provided
64
+ if username and password:
65
+ self.login(username, password, **kwargs)
66
+
67
+ def login(self, username: str, password: str, **kwargs) -> User:
68
+ """
69
+ Log in with username and password.
70
+
71
+ Args:
72
+ username: User's username
73
+ password: User's password
74
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
75
+
76
+ Returns:
77
+ User object for the authenticated user
78
+
79
+ Raises:
80
+ AuthenticationError: If authentication fails
81
+ """
82
+ _, user = self.auth.login(username, password, **kwargs)
83
+ return user
84
+
85
+ def logout(self) -> None:
86
+ """Log out the current user."""
87
+ self.auth.logout()
88
+
89
+ @property
90
+ def is_authenticated(self) -> bool:
91
+ """Check if the client is currently authenticated."""
92
+ return self.auth.is_authenticated()
93
+
94
+ @property
95
+ def current_user(self) -> Optional[User]:
96
+ """Get the currently authenticated user, or None if not authenticated."""
97
+ return self.auth.current_user
98
+
99
+ def register(self, username: str, email: str, password: str, full_name: Optional[str] = None, **kwargs) -> User:
100
+ """
101
+ Register a new user account.
102
+
103
+ Args:
104
+ username: Desired username
105
+ email: User's email address
106
+ password: User's password
107
+ full_name: Optional full name
108
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
109
+
110
+ Returns:
111
+ The newly created User object
112
+ """
113
+ return self.users.register(username, email, password, full_name, **kwargs)
114
+
115
+ def query(self,
116
+ query_text: str,
117
+ mode: str = 'adql',
118
+ file: Optional[Union[str, BinaryIO]] = None,
119
+ table_name: Optional[str] = None,
120
+ **kwargs) -> QueryResult:
121
+ """
122
+ Execute a query synchronously.
123
+
124
+ Args:
125
+ query_text: The query to execute (ADQL or SQL)
126
+ mode: Query mode ('adql' or 'sql')
127
+ file: Optional file path or file-like object to upload as a temporary table
128
+ table_name: Name for the uploaded table (required if file is provided)
129
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
130
+
131
+ Returns:
132
+ QueryResult object containing the query data and metadata
133
+ """
134
+ return self.queries.execute_sync(query_text, mode, file, table_name, **kwargs)
135
+
136
+ def async_query(self,
137
+ query_text: str,
138
+ mode: str = 'adql',
139
+ file: Optional[Union[str, BinaryIO]] = None,
140
+ table_name: Optional[str] = None,
141
+ **kwargs) -> Query:
142
+ """
143
+ Start an asynchronous query.
144
+
145
+ Args:
146
+ query_text: The query to execute (ADQL or SQL)
147
+ mode: Query mode ('adql' or 'sql')
148
+ file: Optional file path or file-like object to upload as a temporary table
149
+ table_name: Name for the uploaded table (required if file is provided)
150
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
151
+
152
+ Returns:
153
+ Query object with status information
154
+ """
155
+ return self.queries.execute_async(query_text, mode, file, table_name, **kwargs)
156
+
157
+ def get_query_status(self, query_id: str, **kwargs) -> Query:
158
+ """
159
+ Get the status of an asynchronous query.
160
+
161
+ Args:
162
+ query_id: ID of the query to check
163
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
164
+
165
+ Returns:
166
+ Updated Query object with current status
167
+ """
168
+ return self.queries.get_status(query_id, **kwargs)
169
+
170
+ def get_query_results(self, query_id: str, **kwargs) -> QueryResult:
171
+ """
172
+ Get the results of a completed asynchronous query.
173
+
174
+ Args:
175
+ query_id: ID of the completed query
176
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
177
+
178
+ Returns:
179
+ QueryResult object with the query data
180
+ """
181
+ return self.queries.get_results(query_id, **kwargs)
182
+
183
+ def cancel_query(self, query_id: str, **kwargs) -> bool:
184
+ """
185
+ Cancel an asynchronous query.
186
+
187
+ Args:
188
+ query_id: ID of the query to cancel
189
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
190
+
191
+ Returns:
192
+ True if the query was successfully canceled
193
+ """
194
+ return self.queries.cancel_query(query_id, **kwargs)
195
+
196
+ def query_and_wait(self,
197
+ query_text: str,
198
+ mode: str = 'adql',
199
+ file: Optional[Union[str, BinaryIO]] = None,
200
+ table_name: Optional[str] = None,
201
+ timeout: Optional[int] = None,
202
+ **kwargs) -> QueryResult:
203
+ """
204
+ Execute a query asynchronously and wait for the results.
205
+
206
+ Args:
207
+ query_text: The query to execute (ADQL or SQL)
208
+ mode: Query mode ('adql' or 'sql')
209
+ file: Optional file path or file-like object to upload as a temporary table
210
+ table_name: Name for the uploaded table (required if file is provided)
211
+ timeout: Maximum time to wait in seconds (None for no timeout)
212
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
213
+
214
+ Returns:
215
+ QueryResult object containing the query data and metadata
216
+ """
217
+ return self.queries.execute_and_wait(query_text, mode, file, table_name, timeout, **kwargs)
218
+
219
+ def get_query_history(self, limit: int = 50, **kwargs) -> List[Query]:
220
+ """
221
+ Get the current user's query history.
222
+
223
+ Args:
224
+ limit: Maximum number of queries to return
225
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
226
+
227
+ Returns:
228
+ List of Query objects representing past queries
229
+
230
+ Raises:
231
+ AuthenticationError: If not authenticated
232
+ """
233
+ if not self.is_authenticated:
234
+ raise AuthenticationError("Authentication required to access query history")
235
+
236
+ return self.queries.get_history(limit, **kwargs)
237
+
238
+ def get_schemas(self, **kwargs) -> List[str]:
239
+ """
240
+ Get a list of accessible database schemas.
241
+
242
+ Args:
243
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
244
+
245
+ Returns:
246
+ List of schema names accessible to the current user
247
+ """
248
+ return self.metadata.get_schemas(**kwargs)
249
+
250
+ def get_tables(self, schema_name: str, **kwargs) -> List[str]:
251
+ """
252
+ Get a list of accessible tables in a schema.
253
+
254
+ Args:
255
+ schema_name: Name of the schema
256
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
257
+
258
+ Returns:
259
+ List of table names in the schema accessible to the current user
260
+ """
261
+ return self.metadata.get_tables(schema_name, **kwargs)
262
+
263
+ def get_columns(self, schema_name: str, table_name: str, **kwargs) -> List[Column]:
264
+ """
265
+ Get a list of columns in a table.
266
+
267
+ Args:
268
+ schema_name: Name of the schema
269
+ table_name: Name of the table
270
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
271
+
272
+ Returns:
273
+ List of Column objects in the table
274
+ """
275
+ return self.metadata.get_columns(schema_name, table_name, **kwargs)
276
+
277
+ def get_database_metadata(self, **kwargs) -> DatabaseMetadata:
278
+ """
279
+ Get comprehensive database metadata for accessible schemas and tables.
280
+
281
+ Args:
282
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
283
+
284
+ Returns:
285
+ DatabaseMetadata object containing all accessible schema and table information
286
+ """
287
+ return self.metadata.get_database_metadata(**kwargs)
288
+
289
+ def update_profile(self,
290
+ email: Optional[str] = None,
291
+ full_name: Optional[str] = None,
292
+ **kwargs) -> User:
293
+ """
294
+ Update the current user's profile information.
295
+
296
+ Args:
297
+ email: New email address (optional)
298
+ full_name: New full name (optional)
299
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
300
+
301
+ Returns:
302
+ The updated User object
303
+
304
+ Raises:
305
+ AuthenticationError: If not authenticated
306
+ """
307
+ if not self.is_authenticated:
308
+ raise AuthenticationError("Authentication required to update profile")
309
+
310
+ return self.users.update_profile(email, full_name, **kwargs)
311
+
312
+ # Admin methods (these require superuser/staff privileges)
313
+
314
+ def create_role(self, name: str, description: Optional[str] = None, **kwargs) -> Role:
315
+ """
316
+ Create a new role (superuser only).
317
+
318
+ Args:
319
+ name: Role name
320
+ description: Optional role description
321
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
322
+
323
+ Returns:
324
+ The newly created Role object
325
+ """
326
+ return self.admin.create_role(name, description, **kwargs)
327
+
328
+ def get_roles(self, skip: int = 0, limit: int = 100, **kwargs) -> List[Role]:
329
+ """
330
+ Get a list of roles (staff only).
331
+
332
+ Args:
333
+ skip: Number of roles to skip (for pagination)
334
+ limit: Maximum number of roles to return
335
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
336
+
337
+ Returns:
338
+ List of Role objects
339
+ """
340
+ return self.admin.get_roles(skip, limit, **kwargs)
341
+
342
+ def add_user_to_role(self, user_id: str, role_id: int, **kwargs) -> bool:
343
+ """
344
+ Add a user to a role (superuser only).
345
+
346
+ Args:
347
+ user_id: ID of the user
348
+ role_id: ID of the role
349
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
350
+
351
+ Returns:
352
+ True if the user was successfully added to the role
353
+ """
354
+ return self.admin.add_user_to_role(user_id, role_id, **kwargs)
355
+
356
+ def remove_user_from_role(self, user_id: str, role_id: int, **kwargs) -> bool:
357
+ """
358
+ Remove a user from a role (superuser only).
359
+
360
+ Args:
361
+ user_id: ID of the user
362
+ role_id: ID of the role
363
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
364
+
365
+ Returns:
366
+ True if the user was successfully removed from the role
367
+ """
368
+ return self.admin.remove_user_from_role(user_id, role_id, **kwargs)
369
+
370
+ def add_schema_permission(self, role_id: int, schema_name: str, permission: str, **kwargs) -> bool:
371
+ """
372
+ Add a schema permission to a role (superuser only).
373
+
374
+ Args:
375
+ role_id: ID of the role
376
+ schema_name: Name of the schema
377
+ permission: Permission type ('read', 'write', or 'all')
378
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
379
+
380
+ Returns:
381
+ True if the permission was successfully added
382
+ """
383
+ return self.admin.add_schema_permission(role_id, schema_name, permission, **kwargs)
384
+
385
+ def add_table_permission(self, role_id: int, schema_name: str, table_name: str, permission: str, **kwargs) -> bool:
386
+ """
387
+ Add a table permission to a role (superuser only).
388
+
389
+ Args:
390
+ role_id: ID of the role
391
+ schema_name: Name of the schema
392
+ table_name: Name of the table
393
+ permission: Permission type ('read', 'write', or 'all')
394
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
395
+
396
+ Returns:
397
+ True if the permission was successfully added
398
+ """
399
+ return self.admin.add_table_permission(role_id, schema_name, table_name, permission, **kwargs)
400
+
401
+ def remove_schema_permission(self, role_id: int, schema_name: str, **kwargs) -> bool:
402
+ """
403
+ Remove a schema permission from a role (superuser only).
404
+
405
+ Args:
406
+ role_id: ID of the role
407
+ schema_name: Name of the schema
408
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
409
+
410
+ Returns:
411
+ True if the permission was successfully removed
412
+ """
413
+ return self.admin.remove_schema_permission(role_id, schema_name, **kwargs)
414
+
415
+ def remove_table_permission(self, role_id: int, schema_name: str, table_name: str, **kwargs) -> bool:
416
+ """
417
+ Remove a table permission from a role (superuser only).
418
+
419
+ Args:
420
+ role_id: ID of the role
421
+ schema_name: Name of the schema
422
+ table_name: Name of the table
423
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
424
+
425
+ Returns:
426
+ True if the permission was successfully removed
427
+ """
428
+ return self.admin.remove_table_permission(role_id, schema_name, table_name, **kwargs)
429
+
430
+ def get_role_permissions(self, role_id: int, **kwargs) -> Dict[str, Any]:
431
+ """
432
+ Get permissions for a role (staff only).
433
+
434
+ Args:
435
+ role_id: ID of the role
436
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
437
+
438
+ Returns:
439
+ Dictionary containing schema and table permissions
440
+ """
441
+ return self.admin.get_role_permissions(role_id, **kwargs)
442
+
443
+ # === Image methods ===
444
+
445
+ def get_image_collections(self, skip: int = 0, limit: int = 100, **kwargs) -> List[Dict[str, Any]]:
446
+ """
447
+ Get a list of accessible image collections.
448
+
449
+ Args:
450
+ skip: Number of collections to skip (for pagination)
451
+ limit: Maximum number of collections to return
452
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
453
+
454
+ Returns:
455
+ List of image collection objects
456
+ """
457
+ return self.images.get_collections(skip, limit, **kwargs)
458
+
459
+ def get_image_collection(self, collection_id: int, **kwargs) -> Dict[str, Any]:
460
+ """
461
+ Get a specific image collection by ID.
462
+
463
+ Args:
464
+ collection_id: ID of the collection to retrieve
465
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
466
+
467
+ Returns:
468
+ Image collection object
469
+ """
470
+ return self.images.get_collection(collection_id, **kwargs)
471
+
472
+ def list_image_files(self, collection_id: int, skip: int = 0, limit: int = 100,
473
+ filter_name: Optional[str] = None, filter_str: Optional[str] = None,
474
+ object_name: Optional[str] = None, **kwargs) -> List[Dict[str, Any]]:
475
+ """
476
+ List image files in a collection with optional filtering.
477
+
478
+ Args:
479
+ collection_id: ID of the image collection
480
+ skip: Number of files to skip (for pagination)
481
+ limit: Maximum number of files to return
482
+ filter_name: Filter by specific filter name (e.g., 'r', 'g', 'i')
483
+ filter_str: Filter filenames that contain this string
484
+ object_name: Filter by object name
485
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
486
+
487
+ Returns:
488
+ List of image file objects
489
+ """
490
+ return self.images.list_files(collection_id, skip, limit, filter_name, filter_str, object_name, **kwargs)
491
+
492
+ def cone_search_images(self, collection_id: int, ra: float, dec: float, radius: float,
493
+ filter_name: Optional[str] = None, limit: int = 100, **kwargs) -> List[Dict[str, Any]]:
494
+ """
495
+ Find images containing a position using a cone search.
496
+
497
+ Args:
498
+ collection_id: ID of the image collection
499
+ ra: Right ascension in degrees
500
+ dec: Declination in degrees
501
+ radius: Search radius in degrees
502
+ filter_name: Optional filter name to restrict results
503
+ limit: Maximum number of results to return
504
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
505
+
506
+ Returns:
507
+ List of image file objects that intersect with the search cone
508
+ """
509
+ return self.images.cone_search(collection_id, ra, dec, radius, filter_name, limit, **kwargs)
510
+
511
+ def download_image(self, file_id: int, output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
512
+ """
513
+ Download an image file.
514
+
515
+ Args:
516
+ file_id: ID of the image file to download
517
+ output_path: Optional path to save the file to. If not provided, the file content is returned as bytes.
518
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
519
+
520
+ Returns:
521
+ If output_path is provided, returns the path to the saved file.
522
+ Otherwise, returns the file content as bytes.
523
+ """
524
+ return self.images.download_file(file_id, output_path, **kwargs)
525
+
526
+ def create_rgb_image(self, r_file_id: int, g_file_id: int, b_file_id: int,
527
+ ra: Optional[float] = None, dec: Optional[float] = None,
528
+ size: Optional[float] = None, size_unit: str = "arcmin",
529
+ stretch: float = 3.0, Q: float = 8.0,
530
+ output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
531
+ """
532
+ Create an RGB composite image using Lupton's method from three images using file IDs.
533
+
534
+ Args:
535
+ r_file_id: ID of the red channel image file
536
+ g_file_id: ID of the green channel image file
537
+ b_file_id: ID of the blue channel image file
538
+ ra: Optional right ascension in degrees (for cutout)
539
+ dec: Optional declination in degrees (for cutout)
540
+ size: Optional size in arcminutes by default
541
+ size_unit: Units for size ("arcmin", "arcsec", or "pixels")
542
+ stretch: Stretch parameter for Lupton algorithm
543
+ Q: Q parameter for Lupton algorithm
544
+ output_path: Optional path to save the image to. If not provided, the image data is returned as bytes.
545
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
546
+
547
+ Returns:
548
+ If output_path is provided, returns the path to the saved file.
549
+ Otherwise, returns the image data as bytes.
550
+ """
551
+ return self.lupton_images.create_rgb(r_file_id, g_file_id, b_file_id, ra, dec, size,
552
+ size_unit, stretch, Q, output_path, **kwargs)
553
+
554
+ def create_rgb_image_by_coordinates(self, collection_id: int, ra: float, dec: float,
555
+ size: float, r_filter: str, g_filter: str, b_filter: str,
556
+ size_unit: str = "arcmin", stretch: float = 3.0, Q: float = 8.0,
557
+ pattern: Optional[str] = None, output_path: Optional[str] = None,
558
+ **kwargs) -> Union[bytes, str]:
559
+ """
560
+ Create an RGB composite image using Lupton's method by finding the nearest images to given coordinates.
561
+
562
+ Args:
563
+ collection_id: ID of the image collection
564
+ ra: Right ascension in degrees
565
+ dec: Declination in degrees
566
+ size: Size in arcminutes by default
567
+ r_filter: Filter name for the red channel
568
+ g_filter: Filter name for the green channel
569
+ b_filter: Filter name for the blue channel
570
+ size_unit: Units for size ("arcmin", "arcsec", or "pixels")
571
+ stretch: Stretch parameter for Lupton algorithm
572
+ Q: Q parameter for Lupton algorithm
573
+ pattern: Optional pattern to match filenames
574
+ output_path: Optional path to save the image to. If not provided, the image data is returned as bytes.
575
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
576
+
577
+ Returns:
578
+ If output_path is provided, returns the path to the saved file.
579
+ Otherwise, returns the image data as bytes.
580
+ """
581
+ return self.lupton_images.create_rgb_by_coordinates(collection_id, ra, dec, size,
582
+ r_filter, g_filter, b_filter,
583
+ size_unit, stretch, Q, pattern,
584
+ output_path, **kwargs)
585
+
586
+ def create_stamp(self, file_id: int, ra: float, dec: float, size: float,
587
+ size_unit: str = "arcmin", format: str = "fits",
588
+ zmin: Optional[float] = None, zmax: Optional[float] = None,
589
+ output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
590
+ """
591
+ Create a postage stamp cutout from an image.
592
+
593
+ Args:
594
+ file_id: ID of the image file
595
+ ra: Right ascension in degrees
596
+ dec: Declination in degrees
597
+ size: Size of the cutout
598
+ size_unit: Units for size ("arcmin", "arcsec", or "pixels")
599
+ format: Output format ("fits" or "png")
600
+ zmin: Optional minimum intensity percentile for PNG output
601
+ zmax: Optional maximum intensity percentile for PNG output
602
+ output_path: Optional path to save the stamp to. If not provided, the image data is returned as bytes.
603
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
604
+
605
+ Returns:
606
+ If output_path is provided, returns the path to the saved file.
607
+ Otherwise, returns the image data as bytes.
608
+ """
609
+ return self.stamp_images.create_stamp(file_id, ra, dec, size, size_unit, format,
610
+ zmin, zmax, output_path, **kwargs)
611
+
612
+ def create_stamp_by_coordinates(self, collection_id: int, ra: float, dec: float,
613
+ size: float, filter: str, size_unit: str = "arcmin",
614
+ format: str = "fits", zmin: Optional[float] = None,
615
+ zmax: Optional[float] = None, pattern: Optional[str] = None,
616
+ output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
617
+ """
618
+ Create a postage stamp by finding the nearest image to given coordinates in a specific filter.
619
+
620
+ Args:
621
+ collection_id: ID of the image collection
622
+ ra: Right ascension in degrees
623
+ dec: Declination in degrees
624
+ size: Size of the cutout
625
+ filter: Filter name
626
+ size_unit: Units for size ("arcmin", "arcsec", or "pixels")
627
+ format: Output format ("fits" or "png")
628
+ zmin: Optional minimum intensity percentile for PNG output
629
+ zmax: Optional maximum intensity percentile for PNG output
630
+ pattern: Optional pattern to match filenames
631
+ output_path: Optional path to save the stamp to. If not provided, the image data is returned as bytes.
632
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
633
+
634
+ Returns:
635
+ If output_path is provided, returns the path to the saved file.
636
+ Otherwise, returns the image data as bytes.
637
+ """
638
+ return self.stamp_images.create_stamp_by_coordinates(collection_id, ra, dec, size, filter,
639
+ size_unit, format, zmin, zmax, pattern,
640
+ output_path, **kwargs)
641
+
642
+ def create_trilogy_rgb(self, r_file_ids: List[int], g_file_ids: List[int], b_file_ids: List[int],
643
+ ra: Optional[float] = None, dec: Optional[float] = None,
644
+ size: Optional[float] = None, size_unit: str = "arcmin",
645
+ noiselum: float = 0.15, satpercent: float = 15.0, colorsatfac: float = 2.0,
646
+ output_path: Optional[str] = None, **kwargs) -> Union[bytes, str]:
647
+ """
648
+ Create an RGB composite image using the Trilogy method from multiple images per channel.
649
+
650
+ Args:
651
+ r_file_ids: List of IDs for red channel image files
652
+ g_file_ids: List of IDs for green channel image files
653
+ b_file_ids: List of IDs for blue channel image files
654
+ ra: Optional right ascension in degrees (for cutout)
655
+ dec: Optional declination in degrees (for cutout)
656
+ size: Optional size in arcminutes by default
657
+ size_unit: Units for size ("arcmin", "arcsec", or "pixels")
658
+ noiselum: Noise luminance parameter for Trilogy algorithm
659
+ satpercent: Saturation percentage parameter for Trilogy algorithm
660
+ colorsatfac: Color saturation factor for Trilogy algorithm
661
+ output_path: Optional path to save the image to. If not provided, the image data is returned as bytes.
662
+ **kwargs: Additional keyword arguments to pass to the request (e.g., verify=False)
663
+
664
+ Returns:
665
+ If output_path is provided, returns the path to the saved file.
666
+ Otherwise, returns the image data as bytes.
667
+ """
668
+ return self.trilogy_images.create_trilogy_rgb(r_file_ids, g_file_ids, b_file_ids,
669
+ ra, dec, size, size_unit,
670
+ noiselum, satpercent, colorsatfac,
671
+ output_path, **kwargs)
@@ -0,0 +1,14 @@
1
+ """
2
+ API endpoint handlers for the Astronomy TAP Client.
3
+ """
4
+
5
+ from .queries import QueriesEndpoint
6
+ from .users import UsersEndpoint
7
+ from .metadata import MetadataEndpoint
8
+ from .admin import AdminEndpoint
9
+ from .images import ImagesEndpoint, LuptonImagesEndpoint, StampImagesEndpoint, TrilogyImagesEndpoint
10
+
11
+ __all__ = [
12
+ 'QueriesEndpoint', 'UsersEndpoint', 'MetadataEndpoint', 'AdminEndpoint',
13
+ 'ImagesEndpoint', 'LuptonImagesEndpoint', 'StampImagesEndpoint', 'TrilogyImagesEndpoint'
14
+ ]