From 305c18d0ac2ffbdecab81fa4014aeee5320c9667 Mon Sep 17 00:00:00 2001 From: Jacob Brady Date: Sun, 17 Mar 2024 23:04:39 -0400 Subject: [PATCH] trailing white space --- peakipy/cli/check_panel.py | 20 ++- peakipy/cli/edit.py | 349 +++++++------------------------------ peakipy/cli/edit_panel.py | 74 +++++--- peakipy/cli/main.py | 49 +++--- peakipy/core.py | 44 +---- test/test_cli.py | 14 +- test/test_main.py | 119 +++++++++++++ 7 files changed, 275 insertions(+), 394 deletions(-) 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])