Skip to content

Commit

Permalink
Merge branch 'main' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
josenimo authored Jun 18, 2024
2 parents c1ad602 + 4e0cc72 commit fc66c5b
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 191 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ hook by running `pre-commit install` in the directory with the `.pre-commit-conf

## Installation

Install napari (see https://napari.org/stable/tutorials/fundamentals/installation)

You can install `napari-cell-gater` via [pip]:

pip install napari-cell-gater

or directly from github via [pip] with:

pip install git+https://github.com/melonora/napari-cell-gater.git@stable

## Visual Workflow

![Image Alt Text](/docs/VisualWorkflow_highres.png)
Expand Down
41 changes: 41 additions & 0 deletions src/cell_gater/_tests/_test_sample_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import tempfile
from typing import Any

import numpy as np
import pandas as pd

from cell_gater.widgets.sample_widget import SampleWidget

rng = np.random.default_rng(42)
# if we get reports that with some column names there is a problem, we get it here to automatically test.
MARKER_DF = pd.DataFrame(
{
"CellID": list(range(5)),
"DNA": rng.random(5),
"Rabbit IgG": rng.random(5),
"Goat IgG": rng.random(5),
"DNA2": rng.random(5),
"CD73": rng.random(5),
"Some.name.with.dots": rng.random(5),
}
)


def test_populate_markers_on_csv_load(make_napari_viewer: Any) -> None:
viewer = make_napari_viewer()
widget = SampleWidget(viewer)
with tempfile.TemporaryDirectory() as tmpdir:
MARKER_DF.to_csv(tmpdir + "/test1.csv", index=False)
MARKER_DF.to_csv(tmpdir + "/test12.csv", index=False)

widget._open_sample_dialog(folder=tmpdir)

lower_marker_cols = [
widget.lower_bound_marker_col.itemText(index) for index in range(widget.lower_bound_marker_col.count())
]
upper_marker_cols = [
widget.upper_bound_marker_col.itemText(index) for index in range(widget.upper_bound_marker_col.count())
]
reference_cols = list(MARKER_DF.columns)
reference_cols.append("sample_id")
assert lower_marker_cols == upper_marker_cols == reference_cols
31 changes: 0 additions & 31 deletions src/cell_gater/_tests/test_reader.py

This file was deleted.

7 changes: 0 additions & 7 deletions src/cell_gater/_tests/test_sample_data.py

This file was deleted.

66 changes: 0 additions & 66 deletions src/cell_gater/_tests/test_widget.py

This file was deleted.

7 changes: 0 additions & 7 deletions src/cell_gater/_tests/test_writer.py

This file was deleted.

65 changes: 0 additions & 65 deletions src/cell_gater/_writer.py

This file was deleted.

9 changes: 5 additions & 4 deletions src/cell_gater/model/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pandas as pd
from napari.utils.events import EmitterGroup, Event


@dataclass
class DataModel:
"""Model containing all necessary fields for gating."""
Expand Down Expand Up @@ -146,6 +147,10 @@ def markers(self):
"""The markers included for gating."""
return self._markers

@markers.setter
def markers(self, markers: Sequence[str]) -> None:
self._markers = markers

@property
def markers_image_indices(self):
"""The markers included for gating."""
Expand All @@ -155,10 +160,6 @@ def markers_image_indices(self):
def markers_image_indices(self, markers_image_indices: Sequence[str]) -> None:
self._markers_image_indices = markers_image_indices

@markers.setter
def markers(self, markers: Sequence[str]) -> None:
self._markers = markers

@property
def active_marker(self):
"""The marker currently used on x-axis for gating."""
Expand Down
4 changes: 2 additions & 2 deletions src/cell_gater/utils/csv_df.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ def stack_csv_files(csv_dir: Path) -> pd.DataFrame | None:
napari_notification(f"Loaded {len(csv_files)} regionprops csvs.")
df = pd.DataFrame()
for file in csv_files:
if not file.name.startswith('.'):
if not file.name.startswith("."):
df_file = pd.read_csv(file)
df_file["sample_id"] = file.stem
df = pd.concat([df, df_file], ignore_index=True)
df["sample_id"] = df.sample_id.astype("category")
else:
print(f"Skipping file {file.name} as it is a hidden file.")
napari_notification(f"Skipping file {file.name} as it is a hidden file.")

return df

Expand Down
5 changes: 3 additions & 2 deletions src/cell_gater/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
)


def napari_notification(msg, severity=NotificationSeverity.INFO):
def napari_notification(msg: str, severity: NotificationSeverity = NotificationSeverity.INFO) -> None:
"""Display a napari notification within the napari viewer with a given severity."""
notification_ = Notification(msg, severity=severity)
notification_manager.dispatch(notification_)
notification_manager.dispatch(notification_)
9 changes: 6 additions & 3 deletions src/cell_gater/widgets/sample_widget.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from pathlib import Path

from napari import Viewer
Expand Down Expand Up @@ -125,11 +127,12 @@ def _dir_dialog(self):
QFileDialog.Options(),
)

def _open_sample_dialog(self):
def _open_sample_dialog(self, folder: str | None = None):
"""Open directory file dialog for regionprop directory."""
folder = self._dir_dialog()
if not folder:
folder = self._dir_dialog()

if folder not in {"", None}:
if isinstance(folder, str) and folder != "":
self._assign_regionprops_to_model(folder)
update_open_history(folder)

Expand Down
14 changes: 10 additions & 4 deletions src/cell_gater/widgets/scatter_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
from itertools import product


import numpy as np
import pandas as pd
from dask_image.imread import imread
Expand Down Expand Up @@ -36,9 +37,10 @@

#Good to have features

#Ideas to maybe implement
#TODO dynamic plotting of points on top of created polygons
#TODO save plots as images for QC, perhaps when saving gates run plotting function to go through all samples and markers and save plots
# Ideas to maybe implement
# TODO dynamic plotting of points on top of created polygons
# TODO save plots as images for QC, perhaps when saving gates run plotting function to go through all samples and markers and save plots


class ScatterInputWidget(QWidget):
"""Widget for a scatter plot with markers on the x axis and any dtype column on the y axis."""
Expand All @@ -58,7 +60,7 @@ def __init__(self, model: DataModel, viewer: Viewer) -> None:

selection_label = QLabel("Select sample:")
self.sample_selection_dropdown = QComboBox()
self.sample_selection_dropdown.addItems(sorted(self.model.samples, key=self.natural_sort_key) )
self.sample_selection_dropdown.addItems(sorted(self.model.samples, key=self.natural_sort_key))
self.sample_selection_dropdown.currentTextChanged.connect(self._on_sample_changed)

marker_label = QLabel("Marker label:")
Expand Down Expand Up @@ -200,10 +202,13 @@ def update_plot(self):
self.scatter_canvas.plot_scatter_plot(self.model)
self.scatter_canvas.fig.draw()


###################
### PLOT POINTS ###
###################

# TODO dynamic plotting of points on top of created polygons

def plot_points(self):
"""Plot positive cells in Napari."""
df = self.model.regionprops_df
Expand Down Expand Up @@ -432,6 +437,7 @@ def _file_dialog(self):
def natural_sort_key(self, s):
"""Key function for natural sorting."""
import re

return [int(text) if text.isdigit() else text.lower() for text in re.split(r"(\d+)", s)]

@property
Expand Down

0 comments on commit fc66c5b

Please sign in to comment.