diff --git a/peakipy/cli/check_panel.py b/peakipy/cli/check_panel.py
index 2bd2ceb4..b60de1d7 100644
--- a/peakipy/cli/check_panel.py
+++ b/peakipy/cli/check_panel.py
@@ -8,7 +8,6 @@
from peakipy.cli.main import check, validate_fit_dataframe
pn.extension()
-# pn.config.theme = "dark"
@dataclass
@@ -35,7 +34,7 @@ def get_cluster(cluster):
data = data_singleton()
cluster_groups = data.df.groupby("clustid")
cluster_group = cluster_groups.get_group(cluster)
- df_pane = pn.pane.DataFrame(
+ df_pane = pn.widgets.Tabulator(
cluster_group[
[
"assignment",
@@ -47,10 +46,12 @@ def get_cluster(cluster):
"center_x_ppm",
"center_y_ppm",
"fwhm_x_hz",
- "fwhm_x_hz",
+ "fwhm_y_hz",
"lineshape",
]
- ]
+ ],
+ selectable=False,
+ disabled=True,
)
return df_pane
@@ -61,7 +62,7 @@ def create_plotly_pane(cluster, plane):
fits=data.fits_path,
data_path=data.data_path,
clusters=[cluster],
- plane=plane,
+ plane=[plane],
config_path=data.config_path,
plotly=True,
)
@@ -105,7 +106,14 @@ def create_check_panel(
check_pane = pn.Card(
info_pane,
pn.Row(select_cluster, select_plane),
- pn.Row(interactive_plotly_pane, interactive_cluster_pane),
+ pn.Row(
+ pn.Column(
+ pn.Card(interactive_plotly_pane, title="Fitted cluster"),
+ pn.Card(
+ interactive_cluster_pane, title="Fitted parameters for cluster"
+ ),
+ )
+ ),
title="Peakipy check",
)
if edit_panel:
diff --git a/peakipy/cli/edit.py b/peakipy/cli/edit.py
index d0ff2e6c..0d85bd96 100644
--- a/peakipy/cli/edit.py
+++ b/peakipy/cli/edit.py
@@ -52,8 +52,6 @@ def __init__(self, peaklist_path: Path, data_path: Path):
self._path = peaklist_path
self._data_path = data_path
args, config = read_config({})
- # self.args = args
- # self.config = config
self._dims = config.get("dims", [0, 1, 2])
self.thres = config.get("thres", 1e6)
self._peakipy_data = LoadData(
@@ -64,6 +62,7 @@ def __init__(self, peaklist_path: Path, data_path: Path):
# make temporary paths
self.make_temp_files()
self.make_data_source()
+ self.make_tabulator_widget()
self.setup_radii_sliders()
self.setup_save_buttons()
self.setup_set_fixed_parameters()
@@ -120,6 +119,34 @@ def make_data_source(self):
self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
return self.source
+ @property
+ def tabulator_columns(self):
+ columns = [
+ "ASS",
+ "CLUSTID",
+ "X_PPM",
+ "Y_PPM",
+ "X_RADIUS_PPM",
+ "Y_RADIUS_PPM",
+ "XW_HZ",
+ "YW_HZ",
+ "VOL",
+ "include",
+ "MEMCNT",
+ ]
+ return columns
+
+ def make_tabulator_widget(self):
+ self.tablulator_widget = pn.widgets.Tabulator(
+ self.peakipy_data.df[self.tabulator_columns],
+ )
+ return self.tablulator_widget
+
+ def select_callback(self, attrname, old, new):
+ for col in self.peakipy_data.df.columns:
+ self.peakipy_data.df.loc[:, col] = self.source.data[col]
+ self.update_memcnt()
+
def setup_radii_sliders(self):
# configure sliders for setting radii
self.slider_X_RADIUS = Slider(
@@ -399,10 +426,15 @@ def setup_plot(self):
)
self.clust_div = Div(
text="""If you want to adjust how the peaks are automatically clustered then try changing the
- width/diameter/height (integer values) of the structuring element used during the binary dilation step
- (you can also remove it by selecting 'None'). Increasing the size of the structuring element will cause
- peaks to be more readily incorporated into clusters. Be sure to save your peak list before doing this as
- any manual edits will be lost."""
+ width/diameter/height (integer values) of the structuring element used during the binary dilation step.
+ Increasing the size of the structuring element will cause
+ peaks to be more readily incorporated into clusters. The mask_method scales the fitting masks based on
+ the provided floating point value and considers any overlapping masks to be part of a cluster.""",
+ )
+ self.recluster_warning = Div(
+ text="""
+ Be sure to save your peak list before reclustering as
+ any manual edits to clusters will be lost.""",
)
self.intro_div = Div(
text="""
peakipy - interactive fit adjustment
@@ -434,170 +466,17 @@ def setup_plot(self):
labels=["fit current plane only"], active=[]
)
- # not sure this is needed
- selected_df = self.peakipy_data.df.copy()
-
self.fit_button.on_event(ButtonClick, self.fit_selected)
- columns = [
- TableColumn(field="ASS", title="Assignment", width=500),
- TableColumn(field="CLUSTID", title="Cluster", editor=IntEditor()),
- TableColumn(
- field="X_PPM",
- title=f"{self.peakipy_data.f2_label}",
- editor=NumberEditor(step=0.0001),
- formatter=NumberFormatter(format="0.0000"),
- ),
- TableColumn(
- field="Y_PPM",
- title=f"{self.peakipy_data.f1_label}",
- editor=NumberEditor(step=0.0001),
- formatter=NumberFormatter(format="0.0000"),
- ),
- TableColumn(
- field="X_RADIUS_PPM",
- title=f"{self.peakipy_data.f2_label} radius (ppm)",
- editor=NumberEditor(step=0.0001),
- formatter=NumberFormatter(format="0.0000"),
- ),
- TableColumn(
- field="Y_RADIUS_PPM",
- title=f"{self.peakipy_data.f1_label} radius (ppm)",
- editor=NumberEditor(step=0.0001),
- formatter=NumberFormatter(format="0.0000"),
- ),
- TableColumn(
- field="XW_HZ",
- title=f"{self.peakipy_data.f2_label} LW (Hz)",
- editor=NumberEditor(step=0.01),
- formatter=NumberFormatter(format="0.00"),
- ),
- TableColumn(
- field="YW_HZ",
- title=f"{self.peakipy_data.f1_label} LW (Hz)",
- editor=NumberEditor(step=0.01),
- formatter=NumberFormatter(format="0.00"),
- ),
- TableColumn(
- field="VOL", title="Volume", formatter=NumberFormatter(format="0.0")
- ),
- TableColumn(
- field="include",
- title="Include",
- width=7,
- editor=SelectEditor(options=["yes", "no"]),
- ),
- TableColumn(field="MEMCNT", title="MEMCNT", editor=IntEditor()),
- ]
-
- self.data_table = DataTable(
- source=self.source,
- columns=columns,
- editable=True,
- width=1200,
- )
-
- self.table_style = InlineStyleSheet(
- css="""
- .slick-row.even { background: #263140; }
- .slick-row.odd { background: #505c6d; }
- .slick-cell.l0 {background: #1f2937;}
- """
- )
- # self.table_style = InlineStyleSheet(
- # css="""
- # .slick-header-columns {
- # background-color: #00296b !important;
- # font-family: arial;
- # font-weight: bold;
- # font-size: 12pt;
- # color: #FFFFFF;
- # text-align: right;
- # }
- # .slick-header-column:hover {
- # background: none repeat scroll 0 0 #fdc500;
- # }
- # .slick-row {
- # font-size: 12pt;
- # font-family: arial;
- # text-align: left;
- # }
- # .slick-row:hover{
- # background: none repeat scroll 0 0 #7c7c7c;
- # }
- # .slick-cell {
- # header-font-weight: 500;
- # border-width: 1px 1px 1px 1px;
- # border-color: #d4d4d4;
- # background-color: #00509D;
- # color: #FFFFFF;
- # }
- # .slick-cell.selected {
- # header-font-weight: 500;
- # border-width: 1px 1px 1px 1px;
- # border-color: #00509D;
- # background-color: #FDC500;
- # color: black;
- # }
-
- # """
- # )
-
- self.data_table.stylesheets = [self.table_style]
-
# callback for adding
# source.selected.on_change('indices', callback)
self.source.selected.on_change("indices", self.select_callback)
- # # Document layout
- # fitting_controls = column(
- # row(
- # column(self.slider_X_RADIUS, self.slider_Y_RADIUS),
- # column(
- # row(column(self.contour_start, self.pos_neg_contour_radiobutton)),
- # column(self.fit_button),
- # ),
- # ),
- # row(
- # column(column(self.select_lineshape_radiobuttons_help), column(self.select_lineshape_radiobuttons)),
- # column(column(self.select_plane), column(self.checkbox_group)),
- # column(self.select_fixed_parameters_help, self.select_fixed_parameters),
- # column(self.select_reference_planes)
- # ),
- # max_width=400,
- # )
- fitting_controls = row(
- column(
- self.slider_X_RADIUS,
- self.slider_Y_RADIUS,
- self.contour_start,
- self.pos_neg_contour_radiobutton,
- self.select_lineshape_radiobuttons_help,
- self.select_lineshape_radiobuttons,
- max_width=400,
- ),
- column(
- self.select_plane,
- self.checkbox_group,
- self.select_fixed_parameters_help,
- self.select_fixed_parameters,
- self.set_xybounds_help,
- self.set_xybounds,
- self.select_reference_planes_help,
- self.select_reference_planes,
- self.set_initial_fit_threshold_help,
- self.set_initial_fit_threshold,
- self.fit_button,
- max_width=400,
- ),
- max_width=800,
- )
-
# reclustering tab
self.struct_el = Select(
title="Structuring element:",
value=StrucEl.disk.value,
- options=[i.value for i in StrucEl] + ["None"],
+ options=[i.value for i in StrucEl],
width=100,
)
self.struct_el_size = TextInput(
@@ -609,36 +488,6 @@ def setup_plot(self):
self.recluster = Button(label="Re-cluster", button_type="warning")
self.recluster.on_event(ButtonClick, self.recluster_peaks)
- # edit_fits tabs
- fitting_layout = fitting_controls
- log_layout = self.fit_reports_div
- recluster_layout = column(
- row(
- self.clust_div,
- ),
- row(
- column(
- self.contour_start,
- self.struct_el,
- self.struct_el_size,
- self.recluster,
- )
- ),
- max_width=400,
- )
- save_layout = column(
- self.savefilename, self.button, self.exit_button, max_width=400
- )
-
- fitting_tab = TabPanel(child=fitting_layout, title="Peak fitting")
- log_tab = TabPanel(child=log_layout, title="Log")
- recluster_tab = TabPanel(child=recluster_layout, title="Re-cluster peaks")
- save_tab = TabPanel(child=save_layout, title="Save edited peaklist")
- self.tabs = Tabs(
- tabs=[fitting_tab, log_tab, recluster_tab, save_tab],
- sizing_mode="scale_both",
- )
-
def recluster_peaks(self, event):
if self.struct_el.value == "mask_method":
self.struc_size = tuple(
@@ -658,7 +507,7 @@ def recluster_peaks(self, event):
)
# update data source
self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
-
+ self.tablulator_widget.value = self.peakipy_data.df[self.tabulator_columns]
return self.peakipy_data.df
def update_memcnt(self):
@@ -675,6 +524,7 @@ def update_memcnt(self):
self.peakipy_data.df.loc[include_no, "color"] = "ghostwhite"
# update source data
self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
+ self.tablulator_widget.value = self.peakipy_data.df[self.tabulator_columns]
return self.peakipy_data.df
def unpack_parameters_to_fix(self):
@@ -690,12 +540,12 @@ def fit_selected(self, event):
selectionIndex = self.source.selected.indices
current = self.peakipy_data.df.iloc[selectionIndex]
- self.peakipy_data.df.loc[selectionIndex, "X_RADIUS_PPM"] = (
- self.slider_X_RADIUS.value
- )
- self.peakipy_data.df.loc[selectionIndex, "Y_RADIUS_PPM"] = (
- self.slider_Y_RADIUS.value
- )
+ # self.peakipy_data.df.loc[selectionIndex, "X_RADIUS_PPM"] = (
+ # self.slider_X_RADIUS.value
+ # )
+ # self.peakipy_data.df.loc[selectionIndex, "Y_RADIUS_PPM"] = (
+ # self.slider_Y_RADIUS.value
+ # )
self.peakipy_data.df.loc[selectionIndex, "X_DIAMETER_PPM"] = (
current["X_RADIUS_PPM"] * 2.0
@@ -724,20 +574,10 @@ def fit_selected(self, event):
print(f"[yellow]Using LS = {lineshape}[/yellow]")
if self.checkbox_group.active == []:
fit_command = f"peakipy fit {self.TEMP_INPUT_CSV} {self.data_path} {self.TEMP_OUT_CSV} --lineshape {lineshape}{fix_command}{reference_planes_command}{initial_fit_threshold_command}{xy_bounds_command}"
- # plot_command = f"peakipy check {self.TEMP_OUT_CSV} {self.data_path} --label --individual --show --outname {self.TEMP_OUT_PLOT / Path('tmp.pdf')}"
- # self.check_pane = create_check_panel(
- # self.TEMP_OUT_CSV, self.data_path, edit_panel=True
- # )
- # plot_command = f"peakipy-check {self.TEMP_OUT_CSV} {self.data_path}"
else:
plane_index = self.select_plane.value
print(f"[yellow]Only fitting plane {plane_index}[/yellow]")
fit_command = f"peakipy fit {self.TEMP_INPUT_CSV} {self.data_path} {self.TEMP_OUT_CSV} --lineshape {lineshape} --plane {plane_index}{fix_command}{reference_planes_command}{initial_fit_threshold_command}{xy_bounds_command}"
- # self.check_pane = create_check_panel(
- # self.TEMP_OUT_CSV, self.data_path, edit_panel=True
- # )
- # plot_command = f"peakipy check {self.TEMP_OUT_CSV} {self.data_path} --label --individual --outname {self.TEMP_OUT_PLOT / Path('tmp.pdf')} --plane {plane_index} --show"
- # plot_command = f"peakipy-check {self.TEMP_OUT_CSV} {self.data_path}"
print(f"[blue]{fit_command}[/blue]")
self.fit_reports += fit_command + "
"
@@ -746,8 +586,6 @@ def fit_selected(self, event):
self.fit_reports += stdout.decode() + "
"
self.fit_reports = self.fit_reports.replace("\n", "
")
self.fit_reports_div.text = log_div % (log_style, self.fit_reports)
- # plot data
- # os.system(plot_command)
def save_peaks(self, event):
if self.savefilename.value:
@@ -765,18 +603,6 @@ def save_peaks(self, event):
else:
self.peakipy_data.df.to_pickle(to_save)
- def select_callback(self, attrname, old, new):
- # print(Fore.RED + "Calling Select Callback")
- # selectionIndex = self.source.selected.indices
- # current = self.peakipy_data.df.iloc[selectionIndex]
-
- for col in self.peakipy_data.df.columns:
- self.peakipy_data.df.loc[:, col] = self.source.data[col]
- # self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
- # update memcnt
- self.update_memcnt()
- # print(Fore.YELLOW + "Finished Calling Select Callback")
-
def peak_pick_callback(self, event):
# global so that df is updated globally
x_radius_ppm = 0.035
@@ -836,82 +662,33 @@ def peak_pick_callback(self, event):
)
self.update_memcnt()
- def slider_callback_x(self, attrname, old, new):
+ def slider_callback(self, dim, channel):
selectionIndex = self.source.selected.indices
current = self.peakipy_data.df.iloc[selectionIndex]
- self.peakipy_data.df.loc[selectionIndex, "X_RADIUS"] = (
- self.slider_X_RADIUS.value * self.peakipy_data.pt_per_ppm_f2
- )
- self.peakipy_data.df.loc[selectionIndex, "X_RADIUS_PPM"] = (
- self.slider_X_RADIUS.value
- )
+ self.peakipy_data.df.loc[selectionIndex, f"{dim}_RADIUS"] = getattr(
+ self, f"slider_{dim}_RADIUS"
+ ).value * getattr(self.peakipy_data, f"pt_per_ppm_{channel}")
+ self.peakipy_data.df.loc[selectionIndex, f"{dim}_RADIUS_PPM"] = getattr(
+ self, f"slider_{dim}_RADIUS"
+ ).value
- self.peakipy_data.df.loc[selectionIndex, "X_DIAMETER_PPM"] = (
- current["X_RADIUS_PPM"] * 2.0
+ self.peakipy_data.df.loc[selectionIndex, f"{dim}_DIAMETER_PPM"] = (
+ current[f"{dim}_RADIUS_PPM"] * 2.0
)
- self.peakipy_data.df.loc[selectionIndex, "X_DIAMETER"] = (
- current["X_RADIUS"] * 2.0
+ self.peakipy_data.df.loc[selectionIndex, f"{dim}_DIAMETER"] = (
+ current[f"{dim}_RADIUS"] * 2.0
)
# set edited rows to True
self.peakipy_data.df.loc[selectionIndex, "Edited"] = True
-
self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
+ self.tablulator_widget.value = self.peakipy_data.df[self.tabulator_columns]
- def slider_callback_y(self, attrname, old, new):
- selectionIndex = self.source.selected.indices
- current = self.peakipy_data.df.iloc[selectionIndex]
- self.peakipy_data.df.loc[selectionIndex, "Y_RADIUS"] = (
- self.slider_Y_RADIUS.value * self.peakipy_data.pt_per_ppm_f1
- )
- self.peakipy_data.df.loc[selectionIndex, "Y_RADIUS_PPM"] = (
- self.slider_Y_RADIUS.value
- )
-
- self.peakipy_data.df.loc[selectionIndex, "Y_DIAMETER_PPM"] = (
- current["Y_RADIUS_PPM"] * 2.0
- )
- self.peakipy_data.df.loc[selectionIndex, "Y_DIAMETER"] = (
- current["Y_RADIUS"] * 2.0
- )
-
- # set edited rows to True
- self.peakipy_data.df.loc[selectionIndex, "Edited"] = True
-
- self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
+ def slider_callback_x(self, attrname, old, new):
+ self.slider_callback("X", "f2")
- # def slider_callback(self, attrname, old, new, dim="X"):
- #
- # selectionIndex = self.source.selected.indices
- # current = self.peakipy_data.df.iloc[selectionIndex]
- # self.peakipy_data.df.loc[selectionIndex, f"{dim}_RADIUS"] = (
- # self.slider_Y_RADIUS.value * self.peakipy_data.pt_per_ppm_f1
- # )
- # self.peakipy_data.df.loc[
- # selectionIndex, f"{dim}_RADIUS_PPM"
- # ] = self.slider_Y_RADIUS.value
- #
- # self.peakipy_data.df.loc[selectionIndex, f"{dim}_DIAMETER_PPM"] = (
- # current[f"{dim}_RADIUS_PPM"] * 2.0
- # )
- # self.peakipy_data.df.loc[selectionIndex, f"{dim}_DIAMETER"] = (
- # current[f"{dim}_RADIUS"] * 2.0
- # )
- #
- # set edited rows to True
- # self.peakipy_data.df.loc[selectionIndex, "Edited"] = True
-
- # selected_df = df[df.CLUSTID.isin(list(current.CLUSTID))]
- # print(list(selected_df))
- # self.source.data = ColumnDataSource.from_df(self.peakipy_data.df)
-
- # def slider_callback_x(self, attrname, old, new):
- #
- # self.slider_callback(attrname, old, new, dim="X")
- #
- # def slider_callback_y(self, attrname, old, new):
- #
- # self.slider_callback(attrname, old, new, dim="Y")
+ def slider_callback_y(self, attrname, old, new):
+ self.slider_callback("Y", "f1")
def update_contour(self, attrname, old, new):
new_cs = eval(self.contour_start.value)
diff --git a/peakipy/cli/edit_panel.py b/peakipy/cli/edit_panel.py
index ddadcc40..de63f8a4 100644
--- a/peakipy/cli/edit_panel.py
+++ b/peakipy/cli/edit_panel.py
@@ -33,35 +33,44 @@ def data_singleton():
return Data()
+def update_peakipy_data_on_edit_of_table(event):
+ data = data_singleton()
+ column = event.column
+ row = event.row
+ value = event.value
+ data.bs.peakipy_data.df.loc[row, column] = value
+ data.bs.update_memcnt()
+
+
def panel_app():
data = data_singleton()
bs = data.bs
bokeh_pane = pn.pane.Bokeh(bs.p)
- # table_pane = pn.pane.Bokeh(bs.data_table)
- table_pane = pn.widgets.Tabulator(
- bs.peakipy_data.df[
- [
- "ASS",
- "CLUSTID",
- "X_PPM",
- "Y_PPM",
- "X_RADIUS_PPM",
- "Y_RADIUS_PPM",
- "XW_HZ",
- "YW_HZ",
- "VOL",
- "include",
- "MEMCNT",
- ]
- ]
- )
-
spectrum_view_settings = pn.WidgetBox(
"# Contour settings", bs.pos_neg_contour_radiobutton, bs.contour_start
)
+ save_peaklist_box = pn.WidgetBox(
+ "# Save your peaklist",
+ bs.savefilename,
+ bs.button,
+ pn.layout.Divider(),
+ bs.exit_button,
+ )
+ recluster_settings = pn.WidgetBox(
+ "# Re-cluster your peaks",
+ bs.clust_div,
+ bs.struct_el,
+ bs.struct_el_size,
+ pn.layout.Divider(),
+ bs.recluster_warning,
+ bs.recluster,
+ sizing_mode="stretch_width",
+ )
button = pn.widgets.Button(name="Fit selected cluster(s)", button_type="primary")
fit_controls = pn.WidgetBox(
"# Fit controls",
+ button,
+ pn.layout.Divider(),
bs.select_plane,
bs.checkbox_group,
pn.layout.Divider(),
@@ -76,32 +85,47 @@ def panel_app():
pn.layout.Divider(),
bs.select_lineshape_radiobuttons_help,
bs.select_lineshape_radiobuttons,
- pn.layout.Divider(),
- button,
)
mask_adjustment_controls = pn.WidgetBox(
"# Fitting mask adjustment", bs.slider_X_RADIUS, bs.slider_Y_RADIUS
)
- def b(event):
+ # bs.source.on_change()
+ def fit_peaks_button_click(event):
check_app.loading = True
bs.fit_selected(None)
check_panel = create_check_panel(bs.TEMP_OUT_CSV, bs.data_path, edit_panel=True)
check_app.objects = check_panel.objects
check_app.loading = False
- button.on_click(b)
+ button.on_click(fit_peaks_button_click)
+
+ def update_source_selected_indices(event):
+ print(event)
+ print(bs.tablulator_widget.selection)
+ bs.source.selected.indices = bs.tablulator_widget.selection
+
+ bs.tablulator_widget.on_click(update_source_selected_indices)
+ bs.tablulator_widget.on_edit(update_peakipy_data_on_edit_of_table)
+
template = pn.template.BootstrapTemplate(
title="Peakipy",
sidebar=[mask_adjustment_controls, fit_controls],
)
spectrum = pn.Card(
- pn.Column(pn.Row(bokeh_pane, spectrum_view_settings), table_pane),
+ pn.Column(
+ pn.Row(
+ bokeh_pane,
+ pn.Column(spectrum_view_settings, save_peaklist_box),
+ recluster_settings,
+ ),
+ bs.tablulator_widget,
+ ),
title="Peakipy fit",
)
check_app = pn.Card(title="Peakipy check")
- template.main.append(pn.Row(spectrum, check_app))
+ template.main.append(pn.Column(check_app, spectrum))
template.show()
diff --git a/peakipy/cli/main.py b/peakipy/cli/main.py
index af17bf38..2ecb064c 100644
--- a/peakipy/cli/main.py
+++ b/peakipy/cli/main.py
@@ -766,33 +766,30 @@ def fit(
def validate_plane_selection(plane, pseudo3D):
- if plane > pseudo3D.n_planes:
+ if (plane == []) or (plane == None):
+ plane = list(range(pseudo3D.n_planes))
+
+ elif max(plane) > (pseudo3D.n_planes - 1):
raise ValueError(
- f"[red]There are {pseudo3D.n_planes} planes in your data you selected --plane {plane}...[red]"
+ f"[red]There are {pseudo3D.n_planes} planes in your data you selected --plane {max(plane)}...[red]"
f"plane numbering starts from 0."
)
- elif plane < 0:
+ elif min(plane) < 0:
raise ValueError(
- f"[red]Plane number can not be negative; you selected --plane {plane}...[/red]"
+ f"[red]Plane number can not be negative; you selected --plane {min(plane)}...[/red]"
)
else:
- return plane
-
+ plane = sorted(plane)
-def validate_ccount(ccount):
- if type(ccount) == int:
- ccount = ccount
- else:
- raise TypeError("ccount should be an integer")
- return ccount
+ return plane
-def validate_rcount(rcount):
- if type(rcount) == int:
- rcount = rcount
+def validate_sample_count(sample_count):
+ if type(sample_count) == int:
+ sample_count = sample_count
else:
- raise TypeError("rcount should be an integer")
- return rcount
+ raise TypeError("Sample count (ccount, rcount) should be an integer")
+ return sample_count
def unpack_plotting_colors(colors):
@@ -1107,7 +1104,7 @@ def next_plot(event):
else:
pdf.savefig()
- plt.close()
+ plt.close()
def create_plotly_wireframe_lines(plot_data: PlottingDataForPlane):
@@ -1309,7 +1306,7 @@ def check(
fits: Path,
data_path: Path,
clusters: Optional[List[int]] = None,
- plane: int = 0,
+ plane: Optional[List[int]] = None,
outname: Path = Path("plots.pdf"),
first: bool = False,
show: bool = False,
@@ -1387,17 +1384,15 @@ def check(
# first only overrides plane option
if first:
- plane = 0
+ selected_planes = [0]
else:
- plane = plane
-
- selected_plane = validate_plane_selection(plane, pseudo3D)
- ccount = validate_ccount(ccount)
- rcount = validate_rcount(rcount)
+ selected_planes = validate_plane_selection(plane, pseudo3D)
+ ccount = validate_sample_count(ccount)
+ rcount = validate_sample_count(rcount)
data_color, fit_color = unpack_plotting_colors(colors)
fits = get_fit_data_for_selected_peak_clusters(fits, clusters)
- peak_clusters = fits.query(f"plane=={selected_plane}").groupby("clustid")
+ peak_clusters = fits.query(f"plane in @selected_planes").groupby("clustid")
# make plotting meshes
x = np.arange(pseudo3D.f2_size)
@@ -1430,7 +1425,7 @@ def check(
empty_mask_array = np.zeros(
(pseudo3D.f1_size, pseudo3D.f2_size), dtype=bool
)
- first_plane = peak_cluster[peak_cluster.plane == selected_plane]
+ first_plane = peak_cluster[peak_cluster.plane == selected_planes[0]]
individual_masks, mask = make_masks_from_plane_data(
empty_mask_array, first_plane
)
diff --git a/peakipy/core.py b/peakipy/core.py
index 7504f309..4d4dad95 100644
--- a/peakipy/core.py
+++ b/peakipy/core.py
@@ -29,7 +29,6 @@
import numpy as np
import nmrglue as ng
-import matplotlib.pyplot as plt
import pandas as pd
import textwrap
from rich import print
@@ -38,15 +37,9 @@
from numpy import sqrt, log, pi, exp, finfo
-from lmfit import Model, Parameters
-from lmfit.model import ModelResult
-from lmfit.models import LinearModel
+from lmfit import Model
from scipy.special import wofz
-from matplotlib import cm
-from mpl_toolkits.mplot3d import Axes3D
-from matplotlib.widgets import Button
-
from bokeh.palettes import Category20
from scipy import ndimage
from skimage.morphology import square, binary_closing, disk, rectangle
@@ -1169,30 +1162,12 @@ def __init__(
self._analysis_to_pipe_dic = {
"#": "INDEX",
- # "": "X_AXIS",
- # "": "Y_AXIS",
- # "": "DX",
- # "": "DY",
"Position F1": "X_PPM",
"Position F2": "Y_PPM",
- # "": "X_HZ",
- # "": "Y_HZ",
- # "": "XW",
- # "": "YW",
"Line Width F1 (Hz)": "XW_HZ",
"Line Width F2 (Hz)": "YW_HZ",
- # "": "X1",
- # "": "X3",
- # "": "Y1",
- # "": "Y3",
"Height": "HEIGHT",
- # "Height": "DHEIGHT",
"Volume": "VOL",
- # "": "PCHI2",
- # "": "TYPE",
- # "": "ASS",
- # "": "CLUSTID",
- # "": "MEMCNT"
}
self._assign_to_pipe_dic = {
"#": "INDEX",
@@ -1206,30 +1181,13 @@ def __init__(
self._sparky_to_pipe_dic = {
"index": "INDEX",
- # "": "X_AXIS",
- # "": "Y_AXIS",
- # "": "DX",
- # "": "DY",
"w1": "X_PPM",
"w2": "Y_PPM",
- # "": "X_HZ",
- # "": "Y_HZ",
- # "": "XW",
- # "": "YW",
"lw1 (hz)": "XW_HZ",
"lw2 (hz)": "YW_HZ",
- # "": "X1",
- # "": "X3",
- # "": "Y1",
- # "": "Y3",
"Height": "HEIGHT",
- # "Height": "DHEIGHT",
"Volume": "VOL",
- # "": "PCHI2",
- # "": "TYPE",
"Assignment": "ASS",
- # "": "CLUSTID",
- # "": "MEMCNT"
}
self._analysis_to_pipe_dic[posF1] = "Y_PPM"
diff --git a/test/test_cli.py b/test/test_cli.py
index 191f2fa3..4b4b2062 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -156,13 +156,13 @@ def test_check_main_with_pv_pv(protein_L):
peakipy.cli.main.check(**args)
-def test_edit_with_default(protein_L):
- args = dict(
- peaklist_path=protein_L / Path("peaks.csv"),
- data_path=protein_L / Path("test1.ft2"),
- test=True,
- )
- peakipy.cli.main.edit(**args)
+# def test_edit_with_default(protein_L):
+# args = dict(
+# peaklist_path=protein_L / Path("peaks.csv"),
+# data_path=protein_L / Path("test1.ft2"),
+# test=True,
+# )
+# peakipy.cli.main.edit(**args)
# if __name__ == "__main__":
diff --git a/test/test_main.py b/test/test_main.py
index ea40fe02..366f934b 100644
--- a/test/test_main.py
+++ b/test/test_main.py
@@ -3,6 +3,7 @@
import numpy as np
import pandas as pd
+import pytest
from pytest import fixture
from peakipy.cli.main import (
@@ -10,6 +11,10 @@
check_for_include_column_and_add_if_missing,
select_specified_planes,
exclude_specified_planes,
+ validate_plane_selection,
+ validate_sample_count,
+ unpack_plotting_colors,
+ get_fit_data_for_selected_peak_clusters,
)
@@ -76,3 +81,117 @@ def test_exclude_specified_planes_2():
actual_plane_numbers, peakipy_data = exclude_specified_planes(plane, PeakipyData())
np.testing.assert_array_equal(expected_plane_numbers, actual_plane_numbers)
assert peakipy_data.data.shape == (2, 10, 20)
+
+
+class MockPseudo3D:
+ def __init__(self, n_planes):
+ self.n_planes = n_planes
+
+
+def test_empty_plane_selection():
+ pseudo3D = MockPseudo3D(n_planes=5)
+ assert validate_plane_selection([], pseudo3D) == [0, 1, 2, 3, 4]
+
+
+def test_plane_selection_none():
+ pseudo3D = MockPseudo3D(n_planes=5)
+ assert validate_plane_selection(None, pseudo3D) == [0, 1, 2, 3, 4]
+
+
+def test_valid_plane_selection():
+ pseudo3D = MockPseudo3D(n_planes=5)
+ assert validate_plane_selection([0, 1, 2], pseudo3D) == [0, 1, 2]
+
+
+def test_invalid_plane_selection_negative():
+ pseudo3D = MockPseudo3D(n_planes=5)
+ with pytest.raises(ValueError):
+ validate_plane_selection([-1], pseudo3D)
+
+
+def test_invalid_plane_selection_too_high():
+ pseudo3D = MockPseudo3D(n_planes=5)
+ with pytest.raises(ValueError):
+ validate_plane_selection([5], pseudo3D)
+
+
+def test_invalid_plane_selection_mix():
+ pseudo3D = MockPseudo3D(n_planes=5)
+ with pytest.raises(ValueError):
+ validate_plane_selection([-1, 3, 5], pseudo3D)
+
+
+def test_valid_sample_count():
+ assert validate_sample_count(10) == 10
+
+
+def test_invalid_sample_count_type():
+ with pytest.raises(TypeError):
+ validate_sample_count("10")
+
+
+def test_invalid_sample_count_float():
+ with pytest.raises(TypeError):
+ validate_sample_count(10.5)
+
+
+def test_invalid_sample_count_list():
+ with pytest.raises(TypeError):
+ validate_sample_count([10])
+
+
+def test_invalid_sample_count_dict():
+ with pytest.raises(TypeError):
+ validate_sample_count({"count": 10})
+
+
+def test_invalid_sample_count_none():
+ with pytest.raises(TypeError):
+ validate_sample_count(None)
+
+
+def test_valid_colors():
+ assert unpack_plotting_colors(("red", "black")) == ("red", "black")
+
+
+def test_default_colors():
+ assert unpack_plotting_colors(()) == ("green", "blue")
+
+
+def test_invalid_colors_type():
+ assert unpack_plotting_colors("red") == ("green", "blue")
+
+
+def test_invalid_colors_single():
+ assert unpack_plotting_colors(("red",)) == ("green", "blue")
+
+
+def test_invalid_colors_length():
+ assert unpack_plotting_colors(("red", "black", "green")) == ("green", "blue")
+
+
+def test_no_clusters():
+ fits = pd.DataFrame({"clustid": [1, 2, 3]})
+ assert get_fit_data_for_selected_peak_clusters(fits, None).equals(fits)
+
+
+def test_empty_clusters():
+ fits = pd.DataFrame({"clustid": [1, 2, 3]})
+ assert get_fit_data_for_selected_peak_clusters(fits, []).equals(fits)
+
+
+def test_valid_clusters():
+ fits = pd.DataFrame({"clustid": [1, 2, 3]})
+ selected_clusters = [1, 3]
+ expected_result = pd.DataFrame({"clustid": [1, 3]})
+ assert (
+ get_fit_data_for_selected_peak_clusters(fits, selected_clusters)
+ .reset_index(drop=True)
+ .equals(expected_result)
+ )
+
+
+def test_invalid_clusters():
+ fits = pd.DataFrame({"clustid": [1, 2, 3]})
+ with pytest.raises(SystemExit):
+ get_fit_data_for_selected_peak_clusters(fits, [4, 5, 6])