masster 0.3.10__py3-none-any.whl → 0.3.12__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/docs/SCX_API_Documentation.md +0 -0
- masster/docs/SCX_DLL_Analysis.md +0 -0
- masster/logger.py +92 -78
- masster/sample/defaults/find_features_def.py +16 -6
- masster/sample/defaults/sample_def.py +1 -1
- masster/sample/h5.py +2 -2
- masster/sample/helpers.py +190 -140
- masster/sample/load.py +13 -9
- masster/sample/plot.py +256 -147
- masster/sample/processing.py +18 -12
- masster/sample/sample.py +10 -4
- masster/sample/sample5_schema.json +38 -29
- masster/sample/save.py +16 -13
- masster/sample/sciex.py +187 -176
- masster/study/defaults/align_def.py +231 -13
- masster/study/defaults/fill_chrom_def.py +1 -5
- masster/study/defaults/integrate_chrom_def.py +1 -5
- masster/study/defaults/study_def.py +2 -2
- masster/study/export.py +144 -131
- masster/study/h5.py +193 -133
- masster/study/helpers.py +757 -246
- masster/study/helpers_optimized.py +99 -57
- masster/study/load.py +57 -25
- masster/study/plot.py +1244 -129
- masster/study/processing.py +194 -86
- masster/study/save.py +7 -7
- masster/study/study.py +154 -89
- masster/study/study5_schema.json +15 -15
- {masster-0.3.10.dist-info → masster-0.3.12.dist-info}/METADATA +1 -1
- {masster-0.3.10.dist-info → masster-0.3.12.dist-info}/RECORD +33 -31
- {masster-0.3.10.dist-info → masster-0.3.12.dist-info}/WHEEL +0 -0
- {masster-0.3.10.dist-info → masster-0.3.12.dist-info}/entry_points.txt +0 -0
- {masster-0.3.10.dist-info → masster-0.3.12.dist-info}/licenses/LICENSE +0 -0
masster/sample/plot.py
CHANGED
|
@@ -56,6 +56,7 @@ from bokeh.models import HoverTool
|
|
|
56
56
|
from holoviews import dim
|
|
57
57
|
from holoviews.plotting.util import process_cmap
|
|
58
58
|
from matplotlib.colors import rgb2hex
|
|
59
|
+
from masster.chromatogram import Chromatogram
|
|
59
60
|
|
|
60
61
|
# Parameters removed - using hardcoded defaults
|
|
61
62
|
|
|
@@ -66,49 +67,51 @@ hv.extension("bokeh")
|
|
|
66
67
|
def _is_notebook_environment():
|
|
67
68
|
"""
|
|
68
69
|
Detect if code is running in a notebook environment (Jupyter, JupyterLab, or Marimo).
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
Returns:
|
|
71
72
|
bool: True if running in a notebook, False otherwise
|
|
72
73
|
"""
|
|
73
74
|
try:
|
|
74
75
|
# Check for Jupyter/JupyterLab
|
|
75
76
|
from IPython import get_ipython
|
|
77
|
+
|
|
76
78
|
if get_ipython() is not None:
|
|
77
79
|
# Check if we're in a notebook context
|
|
78
80
|
shell = get_ipython().__class__.__name__
|
|
79
|
-
if shell in [
|
|
81
|
+
if shell in ["ZMQInteractiveShell", "Shell"]: # Jupyter notebook/lab
|
|
80
82
|
return True
|
|
81
|
-
|
|
83
|
+
|
|
82
84
|
# Check for Marimo
|
|
83
85
|
import sys
|
|
84
|
-
|
|
86
|
+
|
|
87
|
+
if "marimo" in sys.modules:
|
|
85
88
|
return True
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
# Additional check for notebook environments
|
|
88
|
-
if hasattr(__builtins__,
|
|
91
|
+
if hasattr(__builtins__, "__IPYTHON__") or hasattr(__builtins__, "_ih"):
|
|
89
92
|
return True
|
|
90
|
-
|
|
93
|
+
|
|
91
94
|
except ImportError:
|
|
92
95
|
pass
|
|
93
|
-
|
|
96
|
+
|
|
94
97
|
return False
|
|
95
98
|
|
|
96
99
|
|
|
97
100
|
def _display_plot(plot_object, layout=None):
|
|
98
101
|
"""
|
|
99
102
|
Display a plot object in the appropriate way based on the environment.
|
|
100
|
-
|
|
103
|
+
|
|
101
104
|
Args:
|
|
102
105
|
plot_object: The plot object to display (holoviews overlay, etc.)
|
|
103
106
|
layout: Optional panel layout object
|
|
104
|
-
|
|
107
|
+
|
|
105
108
|
Returns:
|
|
106
109
|
The layout object if in notebook environment, None otherwise
|
|
107
110
|
"""
|
|
108
111
|
if _is_notebook_environment():
|
|
109
112
|
# Display inline in notebook
|
|
110
113
|
try:
|
|
111
|
-
# For Jupyter notebooks, just return the plot object -
|
|
114
|
+
# For Jupyter notebooks, just return the plot object -
|
|
112
115
|
# holoviews will handle the display automatically
|
|
113
116
|
return plot_object
|
|
114
117
|
except Exception:
|
|
@@ -395,17 +398,18 @@ def plot_2d(
|
|
|
395
398
|
# Configure marker and size behavior based on size parameter
|
|
396
399
|
use_dynamic_sizing = size.lower() in ["dyn", "dynamic"]
|
|
397
400
|
use_slider_sizing = size.lower() == "slider"
|
|
398
|
-
|
|
401
|
+
|
|
399
402
|
def dynamic_sizing_hook(plot, element):
|
|
400
403
|
"""Hook to convert size-based markers to radius-based for dynamic behavior"""
|
|
401
404
|
try:
|
|
402
|
-
if use_dynamic_sizing and hasattr(plot,
|
|
405
|
+
if use_dynamic_sizing and hasattr(plot, "state") and hasattr(plot.state, "renderers"):
|
|
403
406
|
from bokeh.models import Circle
|
|
407
|
+
|
|
404
408
|
for renderer in plot.state.renderers:
|
|
405
|
-
if hasattr(renderer,
|
|
409
|
+
if hasattr(renderer, "glyph"):
|
|
406
410
|
glyph = renderer.glyph
|
|
407
411
|
# Check if it's a circle/scatter glyph that we can convert
|
|
408
|
-
if hasattr(glyph,
|
|
412
|
+
if hasattr(glyph, "size") and marker_type == "circle":
|
|
409
413
|
# Create a new Circle glyph with radius instead of size
|
|
410
414
|
new_glyph = Circle(
|
|
411
415
|
x=glyph.x,
|
|
@@ -420,7 +424,7 @@ def plot_2d(
|
|
|
420
424
|
except Exception:
|
|
421
425
|
# Silently fail and use regular sizing if hook doesn't work
|
|
422
426
|
pass
|
|
423
|
-
|
|
427
|
+
|
|
424
428
|
if use_dynamic_sizing:
|
|
425
429
|
# Dynamic sizing: use coordinate-based sizing that scales with zoom
|
|
426
430
|
marker_type = "circle"
|
|
@@ -446,7 +450,7 @@ def plot_2d(
|
|
|
446
450
|
size_2 = markersize
|
|
447
451
|
base_radius = None # Not used in static mode
|
|
448
452
|
hooks = []
|
|
449
|
-
|
|
453
|
+
|
|
450
454
|
color_1 = "forestgreen"
|
|
451
455
|
color_2 = "darkorange"
|
|
452
456
|
if filename is not None:
|
|
@@ -519,14 +523,16 @@ def plot_2d(
|
|
|
519
523
|
# find features with ms2_scans not None and iso==0
|
|
520
524
|
features_df = feats[feats["ms2_scans"].notnull()]
|
|
521
525
|
# Create feature points with proper sizing method
|
|
522
|
-
feature_hover_1 = HoverTool(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
526
|
+
feature_hover_1 = HoverTool(
|
|
527
|
+
tooltips=[
|
|
528
|
+
("rt", "@rt"),
|
|
529
|
+
("m/z", "@mz{0.0000}"),
|
|
530
|
+
("feature_uid", "@feature_uid"),
|
|
531
|
+
("inty", "@inty"),
|
|
532
|
+
("quality", "@quality"),
|
|
533
|
+
("rt_delta", "@rt_delta"),
|
|
534
|
+
],
|
|
535
|
+
)
|
|
530
536
|
feature_points_1 = hv.Points(
|
|
531
537
|
features_df,
|
|
532
538
|
kdims=["rt", "mz"],
|
|
@@ -549,14 +555,16 @@ def plot_2d(
|
|
|
549
555
|
)
|
|
550
556
|
# find features without MS2 data
|
|
551
557
|
features_df = feats[feats["ms2_scans"].isnull()]
|
|
552
|
-
feature_hover_2 = HoverTool(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
558
|
+
feature_hover_2 = HoverTool(
|
|
559
|
+
tooltips=[
|
|
560
|
+
("rt", "@rt"),
|
|
561
|
+
("m/z", "@mz{0.0000}"),
|
|
562
|
+
("feature_uid", "@feature_uid"),
|
|
563
|
+
("inty", "@inty"),
|
|
564
|
+
("quality", "@quality"),
|
|
565
|
+
("rt_delta", "@rt_delta"),
|
|
566
|
+
],
|
|
567
|
+
)
|
|
560
568
|
feature_points_2 = hv.Points(
|
|
561
569
|
features_df,
|
|
562
570
|
kdims=["rt", "mz"],
|
|
@@ -583,16 +591,18 @@ def plot_2d(
|
|
|
583
591
|
# Convert to pandas for plotting compatibility
|
|
584
592
|
if hasattr(features_df, "to_pandas"):
|
|
585
593
|
features_df = features_df.to_pandas()
|
|
586
|
-
feature_hover_iso = HoverTool(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
594
|
+
feature_hover_iso = HoverTool(
|
|
595
|
+
tooltips=[
|
|
596
|
+
("rt", "@rt"),
|
|
597
|
+
("m/z", "@mz{0.0000}"),
|
|
598
|
+
("feature_uid", "@feature_uid"),
|
|
599
|
+
("inty", "@inty"),
|
|
600
|
+
("quality", "@quality"),
|
|
601
|
+
("rt_delta", "@rt_delta"),
|
|
602
|
+
("iso", "@iso"),
|
|
603
|
+
("iso_of", "@iso_of"),
|
|
604
|
+
],
|
|
605
|
+
)
|
|
596
606
|
feature_points_iso = hv.Points(
|
|
597
607
|
features_df,
|
|
598
608
|
kdims=["rt", "mz"],
|
|
@@ -623,13 +633,15 @@ def plot_2d(
|
|
|
623
633
|
if len(ms2_orphan) > 0:
|
|
624
634
|
# pandalize
|
|
625
635
|
ms2 = ms2_orphan.to_pandas()
|
|
626
|
-
ms2_hover_3 = HoverTool(
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
636
|
+
ms2_hover_3 = HoverTool(
|
|
637
|
+
tooltips=[
|
|
638
|
+
("rt", "@rt"),
|
|
639
|
+
("prec_mz", "@prec_mz{0.0000}"),
|
|
640
|
+
("index", "@index"),
|
|
641
|
+
("inty_tot", "@inty_tot"),
|
|
642
|
+
("bl", "@bl"),
|
|
643
|
+
],
|
|
644
|
+
)
|
|
633
645
|
feature_points_3 = hv.Points(
|
|
634
646
|
ms2,
|
|
635
647
|
kdims=["rt", "prec_mz"],
|
|
@@ -648,13 +660,15 @@ def plot_2d(
|
|
|
648
660
|
if len(ms2_linked) > 0:
|
|
649
661
|
# pandalize
|
|
650
662
|
ms2 = ms2_linked.to_pandas()
|
|
651
|
-
ms2_hover_4 = HoverTool(
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
663
|
+
ms2_hover_4 = HoverTool(
|
|
664
|
+
tooltips=[
|
|
665
|
+
("rt", "@rt"),
|
|
666
|
+
("prec_mz", "@prec_mz{0.0000}"),
|
|
667
|
+
("index", "@index"),
|
|
668
|
+
("inty_tot", "@inty_tot"),
|
|
669
|
+
("bl", "@bl"),
|
|
670
|
+
],
|
|
671
|
+
)
|
|
658
672
|
feature_points_4 = hv.Points(
|
|
659
673
|
ms2,
|
|
660
674
|
kdims=["rt", "prec_mz"],
|
|
@@ -688,17 +702,17 @@ def plot_2d(
|
|
|
688
702
|
# For slider functionality, we need to work with the feature points directly
|
|
689
703
|
# and not nest DynamicMaps. We'll create the slider using param and panel.
|
|
690
704
|
import param
|
|
691
|
-
import panel as
|
|
692
|
-
|
|
705
|
+
import panel as on
|
|
706
|
+
|
|
693
707
|
class MarkerSizeController(param.Parameterized):
|
|
694
708
|
size_slider = param.Number(default=markersize, bounds=(1, 20), step=0.5)
|
|
695
|
-
|
|
709
|
+
|
|
696
710
|
controller = MarkerSizeController()
|
|
697
|
-
|
|
711
|
+
|
|
698
712
|
# Create a function that generates just the feature overlays with different sizes
|
|
699
713
|
def create_feature_overlay(size_val):
|
|
700
714
|
feature_overlay = None
|
|
701
|
-
|
|
715
|
+
|
|
702
716
|
if feature_points_4 is not None:
|
|
703
717
|
updated_points_4 = feature_points_4.opts(size=size_val)
|
|
704
718
|
feature_overlay = updated_points_4 if feature_overlay is None else feature_overlay * updated_points_4
|
|
@@ -713,22 +727,24 @@ def plot_2d(
|
|
|
713
727
|
feature_overlay = updated_points_2 if feature_overlay is None else feature_overlay * updated_points_2
|
|
714
728
|
if feature_points_iso is not None:
|
|
715
729
|
updated_points_iso = feature_points_iso.opts(size=size_val)
|
|
716
|
-
feature_overlay =
|
|
717
|
-
|
|
730
|
+
feature_overlay = (
|
|
731
|
+
updated_points_iso if feature_overlay is None else feature_overlay * updated_points_iso
|
|
732
|
+
)
|
|
733
|
+
|
|
718
734
|
# Combine with the static raster background
|
|
719
735
|
if feature_overlay is not None:
|
|
720
736
|
combined_overlay = raster * feature_overlay
|
|
721
737
|
else:
|
|
722
738
|
combined_overlay = raster
|
|
723
|
-
|
|
739
|
+
|
|
724
740
|
if title is not None:
|
|
725
741
|
combined_overlay = combined_overlay.opts(title=title)
|
|
726
|
-
|
|
742
|
+
|
|
727
743
|
return combined_overlay
|
|
728
|
-
|
|
744
|
+
|
|
729
745
|
# Create a horizontal control widget on top of the plot
|
|
730
746
|
# Create the slider widget with explicit visibility
|
|
731
|
-
size_slider =
|
|
747
|
+
size_slider = on.widgets.FloatSlider(
|
|
732
748
|
name="Marker Size",
|
|
733
749
|
start=1.0,
|
|
734
750
|
end=20.0,
|
|
@@ -737,19 +753,19 @@ def plot_2d(
|
|
|
737
753
|
width=300,
|
|
738
754
|
height=40,
|
|
739
755
|
margin=(5, 5),
|
|
740
|
-
show_value=True
|
|
756
|
+
show_value=True,
|
|
741
757
|
)
|
|
742
|
-
|
|
758
|
+
|
|
743
759
|
# Create the slider widget row with clear styling
|
|
744
|
-
slider_widget =
|
|
745
|
-
|
|
760
|
+
slider_widget = on.Row(
|
|
761
|
+
on.pane.HTML("<b>Marker Size Control:</b>", width=150, height=40, margin=(5, 10)),
|
|
746
762
|
size_slider,
|
|
747
763
|
height=60,
|
|
748
|
-
margin=10
|
|
764
|
+
margin=10,
|
|
749
765
|
)
|
|
750
|
-
|
|
766
|
+
|
|
751
767
|
# Create slider widget
|
|
752
|
-
size_slider =
|
|
768
|
+
size_slider = on.widgets.FloatSlider(
|
|
753
769
|
name="Marker Size",
|
|
754
770
|
start=1.0,
|
|
755
771
|
end=20.0,
|
|
@@ -758,18 +774,18 @@ def plot_2d(
|
|
|
758
774
|
width=300,
|
|
759
775
|
height=40,
|
|
760
776
|
margin=(5, 5),
|
|
761
|
-
show_value=True
|
|
777
|
+
show_value=True,
|
|
762
778
|
)
|
|
763
|
-
|
|
764
|
-
slider_widget =
|
|
765
|
-
|
|
779
|
+
|
|
780
|
+
slider_widget = on.Row(
|
|
781
|
+
on.pane.HTML("<b>Marker Size:</b>", width=100, height=40, margin=(5, 10)),
|
|
766
782
|
size_slider,
|
|
767
783
|
height=60,
|
|
768
|
-
margin=10
|
|
784
|
+
margin=10,
|
|
769
785
|
)
|
|
770
|
-
|
|
786
|
+
|
|
771
787
|
# Simple reactive plot - slider mode doesn't use dynamic rasterization
|
|
772
|
-
@
|
|
788
|
+
@on.depends(size_slider.param.value)
|
|
773
789
|
def reactive_plot(size_val):
|
|
774
790
|
overlay = create_feature_overlay(float(size_val))
|
|
775
791
|
# Apply static rasterization for slider mode
|
|
@@ -779,19 +795,19 @@ def plot_2d(
|
|
|
779
795
|
aggregator=ds.count(),
|
|
780
796
|
width=raster_max_px,
|
|
781
797
|
height=raster_max_px,
|
|
782
|
-
dynamic=False # Static raster for slider mode
|
|
798
|
+
dynamic=False, # Static raster for slider mode
|
|
783
799
|
).opts(
|
|
784
|
-
cnorm=
|
|
785
|
-
tools=[
|
|
800
|
+
cnorm="eq_hist",
|
|
801
|
+
tools=["hover"],
|
|
786
802
|
width=width,
|
|
787
|
-
height=height
|
|
803
|
+
height=height,
|
|
788
804
|
)
|
|
789
805
|
else:
|
|
790
806
|
return overlay
|
|
791
|
-
|
|
807
|
+
|
|
792
808
|
# Create layout
|
|
793
|
-
layout =
|
|
794
|
-
|
|
809
|
+
layout = on.Column(slider_widget, reactive_plot, sizing_mode="stretch_width")
|
|
810
|
+
|
|
795
811
|
return layout
|
|
796
812
|
else:
|
|
797
813
|
# Create a panel layout without slider
|
|
@@ -1081,18 +1097,20 @@ def plot_2d_oracle(
|
|
|
1081
1097
|
feat_df = feats.copy()
|
|
1082
1098
|
feat_df = feat_df[feat_df["id_level"] == 2]
|
|
1083
1099
|
|
|
1084
|
-
oracle_hover_1 = HoverTool(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1100
|
+
oracle_hover_1 = HoverTool(
|
|
1101
|
+
tooltips=[
|
|
1102
|
+
("rt", "@rt"),
|
|
1103
|
+
("m/z", "@mz{0.0000}"),
|
|
1104
|
+
("feature_uid", "@feature_uid"),
|
|
1105
|
+
("id_level", "@id_level"),
|
|
1106
|
+
("id_class", "@id_class"),
|
|
1107
|
+
("id_label", "@id_label"),
|
|
1108
|
+
("id_ion", "@id_ion"),
|
|
1109
|
+
("id_evidence", "@id_evidence"),
|
|
1110
|
+
("score", "@score"),
|
|
1111
|
+
("score2", "@score2"),
|
|
1112
|
+
],
|
|
1113
|
+
)
|
|
1096
1114
|
feature_points_1 = hv.Points(
|
|
1097
1115
|
feat_df,
|
|
1098
1116
|
kdims=["rt", "mz"],
|
|
@@ -1122,15 +1140,17 @@ def plot_2d_oracle(
|
|
|
1122
1140
|
feat_df = feats.copy()
|
|
1123
1141
|
feat_df = feat_df[(feat_df["ms2_scans"].notnull()) & (feat_df["id_level"] == 1)]
|
|
1124
1142
|
if len(feat_df) > 0:
|
|
1125
|
-
oracle_hover_2 = HoverTool(
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1143
|
+
oracle_hover_2 = HoverTool(
|
|
1144
|
+
tooltips=[
|
|
1145
|
+
("rt", "@rt"),
|
|
1146
|
+
("m/z", "@mz{0.0000}"),
|
|
1147
|
+
("feature_uid", "@feature_uid"),
|
|
1148
|
+
("id_level", "@id_level"),
|
|
1149
|
+
("id_label", "@id_label"),
|
|
1150
|
+
("id_ion", "@id_ion"),
|
|
1151
|
+
("id_class", "@id_class"),
|
|
1152
|
+
],
|
|
1153
|
+
)
|
|
1134
1154
|
feature_points_2 = hv.Points(
|
|
1135
1155
|
feat_df,
|
|
1136
1156
|
kdims=["rt", "mz"],
|
|
@@ -1157,15 +1177,17 @@ def plot_2d_oracle(
|
|
|
1157
1177
|
feat_df = feats.copy()
|
|
1158
1178
|
feat_df = feat_df[(feat_df["ms2_scans"].isnull()) & (feat_df["id_level"] == 1)]
|
|
1159
1179
|
if len(feat_df) > 0:
|
|
1160
|
-
oracle_hover_3 = HoverTool(
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1180
|
+
oracle_hover_3 = HoverTool(
|
|
1181
|
+
tooltips=[
|
|
1182
|
+
("rt", "@rt"),
|
|
1183
|
+
("m/z", "@mz{0.0000}"),
|
|
1184
|
+
("feature_uid", "@feature_uid"),
|
|
1185
|
+
("id_level", "@id_level"),
|
|
1186
|
+
("id_label", "@id_label"),
|
|
1187
|
+
("id_ion", "@id_ion"),
|
|
1188
|
+
("id_class", "@id_class"),
|
|
1189
|
+
],
|
|
1190
|
+
)
|
|
1169
1191
|
feature_points_3 = hv.Points(
|
|
1170
1192
|
feat_df,
|
|
1171
1193
|
kdims=["rt", "mz"],
|
|
@@ -1192,12 +1214,14 @@ def plot_2d_oracle(
|
|
|
1192
1214
|
feat_df = feats.copy()
|
|
1193
1215
|
feat_df = feat_df[(feat_df["ms2_scans"].notnull()) & (feat_df["id_level"] < 1)]
|
|
1194
1216
|
if len(feat_df) > 0:
|
|
1195
|
-
oracle_hover_4 = HoverTool(
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1217
|
+
oracle_hover_4 = HoverTool(
|
|
1218
|
+
tooltips=[
|
|
1219
|
+
("rt", "@rt"),
|
|
1220
|
+
("m/z", "@mz{0.0000}"),
|
|
1221
|
+
("feature_uid", "@feature_uid"),
|
|
1222
|
+
("inty", "@inty"),
|
|
1223
|
+
],
|
|
1224
|
+
)
|
|
1201
1225
|
feature_points_4 = hv.Points(
|
|
1202
1226
|
feat_df,
|
|
1203
1227
|
kdims=["rt", "mz"],
|
|
@@ -1216,12 +1240,14 @@ def plot_2d_oracle(
|
|
|
1216
1240
|
feat_df = feats.copy()
|
|
1217
1241
|
feat_df = feat_df[(feat_df["ms2_scans"].isnull()) & (feat_df["id_level"] < 1)]
|
|
1218
1242
|
if len(feat_df) > 0:
|
|
1219
|
-
oracle_hover_5 = HoverTool(
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1243
|
+
oracle_hover_5 = HoverTool(
|
|
1244
|
+
tooltips=[
|
|
1245
|
+
("rt", "@rt"),
|
|
1246
|
+
("m/z", "@mz{0.0000}"),
|
|
1247
|
+
("feature_uid", "@feature_uid"),
|
|
1248
|
+
("inty", "@inty"),
|
|
1249
|
+
],
|
|
1250
|
+
)
|
|
1225
1251
|
feature_points_5 = hv.Points(
|
|
1226
1252
|
feat_df,
|
|
1227
1253
|
kdims=["rt", "mz"],
|
|
@@ -1941,19 +1967,102 @@ def plot_tic(
|
|
|
1941
1967
|
title=None,
|
|
1942
1968
|
filename=None,
|
|
1943
1969
|
):
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1970
|
+
"""
|
|
1971
|
+
Plot Total Ion Chromatogram (TIC) by summing MS1 peak intensities at each retention time.
|
|
1972
|
+
|
|
1973
|
+
Uses `self.ms1_df` (Polars DataFrame) and aggregates intensities by `rt` (sum).
|
|
1974
|
+
Creates a `Chromatogram` object and uses its `plot()` method to display the result.
|
|
1975
|
+
"""
|
|
1976
|
+
if self.ms1_df is None:
|
|
1977
|
+
self.logger.error("No MS1 data available.")
|
|
1978
|
+
return
|
|
1979
|
+
|
|
1980
|
+
# Import helper locally to avoid circular imports
|
|
1981
|
+
from masster.study.helpers import get_tic
|
|
1982
|
+
|
|
1983
|
+
# Delegate TIC computation to study helper which handles ms1_df and scans_df fallbacks
|
|
1984
|
+
try:
|
|
1985
|
+
chrom = get_tic(self, label=title)
|
|
1986
|
+
except Exception as e:
|
|
1987
|
+
self.logger.exception("Failed to compute TIC via helper: %s", e)
|
|
1988
|
+
return
|
|
1989
|
+
|
|
1990
|
+
if filename is not None:
|
|
1991
|
+
try:
|
|
1992
|
+
chrom.plot(width=1000, height=250)
|
|
1993
|
+
except Exception:
|
|
1994
|
+
import matplotlib.pyplot as plt
|
|
1995
|
+
|
|
1996
|
+
plt.figure(figsize=(10, 3))
|
|
1997
|
+
plt.plot(chrom.rt, chrom.inty, color="black")
|
|
1998
|
+
plt.xlabel("Retention time (s)")
|
|
1999
|
+
plt.ylabel("Intensity")
|
|
2000
|
+
if title:
|
|
2001
|
+
plt.title(title)
|
|
2002
|
+
plt.tight_layout()
|
|
2003
|
+
plt.savefig(filename)
|
|
2004
|
+
return None
|
|
2005
|
+
|
|
2006
|
+
chrom.plot(width=1000, height=250)
|
|
2007
|
+
return None
|
|
2008
|
+
|
|
2009
|
+
|
|
2010
|
+
def plot_bpc(
|
|
2011
|
+
self,
|
|
2012
|
+
title=None,
|
|
2013
|
+
filename=None,
|
|
2014
|
+
rt_unit="s",
|
|
2015
|
+
):
|
|
2016
|
+
"""
|
|
2017
|
+
Plot Base Peak Chromatogram (BPC) using MS1 data.
|
|
2018
|
+
|
|
2019
|
+
Aggregates MS1 points by retention time and selects the maximum intensity (base peak)
|
|
2020
|
+
at each time point. Uses `self.ms1_df` (Polars DataFrame) as the source of MS1 peaks.
|
|
2021
|
+
|
|
2022
|
+
Parameters:
|
|
2023
|
+
title (str, optional): Plot title.
|
|
2024
|
+
filename (str, optional): If provided and ends with `.html` saves an interactive html,
|
|
2025
|
+
otherwise saves a png. If None, returns a displayable object for notebooks.
|
|
2026
|
+
rt_unit (str, optional): Unit label for the x-axis, default 's' (seconds).
|
|
2027
|
+
|
|
2028
|
+
Returns:
|
|
2029
|
+
None or notebook display object (via _display_plot)
|
|
2030
|
+
"""
|
|
2031
|
+
if self.ms1_df is None:
|
|
2032
|
+
self.logger.error("No MS1 data available.")
|
|
2033
|
+
return
|
|
2034
|
+
|
|
2035
|
+
# Import helper locally to avoid circular imports
|
|
2036
|
+
from masster.study.helpers import get_bpc
|
|
2037
|
+
|
|
2038
|
+
# Delegate BPC computation to study helper
|
|
2039
|
+
try:
|
|
2040
|
+
chrom = get_bpc(self, rt_unit=rt_unit, label=title)
|
|
2041
|
+
except Exception as e:
|
|
2042
|
+
self.logger.exception("Failed to compute BPC via helper: %s", e)
|
|
2043
|
+
return
|
|
2044
|
+
|
|
2045
|
+
# If filename was requested, save a static png using bokeh export via the chromatogram plotting
|
|
2046
|
+
if filename is not None:
|
|
2047
|
+
# chromatogram.plot() uses bokeh to show the figure; to save as png we rely on holoviews/hv.save
|
|
2048
|
+
# Create a bokeh figure by plotting to an offscreen axis
|
|
2049
|
+
try:
|
|
2050
|
+
# Use Chromatogram.plot to generate and show the figure (will open in notebook/browser)
|
|
2051
|
+
chrom.plot(width=1000, height=250)
|
|
2052
|
+
except Exception:
|
|
2053
|
+
# Last-resort: create a simple matplotlib plot and save
|
|
2054
|
+
import matplotlib.pyplot as plt
|
|
2055
|
+
|
|
2056
|
+
plt.figure(figsize=(10, 3))
|
|
2057
|
+
plt.plot(chrom.rt, chrom.inty, color="black")
|
|
2058
|
+
plt.xlabel(f"Retention time ({rt_unit})")
|
|
2059
|
+
plt.ylabel("Intensity")
|
|
2060
|
+
if title:
|
|
2061
|
+
plt.title(title)
|
|
2062
|
+
plt.tight_layout()
|
|
2063
|
+
plt.savefig(filename)
|
|
2064
|
+
return None
|
|
2065
|
+
|
|
2066
|
+
# No filename: display using the chromatogram's built-in plotting (bokeh)
|
|
2067
|
+
chrom.plot(width=1000, height=250)
|
|
2068
|
+
return None
|