jolly-roger 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jolly-roger might be problematic. Click here for more details.

jolly_roger/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.3.0'
21
- __version_tuple__ = version_tuple = (0, 3, 0)
20
+ __version__ = version = '0.4.0'
21
+ __version_tuple__ = version_tuple = (0, 4, 0)
jolly_roger/tractor.py CHANGED
@@ -19,7 +19,9 @@ from tqdm.auto import tqdm
19
19
  from jolly_roger.delays import data_to_delay_time, delay_time_to_data
20
20
  from jolly_roger.logging import logger
21
21
  from jolly_roger.plots import plot_baseline_comparison_data
22
+ from jolly_roger.utils import log_dataclass_attributes, log_jolly_roger_version
22
23
  from jolly_roger.uvws import WDelays, get_object_delay_for_ms
24
+ from jolly_roger.wrap import calculate_nyquist_zone, symmetric_domain_wrap
23
25
 
24
26
 
25
27
  @dataclass(frozen=True)
@@ -73,9 +75,12 @@ def tukey_taper(
73
75
 
74
76
  if tukey_x_offset is not None:
75
77
  x_freq = x_freq[:, None] - tukey_x_offset[None, :]
78
+ from jolly_roger.wrap import symmetric_domain_wrap
79
+
80
+ x_freq = symmetric_domain_wrap(values=x_freq, upper_limit=np.pi)
76
81
 
77
82
  taper = np.ones_like(x_freq)
78
- logger.debug(f"{x_freq.shape=} {type(x_freq)=}")
83
+ # logger.debug(f"{x_freq.shape=} {type(x_freq)=}")
79
84
  # Fully zero region
80
85
  taper[np.abs(x_freq) > outer_width] = 0
81
86
 
@@ -455,6 +460,7 @@ def make_plot_results(
455
460
  before_delays = data_to_delay_time(data=before_baseline_data)
456
461
  after_delays = data_to_delay_time(data=after_baseline_data)
457
462
 
463
+ logger.info("Creating figure")
458
464
  # TODO: the baseline data and delay times could be put into a single
459
465
  # structure to pass around easier.
460
466
  plot_path = plot_baseline_comparison_data(
@@ -523,23 +529,32 @@ def _tukey_tractor(
523
529
  """
524
530
 
525
531
  delay_time = data_to_delay_time(data=data_chunk)
532
+ flags_to_return: None | NDArray[np.bool] = None
526
533
 
527
- # Look up the delay offset if requested
534
+ # Set up the offsets. By default we will be tapering around the field,
535
+ # but should w_delays be specified these will be modified to direct
536
+ # towards the nominated object in the if below
528
537
  tukey_x_offset: u.Quantity = np.zeros_like(delay_time.delay)
529
538
 
530
539
  if w_delays is not None:
531
540
  baseline_idx, time_idx = _get_baseline_time_indicies(
532
541
  w_delays=w_delays, data_chunk=data_chunk
533
542
  )
534
- tukey_x_offset = w_delays.w_delays[baseline_idx, time_idx]
535
- # logger.info(f"{tukey_x_offset=}")
543
+ original_tukey_x_offset = w_delays.w_delays[baseline_idx, time_idx]
544
+
545
+ # Make a copy for later use post wrapping
546
+ tukey_x_offset = original_tukey_x_offset.copy()
547
+
548
+ tukey_x_offset = symmetric_domain_wrap(
549
+ values=tukey_x_offset.value, upper_limit=np.max(delay_time.delay).value
550
+ )
536
551
 
537
552
  # need to scale the x offsert to the -pi to pi
538
553
  # The delay should be symmetric
539
554
  tukey_x_offset = (
540
555
  tukey_x_offset / (np.max(delay_time.delay) / np.pi).decompose()
541
556
  ).value
542
- # logger.info(f"{tukey_x_offset=}")
557
+ # # logger.info(f"{tukey_x_offset=}")
543
558
 
544
559
  taper = tukey_taper(
545
560
  x=delay_time.delay,
@@ -547,29 +562,70 @@ def _tukey_tractor(
547
562
  tukey_width=tukey_tractor_options.tukey_width,
548
563
  tukey_x_offset=tukey_x_offset,
549
564
  )
550
- if w_delays is not None:
565
+
566
+ if w_delays is None:
567
+ # This is the easy case
568
+ taper = taper[None, :, None]
569
+
570
+ else:
571
+ # TODO: This pirate reckons that merging the masks together
572
+ # into a single mask throughout may make things easier to
573
+ # manage and visualise.
574
+
551
575
  # The use of the `tukey_x_offset` changes the
552
576
  # shape of the output array. The internals of that
553
577
  # function returns a different shape via the broadcasting
554
578
  taper = np.swapaxes(taper[:, :, None], 0, 1)
555
579
 
556
- # Since we want to dampen the target object we invert the taper
580
+ # Since we want to dampen the target object we invert the taper.
581
+ # By default the taper dampers outside the inner region.
557
582
  taper = 1.0 - taper
558
583
 
584
+ # apply the flags to ignore the tapering if the object is larger
585
+ # than one wrap away
586
+ # Calculate the offset account of nyquist sampling
587
+ no_wraps_for_offset = calculate_nyquist_zone(
588
+ values=original_tukey_x_offset.value,
589
+ upper_limit=np.max(delay_time.delay).value,
590
+ )
591
+ ignore_wrapping_for = (
592
+ no_wraps_for_offset > tukey_tractor_options.ignore_nyquist_zone
593
+ )
594
+ taper[ignore_wrapping_for, :, :] = 1.0
595
+
559
596
  # Delay with the elevation of the target object
560
- # TODO: Allow elevation to be a user parameter
561
597
  elevation_mask = w_delays.elevation < tukey_tractor_options.elevation_cut
562
598
  taper[elevation_mask[time_idx], :, :] = 1.0
563
599
 
564
- # TODO: Handle case of aliased delays
565
-
566
- # TODO: Create heuristic to determine where baseline is long enough to
567
- # ignore the tapering. Aliasing may give us this though...
568
-
569
- # TODO: Create flags where delay is 'close' to 0
570
-
571
- else:
572
- taper = taper[None, :, None]
600
+ # Compute flags to ignore the objects delay crossing 0, Do
601
+ # This by computing the taper towards the field and
602
+ # see if there are any components of the two sets of tapers
603
+ # that are not 1 (where 1 is 'no change').
604
+ field_taper = tukey_taper(
605
+ x=delay_time.delay,
606
+ outer_width=tukey_tractor_options.outer_width / 4,
607
+ tukey_width=tukey_tractor_options.tukey_width,
608
+ tukey_x_offset=None,
609
+ )
610
+ # We need to account for no broadcasting when offset is None
611
+ # as the returned shape is different
612
+ field_taper = field_taper[None, :, None]
613
+ field_taper = 1.0 - field_taper
614
+ intersecting_taper = np.any(
615
+ np.reshape((taper != 1) & (field_taper != 1), (taper.shape[0], -1)), axis=1
616
+ )
617
+ # # Should the data need to be modified in conjunction with the flags
618
+ # taper[
619
+ # intersecting_taper &
620
+ # ~elevation_mask[time_idx] &
621
+ # ~ignore_wrapping_for
622
+ # ] = 0.0
623
+ # Update flags
624
+ flags_to_return = np.zeros_like(data_chunk.masked_data.mask)
625
+ flags_to_return[intersecting_taper] = True
626
+ flags_to_return = (
627
+ ~np.isfinite(data_chunk.masked_data.filled(np.nan)) | flags_to_return
628
+ )
573
629
 
574
630
  # Delay-time is a 3D array: (time, delay, pol)
575
631
  # Taper is 1D: (delay,)
@@ -587,7 +643,7 @@ def _tukey_tractor(
587
643
  )
588
644
  logger.debug(f"{tapered_data.masked_data.shape=} {tapered_data.masked_data.dtype}")
589
645
 
590
- return tapered_data
646
+ return tapered_data, flags_to_return
591
647
 
592
648
 
593
649
  @dataclass
@@ -609,7 +665,7 @@ class TukeyTractorOptions:
609
665
  dry_run: bool = False
610
666
  """Indicates whether the data will be written back to the measurement set"""
611
667
  make_plots: bool = False
612
- """Create a small set of diagnostic plots"""
668
+ """Create a small set of diagnostic plots. This can be slow."""
613
669
  overwrite: bool = False
614
670
  """If the output column exists it will be overwritten"""
615
671
  chunk_size: int = 1000
@@ -620,6 +676,8 @@ class TukeyTractorOptions:
620
676
  """The target object to apply the delay towards."""
621
677
  elevation_cut: u.Quantity = -1 * u.deg
622
678
  """The elevation cut-off for the target object. Defaults to 0 degrees."""
679
+ ignore_nyquist_zone: int = 2
680
+ """Do not apply the tukey taper if object is beyond this Nyquist zone"""
623
681
 
624
682
 
625
683
  def tukey_tractor(
@@ -635,8 +693,10 @@ def tukey_tractor(
635
693
  Args:
636
694
  tukey_tractor_options (TukeyTractorOptions): The settings to use during the taper, and measurement set to apply them to.
637
695
  """
638
- logger.info("jolly-roger")
639
- logger.info(f"Options: {tukey_tractor_options}")
696
+ log_jolly_roger_version()
697
+ log_dataclass_attributes(
698
+ to_log=tukey_tractor_options, class_name="TukeyTaperOptions"
699
+ )
640
700
 
641
701
  # acquire all the tables necessary to get unit information and data from
642
702
  open_ms_tables = get_open_ms_tables(
@@ -671,7 +731,7 @@ def tukey_tractor(
671
731
  chunk_size=tukey_tractor_options.chunk_size,
672
732
  data_column=tukey_tractor_options.data_column,
673
733
  ):
674
- taper_data_chunk = _tukey_tractor(
734
+ taper_data_chunk, flags_to_apply = _tukey_tractor(
675
735
  data_chunk=data_chunk,
676
736
  tukey_tractor_options=tukey_tractor_options,
677
737
  w_delays=w_delays,
@@ -686,6 +746,13 @@ def tukey_tractor(
686
746
  startrow=taper_data_chunk.row_start,
687
747
  nrow=taper_data_chunk.chunk_size,
688
748
  )
749
+ if flags_to_apply is not None:
750
+ open_ms_tables.main_table.putcol(
751
+ columnname="FLAG",
752
+ value=flags_to_apply,
753
+ startrow=taper_data_chunk.row_start,
754
+ nrow=taper_data_chunk.chunk_size,
755
+ )
689
756
 
690
757
  if tukey_tractor_options.make_plots:
691
758
  plot_paths = make_plot_results(
@@ -752,7 +819,7 @@ def get_parser() -> ArgumentParser:
752
819
  tukey_parser.add_argument(
753
820
  "--make-plots",
754
821
  action="store_true",
755
- help="If set, the Tukey tractor will make plots of the results",
822
+ help="If set, the Tukey tractor will make plots of the results. This can be slow.",
756
823
  )
757
824
  tukey_parser.add_argument(
758
825
  "--overwrite",
@@ -776,6 +843,12 @@ def get_parser() -> ArgumentParser:
776
843
  action="store_true",
777
844
  help="Whether the tukey taper is applied towards the target object (e.g. the Sun). If not set, the taper is applied towards large delays.",
778
845
  )
846
+ tukey_parser.add_argument(
847
+ "--ignore-nyquist-zone",
848
+ type=int,
849
+ default=2,
850
+ help="Do not apply the taper if the objects delays beyond this Nyquist zone",
851
+ )
779
852
 
780
853
  return parser
781
854
 
@@ -799,7 +872,9 @@ def cli() -> None:
799
872
  chunk_size=args.chunk_size,
800
873
  target_object=args.target_object,
801
874
  apply_towards_object=args.apply_towards_object,
875
+ ignore_nyquist_zone=args.ignore_nyquist_zone,
802
876
  )
877
+
803
878
  tukey_tractor(tukey_tractor_options=tukey_tractor_options)
804
879
  else:
805
880
  parser.print_help()
jolly_roger/utils.py ADDED
@@ -0,0 +1,28 @@
1
+ """Small helper utility functions"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import is_dataclass
6
+ from typing import Any
7
+
8
+ from jolly_roger import __version__
9
+ from jolly_roger.logging import logger
10
+
11
+
12
+ def log_jolly_roger_version() -> None:
13
+ """Write out a jolly roger header to output"""
14
+ logger.info("Jolly-Roger: Flagging and Tapering")
15
+ logger.info(f"Version: {__version__}")
16
+
17
+
18
+ def log_dataclass_attributes(to_log: Any, class_name: str | None = None) -> None:
19
+ """Log the attributes and values from an Dataclass"""
20
+ if not is_dataclass(to_log):
21
+ return
22
+
23
+ if class_name:
24
+ logger.info(f"Settings for {class_name}")
25
+
26
+ for attribute in to_log.__annotations__:
27
+ value = to_log.__dict__[attribute]
28
+ logger.info(f"{attribute:<30} = {value}")
jolly_roger/wrap.py ADDED
@@ -0,0 +1,51 @@
1
+ """Helper utilities to deal with cyclic boundaries
2
+ on data"""
3
+
4
+ from __future__ import annotations
5
+
6
+ import numpy as np
7
+ from numpy.typing import NDArray
8
+
9
+
10
+ def symmetric_domain_wrap(
11
+ values: NDArray[np.floating], upper_limit: float
12
+ ) -> NDArray[np.floating]:
13
+ """Place a set of values into a cyclic domain that is
14
+ symmetric around zero.
15
+
16
+ Args:
17
+ values (NDArray[np.floating]): Values that need to be mapped into the cyclic domain
18
+ upper_limit (float): The upper bound of the symmetric cyclic domain
19
+
20
+ Returns:
21
+ NDArray[np.floating]: Values that have been mapped to the -upper_limit to upper_limit domain
22
+ """
23
+ # Calculate an appropriate domain mapping
24
+ # The natural domain is going to be mapped
25
+ # to -pi to pi.
26
+ domain_mapping = np.pi / upper_limit
27
+
28
+ real = np.cos(values * domain_mapping)
29
+ imag = np.sin(values * domain_mapping)
30
+
31
+ wrapped_values = real + 1j * imag
32
+
33
+ return np.angle(wrapped_values) / domain_mapping
34
+
35
+
36
+ def calculate_nyquist_zone(
37
+ values: NDArray[np.floating], upper_limit: float
38
+ ) -> NDArray[np.int_]:
39
+ """Return the nyquist zone a value is in for a symmetric
40
+ set of bounds around zero
41
+
42
+ Args:
43
+ values (NDArray[np.floating]): The values to calculate the zone for
44
+ upper_limit (float): The upper bound to the symmetric domain around zero
45
+
46
+ Returns:
47
+ NDArray[np.int_]: The zones values correspond to
48
+ """
49
+ return np.array(
50
+ np.floor((upper_limit + np.abs(values)) / (2.0 * upper_limit)) + 1, dtype=int
51
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jolly-roger
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: The pirate flagger
5
5
  Project-URL: Homepage, https://github.com/flint-crew/jolly-roger
6
6
  Project-URL: Bug Tracker, https://github.com/flint-crew/jolly-roger/issues
@@ -60,6 +60,9 @@ Description-Content-Type: text/markdown
60
60
 
61
61
  The pirate flagger!
62
62
 
63
+ <img src="logo.png" alt="The Jolly Roger Flag" style="width:400px;"/>
64
+
65
+
63
66
  # Installation
64
67
 
65
68
  `pip install jolly-roger`
@@ -1,5 +1,5 @@
1
1
  jolly_roger/__init__.py,sha256=7xiZLdeY-7sgrYGQ1gNdCjgCfqnoPXK7AeaHncY_DGU,204
2
- jolly_roger/_version.py,sha256=AGmG_Lx0-9ztFw_7d9mYbaYuC-2abxE1oXOUNAY29YY,511
2
+ jolly_roger/_version.py,sha256=l5eo51MdCumDFCp44TFT1JH8yCDo1krag-GJubLxnVo,511
3
3
  jolly_roger/_version.pyi,sha256=j5kbzfm6lOn8BzASXWjGIA1yT0OlHTWqlbyZ8Si_o0E,118
4
4
  jolly_roger/baselines.py,sha256=C_vC3v_ciU2T_si31oS0hUmsMNTQA0USxrm4118vYvY,4615
5
5
  jolly_roger/delays.py,sha256=cvLMhChkkB6PkS11v6JU8Wn23Zqv5bQY1HTMzeIGTNw,3015
@@ -8,10 +8,12 @@ jolly_roger/hour_angles.py,sha256=ld3jiEDQXlYLHrChUxYD_UBSxKH0qarstakBPLQ0M8s,60
8
8
  jolly_roger/logging.py,sha256=04YVHnF_8tKDkXNtXQ-iMyJ2BLV-qowbPAqqMFDxYE4,1338
9
9
  jolly_roger/plots.py,sha256=3aRIy4LcFY5mrlPim_geiz1B22wOYp0EfMvGh90GCUA,5473
10
10
  jolly_roger/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- jolly_roger/tractor.py,sha256=Z6LyrhNZZuTKVyFMUhyOGhLgYAjrkfR9YP93LdsCHOY,27907
11
+ jolly_roger/tractor.py,sha256=Gu1iouFCiiYMnFBLJWWyel-FcLKhLJDL2w6z5xmNi1E,31231
12
+ jolly_roger/utils.py,sha256=uye1GnGjh4U-VYhAHNY6al2qsIjiV473NHLGQE6pgwU,798
12
13
  jolly_roger/uvws.py,sha256=ujZdIIxNY2k4HY9p65kUyH-VqN6thNpOrBb-wpL9mYM,12424
13
- jolly_roger-0.3.0.dist-info/METADATA,sha256=Jzt-pUJETG8TYNSXKCe1HUVuS5Fmc7K07RwhR3SFfiQ,4221
14
- jolly_roger-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- jolly_roger-0.3.0.dist-info/entry_points.txt,sha256=q8RYosASYsPShzsIo58NxOhIMuB4F-gQ2uG6zS2p224,98
16
- jolly_roger-0.3.0.dist-info/licenses/LICENSE,sha256=7G-TthaPSOehr-pdj4TJydXj3eIUmerMbCUSatMr8hc,1522
17
- jolly_roger-0.3.0.dist-info/RECORD,,
14
+ jolly_roger/wrap.py,sha256=-MIhJVxdvqIuA5ABAs1CGy_hh4_4ofQDH2MgMgYUpwg,1538
15
+ jolly_roger-0.4.0.dist-info/METADATA,sha256=ugesdS1G55wN0moWF2crG-lwmfcQq_dggr_5n9-ttRo,4293
16
+ jolly_roger-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ jolly_roger-0.4.0.dist-info/entry_points.txt,sha256=q8RYosASYsPShzsIo58NxOhIMuB4F-gQ2uG6zS2p224,98
18
+ jolly_roger-0.4.0.dist-info/licenses/LICENSE,sha256=7G-TthaPSOehr-pdj4TJydXj3eIUmerMbCUSatMr8hc,1522
19
+ jolly_roger-0.4.0.dist-info/RECORD,,