jolly-roger 0.2.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.2.0'
21
- __version_tuple__ = version_tuple = (0, 2, 0)
20
+ __version__ = version = '0.4.0'
21
+ __version_tuple__ = version_tuple = (0, 4, 0)
jolly_roger/plots.py CHANGED
@@ -85,15 +85,17 @@ def plot_baseline_comparison_data(
85
85
  norm = ImageNormalize(
86
86
  after_amp_stokesi, interval=ZScaleInterval(), stretch=SqrtStretch()
87
87
  )
88
+ cmap = plt.cm.viridis
88
89
 
89
90
  fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(
90
- 2, 2, figsize=(10, 10), sharex=True, sharey="row"
91
+ 2, 2, figsize=(12, 10), sharex=True, sharey="row"
91
92
  )
92
93
  im = ax1.pcolormesh(
93
94
  before_baseline_data.time,
94
95
  before_baseline_data.freq_chan,
95
96
  before_amp_stokesi.T,
96
97
  norm=norm,
98
+ cmap=cmap,
97
99
  )
98
100
  ax1.set(
99
101
  ylabel=f"Frequency / {before_baseline_data.freq_chan.unit:latex_inline}",
@@ -104,12 +106,14 @@ def plot_baseline_comparison_data(
104
106
  after_baseline_data.freq_chan,
105
107
  after_amp_stokesi.T,
106
108
  norm=norm,
109
+ cmap=cmap,
107
110
  )
108
111
  ax2.set(
109
112
  ylabel=f"Frequency / {after_baseline_data.freq_chan.unit:latex_inline}",
110
113
  title="After",
111
114
  )
112
- fig.colorbar(im, ax=ax2, label="Stokes I Amplitude / Jy")
115
+ for ax in (ax1, ax2):
116
+ fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
113
117
 
114
118
  # TODO: Move these delay calculations outside of the plotting function
115
119
  # And here we calculate the delay information
@@ -130,6 +134,7 @@ def plot_baseline_comparison_data(
130
134
  before_delays.delay,
131
135
  before_delays_i.T,
132
136
  norm=delay_norm,
137
+ cmap=cmap,
133
138
  )
134
139
  ax3.set(ylabel="Delay / s", title="Before")
135
140
  ax4.pcolormesh(
@@ -137,9 +142,11 @@ def plot_baseline_comparison_data(
137
142
  after_delays.delay,
138
143
  after_delays_i.T,
139
144
  norm=delay_norm,
145
+ cmap=cmap,
140
146
  )
141
147
  ax4.set(ylabel="Delay / s", title="After")
142
- fig.colorbar(im, ax=ax4, label="Stokes I Amplitude / Jy")
148
+ for ax in (ax3, ax4):
149
+ fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
143
150
 
144
151
  if w_delays is not None:
145
152
  for ax, baseline_data in zip( # type:ignore[call-overload]
@@ -152,8 +159,8 @@ def plot_baseline_comparison_data(
152
159
  ax.plot(
153
160
  baseline_data.time,
154
161
  w_delays.w_delays[b_idx],
155
- color="k",
156
- linestyle="--",
162
+ color="tab:red",
163
+ linestyle="-",
157
164
  label=f"Delay for {w_delays.object_name}",
158
165
  )
159
166
  ax.legend()
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
 
@@ -202,17 +207,16 @@ def _get_data_chunk_from_main_table(
202
207
  logger.debug(f"Length of open table: {table_length} rows")
203
208
 
204
209
  lower_row = 0
205
- upper_row = chunk_size
206
210
 
207
211
  while lower_row < table_length:
208
- rows: list[dict[str, Any]] = ms_table[lower_row:upper_row]
209
-
210
- data = _list_to_array(list_of_rows=rows, key=data_column)
211
- flags = _list_to_array(list_of_rows=rows, key="FLAG")
212
- uvws = _list_to_array(list_of_rows=rows, key="UVW")
213
- time_centroid = _list_to_array(list_of_rows=rows, key="TIME_CENTROID")
214
- ant_1 = _list_to_array(list_of_rows=rows, key="ANTENNA1")
215
- ant_2 = _list_to_array(list_of_rows=rows, key="ANTENNA2")
212
+ data = ms_table.getcol(data_column, startrow=lower_row, nrow=chunk_size)
213
+ flags = ms_table.getcol("FLAG", startrow=lower_row, nrow=chunk_size)
214
+ uvws = ms_table.getcol("UVW", startrow=lower_row, nrow=chunk_size)
215
+ time_centroid = ms_table.getcol(
216
+ "TIME_CENTROID", startrow=lower_row, nrow=chunk_size
217
+ )
218
+ ant_1 = ms_table.getcol("ANTENNA1", startrow=lower_row, nrow=chunk_size)
219
+ ant_2 = ms_table.getcol("ANTENNA2", startrow=lower_row, nrow=chunk_size)
216
220
 
217
221
  yield DataChunkArray(
218
222
  data=data,
@@ -226,7 +230,6 @@ def _get_data_chunk_from_main_table(
226
230
  )
227
231
 
228
232
  lower_row += chunk_size
229
- upper_row += chunk_size
230
233
 
231
234
 
232
235
  def get_data_chunks(
@@ -457,6 +460,7 @@ def make_plot_results(
457
460
  before_delays = data_to_delay_time(data=before_baseline_data)
458
461
  after_delays = data_to_delay_time(data=after_baseline_data)
459
462
 
463
+ logger.info("Creating figure")
460
464
  # TODO: the baseline data and delay times could be put into a single
461
465
  # structure to pass around easier.
462
466
  plot_path = plot_baseline_comparison_data(
@@ -525,23 +529,32 @@ def _tukey_tractor(
525
529
  """
526
530
 
527
531
  delay_time = data_to_delay_time(data=data_chunk)
532
+ flags_to_return: None | NDArray[np.bool] = None
528
533
 
529
- # 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
530
537
  tukey_x_offset: u.Quantity = np.zeros_like(delay_time.delay)
531
538
 
532
539
  if w_delays is not None:
533
540
  baseline_idx, time_idx = _get_baseline_time_indicies(
534
541
  w_delays=w_delays, data_chunk=data_chunk
535
542
  )
536
- tukey_x_offset = w_delays.w_delays[baseline_idx, time_idx]
537
- # 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
+ )
538
551
 
539
552
  # need to scale the x offsert to the -pi to pi
540
553
  # The delay should be symmetric
541
554
  tukey_x_offset = (
542
555
  tukey_x_offset / (np.max(delay_time.delay) / np.pi).decompose()
543
556
  ).value
544
- # logger.info(f"{tukey_x_offset=}")
557
+ # # logger.info(f"{tukey_x_offset=}")
545
558
 
546
559
  taper = tukey_taper(
547
560
  x=delay_time.delay,
@@ -549,28 +562,70 @@ def _tukey_tractor(
549
562
  tukey_width=tukey_tractor_options.tukey_width,
550
563
  tukey_x_offset=tukey_x_offset,
551
564
  )
552
- 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
+
553
575
  # The use of the `tukey_x_offset` changes the
554
576
  # shape of the output array. The internals of that
555
577
  # function returns a different shape via the broadcasting
556
578
  taper = np.swapaxes(taper[:, :, None], 0, 1)
557
579
 
558
- # 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.
559
582
  taper = 1.0 - taper
560
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
+
561
596
  # Delay with the elevation of the target object
562
- elevation_mask = w_delays.elevation < (-3 * u.deg)
597
+ elevation_mask = w_delays.elevation < tukey_tractor_options.elevation_cut
563
598
  taper[elevation_mask[time_idx], :, :] = 1.0
564
599
 
565
- # TODO: Handle case of aliased delays
566
-
567
- # TODO: Create heuristic to determine where baseline is long enough to
568
- # ignore the tapering. Aliasing may give us this though...
569
-
570
- # TODO: Create flags where delay is 'close' to 0
571
-
572
- else:
573
- 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
+ )
574
629
 
575
630
  # Delay-time is a 3D array: (time, delay, pol)
576
631
  # Taper is 1D: (delay,)
@@ -588,7 +643,7 @@ def _tukey_tractor(
588
643
  )
589
644
  logger.debug(f"{tapered_data.masked_data.shape=} {tapered_data.masked_data.dtype}")
590
645
 
591
- return tapered_data
646
+ return tapered_data, flags_to_return
592
647
 
593
648
 
594
649
  @dataclass
@@ -610,7 +665,7 @@ class TukeyTractorOptions:
610
665
  dry_run: bool = False
611
666
  """Indicates whether the data will be written back to the measurement set"""
612
667
  make_plots: bool = False
613
- """Create a small set of diagnostic plots"""
668
+ """Create a small set of diagnostic plots. This can be slow."""
614
669
  overwrite: bool = False
615
670
  """If the output column exists it will be overwritten"""
616
671
  chunk_size: int = 1000
@@ -619,6 +674,10 @@ class TukeyTractorOptions:
619
674
  """apply the taper using the delay towards the target object."""
620
675
  target_object: str = "Sun"
621
676
  """The target object to apply the delay towards."""
677
+ elevation_cut: u.Quantity = -1 * u.deg
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"""
622
681
 
623
682
 
624
683
  def tukey_tractor(
@@ -634,8 +693,10 @@ def tukey_tractor(
634
693
  Args:
635
694
  tukey_tractor_options (TukeyTractorOptions): The settings to use during the taper, and measurement set to apply them to.
636
695
  """
637
- logger.info("jolly-roger")
638
- 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
+ )
639
700
 
640
701
  # acquire all the tables necessary to get unit information and data from
641
702
  open_ms_tables = get_open_ms_tables(
@@ -670,7 +731,7 @@ def tukey_tractor(
670
731
  chunk_size=tukey_tractor_options.chunk_size,
671
732
  data_column=tukey_tractor_options.data_column,
672
733
  ):
673
- taper_data_chunk = _tukey_tractor(
734
+ taper_data_chunk, flags_to_apply = _tukey_tractor(
674
735
  data_chunk=data_chunk,
675
736
  tukey_tractor_options=tukey_tractor_options,
676
737
  w_delays=w_delays,
@@ -685,6 +746,13 @@ def tukey_tractor(
685
746
  startrow=taper_data_chunk.row_start,
686
747
  nrow=taper_data_chunk.chunk_size,
687
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
+ )
688
756
 
689
757
  if tukey_tractor_options.make_plots:
690
758
  plot_paths = make_plot_results(
@@ -751,7 +819,7 @@ def get_parser() -> ArgumentParser:
751
819
  tukey_parser.add_argument(
752
820
  "--make-plots",
753
821
  action="store_true",
754
- 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.",
755
823
  )
756
824
  tukey_parser.add_argument(
757
825
  "--overwrite",
@@ -775,6 +843,12 @@ def get_parser() -> ArgumentParser:
775
843
  action="store_true",
776
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.",
777
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
+ )
778
852
 
779
853
  return parser
780
854
 
@@ -798,7 +872,9 @@ def cli() -> None:
798
872
  chunk_size=args.chunk_size,
799
873
  target_object=args.target_object,
800
874
  apply_towards_object=args.apply_towards_object,
875
+ ignore_nyquist_zone=args.ignore_nyquist_zone,
801
876
  )
877
+
802
878
  tukey_tractor(tukey_tractor_options=tukey_tractor_options)
803
879
  else:
804
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.2.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,17 +1,19 @@
1
1
  jolly_roger/__init__.py,sha256=7xiZLdeY-7sgrYGQ1gNdCjgCfqnoPXK7AeaHncY_DGU,204
2
- jolly_roger/_version.py,sha256=iB5DfB5V6YB5Wo4JmvS-txT42QtmGaWcWp3udRT7zCI,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
6
6
  jolly_roger/flagger.py,sha256=tlC-M_MpLpqOvkF544zw2EvOUpbSpasO2zlMlXMcxSs,3034
7
7
  jolly_roger/hour_angles.py,sha256=ld3jiEDQXlYLHrChUxYD_UBSxKH0qarstakBPLQ0M8s,6044
8
8
  jolly_roger/logging.py,sha256=04YVHnF_8tKDkXNtXQ-iMyJ2BLV-qowbPAqqMFDxYE4,1338
9
- jolly_roger/plots.py,sha256=LsueygCHpGvBXZe2y4q1fmJEMyjoMl65JzFMzbduawI,5280
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=ORkQb7T7jxMFVcDihH1McYYGq07OgsvbfT44D82ghL4,27723
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.2.0.dist-info/METADATA,sha256=vFDa_-0nKwhoFVmm9x_qqQgZURZV8jPBprgRue9h7XY,4221
14
- jolly_roger-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- jolly_roger-0.2.0.dist-info/entry_points.txt,sha256=q8RYosASYsPShzsIo58NxOhIMuB4F-gQ2uG6zS2p224,98
16
- jolly_roger-0.2.0.dist-info/licenses/LICENSE,sha256=7G-TthaPSOehr-pdj4TJydXj3eIUmerMbCUSatMr8hc,1522
17
- jolly_roger-0.2.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,,