masster 0.3.1__py3-none-any.whl → 0.3.2__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 masster might be problematic. Click here for more details.

masster/sample/plot.py CHANGED
@@ -283,9 +283,12 @@ def plot_2d(
283
283
  cmap=None,
284
284
  marker="circle",
285
285
  markersize=10,
286
+ size="dynamic",
286
287
  raster_dynamic=True,
287
288
  raster_max_px=8,
288
289
  raster_threshold=0.8,
290
+ height=600,
291
+ width=800,
289
292
  mz_range=None,
290
293
  rt_range=None,
291
294
  ):
@@ -318,6 +321,11 @@ def plot_2d(
318
321
  Marker type to use for feature and MS2 points.
319
322
  markersize (int, default 10):
320
323
  Base size of the markers used for plotting points.
324
+ size (str, default 'dynamic'):
325
+ Controls marker sizing behavior. Options: 'dynamic', 'static', or 'slider'.
326
+ - 'dynamic': Uses coordinate-based sizing that scales with zoom level (markers get larger when zooming in)
327
+ - 'static': Uses screen-based sizing that remains constant regardless of zoom level
328
+ - 'slider': Provides an interactive slider to dynamically adjust marker size
321
329
  raster_dynamic (bool, default True):
322
330
  Whether to use dynamic rasterization for the background point cloud.
323
331
  raster_max_px (int, default 8):
@@ -357,9 +365,9 @@ def plot_2d(
357
365
  # keep only rt, mz, and inty
358
366
  spectradf = spectradf.select(["rt", "mz", "inty"])
359
367
  if mz_range is not None:
360
- spectradf = spectradf[(spectradf["mz"] >= mz_range[0]) & (spectradf["mz"] <= mz_range[1])]
368
+ spectradf = spectradf.filter((pl.col("mz") >= mz_range[0]) & (pl.col("mz") <= mz_range[1]))
361
369
  if rt_range is not None:
362
- spectradf = spectradf[(spectradf["rt"] >= rt_range[0]) & (spectradf["rt"] <= rt_range[1])]
370
+ spectradf = spectradf.filter((pl.col("rt") >= rt_range[0]) & (pl.col("rt") <= rt_range[1]))
363
371
  maxrt = spectradf["rt"].max()
364
372
  minrt = spectradf["rt"].min()
365
373
  maxmz = spectradf["mz"].max()
@@ -384,19 +392,81 @@ def plot_2d(
384
392
  tools=["hover"],
385
393
  )
386
394
 
387
- size_1 = 1 * markersize
395
+ # Configure marker and size behavior based on size parameter
396
+ use_dynamic_sizing = size.lower() in ["dyn", "dynamic"]
397
+ use_slider_sizing = size.lower() == "slider"
398
+
399
+ def dynamic_sizing_hook(plot, element):
400
+ """Hook to convert size-based markers to radius-based for dynamic behavior"""
401
+ try:
402
+ if use_dynamic_sizing and hasattr(plot, 'state') and hasattr(plot.state, 'renderers'):
403
+ from bokeh.models import Circle
404
+ for renderer in plot.state.renderers:
405
+ if hasattr(renderer, 'glyph'):
406
+ glyph = renderer.glyph
407
+ # Check if it's a circle/scatter glyph that we can convert
408
+ if hasattr(glyph, 'size') and marker_type == "circle":
409
+ # Create a new Circle glyph with radius instead of size
410
+ new_glyph = Circle(
411
+ x=glyph.x,
412
+ y=glyph.y,
413
+ radius=base_radius,
414
+ fill_color=glyph.fill_color,
415
+ line_color=glyph.line_color,
416
+ fill_alpha=glyph.fill_alpha,
417
+ line_alpha=glyph.line_alpha,
418
+ )
419
+ renderer.glyph = new_glyph
420
+ except Exception:
421
+ # Silently fail and use regular sizing if hook doesn't work
422
+ pass
423
+
424
+ if use_dynamic_sizing:
425
+ # Dynamic sizing: use coordinate-based sizing that scales with zoom
426
+ marker_type = "circle"
427
+ # Calculate radius based on data range for coordinate-based sizing
428
+ rtrange = maxrt - minrt
429
+ mzrange = maxmz - minmz
430
+ # Use a fraction of the smaller dimension for radius
431
+ base_radius = min(rtrange, mzrange) * 0.0005 * markersize
432
+ size_1 = markersize # Use regular size initially, hook will convert to radius
433
+ size_2 = markersize
434
+ hooks = [dynamic_sizing_hook]
435
+ elif use_slider_sizing:
436
+ # Slider sizing: create an interactive slider for marker size
437
+ marker_type = marker # Use the original marker parameter
438
+ size_1 = markersize # Use markersize initially, will be updated by slider
439
+ size_2 = markersize
440
+ base_radius = None # Not used in slider mode
441
+ hooks = []
442
+ else:
443
+ # Static sizing: use pixel-based sizing that stays fixed
444
+ marker_type = marker # Use the original marker parameter
445
+ size_1 = markersize
446
+ size_2 = markersize
447
+ base_radius = None # Not used in static mode
448
+ hooks = []
449
+
388
450
  color_1 = "forestgreen"
389
- size_2 = 1 * markersize
390
451
  color_2 = "darkorange"
391
452
  if filename is not None:
392
453
  dyn = False
393
454
  if not filename.endswith(".html"):
394
- size_1 = 2
455
+ if use_dynamic_sizing:
456
+ # For exported files, use smaller coordinate-based size
457
+ size_1 = 2
458
+ size_2 = 2
459
+ else:
460
+ size_1 = 2
461
+ size_2 = 2
395
462
  color_1 = "forestgreen"
396
- size_2 = 2
397
463
  color_2 = "darkorange"
398
464
  raster_dynamic = False
399
465
 
466
+ # For slider functionality, disable raster dynamic to avoid DynamicMap nesting
467
+ if use_slider_sizing:
468
+ raster_dynamic = False
469
+
400
470
  dyn = raster_dynamic
401
471
  raster = hd.rasterize(
402
472
  points,
@@ -408,8 +478,8 @@ def plot_2d(
408
478
  cmap=process_cmap(cmap, provider="bokeh"), # blues
409
479
  tools=["hover"],
410
480
  hooks=[new_bounds_hook],
411
- width=1000,
412
- height=1000,
481
+ width=width,
482
+ height=height,
413
483
  cnorm="log",
414
484
  xlabel="Retention time (s)",
415
485
  ylabel="m/z",
@@ -448,6 +518,7 @@ def plot_2d(
448
518
  feats = feats[feats["iso"] == 0]
449
519
  # find features with ms2_scans not None and iso==0
450
520
  features_df = feats[feats["ms2_scans"].notnull()]
521
+ # Create feature points with proper sizing method
451
522
  feature_points_1 = hv.Points(
452
523
  features_df,
453
524
  kdims=["rt", "mz"],
@@ -463,9 +534,10 @@ def plot_2d(
463
534
  label="Features with MS2 data",
464
535
  ).options(
465
536
  color=color_1,
466
- marker=marker,
537
+ marker=marker_type,
467
538
  size=size_1,
468
539
  tools=["hover"],
540
+ hooks=hooks,
469
541
  )
470
542
  # find features without MS2 data
471
543
  features_df = feats[feats["ms2_scans"].isnull()]
@@ -483,9 +555,10 @@ def plot_2d(
483
555
  label="Features without MS2 data",
484
556
  ).options(
485
557
  color="red",
558
+ marker=marker_type,
486
559
  size=size_2,
487
- marker=marker,
488
560
  tools=["hover"],
561
+ hooks=hooks,
489
562
  )
490
563
 
491
564
  if show_isotopes:
@@ -510,9 +583,10 @@ def plot_2d(
510
583
  label="Isotopes",
511
584
  ).options(
512
585
  color="violet",
513
- marker=marker,
586
+ marker=marker_type,
514
587
  size=size_1,
515
588
  tools=["hover"],
589
+ hooks=hooks,
516
590
  )
517
591
  if show_ms2:
518
592
  # find all self.scans_df with mslevel 2 that are not linked to a feature
@@ -569,8 +643,119 @@ def plot_2d(
569
643
  if title is not None:
570
644
  overlay = overlay.opts(title=title)
571
645
 
572
- # Create a panel layout
573
- layout = panel.Column(overlay)
646
+ # Handle slider functionality
647
+ if use_slider_sizing:
648
+ # For slider functionality, we need to work with the feature points directly
649
+ # and not nest DynamicMaps. We'll create the slider using param and panel.
650
+ import param
651
+ import panel as pn
652
+
653
+ class MarkerSizeController(param.Parameterized):
654
+ size_slider = param.Number(default=markersize, bounds=(1, 20), step=0.5)
655
+
656
+ controller = MarkerSizeController()
657
+
658
+ # Create a function that generates just the feature overlays with different sizes
659
+ def create_feature_overlay(size_val):
660
+ feature_overlay = None
661
+
662
+ if feature_points_4 is not None:
663
+ updated_points_4 = feature_points_4.opts(size=size_val)
664
+ feature_overlay = updated_points_4 if feature_overlay is None else feature_overlay * updated_points_4
665
+ if feature_points_3 is not None:
666
+ updated_points_3 = feature_points_3.opts(size=size_val)
667
+ feature_overlay = updated_points_3 if feature_overlay is None else feature_overlay * updated_points_3
668
+ if feature_points_1 is not None:
669
+ updated_points_1 = feature_points_1.opts(size=size_val)
670
+ feature_overlay = updated_points_1 if feature_overlay is None else feature_overlay * updated_points_1
671
+ if not show_only_features_with_ms2 and feature_points_2 is not None:
672
+ updated_points_2 = feature_points_2.opts(size=size_val)
673
+ feature_overlay = updated_points_2 if feature_overlay is None else feature_overlay * updated_points_2
674
+ if feature_points_iso is not None:
675
+ updated_points_iso = feature_points_iso.opts(size=size_val)
676
+ feature_overlay = updated_points_iso if feature_overlay is None else feature_overlay * updated_points_iso
677
+
678
+ # Combine with the static raster background
679
+ if feature_overlay is not None:
680
+ combined_overlay = raster * feature_overlay
681
+ else:
682
+ combined_overlay = raster
683
+
684
+ if title is not None:
685
+ combined_overlay = combined_overlay.opts(title=title)
686
+
687
+ return combined_overlay
688
+
689
+ # Create a horizontal control widget on top of the plot
690
+ # Create the slider widget with explicit visibility
691
+ size_slider = pn.widgets.FloatSlider(
692
+ name="Marker Size",
693
+ start=1.0,
694
+ end=20.0,
695
+ step=0.5,
696
+ value=markersize,
697
+ width=300,
698
+ height=40,
699
+ margin=(5, 5),
700
+ show_value=True
701
+ )
702
+
703
+ # Create the slider widget row with clear styling
704
+ slider_widget = pn.Row(
705
+ pn.pane.HTML("<b>Marker Size Control:</b>", width=150, height=40, margin=(5, 10)),
706
+ size_slider,
707
+ height=60,
708
+ margin=10
709
+ )
710
+
711
+ # Create slider widget
712
+ size_slider = pn.widgets.FloatSlider(
713
+ name="Marker Size",
714
+ start=1.0,
715
+ end=20.0,
716
+ step=0.5,
717
+ value=markersize,
718
+ width=300,
719
+ height=40,
720
+ margin=(5, 5),
721
+ show_value=True
722
+ )
723
+
724
+ slider_widget = pn.Row(
725
+ pn.pane.HTML("<b>Marker Size:</b>", width=100, height=40, margin=(5, 10)),
726
+ size_slider,
727
+ height=60,
728
+ margin=10
729
+ )
730
+
731
+ # Simple reactive plot - slider mode doesn't use dynamic rasterization
732
+ @pn.depends(size_slider.param.value)
733
+ def reactive_plot(size_val):
734
+ overlay = create_feature_overlay(float(size_val))
735
+ # Apply static rasterization for slider mode
736
+ if raster_dynamic:
737
+ return hd.rasterize(
738
+ overlay,
739
+ aggregator=ds.count(),
740
+ width=raster_max_px,
741
+ height=raster_max_px,
742
+ dynamic=False # Static raster for slider mode
743
+ ).opts(
744
+ cnorm='eq_hist',
745
+ tools=['hover'],
746
+ width=width,
747
+ height=height
748
+ )
749
+ else:
750
+ return overlay
751
+
752
+ # Create layout
753
+ layout = pn.Column(slider_widget, reactive_plot, sizing_mode='stretch_width')
754
+
755
+ return layout
756
+ else:
757
+ # Create a panel layout without slider
758
+ layout = panel.Column(overlay)
574
759
 
575
760
  if filename is not None:
576
761
  # if filename includes .html, save the panel layout to an HTML file
@@ -578,10 +763,17 @@ def plot_2d(
578
763
  layout.save(filename, embed=True)
579
764
  else:
580
765
  # save the panel layout as a png
581
- hv.save(overlay, filename, fmt="png")
766
+ if use_slider_sizing:
767
+ # For slider plots, save the current state of the param_plot
768
+ hv.save(create_feature_overlay(markersize), filename, fmt="png")
769
+ else:
770
+ hv.save(overlay, filename, fmt="png")
582
771
  else:
583
772
  # Check if we're in a notebook environment and display appropriately
584
- return _display_plot(overlay, layout)
773
+ if use_slider_sizing:
774
+ return _display_plot(layout, layout)
775
+ else:
776
+ return _display_plot(overlay, layout)
585
777
 
586
778
 
587
779
  def plot_2d_oracle(
masster/sample/sample.py CHANGED
@@ -287,7 +287,7 @@ class Sample:
287
287
  """
288
288
  # Reset logger configuration flags to allow proper reconfiguration after reload
289
289
  try:
290
- import masster.sample.logger as logger_module
290
+ import masster.logger as logger_module
291
291
 
292
292
  if hasattr(logger_module, "_SAMPLE_LOGGER_CONFIGURED"):
293
293
  logger_module._SAMPLE_LOGGER_CONFIGURED = False
masster/study/plot.py CHANGED
@@ -157,9 +157,28 @@ def plot_consensus_2d(
157
157
  colorby="number_samples",
158
158
  sizeby="inty_mean",
159
159
  markersize=6,
160
+ size="dynamic",
160
161
  alpha=0.7,
161
162
  cmap=None,
163
+ width=900,
164
+ height=900
162
165
  ):
166
+ """
167
+ Plot consensus features in a 2D scatter plot with retention time vs m/z.
168
+
169
+ Parameters:
170
+ filename (str, optional): Path to save the plot
171
+ colorby (str): Column name to use for color mapping (default: "number_samples")
172
+ sizeby (str): Column name to use for size mapping (default: "inty_mean")
173
+ markersize (int): Base marker size (default: 6)
174
+ size (str): Controls whether points scale with zoom. Options:
175
+ 'dynamic' - points use circle() and scale with zoom
176
+ 'static' - points use scatter() and maintain fixed pixel size
177
+ alpha (float): Transparency level (default: 0.7)
178
+ cmap (str, optional): Color map name
179
+ width (int): Plot width in pixels (default: 900)
180
+ height (int): Plot height in pixels (default: 900)
181
+ """
163
182
  if self.consensus_df is None:
164
183
  self.logger.error("No consensus map found.")
165
184
  return
@@ -238,21 +257,33 @@ def plot_consensus_2d(
238
257
  )
239
258
  # scatter plot rt vs mz
240
259
  p = bp.figure(
241
- width=800,
242
- height=600,
260
+ width=width,
261
+ height=height,
243
262
  title="Consensus map",
244
263
  )
245
264
  p.xaxis.axis_label = "Retention Time (min)"
246
265
  p.yaxis.axis_label = "m/z"
247
- scatter_renderer = p.scatter(
248
- x="rt",
249
- y="mz",
250
- size="markersize",
251
- fill_color={"field": colorby, "transform": color_mapper},
252
- line_color=None,
253
- alpha=alpha,
254
- source=source,
255
- )
266
+ scatter_renderer: Any = None
267
+ if size.lower() in ["dyn", "dynamic"]:
268
+ scatter_renderer = p.circle(
269
+ x="rt",
270
+ y="mz",
271
+ radius=markersize / 10,
272
+ fill_color={"field": colorby, "transform": color_mapper},
273
+ line_color=None,
274
+ alpha=alpha,
275
+ source=source,
276
+ )
277
+ else:
278
+ scatter_renderer = p.scatter(
279
+ x="rt",
280
+ y="mz",
281
+ size="markersize",
282
+ fill_color={"field": colorby, "transform": color_mapper},
283
+ line_color=None,
284
+ alpha=alpha,
285
+ source=source,
286
+ )
256
287
  # add hover tool
257
288
  hover = HoverTool(
258
289
  tooltips=[
@@ -292,16 +323,32 @@ def plot_samples_2d(
292
323
  samples=None,
293
324
  filename=None,
294
325
  markersize=2,
295
- size="const",
326
+ size="dynamic",
296
327
  alpha_max=0.8,
297
328
  alpha="inty",
298
329
  cmap="Turbo256",
299
- max_features=50000, # Reduced default for better performance with many samples
330
+ max_features=50000,
331
+ width=900,
332
+ height=900
300
333
  ):
301
334
  """
302
335
  Plot all feature maps for sample_uid in parameter uids in an overlaid scatter plot.
303
336
  Each sample is a different color. Alpha scales with intensity.
304
337
  OPTIMIZED VERSION: Uses vectorized operations and batch processing.
338
+
339
+ Parameters:
340
+ samples: Sample UIDs to plot
341
+ filename (str, optional): Path to save the plot
342
+ markersize (int): Base marker size (default: 2)
343
+ size (str): Controls whether points scale with zoom. Options:
344
+ 'dynamic' or 'dyn' - points use circle() and scale with zoom
345
+ 'const', 'static' or other - points use scatter() and maintain fixed pixel size
346
+ alpha_max (float): Maximum transparency level (default: 0.8)
347
+ alpha (str): Column name to use for alpha mapping (default: "inty")
348
+ cmap (str): Color map name (default: "Turbo256")
349
+ max_features (int): Maximum number of features to plot (default: 50000)
350
+ width (int): Plot width in pixels (default: 900)
351
+ height (int): Plot height in pixels (default: 900)
305
352
  """
306
353
 
307
354
  sample_uids = self._get_sample_uids(samples)
@@ -314,8 +361,8 @@ def plot_samples_2d(
314
361
  color_map = {uid: colors[i * (256 // max(1, len(sample_uids)))] for i, uid in enumerate(sample_uids)}
315
362
 
316
363
  p = figure(
317
- width=600,
318
- height=600,
364
+ width=width,
365
+ height=height,
319
366
  title="Sample Features",
320
367
  )
321
368
  p.xaxis.axis_label = "Retention Time (RT)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: masster
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Mass spectrometry data analysis package
5
5
  Project-URL: homepage, https://github.com/zamboni-lab/masster
6
6
  Project-URL: repository, https://github.com/zamboni-lab/masster
@@ -16,10 +16,10 @@ masster/sample/helpers.py,sha256=OEgvR3bptA-tEqHAFVPjWpbagKXAU1h0bePPi9ttHa4,348
16
16
  masster/sample/lib.py,sha256=9r2XlF_BaJ4WNAsQo8hElieRLwsAv0yrbYq4DJ0iVOM,33496
17
17
  masster/sample/load.py,sha256=y-KUJ2nCFX_06FHPUOh-CzRRvaTx14xNcXoL19bU8qY,47562
18
18
  masster/sample/parameters.py,sha256=Gg2KcuNbV_wZ_Wwv93QlM5J19ji0oSIvZLPV1NoBmq0,4456
19
- masster/sample/plot.py,sha256=wd-4OosFT8MoO0fM8PSMskZK_yg8i8vfbiTieAzgrv4,62831
19
+ masster/sample/plot.py,sha256=uUJAd2qxhVG6Ev2hLuU406zFA2TDkkBz2MG12P9fLik,71449
20
20
  masster/sample/processing.py,sha256=NjNLt47Fy0UF3Xs35NBhADg57qTC6Lfa4Xz8Y30v83A,58250
21
21
  masster/sample/quant.py,sha256=tHNjvUFTdehKR31BXBZnVsBxMD9XJHgaltITOjr71uE,7562
22
- masster/sample/sample.py,sha256=UlyA7cZtV_IMO8PRaYaUqf8cfAGfavVVfNDo0g_6OJw,16185
22
+ masster/sample/sample.py,sha256=7ivuAMb3JlFikLOxZjTGwYmuqGehLz9d47gQxfSRtf4,16178
23
23
  masster/sample/sample5_schema.json,sha256=3SPFQZH4SooLYUt_lW-PCOE9rHnl56Vhc2XG-r1nyEQ,3586
24
24
  masster/sample/save.py,sha256=o9eFSqqr7KYwvCD3gOJt_nZ4h3pkflWqs0n0oSLM-sU,31970
25
25
  masster/sample/sciex.py,sha256=q6PdcjCtV2PWnJiXuvfISu09zjkaTR_fvHvWN9OvOcM,46870
@@ -36,7 +36,7 @@ masster/study/helpers.py,sha256=SeW17rA3BIM2I2Whiye6wegRRSCabIpQoCsjOCafjKw,7488
36
36
  masster/study/helpers_optimized.py,sha256=EgOgPaL3c2LA8jDhnlEHvzb7O9Um-vnMIcnNaoH90gA,13620
37
37
  masster/study/load.py,sha256=TLxVhXu0HHb51lGggXitQLtfNxz2JJfKMkAXJbxhvhM,46880
38
38
  masster/study/parameters.py,sha256=0elaF7YspTsB7qyajWAbRNL2VfKlGz5GJLifmO8IGkk,3276
39
- masster/study/plot.py,sha256=hOG8bBT3mYV63FieEk-gYKtOyIXWppkTu21VeGbRnGk,21918
39
+ masster/study/plot.py,sha256=4i3u4geOinCefsambnEGVPF4XuyKTK-_eT5xAWgC7Ik,24045
40
40
  masster/study/processing.py,sha256=BQuSBO7O8iTlCjXenECyg0_PAsPF1NNiUllypuemPZI,46101
41
41
  masster/study/save.py,sha256=bcRADWTvhTER9WRkT9zNU5mDUPQZkZB2cuJwpRsYmrM,6589
42
42
  masster/study/study.py,sha256=5TZgG7tr7mzqHh1tm48V8SEcvRcWiFYG9iDqz0U9ACc,27073
@@ -52,8 +52,8 @@ masster/study/defaults/integrate_chrom_def.py,sha256=Rih3-vat7fHGVfIvRitjNJJI3zL
52
52
  masster/study/defaults/integrate_def.py,sha256=Vf4SAzdBfnsSZ3IRaF0qZvWu3gMDPHdgPfMYoPKeWv8,7246
53
53
  masster/study/defaults/merge_def.py,sha256=EBsKE3hsAkTEzN9dpdRD5W3_suTKy_WZ_96rwS0uBuE,8572
54
54
  masster/study/defaults/study_def.py,sha256=hj8bYtEPwzdowC95yfyoCFt6fZkQePLjpJtmpNz9Z5M,9533
55
- masster-0.3.1.dist-info/METADATA,sha256=VLzNZSby0weoT9QUfjleppVOtuvt_GtZu6AfLRM9MSg,44356
56
- masster-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
- masster-0.3.1.dist-info/entry_points.txt,sha256=ZHguQ_vPmdbpqq2uGtmEOLJfgP-DQ1T0c07Lxh30wc8,58
58
- masster-0.3.1.dist-info/licenses/LICENSE,sha256=bx5iLIKjgAdYQ7sISn7DsfHRKkoCUm1154sJJKhgqnU,35184
59
- masster-0.3.1.dist-info/RECORD,,
55
+ masster-0.3.2.dist-info/METADATA,sha256=LTK6jfDeryui93xgzncpurTiYrc_iuzRMao82DQ0eMI,44356
56
+ masster-0.3.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
+ masster-0.3.2.dist-info/entry_points.txt,sha256=ZHguQ_vPmdbpqq2uGtmEOLJfgP-DQ1T0c07Lxh30wc8,58
58
+ masster-0.3.2.dist-info/licenses/LICENSE,sha256=bx5iLIKjgAdYQ7sISn7DsfHRKkoCUm1154sJJKhgqnU,35184
59
+ masster-0.3.2.dist-info/RECORD,,