astrohack 1.0.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {astrohack-1.0.0/src/astrohack.egg-info → astrohack-1.0.2}/PKG-INFO +1 -1
  2. {astrohack-1.0.0 → astrohack-1.0.2}/pyproject.toml +1 -1
  3. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/beamcut.py +2 -0
  4. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/beamcut.py +31 -11
  5. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/extract_pointing.py +10 -5
  6. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/image_comparison_tool.py +44 -9
  7. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/extract_holog.py +4 -0
  8. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/image_comparison_tool.py +1 -1
  9. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/beamcut_mds.py +7 -1
  10. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/text.py +3 -0
  11. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/visualization/plot_tools.py +33 -2
  12. {astrohack-1.0.0 → astrohack-1.0.2/src/astrohack.egg-info}/PKG-INFO +1 -1
  13. {astrohack-1.0.0 → astrohack-1.0.2}/LICENSE +0 -0
  14. {astrohack-1.0.0 → astrohack-1.0.2}/MANIFEST.in +0 -0
  15. {astrohack-1.0.0 → astrohack-1.0.2}/README.rst +0 -0
  16. {astrohack-1.0.0 → astrohack-1.0.2}/setup.cfg +0 -0
  17. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/__init__.py +0 -0
  18. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/__init__.py +0 -0
  19. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/antenna_surface.py +0 -0
  20. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/base_panel.py +0 -0
  21. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/panel_fitting.py +0 -0
  22. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/polygon_panel.py +0 -0
  23. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/ring_panel.py +0 -0
  24. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/antenna/telescope.py +0 -0
  25. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/cassegrain_ray_tracing.py +0 -0
  26. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/combine.py +0 -0
  27. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/beamcut.param.json +0 -0
  28. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/beamcut_mds.param.json +0 -0
  29. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/cassegrain_ray_tracing.param.json +0 -0
  30. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/combine.param.json +0 -0
  31. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/create_param_json.py +0 -0
  32. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/dask.config.yaml +0 -0
  33. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/extract_holog.param.json +0 -0
  34. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/extract_locit.param.json +0 -0
  35. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/extract_pointing.param.json +0 -0
  36. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/holog.param.json +0 -0
  37. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/holog_mds.param.json +0 -0
  38. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/image_comparison_tool.param.json +0 -0
  39. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/image_mds.param.json +0 -0
  40. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/locit.param.json +0 -0
  41. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/locit_mds.param.json +0 -0
  42. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/panel.param.json +0 -0
  43. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/panel_mds.param.json +0 -0
  44. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/point_mds.param.json +0 -0
  45. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/config/position_mds.param.json +0 -0
  46. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/__init__.py +0 -0
  47. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/cassegrain_ray_tracing.py +0 -0
  48. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/combine.py +0 -0
  49. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/extract_holog.py +0 -0
  50. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/extract_locit.py +0 -0
  51. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/holog.py +0 -0
  52. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/holog_obs_dict.py +0 -0
  53. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/locit.py +0 -0
  54. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/core/panel.py +0 -0
  55. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/aca_7m.zarr/.zattrs +0 -0
  56. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/aca_7m.zarr/.zgroup +0 -0
  57. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/aca_7m.zarr/.zmetadata +0 -0
  58. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_da.zarr/.zattrs +0 -0
  59. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_da.zarr/.zgroup +0 -0
  60. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_da.zarr/.zmetadata +0 -0
  61. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_dv.zarr/.zattrs +0 -0
  62. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_dv.zarr/.zgroup +0 -0
  63. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_dv.zarr/.zmetadata +0 -0
  64. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_tp.zarr/.zattrs +0 -0
  65. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_tp.zarr/.zgroup +0 -0
  66. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/alma_tp.zarr/.zmetadata +0 -0
  67. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/fix_telescope_files.bash +0 -0
  68. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/.zattrs +0 -0
  69. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/.zgroup +0 -0
  70. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/.zmetadata +0 -0
  71. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/point_cloud/.zarray +0 -0
  72. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/point_cloud/0.0 +0 -0
  73. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/qps_coefficients/.zarray +0 -0
  74. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/ngvla_proto_2025.zarr/qps_coefficients/0 +0 -0
  75. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/vla.zarr/.zattrs +0 -0
  76. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/vla.zarr/.zgroup +0 -0
  77. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/vla.zarr/.zmetadata +0 -0
  78. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/vlba.zarr/.zattrs +0 -0
  79. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/vlba.zarr/.zgroup +0 -0
  80. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/data/telescopes/vlba.zarr/.zmetadata +0 -0
  81. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/extract_locit.py +0 -0
  82. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/extract_pointing.py +0 -0
  83. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/holog.py +0 -0
  84. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/__init__.py +0 -0
  85. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/base_mds.py +0 -0
  86. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/dio.py +0 -0
  87. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/holog_mds.py +0 -0
  88. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/image_mds.py +0 -0
  89. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/locit_mds.py +0 -0
  90. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/panel_mds.py +0 -0
  91. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/point_mds.py +0 -0
  92. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/io/position_mds.py +0 -0
  93. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/locit.py +0 -0
  94. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/panel.py +0 -0
  95. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/__init__.py +0 -0
  96. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/algorithms.py +0 -0
  97. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/constants.py +0 -0
  98. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/conversion.py +0 -0
  99. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/file.py +0 -0
  100. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/fits.py +0 -0
  101. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/graph.py +0 -0
  102. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/gridding.py +0 -0
  103. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/imaging.py +0 -0
  104. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/phase_fitting.py +0 -0
  105. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/ray_tracing_general.py +0 -0
  106. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/tools.py +0 -0
  107. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/validation.py +0 -0
  108. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/verification_tools.py +0 -0
  109. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/veritas.py +0 -0
  110. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/utils/zernike_aperture_fitting.py +0 -0
  111. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/visualization/__init__.py +0 -0
  112. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/visualization/array_cfg_plot.py +0 -0
  113. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack/visualization/observation_summary.py +0 -0
  114. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack.egg-info/SOURCES.txt +0 -0
  115. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack.egg-info/dependency_links.txt +0 -0
  116. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack.egg-info/requires.txt +0 -0
  117. {astrohack-1.0.0 → astrohack-1.0.2}/src/astrohack.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astrohack
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Holography Antenna Commissioning Kit
5
5
  Author-email: Joshua Hoskins <jhoskins@nrao.edu>, Victor de Souza Magalhaes <vdesouza@nrao.edu>, Jan-Willem Steeb <jsteeb@nrao.edu>
6
6
  Requires-Python: <3.14,>=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astrohack"
3
- version = "1.0.0"
3
+ version = "1.0.2"
4
4
  description = "Holography Antenna Commissioning Kit"
5
5
  readme = "README.md"
6
6
  requires-python = ">= 3.11, < 3.14"
@@ -2,6 +2,7 @@ import pathlib
2
2
  import numpy as np
3
3
 
4
4
  from toolviper.utils.parameter import validate
5
+ import toolviper.utils.logger as logger
5
6
 
6
7
  from astrohack.core.beamcut import process_beamcut_chunk
7
8
  from astrohack.utils import get_default_file_name
@@ -143,6 +144,7 @@ def beamcut(
143
144
  )
144
145
  if executed_graph:
145
146
  if destination is not None:
147
+ logger.info("Producing plots...")
146
148
  beamcut_mds.plot_beamcut_in_amplitude(
147
149
  destination,
148
150
  lm_unit=lm_unit,
@@ -398,7 +398,9 @@ def _perform_curvefit_with_given_functions(
398
398
  return False, np.full_like(initial_guesses, np.nan)
399
399
 
400
400
 
401
- def _identify_pb_and_sidelobes_in_fit(datalabel, x_data, fit_pars):
401
+ def _identify_pb_and_sidelobes_in_fit(
402
+ datalabel, x_data, fit_pars, primary_fwhm_estimate
403
+ ):
402
404
  """
403
405
  Identify primary beam and first sidelobes in fit using expected beam shape heuristics.
404
406
 
@@ -440,7 +442,16 @@ def _identify_pb_and_sidelobes_in_fit(datalabel, x_data, fit_pars):
440
442
  i_pb_cen = np.argmin(np.abs(centers))
441
443
  # This assumes the primary beam is the strongest
442
444
  i_pb_amp = np.argmax(amps)
443
- pb_problem = i_pb_cen != i_pb_amp
445
+
446
+ pb_problem = False
447
+ if i_pb_cen != i_pb_amp:
448
+ pb_problem = True
449
+ else:
450
+ if (
451
+ fwhms[i_pb_cen] < 0.5 * primary_fwhm_estimate
452
+ or fwhms[i_pb_cen] > 3.0 * primary_fwhm_estimate
453
+ ):
454
+ pb_problem = True
444
455
 
445
456
  if pb_problem:
446
457
  logger.warning(f"Cannot reliably identify primary beam for {datalabel}.")
@@ -496,20 +507,29 @@ def _beamcut_multi_lobes_gaussian_fit(cut_xdtree, datalabel):
496
507
  initial_guesses, bounds, n_peaks = _build_multi_gaussian_initial_guesses(
497
508
  x_data, y_data, primary_fwhm
498
509
  )
499
- fit_succeeded, fit_pars = _perform_curvefit_with_given_functions(
500
- x_data,
501
- y_data,
502
- initial_guesses,
503
- bounds,
504
- _multi_gaussian,
505
- this_corr_data_label,
506
- )
510
+
511
+ # This is a test for empty data
512
+ if np.nansum(y_data) == 0:
513
+ logger.warning(
514
+ f"{this_corr_data_label} appears to contain no valid data"
515
+ )
516
+ fit_succeeded = False
517
+ fit_pars = np.full_like(initial_guesses, np.nan)
518
+ else:
519
+ fit_succeeded, fit_pars = _perform_curvefit_with_given_functions(
520
+ x_data,
521
+ y_data,
522
+ initial_guesses,
523
+ bounds,
524
+ _multi_gaussian,
525
+ this_corr_data_label,
526
+ )
507
527
 
508
528
  if fit_succeeded:
509
529
  fit = _multi_gaussian(x_data, *fit_pars)
510
530
  n_peaks, fit_pars, pb_center, pb_fwhm, first_side_lobe_ratio = (
511
531
  _identify_pb_and_sidelobes_in_fit(
512
- this_corr_data_label, x_data, fit_pars
532
+ this_corr_data_label, x_data, fit_pars, primary_fwhm
513
533
  )
514
534
  )
515
535
  else:
@@ -179,7 +179,7 @@ def extract_pointing_chunk(pnt_params: dict, output_mds: AstrohackPointFile):
179
179
  tb.close()
180
180
  table_obj.close()
181
181
 
182
- _evaluate_time_samping(direction_time, ant_name)
182
+ _evaluate_time_sampling(direction_time, ant_name)
183
183
 
184
184
  pnt_xds = xr.Dataset()
185
185
  coords = {"time": direction_time}
@@ -379,17 +379,22 @@ def _extract_scan_time_dict_jit(time, scan_ids, state_ids, ddi_ids, mapping_stat
379
379
  return scan_time_dict
380
380
 
381
381
 
382
- def _evaluate_time_samping(
383
- time_sampling, data_label, threshold=0.01, expected_interval=0.1
382
+ def _evaluate_time_sampling(
383
+ time_sampling, data_label, threshold=0.01, expected_interval=None
384
384
  ):
385
+ intervals = np.diff(time_sampling)
386
+ unq_intervals, counts = np.unique(intervals, return_counts=True)
387
+ if expected_interval is None:
388
+ i_max_count = np.argmax(counts)
389
+ expected_interval = unq_intervals[i_max_count]
390
+
385
391
  bin_sz = expected_interval / 4
386
392
  time_bin_edge = np.arange(-bin_sz / 2, 2.5 * expected_interval, bin_sz)
387
393
  time_bin_axis = time_bin_edge[:-1] + bin_sz / 2
388
394
  i_mid = int(np.argmin(np.abs(time_bin_axis - expected_interval)))
389
395
 
390
- intervals = np.diff(time_sampling)
391
396
  hist, edges = np.histogram(intervals, bins=time_bin_edge)
392
- n_total = np.sum(hist)
397
+ n_total = np.nansum(hist)
393
398
  outlier_fraction = 1 - hist[i_mid] / n_total
394
399
 
395
400
  if outlier_fraction > threshold:
@@ -127,7 +127,8 @@ class FITSImage:
127
127
  """
128
128
  self.filename = fits_filename
129
129
  self.telescope_name = telescope_name
130
- self.rootname = ".".join(fits_filename.split(".")[:-1]) + "."
130
+ fits_real_filename = fits_filename.split("/")[-1]
131
+ self.rootname = ".".join(fits_real_filename.split(".")[:-1]) + "."
131
132
  self.header, self.data = read_fits(self.filename, header_as_dict=True)
132
133
  stokes_iaxis = get_stokes_axis_iaxis(self.header)
133
134
 
@@ -152,12 +153,16 @@ class FITSImage:
152
153
  self.y_axis, _, self.y_unit = get_axis_from_fits_header(
153
154
  self.header, 2, pixel_offset=False
154
155
  )
156
+ offset_scale = 1.5
157
+ x_offset = offset_scale * np.unique(np.diff(self.x_axis))[0]
158
+ y_offset = offset_scale * np.unique(np.diff(self.y_axis))[0]
159
+ self.x_axis = np.flip(self.x_axis + x_offset)
160
+ self.y_axis = np.flip(self.y_axis + y_offset)
155
161
  self.x_unit = "m"
156
162
  self.y_unit = "m"
157
163
  elif "Astrohack" in self.header["ORIGIN"]:
158
164
  self.x_axis, _, self.x_unit = get_axis_from_fits_header(self.header, 1)
159
165
  self.y_axis, _, self.y_unit = get_axis_from_fits_header(self.header, 2)
160
- self.data = np.fliplr(self.data)
161
166
  else:
162
167
  raise NotImplementedError(f'Unrecognized origin:\n{self.header["origin"]}')
163
168
  self._create_base_mask()
@@ -208,11 +213,13 @@ class FITSImage:
208
213
  x_mesh_dest, y_mesh_dest = np.meshgrid(
209
214
  ref_image.x_axis, ref_image.y_axis, indexing="ij"
210
215
  )
216
+ raveled_data = self.data.ravel()
217
+ valid_data = np.isfinite(raveled_data)
211
218
  resamp = griddata(
212
- (x_mesh_orig.ravel(), y_mesh_orig.ravel()),
213
- self.data.ravel(),
219
+ (x_mesh_orig.ravel()[valid_data], y_mesh_orig.ravel()[valid_data]),
220
+ raveled_data[valid_data],
214
221
  (x_mesh_dest.ravel(), y_mesh_dest.ravel()),
215
- method="linear",
222
+ method="nearest",
216
223
  )
217
224
  size = ref_image.x_axis.shape[0], ref_image.y_axis.shape[0]
218
225
  self.x_axis = ref_image.x_axis
@@ -592,7 +599,15 @@ class FITSImage:
592
599
  reorder_axis=False,
593
600
  )
594
601
 
595
- def scatter_plot(self, destination, ref_image, dpi=300, display=False):
602
+ def scatter_plot(
603
+ self,
604
+ destination,
605
+ ref_image,
606
+ dpi=300,
607
+ display=False,
608
+ max_radius=None,
609
+ min_radius=None,
610
+ ):
596
611
  """
597
612
  Produce a scatter plot of self.data agains ref_image.data
598
613
  Args:
@@ -600,6 +615,8 @@ class FITSImage:
600
615
  ref_image: Reference FITSImage object
601
616
  dpi: png resolution on disk
602
617
  display: Show interactive view of plot
618
+ max_radius: Maximum radius for scatter plot comparison as the outer panels can be crappy.
619
+ min_radius: Minimum radius for scatter plot comparison as the innermost panels can be crappy.
603
620
 
604
621
  Returns:
605
622
  None
@@ -610,10 +627,23 @@ class FITSImage:
610
627
 
611
628
  fig, ax = plt.subplots(1, 1, figsize=[10, 8])
612
629
 
630
+ x_mesh_orig, y_mesh_orig = np.meshgrid(self.x_axis, self.y_axis, indexing="ij")
631
+ radius = np.sqrt(x_mesh_orig**2 + y_mesh_orig**2)
632
+
633
+ telescope = get_proper_telescope(self.telescope_name)
634
+ if min_radius is None:
635
+ min_radius = telescope.inner_radial_limit
636
+ if max_radius is None:
637
+ max_radius = telescope.outer_radial_limit - 1.0
613
638
  scatter_mask = np.isfinite(ref_image.data)
614
639
  scatter_mask = np.where(np.isfinite(self.data), scatter_mask, False)
640
+ scatter_mask = np.where(radius < max_radius, scatter_mask, False)
641
+ scatter_mask = np.where(radius > min_radius, scatter_mask, False)
642
+
615
643
  ydata = self.data[scatter_mask]
616
644
  xdata = ref_image.data[scatter_mask]
645
+ pl_max = np.max((np.max(xdata), np.max(ydata)))
646
+ pl_min = np.min((np.min(xdata), np.min(ydata)))
617
647
 
618
648
  scatter_plot(
619
649
  ax,
@@ -622,6 +652,12 @@ class FITSImage:
622
652
  ydata,
623
653
  f"{self.filename} [{self.unit}]",
624
654
  add_regression=True,
655
+ regression_method="siegelslopes",
656
+ add_regression_reference=True,
657
+ regression_reference_label="Perfect agreement",
658
+ xlim=(pl_min, pl_max),
659
+ ylim=[pl_min, pl_max],
660
+ force_equal_aspect=True,
625
661
  )
626
662
  close_figure(
627
663
  fig,
@@ -641,7 +677,6 @@ def image_comparison_chunk(compare_params):
641
677
  Returns:
642
678
  A DataTree containing the Image and its reference Image.
643
679
  """
644
-
645
680
  image = FITSImage.from_fits_file(
646
681
  compare_params["this_image"], compare_params["telescope_name"]
647
682
  )
@@ -698,8 +733,8 @@ def image_comparison_chunk(compare_params):
698
733
  if compare_params["plot_scatter"]:
699
734
  image.scatter_plot(destination, ref_image, dpi=dpi, display=display)
700
735
 
701
- img_node = xr.DataTree(name=image.filename, dataset=image.export_as_xds())
702
- ref_node = xr.DataTree(name=ref_image.filename, dataset=ref_image.export_as_xds())
736
+ img_node = xr.DataTree(name=image.rootname, dataset=image.export_as_xds())
737
+ ref_node = xr.DataTree(name=ref_image.rootname, dataset=ref_image.export_as_xds())
703
738
  tree_node = xr.DataTree(
704
739
  name=image.rootname[:-1], children={"Reference": ref_node, "Image": img_node}
705
740
  )
@@ -1,4 +1,5 @@
1
1
  import pathlib
2
+ from copy import deepcopy
2
3
 
3
4
  import toolviper.utils.parameter
4
5
  import toolviper.utils.logger as logger
@@ -187,6 +188,9 @@ def extract_holog(
187
188
  }
188
189
 
189
190
  """
191
+ # This copy here ensures that the user space holog_obs_dict given to extract_holog is not modified during execution
192
+ if holog_obs_dict is not None:
193
+ holog_obs_dict = deepcopy(holog_obs_dict)
190
194
 
191
195
  # Doing this here allows it to get captured by locals()
192
196
  holog_name = get_default_file_name(ms_name, ".holog.zarr", holog_name)
@@ -201,7 +201,7 @@ def rms_table_from_zarr_datatree(
201
201
  raise FileNotFoundError
202
202
 
203
203
  xdt = xr.open_datatree(input_params["zarr_data_tree"])
204
- if xdt.attrs["origin"] != "compare_fits_images":
204
+ if xdt.attrs["origin_info"]["creator_function"] != "compare_fits_images":
205
205
  logger.error("Data tree file was not created by astrohack.compare_fits_images")
206
206
  raise ValueError
207
207
 
@@ -718,6 +718,8 @@ def _plot_single_cut_in_amplitude(cut_xds, axes, par_dict):
718
718
  # Init
719
719
  sub_title = _make_parallel_hand_sub_title(cut_xds.attrs)
720
720
  max_amp = cut_xds.attrs["all_corr_ymax"]
721
+ if max_amp < 1e-2:
722
+ max_amp = 1.0
721
723
  y_off = 0.05 * max_amp
722
724
  lm_unit = par_dict["lm_unit"]
723
725
  lm_fac = convert_unit("rad", lm_unit, "trigonometric")
@@ -944,7 +946,11 @@ def _plot_single_cut_in_attenuation(cut_xds, ax, par_dict):
944
946
  x_data = lm_fac * cut_xds["lm_dist"].values
945
947
  amps = cut_xds[f"{parallel_hand}_amplitude"].values
946
948
  max_amp = np.max(amps)
947
- y_data = to_db(amps / max_amp)
949
+ if max_amp == 0:
950
+ y_data = np.full_like(x_data, -1)
951
+ else:
952
+ y_data = to_db(amps / max_amp)
953
+
948
954
  y_min = np.min(y_data)
949
955
  if y_min < min_attenuation:
950
956
  min_attenuation = y_min
@@ -132,6 +132,9 @@ def get_default_file_name(
132
132
  else:
133
133
  output_filename = user_filename
134
134
 
135
+ if output_filename[-1] == "/":
136
+ output_filename = output_filename[:-1]
137
+
135
138
  logger.info(f"Creating output file name: {output_filename}")
136
139
  return output_filename
137
140
 
@@ -1,6 +1,6 @@
1
1
  import matplotlib.image
2
2
  import numpy as np
3
- from scipy.stats import linregress
3
+ from scipy.stats import linregress, theilslopes, siegelslopes
4
4
 
5
5
  from matplotlib import pyplot as plt
6
6
  from matplotlib.patches import Rectangle
@@ -251,6 +251,12 @@ def scatter_plot(
251
251
  add_regression=False,
252
252
  regression_linestyle="-",
253
253
  regression_color="black",
254
+ regression_method="linregress",
255
+ add_regression_reference=False,
256
+ regression_reference=(1.0, 0.0),
257
+ regression_reference_color="orange",
258
+ regression_reference_label="Regression refrence",
259
+ force_equal_aspect=False,
254
260
  add_legend=True,
255
261
  legend_location="best",
256
262
  ):
@@ -287,6 +293,12 @@ def scatter_plot(
287
293
  add_regression: Add a linear regression between X and y data
288
294
  regression_linestyle: Line style for the regression plot
289
295
  regression_color: Color for the regression plot
296
+ regression_method: Which scipy function to use for the linear regression: linregress, theilslopes or siegelslopes
297
+ add_regression_reference: Add reference for the expected regression result
298
+ regression_reference: 2 value array/tuple/list with a slope and intercept for reference
299
+ regression_reference_color: Color for reference regression
300
+ regression_reference_label: Label for reference regression
301
+ force_equal_aspect: Force equal aspect on plot box
290
302
  add_legend: add legend to the plot
291
303
  legend_location: Location of the legend in the plot
292
304
  """
@@ -325,7 +337,14 @@ def scatter_plot(
325
337
  )
326
338
 
327
339
  if add_regression:
328
- slope, intercept, _, _, _ = linregress(xdata, ydata)
340
+ if regression_method == "linregress":
341
+ slope, intercept, _, _, _ = linregress(xdata, ydata)
342
+ elif regression_method == "theilslopes":
343
+ slope, intercept, _, _ = theilslopes(ydata, xdata)
344
+ elif regression_method == "siegelslopes":
345
+ slope, intercept = siegelslopes(ydata, xdata)
346
+ else:
347
+ raise RuntimeError(f"Unknown linear regression method: {regression_method}")
329
348
  regression_label = f"y = {slope:.4f}*x + {intercept:.4f}"
330
349
  yregress = slope * xdata + intercept
331
350
  ax.plot(
@@ -336,6 +355,15 @@ def scatter_plot(
336
355
  label=regression_label,
337
356
  lw=2,
338
357
  )
358
+ if add_regression_reference:
359
+ reg_ref = regression_reference[0] * xdata + regression_reference[1]
360
+ ax.plot(
361
+ xdata,
362
+ reg_ref,
363
+ ls=regression_linestyle,
364
+ color=regression_reference_color,
365
+ label=regression_reference_label,
366
+ )
339
367
 
340
368
  if model is not None:
341
369
  ax.plot(
@@ -373,6 +401,9 @@ def scatter_plot(
373
401
  ax_res.set_ylabel("Residuals")
374
402
  ax_res.set_xlabel(xlabel)
375
403
 
404
+ if force_equal_aspect:
405
+ ax.set_aspect("equal", adjustable="box")
406
+
376
407
  if title is not None:
377
408
  ax.set_title(title)
378
409
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astrohack
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Holography Antenna Commissioning Kit
5
5
  Author-email: Joshua Hoskins <jhoskins@nrao.edu>, Victor de Souza Magalhaes <vdesouza@nrao.edu>, Jan-Willem Steeb <jsteeb@nrao.edu>
6
6
  Requires-Python: <3.14,>=3.11
File without changes
File without changes
File without changes
File without changes