Commit 519258d7d2919e97330d71ddf89ca9c59b73e94e

Authored by Alain Klotz
1 parent 7a80ffac
Exists in master

Add doc for components and devices.

docs/source/device_definition.rst 0 → 100644
... ... @@ -0,0 +1,78 @@
  1 +***********************************************
  2 +Device management with guitastro Python classes
  3 +***********************************************
  4 +
  5 +This section explains how to use guitastro to drive a device.
  6 +
  7 +1. Context
  8 +**********
  9 +
  10 +A device is a system constituted by:
  11 +
  12 + * **communication**: Communication means that messages to drive the device are exchanged between guitastro and the device. Messages can be transported by Serial or TCP protocols.
  13 + * **components**: A component is a part of the device identified to realize an astronomical action. For example a focuser is a component. Some devices are constituted by many compenents. For example a camera équiped by a shutter and a filter wheel is a device constituted by 3 components.
  14 +
  15 +In Guitastro we define the following **categories** of components:
  16 +
  17 + * **MountPointing**: It is a mechanical motorized mount able to point a direction in the sky using two axes.
  18 + * **DetectorFocuser**: It is a mechanical motorized motor placed close the detector to able changing the focus.
  19 +
  20 +The communication chanel is the same for all the components.
  21 +The commands are contents to pilot the components. There are two types of commands:
  22 +
  23 + * **uniform**: From the side of a Guitastro user, the commands are the same for a given component category. For example to slew a mount the command is always "DO RADEC_GOTO".
  24 + * **native**: From the side of device controler, the commands are translated by guitastro into the device manufactured grammar.
  25 +
  26 +A uniform command is constituted by an **action**, an **operation** and optional parameters:
  27 +
  28 + * **action**: Main actions are GET, SET, DO. GET and SET manage variables and DO manage messages towards the device.
  29 + * **operation**: It is a word that depends on the action.
  30 +
  31 +2. Commands of a component
  32 +**************************
  33 +
  34 +A command can be generalized as component/action/operation/parameters
  35 +
  36 +When the component/action = MountPointing/SET, the operation is a variable symbol and parameters is the variable value:
  37 +
  38 +.. code-block:: python
  39 +
  40 + import guitastro
  41 + comp = ComponentMountPointing()
  42 + comp.command("SET", "target", "RADEC 12h56m -08d45m")
  43 +
  44 +When the component/action = MountPointing/GET, the operation is a variable symbol. The result is the variable value:
  45 +
  46 +.. code-block:: python
  47 +
  48 + import guitastro
  49 + comp = ComponentMountPointing()
  50 + comp.command("GET", "target")
  51 +
  52 +When the component/action/operation = MountPointing/DO/RADEC_GOTO, the operation start a slewing to the target defined in the target variable previously defined:
  53 +
  54 +.. code-block:: python
  55 +
  56 + import guitastro
  57 + comp = ComponentMountPointing()
  58 + comp.command("SET", "target", "RADEC 12h56m -08d45m")
  59 + comp.command("DO", "RADEC_GOTO")
  60 +
  61 +
  62 +A given category of component have a list of operations. For example for a MountPointing:
  63 +
  64 + * **RADEC_COORD**: Returns the current coordinates in the RA,Dec system
  65 + * **RADEC_GOTO**: Slew the mount until a target previously defined by a command
  66 +
  67 +3. Integration of components into devices for Guitastro
  68 +*******************************************************
  69 +
  70 +The device codes a written outside the guitastro module.
  71 +For a given device the code is a module. For example, guitastro_device_meade_mounts.
  72 +The device modules are downloaded in the site of Guitastro.
  73 +To drive a device it is necessary to install guitastro the device module.
  74 +
  75 +4. Simulation and real devices
  76 +******************************
  77 +
  78 +Blabla.
... ...
docs/source/index.rst
... ... @@ -153,7 +153,15 @@ Describe how to instanciate guitastro classes to use them in a personal Python c
153 153  
154 154 using_guitastro_classes
155 155  
156   -9. Class and method documentation
  156 +9. Driving devices with guitastro
  157 +*********************************
  158 +
  159 +.. toctree::
  160 + :maxdepth: 3
  161 +
  162 + device_definition
  163 +
  164 +10. Class and method documentation
157 165 ***********************************
158 166  
159 167 For developers of Python code of GuitAstro:
... ...
docs/source/using_guitastro_classes.rst
... ... @@ -62,10 +62,6 @@ We want to compute sum of the angles 2h3m27s and -0d28m12.4s. Display the result
62 62 4. Using the Python module guitastro for dates
63 63 **********************************************
64 64  
65   -Celme module is located in the folder pyros/src/utils
66   -Examples: pyros/src/utils/report/dates.py
67   -help(Date)
68   -
69 65 4.1. Simple example to convert dates
70 66 ====================================
71 67  
... ... @@ -97,13 +93,6 @@ Date ISO = 2018-10-13T23:13:44.958
97 93 5. Using the Python module guitastro for ephemeris
98 94 **************************************************
99 95  
100   -| Celme module is located in the folder pyros/src/utils
101   -| Examples: pyros/src/utils/report/targets.py
102   -
103   -.. code-block:: python
104   -
105   - help(Target)
106   -
107 96 5.1. Simple example to compute Sun coordinates
108 97 ==============================================
109 98  
... ...
src/guitastro/__init__.py
... ... @@ -120,6 +120,26 @@ guitastro.mount
120 120 .. automodule:: guitastro.mount
121 121 :members:
122 122  
  123 +guitastro.communications
  124 +------------------------
  125 +.. automodule:: guitastro.communications
  126 + :members:
  127 +
  128 +guitastro.component
  129 +-------------------
  130 +.. automodule:: guitastro.component
  131 + :members:
  132 +
  133 +guitastro.component_detector_focuser
  134 +------------------------------------
  135 +.. automodule:: guitastro.component_detector_focuser
  136 + :members:
  137 +
  138 +guitastro.component_mount_pointing
  139 +------------------------------------
  140 +.. automodule:: guitastro.component_mount_pointing
  141 + :members:
  142 +
123 143 """
124 144 from __future__ import (absolute_import, division, print_function,
125 145 unicode_literals)
... ... @@ -146,6 +166,10 @@ from .filenames import FileNames
146 166 from .astrotables import AstroTable
147 167 from .splinefit import Splinefit
148 168  
  169 +from .component import Component
  170 +from .component_mount_pointing import ComponentMountPointing
  171 +from .component_detector_focuser import ComponentDetectorFocuser
  172 +
149 173 from .camera import Camera
150 174 from .mount import Mount
151 175  
... ...
src/guitastro/communications.py
... ... @@ -48,7 +48,65 @@ class CommunicationException(GuitastroException):
48 48 class Communication():
49 49 """Communication channel manager
50 50  
51   - Usage : Communication("SERIAL", port="//./COM1")
  51 + This class allows to open and close TCP or SERIAL communications.
  52 + There are methods to put and read messages.
  53 +
  54 + :Example:
  55 +
  56 + To get only informations on communication channels SERIAL and TCP:
  57 + ::
  58 +
  59 + >>> chan = Communication("INFOS")
  60 + >>> available_serial_ports = chan.get_available_serial_ports()
  61 + >>> my_ip_address = chan.get_ip()
  62 +
  63 + To open a SERIAL port:
  64 + ::
  65 +
  66 + >>> chan = Communication("SERIAL", port="//./COM1", baud_rate=9600)
  67 +
  68 + To open a TCP port:
  69 + ::
  70 +
  71 + >>> chan = Communication("TCP", hostname="192.168.0.4", port="8080")
  72 +
  73 + To close an opened port:
  74 + ::
  75 +
  76 + >>> chan.close()
  77 +
  78 + To send a message through an opened port:
  79 + ::
  80 +
  81 + >>> chan.put("Hello")
  82 +
  83 + To read a message through an opened port:
  84 + ::
  85 +
  86 + >>> result = chan.read()
  87 +
  88 + Very often protocols need to add suffix characters to the messages.
  89 + The following optional keys can be added to Communication:
  90 +
  91 + * END_OF_COMMAND_TO_SEND (str): Suffix systematically added at the end of messages sent.
  92 + * END_OF_COMMAND_TO_RECEIVE (str): Suffix systematically suppressed at the end of messages received.
  93 + * DELAY_INIT_CHAN (float): Delay (seconds) executed just after the communication opening because some devices cannot be joined before.
  94 + * DELAY_PUT_READ (float): Delay (seconds) between put and read commands.
  95 +
  96 + :Example:
  97 +
  98 + Open a serial port with many parameters, send a message a wait for a response:
  99 +
  100 + ::
  101 +
  102 + chan = Communication("SERIAL", port="//./COM1", baud_rate=9600, end_of_command_to_send="\\n", delay_ini_chan=1.8, delay_put_read=0.2)
  103 + result = chan.putread("Hello")
  104 +
  105 + It is possible to open a simulation of the communication channel .
  106 + To do that the following optional keys can be added to Communication:
  107 +
  108 + * REAL (bool): Default value is True
  109 +
52 110 """
53 111  
54 112 # === Constant for error codes
... ... @@ -178,9 +236,22 @@ class Communication():
178 236 self._hostname_chan = self._channel_params["HOSTNAME"]
179 237 self._real = self._channel_params["REAL"]
180 238  
181   - def get_ip(self):
182   - """
183   - Tool to return the IP of this computer
  239 + def get_ip(self)->str:
  240 + """Tool to return the IP of this computer
  241 +
  242 + Returns:
  243 +
  244 + The IP addresse of the local machine.
  245 +
  246 + :Example:
  247 +
  248 + To get the IP of the computer:
  249 +
  250 + ::
  251 +
  252 + >>> chan = Communication("INFOS")
  253 + >>> my_ip_address = chan.get_ip()
  254 +
184 255 """
185 256 ip = '127.0.0.1'
186 257 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
... ... @@ -207,6 +278,20 @@ class Communication():
207 278  
208 279 def get_available_serial_ports(self):
209 280 """Return a list of available serial ports of the local computer
  281 +
  282 + Returns:
  283 +
  284 + The list of serial port names of the local machine
  285 +
  286 + :Example:
  287 +
  288 + To get the IP of the computer:
  289 +
  290 + ::
  291 +
  292 + >>> chan = Communication("INFOS")
  293 + >>> available_serial_ports = chan.get_available_serial_ports()
  294 +
210 295 """
211 296 # --
212 297 prefix_myserial_ports = ""
... ... @@ -245,6 +330,8 @@ class Communication():
245 330 return self.NO_ERROR
246 331  
247 332 def open_chan(self):
  333 + """Open the channel
  334 + """
248 335 err = self.NO_ERROR
249 336 if self._real == False:
250 337 fid = "simulation"
... ... @@ -315,6 +402,8 @@ class Communication():
315 402 return self.NO_ERROR
316 403  
317 404 def close_chan(self):
  405 + """Close the channel
  406 + """
318 407 err = self.NO_ERROR
319 408 fid = self._fid_chan
320 409 if self._fid_chan == None:
... ... @@ -343,6 +432,11 @@ class Communication():
343 432  
344 433 def put_chan(self, cmd: str):
345 434 """ Send a message into the opened channel
  435 +
  436 + Args:
  437 +
  438 + cmd: Message to send. Suffix defined by the option key END_OF_COMMAND_TO_SEND is added inside the method.
  439 +
346 440 """
347 441 # --- check the opening of the serial port
348 442 err, fid = self.check_chan()
... ... @@ -361,6 +455,13 @@ class Communication():
361 455 return (err, cmd)
362 456  
363 457 def read_chan(self):
  458 + """ Receive a message from the opened channel
  459 +
  460 + Returns:
  461 +
  462 + The string of the message read.
  463 +
  464 + """
364 465 # --- check the opening of the serial port
365 466 err, fid = self.check_chan()
366 467 lignes = "" ; # fid.readlines()
... ... @@ -446,16 +547,37 @@ class Communication():
446 547 delay_init_chan = property(_get_delay_init_chan, _set_delay_init_chan)
447 548 verbose_chan = property(_get_verbose_chan, _set_verbose_chan)
448 549  
449   - def putread_chan(self, cmd, index=-1):
450   - return self.putread_chan_check(cmd, index)
  550 + def putread_chan(self, cmd:str, index:int=-1):
  551 + """ Send and Receive a message from the opened channel
  552 +
  553 + Args:
451 554  
452   - def putread_chan_nocheck(self, cmd, index=-1):
  555 + cmd: The message to send
  556 + index: >0 to extract only the word returned at the index position
  557 +
  558 + Returns:
  559 +
  560 + A string containing the response received.
453 561 """
454   - cmd is the message to send
455   - index >0 to extract only the word returned at the index position
  562 + return self.putread_chan_check(cmd, index)
  563 +
  564 + def putread_chan_nocheck(self, cmd:str, index:int=-1):
  565 + """ Send and Receive a message from the opened channel
  566 +
  567 + No lock version. See comments of the putread_chan_check
  568 +
  569 + Args:
  570 +
  571 + cmd: The message to send
  572 + index: >0 to extract only the word returned at the index position
  573 +
  574 + Returns:
  575 +
  576 + A string containing the response received.
456 577 """
457 578 if self.verbose_chan==True:
458   - self.log.self.log.print("Putread: "+cmd)
  579 + #self.log.self.log.print("Putread: "+cmd)
  580 + pass
459 581 self._lock_putread = True
460 582 self.put_chan(cmd)
461 583 time.sleep(self._delay_put_read)
... ... @@ -474,11 +596,18 @@ class Communication():
474 596  
475 597 #def putread_chan_lock_verify(self, cmd, index=-1):
476 598 def putread_chan_check(self, cmd, index=-1):
477   - """
478   - Version with a lock to avoid multiple calls to the controller.
  599 + """ Send and Receive a message from the opened channel
  600 +
  601 + Same as putread_chan but a lock is added to avoid multiple calls to the controller.
  602 +
  603 + Args:
  604 +
  605 + cmd: The message to send
  606 + index: >0 to extract only the word returned at the index position
  607 +
  608 + Returns:
479 609  
480   - cmd is the message to send
481   - index >0 to extract only the word returned at the index position
  610 + A string containing the response received.
482 611 """
483 612 if self.verbose_chan==True:
484 613 # self.log.self.log.print("Putread: "+cmd)
... ... @@ -494,7 +623,7 @@ class Communication():
494 623 time.sleep(0.05)
495 624 dt = time.time() - t0
496 625 if dt>2.0:
497   - self.log.print("Serial port locked timeout for {}".format(cmd))
  626 + #self.log.print("Serial port locked timeout for {}".format(cmd))
498 627 break
499 628 # --- verify the lock state
500 629 if self._lock_putread == False:
... ...
src/guitastro/component.py
... ... @@ -56,8 +56,9 @@ class Component(ComponentException):
56 56  
57 57 A command of a component is constituted by:
58 58  
59   - * Action : A string word as GET, SET, DO
60   - * Parameters : List or/and dict depending the action
  59 + * Action: A string word as GET, SET, DO
  60 + * Operation: A string word that dependis on the action
  61 + * Parameters: List or/and dict depending the action/operation
61 62  
62 63 A command is sent to the component using the method command:
63 64  
... ... @@ -99,7 +100,7 @@ class Component(ComponentException):
99 100 Note that the parameters of an operation should be assigned by the Action SET
100 101 before calling DO.
101 102  
102   - :Example:
  103 + :Example:
103 104  
104 105 ::
105 106  
... ... @@ -222,8 +223,11 @@ class Component(ComponentException):
222 223 return res
223 224  
224 225 def parameters(self, action="", operation=""):
225   - """Get the list of operations for a given action
226   - or get the description of a given operation.
  226 + """Get the list of operations for a given action or get the description of a given operation.
  227 +
  228 + Args:
  229 +
  230 + action: If not given, returns the list of Actions
227 231 """
228 232 actions = self.actions
229 233 if action=="":
... ... @@ -244,7 +248,19 @@ class Component(ComponentException):
244 248 def command(self, action:str, *args, **kwargs):
245 249 """General method to send command.
246 250  
247   - Action must be a str amongst elements of the list returned by the method prop
  251 + Args:
  252 +
  253 + action: A str amongst elements of the list returned by the method prop
  254 + *args: args[0] should be an operation
  255 + **kwargs: Dictionary of options associated to the operation
  256 +
  257 + Returns:
  258 +
  259 + The result of the command.
  260 +
  261 + Use the method parameters(action) to get the available operations for a given action.
  262 + The kwargs are defined for each operation.
  263 +
248 264 """
249 265 result = None
250 266 action = action.upper()
... ...
src/guitastro/component_mount_pointing.py
... ... @@ -83,7 +83,7 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
83 83 # --- call the real
84 84 kwargs = {}
85 85 kwargs['computed'] = (ra, dec, equinox, epoch, dra, ddec)
86   - self._my_do(*args, **kwargs)
  86 + result = self._my_do(*args, **kwargs)
87 87 # --- update the motion
88 88 self._set("motion", "slewing")
89 89 else:
... ... @@ -96,10 +96,12 @@ class ComponentMountPointing(ComponentMountPointingException, Component, Guitast
96 96 # TODO
97 97 # --- update the motion
98 98 self._set("motion", "stoped")
  99 + result = self._my_do(*args, **kwargs)
99 100 elif operation == "RADEC_COORD":
100 101 # --- make the simulation
101 102 # TODO
102 103 self.log = f"start simulation of {operation}"
  104 + result = self._my_do(*args, **kwargs)
103 105 result = "12h00m45.78s -06d55m23.2s"
104 106 return self._fresult(result)
105 107  
... ...
src/guitastro/ephemeris.py
... ... @@ -443,21 +443,33 @@ class Ephemeris(EphemerisException, GuitastroTools):
443 443 return sra, sdec, equinox, t0
444 444  
445 445 def radec(self, target: str, **kwargs)-> tuple:
446   - """Return ra, dec, euqinox, epoch of a target.
  446 + """Return ra, dec, equinox, epoch of a target.
447 447  
448   - target is a string that starts with a keyword:
  448 + Args:
449 449  
450   - * RADEC: to indicate an equatorial Right Ascension, Declination position.
451   - * CELESTRACK: to indicate a satellite name in the Celestrack TLE files.
452   - * DATERADECS: to indicate a list of equatorial Right Ascension, Declination positions.
453   - * TLE: to indicate a satellite defined by its TLE (Two Line Elements).
  450 + target: A string that starts with a keyword:
454 451  
455   - kwargs keys can be:
  452 + * 'RADEC': Followed by an equatorial Right Ascension, Declination position.
  453 + * 'RADECDRIFT': Followed by an equatorial Right Ascension, Declination position and the drift.
  454 + * 'CELESTRACK' or 'TLEFILES': Followed by a satellite name in the Celestrack TLE files.
  455 + * 'DATERADECS': Followed by a list of equatorial Right Ascension, Declination positions.
  456 + * 'TLE': Followed by a satellite defined by its TLE (Two Line Elements).
  457 + * 'GCNC': Followed by a number which is a GRB name (e.g. GCNC 990123) to search information in GCN circulars.
  458 + * 'MPC': Followed by a name which is a Solar System Body.
456 459  
457   - * date: To compute ephemeris for a given date ("now" for now).
458   - * unit_ra: To indicate the output format (see Angle)
459   - * unit_dec: To indicate the output format (see Angle)
460   - * target_type: To indicate the input format if the keyword is not indicated in the target string.
  460 + **kwargs: A dictionary of options, keys can be:
  461 +
  462 + * 'date': To compute ephemeris for a given date ("now" for now).
  463 + * 'unit_ra': To indicate the output format (see Angle)
  464 + * 'unit_dec': To indicate the output format (see Angle)
  465 + * 'target_type': To indicate the input format if the keyword is not indicated in the target string.
  466 +
  467 + Returns:
  468 +
  469 + ra: Target Right Ascention position at the given date for a given equinox
  470 + dec: Target Declination at the given date for a given equinox
  471 + equinox: Equinox of the position
  472 + epoch: Date of the position when the object is moving
461 473  
462 474 """
463 475 equinox = "J2000"
... ... @@ -724,6 +736,21 @@ class Ephemeris(EphemerisException, GuitastroTools):
724 736  
725 737 def radec_speed(self, target, **kwargs):
726 738 """Return ra, dec J2000 of a target as method radec and add the speed (deg/s)
  739 +
  740 + Args:
  741 +
  742 + target: See radec for explanations
  743 + **kwargs: See radec for explanations
  744 +
  745 + Returns:
  746 +
  747 + ra: Target Right Ascention position at the given date for a given equinox
  748 + dec: Target Declination at the given date for a given equinox
  749 + equinox: Equinox of the position
  750 + epoch: Date of the position when the object is moving
  751 + dra: Velocity on Right Ascension axis (deg/s)
  752 + ddec: Velocity on Declination axis (deg/s)
  753 +
727 754 """
728 755 ra, dec, equinox, epoch = self.radec(target, **kwargs)
729 756 target = str(target)
... ... @@ -762,6 +789,16 @@ class Ephemeris(EphemerisException, GuitastroTools):
762 789  
763 790 def altitude2tp(self, alti:float, p0m:float=101325):
764 791 """Compute the theoretical pressure and temperature in the Earth atmosphere given an altitude
  792 +
  793 + Args:
  794 +
  795 + alti: The altitude of the observation site in meters
  796 + p0m: The pressure at the sea level. Default is 101325 Pascal.
  797 +
  798 + Returns:
  799 +
  800 + pressure: The pressure in Pascal
  801 + temperature: The temperature in Kelvin
765 802 """
766 803 tk0m=273.15+15
767 804 if alti<11000:
... ... @@ -832,6 +869,52 @@ class Ephemeris(EphemerisException, GuitastroTools):
832 869  
833 870 def target2night(self, target, night:str, ephem_sun:dict=None, ephem_moon:dict=None, **kwargs)->dict:
834 871 """Compute the ephemeris at every second of local coodinates for a night
  872 +
  873 + Two computations are importants:
  874 +
  875 + * 'observability': An integer defining why the target is visible or not.
  876 + * 'observability': A float value from 0 (not observable) to 100 (best conditions to observe)
  877 +
  878 + Args:
  879 +
  880 + target: A target in the formalism of the method radec
  881 + night: A night symbol (e.g. 20230330)
  882 + ephem_sun: A dictionary result of the method called target2night("sun", night)
  883 + ephem_moon: A dictionary result of the method called target2night("moon", night)
  884 + kwargs: A dictionary of options:
  885 +
  886 + * horizon: An object of the class Horizon of Guitastro that defines the horizon line.
  887 + * preference: A string "bestelev" or "immediate" to compute the observability
  888 + * duskelev: A float, the elevation of the Sun defining the start and end of the night.
  889 +
  890 + Returns:
  891 +
  892 + A dictionary:
  893 +
  894 + * 'night': String of the night
  895 + * 'home': String of the GPS position
  896 + * 'target': String of the input target
  897 + * 'ndate': Number of dates used to compute amers before interpolations
  898 + * 'jd': Numpy array of Julian days
  899 + * 'alt': Numpy array of the elevation (deg)
  900 + * 'az': Numpy array of the azimut (deg)
  901 + * 'ha': Numpy array of the apparent hour angle (deg)
  902 + * 'dec': Numpy array of the apparent declination (deg)
  903 + * 'ra_equinox': Numpy array of the Right Ascension at the input equinox (deg)
  904 + * 'dec_equinox': Numpy array of the Declination at the input equinox (deg)
  905 + * 'cosphi': Numpy array of cos(ra_equinox)
  906 + * 'sinphi': Numpy array of sin(ra_equinox)
  907 + * 'costheta': Numpy array of cos(dec_equinox)
  908 + * 'sintheta': Numpy array of sin(dec_equinox)
  909 + * 'distsun': Numpy array of the distance from the Sun (deg)
  910 + * 'distmoon': Numpy array of the distance from the Moon (deg)
  911 + * 'visibility': Numpy array of the visibility
  912 + * 'observability': Numpy array of the observability (>0 means observable)
  913 + * 'horizon': Numpy array of horizon elevations (deg)
  914 +
  915 + The Numpy arrays are 1D of 86400 elements.
  916 + if ephem_sun==None or ephem_moon==None, the distances between the target and the Sun and Moon will not be calculated.
  917 +
835 918 """
836 919 nsec = 86400
837 920 if ephem_sun==None or ephem_moon==None:
... ... @@ -845,7 +928,7 @@ class Ephemeris(EphemerisException, GuitastroTools):
845 928 hor_az = np.arange(0, 361)
846 929 hor_elev = np.zeros(len(hor_az))
847 930 if 'preference' in kwargs.keys():
848   - preference = kwargs['preference']
  931 + preference = kwargs['preference'].lower()
849 932 else:
850 933 preference="bestelev"
851 934 if 'duskelev' in kwargs.keys():
... ... @@ -961,41 +1044,42 @@ class Ephemeris(EphemerisException, GuitastroTools):
961 1044 sinthetas = aangles[9]
962 1045 distsuns = aangles[10]
963 1046 distmoons = aangles[11]
964   - # --- visibility
965   - disponibilitys = np.zeros(nsec)
  1047 + # --- observability and visibility
966 1048 visibilitys = np.zeros(nsec)
  1049 + observabilitys = np.zeros(nsec)
967 1050 horizons = np.zeros(nsec)
968 1051 if sunmoon==True:
  1052 + # --- visibility
969 1053 kk = 0
970 1054 for alt, az in zip(alts, azs):
971 1055 k = int(np.floor(az))%360
972 1056 altmini = hor_elev[k]
973 1057 horizons[kk] = altmini
974 1058 if alt < altmini:
975   - disponibilitys[kk] += 1
  1059 + visibilitys[kk] += 1
976 1060 if ephem_sun['alt'][kk] > duskelev:
977   - disponibilitys[kk] += 2
  1061 + visibilitys[kk] += 2
978 1062 kk += 1
979   - disponibilitys = np.where(distsuns > 30, disponibilitys, disponibilitys+4)
980   - disponibilitys = np.where(distmoons > 30, disponibilitys, disponibilitys+8)
981   - # --- visibility
  1063 + visibilitys = np.where(distsuns > 30, visibilitys, visibilitys+4)
  1064 + visibilitys = np.where(distmoons > 30, visibilitys, visibilitys+8)
  1065 + # --- observability
982 1066 nvis = 0
983 1067 altmaxi = 0
984 1068 for kk in range(nsec):
985   - if disponibilitys[kk] == 0:
  1069 + if visibilitys[kk] == 0:
986 1070 nvis += 1
987 1071 if alts[kk] > altmaxi:
988 1072 altmaxi = alts[kk]
989 1073 if preference=="immediate":
990 1074 kvis = nvis
991 1075 for kk in range(nsec):
992   - if disponibilitys[kk] == 0:
993   - visibilitys[kk] = kvis/nvis*100
  1076 + if visibilitys[kk] == 0:
  1077 + observabilitys[kk] = kvis/nvis*100
994 1078 kvis -= 1
995 1079 if preference=="bestelev":
996 1080 for kk in range(nsec):
997   - if disponibilitys[kk] == 0:
998   - visibilitys[kk] = alts[kk]/altmaxi*100
  1081 + if visibilitys[kk] == 0:
  1082 + observabilitys[kk] = alts[kk]/altmaxi*100
999 1083 # --- dictionary
1000 1084 eph = {}
1001 1085 eph['night'] = night
... ... @@ -1015,8 +1099,8 @@ class Ephemeris(EphemerisException, GuitastroTools):
1015 1099 eph['sintheta'] = sinthetas
1016 1100 eph['distsun'] = distsuns
1017 1101 eph['distmoon'] = distmoons
1018   - eph['disponibility'] = disponibilitys
1019 1102 eph['visibility'] = visibilitys
  1103 + eph['observability'] = observabilitys
1020 1104 eph['horizon'] = horizons
1021 1105 return eph
1022 1106  
... ... @@ -1156,6 +1240,14 @@ if __name__ == &quot;__main__&quot;:
1156 1240 Compute the ephemeris of a target along all a night
1157 1241 """
1158 1242 import matplotlib.pyplot as plt
  1243 + def compute_hours(ephem):
  1244 + jd0 = ephem['jd'][0]
  1245 + frac = jd0 - np.floor(jd0)
  1246 + offset = frac - 0.5
  1247 + hours = (ephem['jd'] - jd0 + offset)*24
  1248 + date = Date(jd0).iso(0)
  1249 + return hours, date
  1250 + # ---
1159 1251 eph = Ephemeris()
1160 1252 eph.set_home("guitalens")
1161 1253 night = "20230320"
... ... @@ -1173,7 +1265,6 @@ if __name__ == &quot;__main__&quot;:
1173 1265 ephem_sun = eph.target2night(target, night)
1174 1266 dt = time.time()-t0
1175 1267 print(f"SUN dt={dt}")
1176   - plt.plot(ephem_sun['jd'], ephem_sun['alt'], "y-")
1177 1268 # --- moon
1178 1269 target = "moon"
1179 1270 t0 = time.time()
... ... @@ -1186,12 +1277,12 @@ if __name__ == &quot;__main__&quot;:
1186 1277 ephem = eph.target2night(target, night, ephem_sun, ephem_moon, horizon=hor, preference=preference, duskelev=duskelev)
1187 1278 dt = time.time()-t0
1188 1279 print(f"TARGET dt={dt}")
1189   - #plt.plot(ephem['jd'], ephem['ha'], "r:")
1190   - #plt.plot(ephem['jd'], ephem['dec'], "r-")
1191   - plt.plot(ephem['jd'], ephem['alt'], "b-")
1192   - #plt.plot(ephem['jd'], ephem['az'], ":-")
1193   - plt.plot(ephem['jd'], ephem['disponibility'], "k:")
1194   - plt.plot(ephem['jd'], ephem['visibility'], "k-")
1195   - plt.plot(ephem['jd'], ephem['horizon'], "g-")
  1280 + hours, date = compute_hours(ephem)
  1281 + plt.plot(hours, ephem_sun['alt'], "y-")
  1282 + plt.plot(hours, ephem['alt'], "b-")
  1283 + plt.plot(hours, ephem['horizon'], "g-")
  1284 + plt.plot(hours, ephem['visibility'], "k:")
  1285 + plt.plot(hours, ephem['observability'], "k-")
1196 1286 plt.grid()
1197   -
  1287 + plt.ylabel('Degrees')
  1288 + plt.xlabel(f'Hours since {date}')
... ...
src/guitastro/filenames.py
... ... @@ -1128,7 +1128,7 @@ class FileNames(FileNamesException, GuitastroTools):
1128 1128 fn = FileNames()
1129 1129 fn.naming_date("2023-01-03T12:43:27.876983")
1130 1130  
1131   - The result should be {'iso': '2023-01-03T12:43:27.876983', 'iso_': '2023_01_03_12_43_27_876983', 'yyyy': '2023', 'mm': '43', 'dd': '03', 'hh': '12', 'ss': '27', 'yyyymmdd': '20230103', 'hhmmss': '124327', 'hhmmssssssss': '124327876983'}
  1131 + The result should be {'iso': '2023-01-03T12:43:27.876983', 'iso\_': '2023_01_03_12_43_27_876983', 'yyyy': '2023', 'mm': '43', 'dd': '03', 'hh': '12', 'ss': '27', 'yyyymmdd': '20230103', 'hhmmss': '124327', 'hhmmssssssss': '124327876983'}
1132 1132 """
1133 1133 date = Date(date);
1134 1134 digits = date.digits(6)
... ...