Commit ce65a752aa935493f897ffe165c4929ecf15ae5f

Authored by Alain Klotz
1 parent 127eb18e
Exists in master

Update the Filename class.

docs/source/index.rst
... ... @@ -30,8 +30,8 @@ The source code of Guitastro is available using `Git <https://git-scm.com/>`_.
30 30 Windows users can install `Tortoige Git <https://tortoisegit.org/>`_ which is user friendly.
31 31  
32 32 You can get Guitastro anywhere in your computer. The examples of this guide consider
33   -to place Guitastro in the folder Documents of your user. Of the user is xxx
34   -for linux will install guitastro in the folder /home/xxx/Documents. For
  33 +to place Guitastro in the folder Documents of your user. If the user is xxx
  34 +for linux you have to install guitastro in the folder /home/xxx/Documents. For
35 35 Windows users it corresponds to C:\\Users\\xxx\\Documents
36 36  
37 37 Clone the code from the Git repository `https://gitlab.irap.omp.eu/guitastrolib/guitastro.git <https://gitlab.irap.omp.eu/guitastrolib/guitastro.git>`_.
... ...
src/guitastro/__init__.py
... ... @@ -49,6 +49,21 @@ guitastro.targets
49 49 .. automodule:: guitastro.targets
50 50 :members:
51 51  
  52 +guitastro.ephemeris
  53 +-------------------
  54 +.. automodule:: guitastro.ephemeris
  55 + :members:
  56 +
  57 +guitastro.filenames
  58 +-------------------
  59 +.. automodule:: guitastro.filenames
  60 + :members:
  61 +
  62 +guitastro.astrotables
  63 +---------------------
  64 +.. automodule:: guitastro.astrotables
  65 + :members:
  66 +
52 67 guitastro.camera
53 68 -----------------
54 69 .. automodule:: guitastro.camera
... ... @@ -74,10 +89,6 @@ guitastro.etc
74 89 .. automodule:: guitastro.etc
75 90 :members:
76 91  
77   -guitastro.ephemeris
78   --------------------
79   -.. automodule:: guitastro.ephemeris
80   - :members:
81 92  
82 93 guitastro.visu
83 94 --------------
... ... @@ -99,16 +110,6 @@ guitastro.voevent
99 110 .. automodule:: guitastro.voevent
100 111 :members:
101 112  
102   -guitastro.filenames
103   --------------------
104   -.. automodule:: guitastro.filenames
105   - :members:
106   -
107   -guitastro.astrotables
108   ----------------------
109   -.. automodule:: guitastro.astrotables
110   - :members:
111   -
112 113 guitastro.splinefit
113 114 -------------------
114 115 .. automodule:: guitastro.splinefit
... ...
src/guitastro/filenames.py
... ... @@ -20,6 +20,7 @@ client.notify_all(message)
20 20 import os, glob, sys
21 21 import datetime
22 22 import math
  23 +#import inspect
23 24  
24 25 import tkinter as tk
25 26 from tkinter.filedialog import askopenfilename, asksaveasfilename
... ... @@ -107,6 +108,7 @@ class Filenames(FilenamesException, GuitastroTools):
107 108 self._verbose_level = self.VERBOSE_NONE
108 109 self._longiau_deg = 0.0
109 110 self._naming = self.NAMING_NONE
  111 + self._outfilename = os.path.join(self._image_dir, "noname"+self.extension())
110 112  
111 113 def __repr__(self):
112 114 return str(self)
... ... @@ -118,6 +120,8 @@ class Filenames(FilenamesException, GuitastroTools):
118 120 def longitude(self, longiau_deg:float):
119 121 """ Set the longitude of the observation siteobs.
120 122  
  123 + This method is useful for the method get_night().
  124 +
121 125 Args:
122 126 longiau_deg: The longitude of the siteobs must expressed in degrees, east increasing, from 0 to 360 (IAU convention).
123 127  
... ... @@ -213,22 +217,58 @@ class Filenames(FilenamesException, GuitastroTools):
213 217 print(fn.naming_rules())
214 218  
215 219 """
  220 + date_iso = datetime.datetime.utcnow().isoformat()
  221 + name = self._naming_list[self._naming]
216 222 texte = ""
217   - texte +=f"Naming rules for {self._naming_list[self._naming]}\n\n"
218 223 if self._naming == self.NAMING_NONE:
219   - texte += "No specific rule.\n"
  224 + texte += "Filename naming rules\n"
  225 + else:
  226 + texte +=f"Filename naming rules for {name}\n"
  227 + date_iso = datetime.datetime.utcnow().isoformat()
  228 + texte += "Updated on 2022-11-19T12:48:00\n"
  229 + texte +=f"Written on {date_iso}\n"
  230 + # ---
  231 + h1 = "1. Introduction"
  232 + texte +=f"\n{h1}\n" + "="*len(h1) + "\n\n"
  233 + if self._naming == self.NAMING_NONE:
  234 + texte += "No specific rule. File names can be formed as you want.\n"
  235 + return texte
  236 + elif self._naming == self.NAMING_PYROS1:
  237 + texte +=f"{name} is a file name format for the Python Robotic Observatory Software.\n"
  238 + texte += "Specific terminology:\n"
  239 + texte += "* Unit: The set of Mount and Channels that define a 'telescope'.\n"
  240 + texte += "* Channel: An optical path + camera of a Unit. A unit can have many Channels.\n"
  241 + texte += "* Album: A set of Channels for which data can be combined by post processing.\n"
  242 + texte += "* Sequence: A set of Albums. A sequence is a hierarchy Channels/Planes/Frames.\n"
  243 + texte += "* Plane: A set of Frames. A Plane is composed by Frames.\n"
  244 + texte += "* Frame: A unitary record (e.g. an image).\n"
  245 + # ---
  246 + h1 = "2. Format of the file names"
  247 + texte +=f"\n{h1}\n" + "="*len(h1) + "\n\n"
220 248 if self._naming == self.NAMING_PYROS1:
221   - texte +=" 1 2 3 4\n"
222   - texte += "0123456789 123456789 123456789 123456789 12345\n"
223   - texte += "TT_YYYYMMDD_hhmmsscccccc_UUU_V_CCC_IIIIIIIIII\n"
  249 + texte += " 1 2 3 4 5\n"
  250 + texte += "0123456789 123456789 123456789 123456789 123456789 12\n"
  251 + texte += "TT_YYYYMMDD_hhmmsscccccc_V_UUU_CCC_IIIIIIIIII_PPP_FFF\n"
224 252 texte += "\n"
225 253 texte +=f"TT = 'ftype' = file type amongst {self._naming_0_stypes}\n"
226 254 texte += "YYYYMMDD = 'date' = Year Month Day. e.g. 20221109\n"
227 255 texte += "hhmmsscccccc = 'time' = Hours Minutes Seconds Microseconds. e.g. 235604123456\n"
228   - texte += "UUU = 'unit' = Telescope unit. e.g. TNC\n"
229 256 texte += "V = 'version' = Version of the naming rule. e.g. 1\n"
  257 + texte += "UUU = 'unit' = Telescope unit. e.g. TNC\n"
230 258 texte += "CCC = 'channel' = Optical channel designation. e.g. CH1\n"
231 259 texte += "IIIIIIIIII = 'id_seq' = ID of the file in the database. e.g. 0000001234\n"
  260 + texte += "PPP = 'plane' = index of the plane. e.g. 001\n"
  261 + texte += "FFF = 'frame' = index of the frame. e.g. 012\n"
  262 + # ---
  263 + h1 = "3. Remarks"
  264 + texte +=f"\n{h1}\n" + "="*len(h1) + "\n\n"
  265 + if self._naming == self.NAMING_PYROS1:
  266 + texte += "Example: L0_20221109_235406123456_1_TNC_CH1_0123456789_001_001\n"
  267 + texte += "UUU, CCC are accronyms (string)\n"
  268 + texte += "V, IIIIIIIIII, PPP, FFF are digits (int)\n"
  269 + texte += "CCC is a Channel for L0,L1 ftype levels but should by the Album for L2 level"
  270 + # ---
  271 + texte += "\n\n=== End of the Document ==="
232 272 return texte
233 273  
234 274 def naming_ident(self, fname:str) -> dict:
... ... @@ -245,22 +285,24 @@ class Filenames(FilenamesException, GuitastroTools):
245 285 ::
246 286  
247 287 fn = Filenames()
248   - fn.naming_ident("L0_20221109_235406123456_TNC_1_CH1_0123456789")
  288 + fn.naming_ident("L0_20221109_235406123456_1_TNC_CH1_0123456789_001_001")
249 289 >>> 'PyROS.1'
250 290 """
251 291 naming = self.NAMING_NONE
252 292 n = len(fname)
253   - if n == 45:
  293 + if n == 53:
254 294 words = fname.split("_")
255 295 n = len(words)
256   - if n == 7:
  296 + if n == 9:
257 297 param['ftype'] = words[0]
258 298 param['date'] = words[1]
259 299 param['time'] = words[2]
260   - param['unit'] = words[3]
261   - param['version'] = words[4]
  300 + param['version'] = words[3]
  301 + param['unit'] = words[4]
262 302 param['channel'] = words[5]
263 303 param['id_seq'] = words[6]
  304 + param['plane'] = words[7]
  305 + param['frame'] = words[8]
264 306 try:
265 307 fname_verified = self.naming_set(**param)
266 308 if fname == fname_verified:
... ... @@ -284,9 +326,9 @@ class Filenames(FilenamesException, GuitastroTools):
284 326  
285 327 fn = Filenames()
286 328 fn.naming("PyROS.1")
287   - params = fn.naming_get("L0_20221109_235406123456_TNC_1_CH1_0123456789")
  329 + params = fn.naming_get("L0_20221109_235406123456_1_TNC_CH1_0123456789_001_012")
288 330  
289   - The answer (params dict) should be {'ftype': 'L0', 'date': '20221109', 'time': '235406123456', 'unit': 'TNC', 'version': '1', 'channel': 'CH1', 'id_seq': '0123456789'}
  331 + The answer (params dict) should be {'ftype': 'L0', 'date': '20221109', 'time': '235406123456', 'version': '1', 'unit': 'TNC', 'channel': 'CH1', 'id_seq': '0123456789', 'plane': '001', 'frame': '012'}
290 332 """
291 333 param = {}
292 334 see_rules = self._see_rules
... ... @@ -294,21 +336,25 @@ class Filenames(FilenamesException, GuitastroTools):
294 336 param['fname'] = fname
295 337 elif self._naming == self.NAMING_PYROS1:
296 338 n = len(fname)
297   - if n != 45:
298   - texte = "File name must be a string of 45 characters" + see_rules
  339 + nn = 53
  340 + if n != nn:
  341 + texte =f"File name must be a string of {nn} characters" + see_rules
299 342 raise FilenamesException(FilenamesException.BAD_FILENAME_RULE, texte)
300 343 words = fname.split("_")
301 344 n = len(words)
302   - if n != 7:
303   - texte = "File name must contain 6 underscore characters" + see_rules
  345 + nn = 9
  346 + if n != nn:
  347 + texte =f"File name must contain {nn-1} underscore characters" + see_rules
304 348 raise FilenamesException(FilenamesException.BAD_FILENAME_RULE, texte)
305 349 param['ftype'] = words[0]
306 350 param['date'] = words[1]
307 351 param['time'] = words[2]
308   - param['unit'] = words[3]
309   - param['version'] = words[4]
  352 + param['version'] = words[3]
  353 + param['unit'] = words[4]
310 354 param['channel'] = words[5]
311 355 param['id_seq'] = words[6]
  356 + param['plane'] = words[7]
  357 + param['frame'] = words[8]
312 358 fname_verified = self.naming_set(**param)
313 359 if fname != fname_verified:
314 360 raise FilenamesException(FilenamesException.BAD_FILENAME_RULE)
... ... @@ -334,13 +380,16 @@ class Filenames(FilenamesException, GuitastroTools):
334 380 param['ftype'] = "L0"
335 381 param['date'] = "20221109"
336 382 param['time'] = "235406123456"
337   - param['unit'] = "TNC"
338 383 param['version'] = "1"
  384 + param['unit'] = "TNC"
339 385 param['channel'] = "CH1"
340 386 param['id_seq'] = "0123456789"
  387 + param['plane'] = "001"
  388 + param['frame'] = "001"
341 389 fname = fn.naming_set(**param)
342 390  
343   - The answer should be L0_20221109_235406123456_TNC_1_CH1_0123456789.
  391 + The answer should be L0_20221109_235406123456_TNC_1_CH1_0123456789_001_001.
  392 + If param['date'] is "*" then the date and time will be the current UTC time.
344 393 """
345 394 see_rules = self._see_rules
346 395 if self._naming == self.NAMING_NONE:
... ... @@ -353,10 +402,12 @@ class Filenames(FilenamesException, GuitastroTools):
353 402 param['ftype'] = ""
354 403 param['date'] = ""
355 404 param['time'] = ""
356   - param['unit'] = ""
357 405 param['version'] = ""
  406 + param['unit'] = ""
358 407 param['channel'] = ""
359 408 param['id_seq'] = ""
  409 + param['plane'] = ""
  410 + param['frame'] = ""
360 411 for key, val in kwargs.items():
361 412 if key in param.keys():
362 413 param[key] = val
... ... @@ -376,6 +427,13 @@ class Filenames(FilenamesException, GuitastroTools):
376 427 if len(param['date']) == 8:
377 428 if param['date'].isdigit():
378 429 valid = True
  430 + elif param['date'] == "*":
  431 + d = datetime.datetime.utcnow().isoformat()
  432 + fdate = d[0:4]+d[5:7]+d[8:10]
  433 + ftime = d[11:13]+d[14:16]+d[17:19]+d[20:26]
  434 + param['date'] = fdate
  435 + param['time'] = ftime
  436 + valid = True
379 437 if valid == False:
380 438 texte = f"Date {param['date']} must be a string of 8 digits" + see_rules
381 439 raise FilenamesException(FilenamesException.BAD_PARAM, texte)
... ... @@ -388,24 +446,27 @@ class Filenames(FilenamesException, GuitastroTools):
388 446 if valid == False:
389 447 texte = f"Date {param['time']} must be a string of 12 digits" + see_rules
390 448 raise FilenamesException(FilenamesException.BAD_PARAM, texte)
391   - # --- verify unit
392   - valid = False
393   - if isinstance(param['unit'],str):
394   - if len(param['unit']) == 3:
395   - valid = True
396   - if valid == False:
397   - texte = f"Unit {param['unit']} must be a string of 3 characters" + see_rules
398   - raise FilenamesException(FilenamesException.BAD_PARAM, texte)
399 449 # --- verify version
400 450 valid = False
401 451 if isinstance(param['version'],str):
402 452 if len(param['version']) == 1:
403 453 valid = True
  454 + elif isinstance(param['version'],int):
  455 + param['version'] = f"{param['version']:01d}"
  456 + valid = True
404 457 if valid == False:
405 458 texte = f"Version {param['version']} must be a string of 1 character" + see_rules
406 459 raise FilenamesException(FilenamesException.BAD_PARAM, texte)
407 460 # --- verify unit
408 461 valid = False
  462 + if isinstance(param['unit'],str):
  463 + if len(param['unit']) == 3:
  464 + valid = True
  465 + if valid == False:
  466 + texte = f"Unit {param['unit']} must be a string of 3 characters" + see_rules
  467 + raise FilenamesException(FilenamesException.BAD_PARAM, texte)
  468 + # --- verify channel
  469 + valid = False
409 470 if isinstance(param['channel'],str):
410 471 if len(param['channel']) == 3:
411 472 valid = True
... ... @@ -417,13 +478,36 @@ class Filenames(FilenamesException, GuitastroTools):
417 478 if isinstance(param['id_seq'],str):
418 479 if len(param['id_seq']) == 10:
419 480 valid = True
420   - elif isinstance(param['id_seq'],type(int)):
  481 + elif isinstance(param['id_seq'],int):
421 482 param['id_seq'] = f"{param['id_seq']:010d}"
422 483 valid = True
423 484 if valid == False:
424 485 texte = f"Id_seq {param['id_seq']} must be a string of 10 characters or an integer" + see_rules
425 486 raise FilenamesException(FilenamesException.BAD_PARAM, texte)
426   - fname = param['ftype']+"_"+param['date']+"_"+param['time']+"_"+param['unit']+"_"+param['version']+"_"+param['channel']+"_"+param['id_seq']
  487 + # --- verify plane
  488 + valid = False
  489 + if isinstance(param['plane'],str):
  490 + if len(param['plane']) == 3:
  491 + valid = True
  492 + elif isinstance(param['plane'],int):
  493 + param['plane'] = f"{param['plane']:03d}"
  494 + valid = True
  495 + if valid == False:
  496 + texte = f"Plane {param['plane']} must be a string of 3 characters or an integer" + see_rules
  497 + raise FilenamesException(FilenamesException.BAD_PARAM, texte)
  498 + # --- verify frame
  499 + valid = False
  500 + if isinstance(param['frame'],str):
  501 + if len(param['frame']) == 3:
  502 + valid = True
  503 + elif isinstance(param['frame'],int):
  504 + param['frame'] = f"{param['frame']:03d}"
  505 + valid = True
  506 + if valid == False:
  507 + texte = f"Frame {param['frame']} must be a string of 3 characters or an integer" + see_rules
  508 + raise FilenamesException(FilenamesException.BAD_PARAM, texte)
  509 + # --- form final file name
  510 + fname = param['ftype']+"_"+param['date']+"_"+param['time']+"_"+param['version']+"_"+param['unit']+"_"+param['channel']+"_"+param['id_seq']+"_"+param['plane']+"_"+param['frame']
427 511 return fname
428 512  
429 513  
... ... @@ -448,13 +532,13 @@ class Filenames(FilenamesException, GuitastroTools):
448 532 return self._image_dir
449 533  
450 534 def extension(self, extension: str="") -> str:
451   - """Set/Get the extension of the FITS files.
  535 + """Set/Get the extension of the files.
452 536  
453 537 Args:
454   - extension: Set the file extension to add automatically to FITS files saved with method save. Defaut value is ".fit".
  538 + extension: Set the file extension to (usefull add automatically to files when record). Defaut value is ".fit".
455 539  
456 540 Returns:
457   - The current extension added to FITS files.
  541 + The current extension added to files.
458 542  
459 543 """
460 544 if extension != "":
... ... @@ -466,7 +550,7 @@ class Filenames(FilenamesException, GuitastroTools):
466 550 def get_night(self, date: str="now")->str:
467 551 """Get the current night identifier as a string of 8 characters: YYYYMMDD.
468 552  
469   - The night identifier changes at local noon. So it used the longitude of the siteobs.
  553 + The night identifier changes at local noon. So it uses the longitude defined in longitude() method.
470 554  
471 555 Args:
472 556 date: Input date to compute the night identifier. Defaut value is "now".
... ... @@ -476,9 +560,9 @@ class Filenames(FilenamesException, GuitastroTools):
476 560  
477 561 ::
478 562  
479   - ima1 = Ima()
480   - ima1.longitude = 2.3456
481   - night = ima1.get_night("now")
  563 + fn = Filenames()
  564 + fn.longitude = 2.3456
  565 + night = fn.get_night("now")
482 566  
483 567 The variable night contains the current night in the format YYYYMMDD.
484 568 """
... ... @@ -516,8 +600,8 @@ class Filenames(FilenamesException, GuitastroTools):
516 600  
517 601 ::
518 602  
519   - ima1 = Ima()
520   - fullname = ima1.fullfilename("m57")
  603 + fn = Filenames()
  604 + fullname = fn.fullfilename("m57")
521 605  
522 606 The result could be '/srv/develop/guitastro/test/data/m57.fit'
523 607  
... ... @@ -546,8 +630,8 @@ class Filenames(FilenamesException, GuitastroTools):
546 630  
547 631 ::
548 632  
549   - ima1 = Ima()
550   - fullname = ima1.basename("m57")
  633 + fn = Filenames()
  634 + fullname = fn.basename("m57")
551 635  
552 636 The result could be 'm57.fit'
553 637  
... ... @@ -558,11 +642,18 @@ class Filenames(FilenamesException, GuitastroTools):
558 642 basename = os.path.basename(fitsname)
559 643 return basename
560 644  
561   - def itername(self, fitsname:str):
562   - """Get an iterator according a series of FITS files.
  645 + def itername(self, fitsname:str, **kwargs):
  646 + """Get an iterator according a series of files.
  647 +
  648 + The list of file names is generated internally by the method genename().
563 649  
564 650 Args:
565 651 fitsname: Input file name of a series with or without path or extension.
  652 + **kwargs: a dictionnary of optional parameters:
  653 +
  654 + * 'first': Lower index to keep indexes.
  655 + * 'last': Upper index to keep indexes.
  656 + * 'indexes': List of indexes to keep (integers).
566 657  
567 658 Returns:
568 659 The iterator of the series of files.
... ... @@ -572,7 +663,8 @@ class Filenames(FilenamesException, GuitastroTools):
572 663  
573 664 ::
574 665  
575   - for fitsname in ima1.itername("m63-1"):
  666 + fn = Filenames()
  667 + for fitsname in fn.itername("m63-1"):
576 668 print(f"fitsname={fitsname}")
577 669  
578 670 """
... ... @@ -587,11 +679,18 @@ class Filenames(FilenamesException, GuitastroTools):
587 679 fitsname = os.path.join(dirname, genename + sep + index + suffix + file_extension)
588 680 yield fitsname
589 681  
590   - def enumname(self, fitsname: str):
591   - """Get an enumerator according a series of FITS files.
  682 + def enumname(self, fitsname: str, **kwargs):
  683 + """Get an enumerator according a series of files.
  684 +
  685 + The list of file names is generated internally by the method genename().
592 686  
593 687 Args:
594 688 fitsname: Input file name of a series with or without path or extension.
  689 + **kwargs: a dictionnary of optional parameters:
  690 +
  691 + * 'first': Lower index to keep indexes.
  692 + * 'last': Upper index to keep indexes.
  693 + * 'indexes': List of indexes to keep (integers).
595 694  
596 695 Returns:
597 696 The iterator of the series of files.
... ... @@ -601,11 +700,12 @@ class Filenames(FilenamesException, GuitastroTools):
601 700  
602 701 ::
603 702  
604   - for k, fitsname in enumarate(ima1.enumname("m63-1")):
  703 + fn = Filenames()
  704 + for k, fitsname in enumarate(fn.enumname("m63-1")):
605 705 print(f"fitsname {k} = {fitsname}")
606 706  
607 707 """
608   - dico = self.genename(fitsname)
  708 + dico = self.genename(fitsname, **kwargs)
609 709 dirname = dico['dirname']
610 710 genename = dico['genename']
611 711 sep = dico['sep']
... ... @@ -631,35 +731,70 @@ class Filenames(FilenamesException, GuitastroTools):
631 731 return filename, file_extension
632 732  
633 733 def genenames(self, wildcard: str)-> dict:
  734 + """Generate a dictionnary describing a series of files associated to a wildcard file name.
  735 +
  736 + Generated file names are existing in the disk.
  737 + This method is similar as genename() but the input file is a wildcard and the output dictionary is different.
  738 +
  739 + Args:
  740 + wildcard: Any wildcard with extension. The path is that defined in the method path() is not in the wildcard.
  741 +
  742 + Returns:
  743 + A dictionary containing the following keys:
  744 +
  745 + * 'dirname': Directory name (path only)
  746 + * 'shortname': Short name (no path, extension)
  747 + * 'filename': Full name of file (no path, shortname, no extension)
  748 + * 'ext': Extension of the file
  749 +
  750 + For example, you have 3 files m63-1.fit, m63-2.fit and m63-3.fit.
  751 + You have to put one of the three file names as input of the method genename:
  752 +
  753 + ::
  754 +
  755 + fn = Filenames()
  756 + result = fn.genenames("m63-*.fits")
  757 +
  758 + """
634 759 # fn.genenames("IM_*.fits.gz")
635 760 wildcard = self.fullfilename(wildcard)
636   - print(f"wildcard = {wildcard}")
  761 + #print(f"wildcard = {wildcard}")
637 762 ftmps = glob.glob(wildcard)
638 763 allshortnames = []
639 764 for ftmp in ftmps:
640 765 basename = os.path.basename(ftmp)
  766 + dirname = os.path.dirname(ftmp)
641 767 allshortnames.append(basename)
642 768 # ---
643 769 allshorts = []
644 770 for shortname in allshortnames:
645 771 dico = {}
646   - nchar = len(shortname)
  772 + #nchar = len(shortname)
647 773 filename , file_extension = self._split_extension(shortname)
  774 + dico['dirname'] = dirname
648 775 dico['shortname'] = shortname
649 776 dico['filename'] = filename
650 777 dico['ext'] = file_extension
651   - for shortname_ref in allshortnames:
652   - nchar_ref = len(shortname_ref)
  778 + #for shortname_ref in allshortnames:
  779 + # nchar_ref = len(shortname_ref)
653 780 allshorts.append(dico)
654 781  
655 782 return allshorts
656 783  
657   - def genename(self,fitsname: str)-> dict:
658   - """
659   - Generate a dictionnary describing a series of files associated to a FITS file name.
  784 + def genename(self,fitsname: str, **kwargs)-> dict:
  785 + """Generate a dictionnary describing a series of files associated to a file name.
  786 +
  787 + The algorithm search for files having indexes with a name similar than fitsname.
  788 + Generated file names are existing in the disk.
  789 + This method adds the possibility to restrict indexes using options 'first', 'last' and 'indexes' (using optional parameters).
660 790  
661 791 Args:
662 792 fitsname: Any file name with or without path or extension.
  793 + **kwargs: a dictionnary of optional parameters:
  794 +
  795 + * 'first': Lower index to keep indexes.
  796 + * 'last': Upper index to keep indexes.
  797 + * 'indexes': List of indexes to keep (integers).
663 798  
664 799 Returns:
665 800 A dictionary containing the following keys:
... ... @@ -676,8 +811,21 @@ class Filenames(FilenamesException, GuitastroTools):
676 811  
677 812 ::
678 813  
679   - ima1 = Ima()
680   - result = ima1.genename("m63-1")
  814 + fn = Filenames()
  815 + result = fn.genename("m63-1")
  816 +
  817 + The index "1" is not necessary. So the command fn.genename("m63-") gives the same result.
  818 +
  819 + For example, you have 6 files m63-1.fit, m63-2.fit ... m63-6.fit.
  820 + You have to put one of the three file names as input of the method genename
  821 + and if you want to keep only files after the index 4:
  822 +
  823 + ::
  824 +
  825 + fn = Filenames()
  826 + result = fn.genename("m63-1", first=4)
  827 +
  828 + Indexes of 'first' and 'last' options can be negative (to start from end to begin).
681 829  
682 830 """
683 831 fitsname = self.fullfilename(fitsname)
... ... @@ -766,7 +914,6 @@ class Filenames(FilenamesException, GuitastroTools):
766 914 indexes = []
767 915 if len(ftmps) > 0:
768 916 k = len(genename+sep)
769   - indexes = []
770 917 #print(f"2 genename+sep = {genename+sep}")
771 918 #print(f"2 k = {k}")
772 919 nsuffix = len(suffix)
... ... @@ -787,21 +934,212 @@ class Filenames(FilenamesException, GuitastroTools):
787 934 first = indexes[0]
788 935 if first[0] == "0":
789 936 ndigit = len(first)
790   - dico = {}
791   - dico["dirname"] = dirname
792   - dico["genename"] = genename
793   - dico["sep"] = sep
794   - dico["indexes"] = indexes
795   - dico["suffix"] = suffix
796   - dico["file_extension"] = file_extension
797   - dico["ndigit"] = ndigit
798   - return dico
799   -
800   - def _askopenfilename(self, title=""):
  937 + genname = {}
  938 + genname["dirname"] = dirname
  939 + genname["genename"] = genename
  940 + genname["sep"] = sep
  941 + genname["indexes"] = indexes
  942 + genname["suffix"] = suffix
  943 + genname["file_extension"] = file_extension
  944 + genname["ndigit"] = ndigit
  945 + # --- process optional parameters
  946 + if len(indexes) > 0:
  947 + first = int(genname["indexes"][0])
  948 + n = int(genname["indexes"][-1])
  949 + if "first" in kwargs.keys():
  950 + bound = first
  951 + first = kwargs["first"]
  952 + if first < 0:
  953 + first = n + first + 1
  954 + if first > n:
  955 + first = n
  956 + if first < bound:
  957 + first = bound
  958 + last = n
  959 + if "last" in kwargs.keys():
  960 + last = kwargs["last"]
  961 + if last < 0:
  962 + last = n + last + 1
  963 + if last < first:
  964 + last = first
  965 + if last > n:
  966 + last = n
  967 + indexes = []
  968 + for index in genname["indexes"]:
  969 + rank = int(index)
  970 + if rank >= first and rank <= last:
  971 + if "indexes" in kwargs.keys():
  972 + if rank in kwargs["indexes"]:
  973 + indexes.append(index)
  974 + else:
  975 + indexes.append(index)
  976 + genname["indexes"] = indexes
  977 + # ---
  978 + return genname
  979 +
  980 +
  981 + def innames(self, fitsname, **kwargs):
  982 + """Same method as genename()
  983 + """
  984 + return self.genename(fitsname, **kwargs)
  985 +
  986 + def inoutnames(self, fitsname, outfilename, **kwargs):
  987 + """Generate file names informations to process a series of files.
  988 +
  989 + This method is the same than innames() but add the output names.
  990 +
  991 + Args:
  992 + fitsname: Any file name with or without path or extension.
  993 + outfilename: Generic name of the files that must be created later.
  994 + **kwargs: a dictionnary of optional parameters:
  995 +
  996 + * 'first': Lower index to keep indexes.
  997 + * 'last': Upper index to keep indexes.
  998 + * 'indexes': List of indexes to keep (integers).
  999 +
  1000 + Returns:
  1001 + A tupple of three elements.
  1002 +
  1003 + * A dictionary containing the following keys:
  1004 +
  1005 + * 'dirname': Path name
  1006 + * 'genename': Generic name of files
  1007 + * 'sep': Separator between generic name and indexes
  1008 + * 'indexes': List of indexes sorted increasing
  1009 + * 'file_extension': File extension
  1010 + * 'ndigit': Number of digits in case of indexes with leading zeros
  1011 +
  1012 + * A string that includes the path and the generic shortname.
  1013 +
  1014 + * The extension of the outpout files.
  1015 +
  1016 + For example, you have 6 files m63-1.fit, m63-2.fit ... m63-6.fit.
  1017 + You have to put one of the three file names as input of the method genename
  1018 + and if you want to keep only files after the index 4:
  1019 +
  1020 + ::
  1021 +
  1022 + fn = Filenames()
  1023 + result = fn.inoutnames("m63-1", "i", first=4)
  1024 +
  1025 + result can be ({'dirname': '/tmp', 'genename': 'm63', 'sep': '-', 'indexes': ['5', '6'], 'suffix': '', 'file_extension': '.fit', 'ndigit': 0}, '/tmp/j', '.fit')
  1026 + """
  1027 + genename = self.innames(fitsname, **kwargs)
  1028 + # ---
  1029 + outfilename = self.fullfilename(outfilename)
  1030 + outfilegene, outfile_extension = os.path.splitext(outfilename)
  1031 + return genename, outfilegene, outfile_extension
  1032 +
  1033 + def outfilename(self, output=""):
  1034 + """Get or set the output file name
  1035 +
  1036 + Store the output current filename. Useful for image series or stacking.
  1037 + There is no verification if the file exists.
  1038 +
  1039 + Args:
  1040 + output: Any file name with or without path or extension.
  1041 +
  1042 + Returns:
  1043 + A string containing the full name of the file (path, shortname, extension).
  1044 +
  1045 + ::
  1046 +
  1047 + fn = Filenames()
  1048 + fullname = fn.outfilename("j1")
  1049 +
  1050 + """
  1051 + fitsname = output
  1052 + if fitsname == "":
  1053 + # --- return the current output name
  1054 + res = self._outfilename
  1055 + else:
  1056 + # --- return a simulation of a output name
  1057 + genename = self.genename(fitsname)
  1058 + outfilename_short = genename["genename"] + genename["file_extension"]
  1059 + outfilename = os.path.join( genename["dirname"], outfilename_short)
  1060 + res = outfilename
  1061 + return res
  1062 +
  1063 + def indexnames(self, genename_in, outfilegene, outfile_extension, index, k=""):
  1064 + """Get input and output file names after using genename().
  1065 +
  1066 + Generate input and output full file names.
  1067 + Before using this method, use genename() or innames() to generate the genename_in dictionary.
  1068 + There is no verification if the output file exists.
  1069 +
  1070 + Args:
  1071 + genename_in: Dictionary generated using the method genename().
  1072 + outfilegene: Generic name (with ou withou path) of the output file
  1073 + outfile_extension: Extension of the output file
  1074 + index: Index if the intput file. Should be one of the elements of genename_in['indexes'].
  1075 + k: Index of the output file. If k is not given, it will be equal to index.
  1076 +
  1077 + Returns:
  1078 + A tupple of four elements.
  1079 +
  1080 + * A string of the short input file name.
  1081 + * A string of the short output file name.
  1082 + * A string of the full input file name.
  1083 + * A string of the full output file name.
  1084 +
  1085 + ::
  1086 +
  1087 + fn = Filenames()
  1088 + fn.path = '/tmp'
  1089 + gen = fn.innames("m63-1", first=4)
  1090 + shortin, shortout, fullin, fullout = fn.outfilename(gen, "/tmp/res/j", gen['indexes'][0], 1)
  1091 +
  1092 + The results should be shortin='m63-4.fit', shortout='j1.fit', shortin='/tmp/m63-4.fit', shortout='/tmp/res/j1.fit'
  1093 + """
  1094 + filename_short = genename_in["genename"] + genename_in["sep"] + str(index) + genename_in["suffix"] + genename_in["file_extension"]
  1095 + filename = os.path.join( genename_in["dirname"], filename_short)
  1096 + if k=="":
  1097 + kstr = str(index)
  1098 + else:
  1099 + kstr = str(k)
  1100 + outgen = self.genename(outfilegene)
  1101 + outfname = os.path.join(outgen['dirname'],outgen['genename']) + kstr + outgen['suffix'] + outfile_extension
  1102 + basename = os.path.basename(outfname)
  1103 + #called_by = inspect.stack()[1][3]
  1104 + #print(f"ImaSeries.{called_by} {k}/{n} : {filename_short} -> {basename}")
  1105 + return filename_short, basename, filename, outfname
  1106 +
  1107 + def deletenames(self, fitsname, **kwargs):
  1108 + """Delete file names from a series of files.
  1109 +
  1110 + This method get the file names using innames().
  1111 +
  1112 + Args:
  1113 + fitsname: Any file name with or without path or extension.
  1114 + **kwargs: a dictionnary of optional parameters:
  1115 +
  1116 + * 'first': Lower index to keep indexes.
  1117 + * 'last': Upper index to keep indexes.
  1118 + * 'indexes': List of indexes to keep (integers).
  1119 +
  1120 + Returns:
  1121 + List of deleted files.
  1122 +
  1123 + ::
  1124 +
  1125 + fn = Filenames()
  1126 + result = fn.deletenames("m63-1", first=4)
  1127 +
  1128 + result can be ({'dirname': '/tmp', 'genename': 'm63', 'sep': '-', 'indexes': ['5', '6'], 'suffix': '', 'file_extension': '.fit', 'ndigit': 0}, '/tmp/j', '.fit')
  1129 + """
  1130 + fnames = []
  1131 + for fname in self.itername(fitsname, **kwargs):
  1132 + os.remove(fname)
  1133 + fnames.append(fname)
  1134 + return fnames
  1135 +
  1136 + def _askopenfilename(self, title="", filetypes=""):
801 1137 root = tk.Tk()
802 1138 root.withdraw() # pour ne pas afficher la fenรชtre Tk
803 1139 initialdir = self._image_dir
804   - fitsname = askopenfilename(title=title, initialdir=initialdir, filetypes = [("FITS","*.fit;*.fits;*.fts"),("All", "*")])
  1140 + if filetypes == "":
  1141 + filetypes = [("FITS","*.fit;*.fits;*.fts"),("All", "*")]
  1142 + fitsname = askopenfilename(title=title, initialdir=initialdir, filetypes = filetypes)
805 1143 if fitsname != "":
806 1144 filename, file_extension = os.path.splitext(fitsname)
807 1145 if file_extension=="":
... ... @@ -810,11 +1148,13 @@ class Filenames(FilenamesException, GuitastroTools):
810 1148 root.destroy()
811 1149 return fitsname
812 1150  
813   - def _asksaveasfilename(self, title=""):
  1151 + def _asksaveasfilename(self, title="", filetypes=""):
814 1152 root = tk.Tk()
815 1153 root.withdraw() # pour ne pas afficher la fenรชtre Tk
816 1154 initialdir = self._image_dir
817   - fitsname = asksaveasfilename(title=title, initialdir=initialdir, filetypes = [("FITS","*.fit;*.fits;*.fts"),("All", "*")])
  1155 + if filetypes == "":
  1156 + filetypes = [("FITS","*.fit;*.fits;*.fts"),("All", "*")]
  1157 + fitsname = asksaveasfilename(title=title, initialdir=initialdir, filetypes = filetypes)
818 1158 if fitsname != "":
819 1159 filename, file_extension = os.path.splitext(fitsname)
820 1160 if file_extension=="":
... ... @@ -833,7 +1173,7 @@ class Filenames(FilenamesException, GuitastroTools):
833 1173  
834 1174 if __name__ == "__main__":
835 1175  
836   - default = 3
  1176 + default = 4
837 1177 example = input(f"Select the example (0 to 3) ({default}) ")
838 1178 try:
839 1179 example = int(example)
... ... @@ -879,9 +1219,24 @@ if __name__ == &quot;__main__&quot;:
879 1219 param['date'] = "20221109"
880 1220 param['time'] = "235406123456"
881 1221 param['unit'] = "TNC"
882   - param['version'] = "1"
  1222 + param['version'] = 1
883 1223 param['channel'] = "CH1"
884 1224 param['id_seq'] = "0123456789"
  1225 + param['plane'] = 1
  1226 + param['frame'] = 1
885 1227 fname = fn.naming_set(**param)
886 1228 print(f"fname={fname}")
887 1229 param = fn.naming_get(fname)
  1230 +
  1231 + if example == 4:
  1232 + """
  1233 + """
  1234 + fn = Filenames()
  1235 + for k in range(2, 10, 1):
  1236 + fname = f"i{k}.fit"
  1237 + print(fname)
  1238 + fname = fn.fullfilename(fname)
  1239 + with open(fname, "wt") as f:
  1240 + pass
  1241 + gen, out, outext = fn.inoutnames("i1", 'j', first=3, last=7)
  1242 + res = fn.indexnames(gen, out, outext, '3', 0)
... ...
src/guitastro/ima.py
... ... @@ -67,9 +67,9 @@ from lmfit.models import Model # GaussianModel, LinearModel
67 67 import multiprocessing
68 68  
69 69 try:
70   - from .filenames import Filenames, FilenamesException
  70 + from .filenames import Filenames
71 71 except:
72   - from filenames import Filenames, FilenamesException
  72 + from filenames import Filenames
73 73  
74 74 try:
75 75 from .dates import Date
... ... @@ -86,13 +86,6 @@ try:
86 86 except:
87 87 from wcs import Wcs
88 88  
89   -"""
90   -try:
91   - from .maps import Maps
92   -except:
93   - from maps import Maps
94   -"""
95   -
96 89 try:
97 90 from .astrotables import AstroTable
98 91 except:
... ... @@ -174,6 +167,32 @@ class Ima(ImaException, GuitastroTools):
174 167  
175 168 ima1.offset(100)
176 169  
  170 + Note:
  171 +
  172 + The class Ima imports the following methods from the class Filenames. See the Filenames class documentation for these methods:
  173 +
  174 + * longitude
  175 + * namings
  176 + * naming
  177 + * naming_rules
  178 + * naming_ident
  179 + * naming_get
  180 + * naming_set
  181 + * path
  182 + * extension
  183 + * get_night
  184 + * fullfilename
  185 + * basename
  186 + * itername
  187 + * enumname
  188 + * genename
  189 + * genenames
  190 + * innames
  191 + * inoutnames
  192 + * outfilename
  193 + * indexnames
  194 + * deletenames
  195 +
177 196 """
178 197  
179 198 VERBOSE_NONE = 0
... ... @@ -231,6 +250,12 @@ class Ima(ImaException, GuitastroTools):
231 250 self.itername = fn.itername
232 251 self.enumname = fn.enumname
233 252 self.genename = fn.genename
  253 + self.genenames = fn.genenames
  254 + self.innames = fn.innames
  255 + self.inoutnames = fn.inoutnames
  256 + self.outfilename = fn.outfilename
  257 + self.indexnames = fn.indexnames
  258 + self.deletenames = fn.deletenames
234 259 self._askopenfilename = fn._askopenfilename
235 260 self._asksaveasfilename = fn._asksaveasfilename
236 261  
... ...
src/guitastro/imaseries.py
... ... @@ -5,12 +5,17 @@
5 5  
6 6 import numpy as np
7 7 import os
8   -import inspect
  8 +#import inspect
9 9 from astropy.wcs import WCS
10 10 import multiprocessing
11 11 import psutil
12 12  
13 13 try:
  14 + from .filenames import Filenames
  15 +except:
  16 + from filenames import Filenames
  17 +
  18 +try:
14 19 from .ima import Ima
15 20 except:
16 21 from ima import Ima
... ... @@ -43,7 +48,35 @@ class ImaSeriesException(GuitastroException):
43 48  
44 49  
45 50 class ImaSeries(ImaSeriesException, GuitastroTools):
  51 + """Image processing applied to a series of images.
  52 +
  53 + This class process a series of images to generate another series of images.
  54 +
  55 + The class ImaSeries imports the following methods from the class Filenames. See the Filenames class documentation for these methods:
  56 +
  57 + * longitude
  58 + * namings
  59 + * naming
  60 + * naming_rules
  61 + * naming_ident
  62 + * naming_get
  63 + * naming_set
  64 + * path
  65 + * extension
  66 + * get_night
  67 + * fullfilename
  68 + * basename
  69 + * itername
  70 + * enumname
  71 + * genename
  72 + * genenames
  73 + * innames
  74 + * inoutnames
  75 + * outfilename
  76 + * indexnames
  77 + * deletenames
46 78  
  79 + """
47 80 VERBOSE_NONE = 0
48 81 VERBOSE_SPECIFIC = 1
49 82 VERBOSE_DEBUG = 2
... ... @@ -57,9 +90,31 @@ class ImaSeries(ImaSeriesException, GuitastroTools):
57 90 self._outfilename = os.path.join(self._ima.path(), "noname"+self._ima.extension())
58 91 self._ima.do_multiprocessing = False
59 92 self.do_multiprocessing = True
60   -
61   - def longitude(self, longiau_deg:float):
62   - self._ima.longitude(longiau_deg)
  93 + # --- import Filenames methods
  94 + fn = Filenames()
  95 + self.longitude = fn.longitude
  96 + self.namings = fn.namings
  97 + self.naming = fn.naming
  98 + self.naming_rules = fn.naming_rules
  99 + self.naming_ident = fn.naming_ident
  100 + self.naming_get = fn.naming_get
  101 + self.naming_set = fn.naming_set
  102 + self.path = fn.path
  103 + self.extension = fn.extension
  104 + self.get_night = fn.get_night
  105 + self.fullfilename = fn.fullfilename
  106 + self.basename = fn.basename
  107 + self.itername = fn.itername
  108 + self.enumname = fn.enumname
  109 + self.genename = fn.genename
  110 + self.genenames = fn.genenames
  111 + self.innames = fn.innames
  112 + self.inoutnames = fn.inoutnames
  113 + self.outfilename = fn.outfilename
  114 + self.indexnames = fn.indexnames
  115 + self.deletenames = fn.deletenames
  116 + self._askopenfilename = fn._askopenfilename
  117 + self._asksaveasfilename = fn._asksaveasfilename
63 118  
64 119 def execute_process_type1(self, method_process, process_args, genename, outfilegene, outfile_extension):
65 120 """Generic method for multiprocesses
... ... @@ -119,79 +174,6 @@ class ImaSeries(ImaSeriesException, GuitastroTools):
119 174 print(message)
120 175  
121 176 # =============================================
122   - # Managing filenames
123   - # =============================================
124   -
125   - def path(self, path=""):
126   - return self._ima.path(path)
127   -
128   - def extension(self, extension=""):
129   - return self._ima.extension(extension)
130   -
131   - def outfilename(self, output=""):
132   - basename = os.path.basename(self._outfilename)
133   - shortname, file_extension = os.path.splitext(basename)
134   - res = self._outfilename
135   - if output != "":
136   - res = shortname
137   - return res
138   -
139   - def fullfilename(self,fitsname:str)->str:
140   - return self._ima.path(fitsname)
141   -
142   - def basename(self,fitsname:str)->str:
143   - return self._ima.basename(fitsname)
144   -
145   - def genename(self,fitsname:str)->str:
146   - return self._ima.genename(fitsname)
147   -
148   - def inoutnames(self, fitsname, outfilename, **kwargs):
149   - # ---
150   - genename = self._ima.genename(fitsname)
151   - n = len(genename["indexes"])
152   - # ---
153   - first = 1
154   - if "first" in kwargs.keys():
155   - first = kwargs["first"]
156   - if first > n:
157   - first = n
158   - end = n
159   - if "end" in kwargs.keys():
160   - end = kwargs["end"]
161   - if end < first:
162   - end = first
163   - if end > n:
164   - end = n
165   - indexes = []
166   - for index in genename["indexes"]:
167   - rank = int(index)
168   - if rank >= first and rank <= end:
169   - indexes.append(index)
170   - genename["indexes"] = indexes
171   - # ---
172   - outfilename = self._ima.fullfilename(outfilename)
173   - outfilegene, outfile_extension = os.path.splitext(outfilename)
174   - #print(f"genename={genename}")
175   - #print(f"outfilegene={outfilegene}")
176   - #print(f"outfile_extension={outfile_extension}")
177   - return genename, outfilegene, outfile_extension
178   -
179   - def indexnames(self, genename, outfilegene, outfile_extension, index, k):
180   - n = len(genename["indexes"])
181   - filename_short = genename["genename"] + genename["sep"] + index + genename["suffix"] + genename["file_extension"]
182   - filename = os.path.join( genename["dirname"], filename_short)
183   - outfname = outfilegene + str(k) + outfile_extension
184   - basename = os.path.basename(outfname)
185   - called_by = inspect.stack()[1][3]
186   - print(f"ImaSeries.{called_by} {k}/{n} : {filename_short} -> {basename}")
187   - return filename_short, basename, filename, outfname
188   -
189   - def deletenames(self, fitsname):
190   - for fname in self._ima.itername(fitsname):
191   - print(f"Delete = {fname}")
192   - os.remove(fname)
193   -
194   - # =============================================
195 177 # Image processing (pixels are modified)
196 178 # =============================================
197 179  
... ...
src/guitastro/imastack.py
... ... @@ -9,6 +9,11 @@ import datetime
9 9 import numpy as np
10 10  
11 11 try:
  12 + from .filenames import Filenames
  13 +except:
  14 + from filenames import Filenames
  15 +
  16 +try:
12 17 from .ima import Ima
13 18 except:
14 19 from ima import Ima
... ... @@ -43,6 +48,35 @@ class ImaStackException(GuitastroException):
43 48  
44 49  
45 50 class ImaStack(ImaStackException, GuitastroTools):
  51 + """Image processing applied to a series of images.
  52 +
  53 + This class process a series of images to generate only one image.
  54 +
  55 + The class ImaSeries imports the following methods from the class Filenames. See the Filenames class documentation for these methods:
  56 +
  57 + * longitude
  58 + * namings
  59 + * naming
  60 + * naming_rules
  61 + * naming_ident
  62 + * naming_get
  63 + * naming_set
  64 + * path
  65 + * extension
  66 + * get_night
  67 + * fullfilename
  68 + * basename
  69 + * itername
  70 + * enumname
  71 + * genename
  72 + * genenames
  73 + * innames
  74 + * inoutnames
  75 + * outfilename
  76 + * indexnames
  77 + * deletenames
  78 +
  79 + """
46 80  
47 81 VERBOSE_NONE = 0
48 82 VERBOSE_SPECIFIC = 1
... ... @@ -55,6 +89,31 @@ class ImaStack(ImaStackException, GuitastroTools):
55 89 self._verbose_level = self.VERBOSE_NONE
56 90 self._longiau_deg = 0.0
57 91 self._outfilename = os.path.join(self._ima.path(), "noname"+self._ima.extension())
  92 + # --- import Filenames methods
  93 + fn = Filenames()
  94 + self.longitude = fn.longitude
  95 + self.namings = fn.namings
  96 + self.naming = fn.naming
  97 + self.naming_rules = fn.naming_rules
  98 + self.naming_ident = fn.naming_ident
  99 + self.naming_get = fn.naming_get
  100 + self.naming_set = fn.naming_set
  101 + self.path = fn.path
  102 + self.extension = fn.extension
  103 + self.get_night = fn.get_night
  104 + self.fullfilename = fn.fullfilename
  105 + self.basename = fn.basename
  106 + self.itername = fn.itername
  107 + self.enumname = fn.enumname
  108 + self.genename = fn.genename
  109 + self.genenames = fn.genenames
  110 + self.innames = fn.innames
  111 + self.inoutnames = fn.inoutnames
  112 + self.outfilename = fn.outfilename
  113 + self.indexnames = fn.indexnames
  114 + self.deletenames = fn.deletenames
  115 + self._askopenfilename = fn._askopenfilename
  116 + self._asksaveasfilename = fn._asksaveasfilename
58 117  
59 118 def longitude(self, longiau_deg:float):
60 119 self._ima.longitude(longiau_deg)
... ...