Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1811d57
standardize_metadata action, refactoring
colinvwood Feb 11, 2026
388ddff
update qSIP2 version
colinvwood Feb 11, 2026
6113c09
remove pickle formats, add SourcWADs type and formats, add wad calcul…
colinvwood Feb 11, 2026
4533617
remake wad visualizer
colinvwood Feb 12, 2026
14d1ea8
remake sample curves plot
colinvwood Feb 12, 2026
a582d94
remake density outlier plot
colinvwood Feb 12, 2026
03dfd2c
fix tooltip bug
colinvwood Feb 12, 2026
72288b3
remove comparison group finder thing
colinvwood Feb 12, 2026
d9bc8c5
feature wads types & formats, refactor prevalence filtering action
colinvwood Feb 16, 2026
cfc10b4
calculate eaf refactor and types & formats
colinvwood Feb 17, 2026
b68dbd3
eaf action and visualizer refactor, types changes
colinvwood Feb 19, 2026
6f5d2f6
remove filtered features visualizer
colinvwood Feb 19, 2026
e288f8c
remove unused ggplot2 util
colinvwood Feb 19, 2026
8ba8b79
separate qsip data constructors
colinvwood Feb 19, 2026
f2eb350
lint
colinvwood Feb 19, 2026
7373fde
naming, help text changes
colinvwood Feb 23, 2026
f141ea9
feature table wads instead of feature data wads
colinvwood Feb 23, 2026
74af492
filtered features visualizer
colinvwood Feb 24, 2026
9222b64
tutorial updates
colinvwood Feb 25, 2026
868e9ed
usage examples for tutorial
colinvwood Feb 26, 2026
e8de6ab
update env file for ci
colinvwood Feb 27, 2026
14d5895
try newer mac runner
colinvwood Feb 27, 2026
0a6d5d5
use released channel
colinvwood Feb 27, 2026
e7e0737
ci changes
colinvwood Feb 27, 2026
177d4da
use newer workflow
colinvwood Feb 27, 2026
bb225dd
include pip as dependency
colinvwood Feb 27, 2026
afec4c8
try custom lib-community-ci.yml
colinvwood Feb 27, 2026
fdb3ded
yml not yaml
colinvwood Feb 27, 2026
f97576f
remove @dev
colinvwood Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 6 additions & 51 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,57 +1,12 @@
name: Test and lint

name: ci-q2-qsip2
on:
pull_request:
branches: ["main"]
push:
branches: ["main"]

jobs:
build-and-test-qiime2-amplicon-2024-5:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-13]

steps:
- uses: actions/checkout@v2

- name: Set up Conda
uses: conda-incubator/setup-miniconda@v3
with:
activate-environment: q2-qsip2-qiime2-amplicon-2025.4
environment-file: environments/q2-qsip2-qiime2-amplicon-2025.4.yml
auto-activate-base: false

- name: Install plugin
shell: bash -l {0}
run: make install
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

- name: Run tests
shell: bash -l {0}
run: make test

lint:
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3

- name: set up python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.11

- name: install dependencies
run: |
python -m pip install --upgrade pip
pip install -q https://github.com/qiime2/q2lint/archive/master.zip
pip install -q flake8

- name: run flake8
run: flake8

- name: run q2lint
run: q2lint
ci:
uses: caporaso-lab/q2-qsip2/.github/workflows/lib-community-ci-custom.yml
with:
github-repo: q2-qsip2
env-file-name: q2-qsip2-qiime2-amplicon-2026.1.yml
83 changes: 83 additions & 0 deletions .github/workflows/lib-community-ci-custom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# # Example of workflow trigger for calling workflow (the client).
# name: ci-vizard
# on:
# pull_request:
# branches: ["dev"]
# push:
# branches: ["dev"]
# jobs:
# ci:
# uses: qiime2/distributions/.github/workflows/lib-community-ci.yaml@dev
# with:
# github-repo: q2-vizard
# env-file-name: 2024.5-vizard-environment.yml

on:
workflow_call:
inputs:
github-repo:
description: "Plugin repository name on Github"
type: string
required: true

env-file-name:
description: "Name of environment file to test with"
type: string
required: true

env:
name: ${{ inputs.github-repo }}
filepath: environment-files/${{ inputs.env-file-name }}

jobs:
install-env-and-test:
strategy:
matrix:
os: [ubuntu-latest, macos-15-intel]
runs-on: ${{ matrix.os }}
steps:
- name: "checkout source"
uses: actions/checkout@v4

- name: "set up python 3.9"
uses: actions/setup-python@v4
with:
python-version: 3.9

- name: "Manually set up conda environment, install ${{ env.name }} locally and run tests"
shell: bash -el {0}
run: |
if ! command -v conda >/dev/null 2>&1; then
echo "Miniconda not found. Installing Miniconda..."
if [[ "$(uname)" == "Darwin" ]]; then
MINICONDA_INSTALLER_SCRIPT=Miniconda3-latest-MacOSX-x86_64.sh
else
MINICONDA_INSTALLER_SCRIPT=Miniconda3-latest-Linux-x86_64.sh
fi
curl -LO https://repo.anaconda.com/miniconda/$MINICONDA_INSTALLER_SCRIPT
bash $MINICONDA_INSTALLER_SCRIPT -b -p $HOME/miniconda
export PATH="$HOME/miniconda/bin:$PATH"
fi

echo "Creating environment '${{ env.name }}' from file '${{ env.filepath }}'"
conda env create --file "${{ env.filepath }}" --name "${{ env.name }}"

echo "Activating environment '${{ env.name }}'"
set +e
source "$(conda info --base)/etc/profile.d/conda.sh"
conda activate "${{ env.name }}" || true

echo "Installing '${{ env.name }}' locally..."
set -e
pip install .
make install
echo "Installation successful!"

echo "Running 'qiime info' to check package versions..."
qiime info

echo "Installing pytest to run '${{ env.name }}'' test suite..."
conda install -y pytest

echo "Running tests for '${{ env.name }}'..."
pytest
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test: all
py.test

REPO = jeffkimbrel/qSIP2
HASH = b867c96aa7b72b72f47942f11dbcd18319cae8bc
HASH = af7e19757ae73c837507bbd672054bb9fdee74d5
install: all
pip install .
Rscript -e 'devtools::install_github("$(REPO)", ref="$(HASH)")'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: q2-qsip2-qiime2-amplicon-2026.1
channels:
- https://packages.qiime2.org/qiime2/2026.1/amplicon/passed
- https://packages.qiime2.org/qiime2/2026.1/amplicon/released
- conda-forge
- bioconda
dependencies:
Expand All @@ -14,3 +14,5 @@ dependencies:
- r-clock
- rpy2
- pip
- pip:
- q2-qsip2@git+https://github.com/caporaso-lab/q2-qsip2.git
148 changes: 148 additions & 0 deletions q2_qsip2/_constructors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# ----------------------------------------------------------------------------
# Copyright (c) 2024-2026, QIIME 2 development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------

import biom
import pandas as pd
import rpy2.robjects as ro
from rpy2.robjects.packages import importr
from rpy2.robjects.methods import RS4
from rpy2.robjects import pandas2ri

import rachis

from q2_qsip2.metadata import (
standardize_metadata, _extract_source_metadata, _get_source_vectors
)

qsip2 = importr('qSIP2')
base = importr('base')
importr('S7')


def _create_qsip_data(
table: biom.Table, metadata: rachis.Metadata, relative: bool = False
) -> RS4:
'''
Create a `qsip_data` R object from a feature table and standardized
metadata.

Parameters
----------
table : biom.Table
The feature table containing sample ids on one axis and feature ids
on the other.
metadata : rachis.Metadata
The standardized sample-level metadata containing all required
sample-level and source-level variables.
relative : bool
Whether `table` is in relative frequency format.

Returns
-------
RObject
The qSIP2 data object as created by the qSIP2 R package. This wraps the
sample metadata, the source metadata, and the feature table.
'''
# validate metadata by restandardizing
sample_metadata = standardize_metadata(sample_metadata=metadata)

# split standardized metadata into sample- & source-level
source_metadata = _extract_source_metadata(metadata)

# convert to dataframes
sample_df = sample_metadata.to_dataframe()
sample_index_name = sample_df.index.name
sample_df.reset_index(inplace=True)

source_df = source_metadata.to_dataframe()
source_index_name = source_df.index.name
source_df.reset_index(inplace=True)

table_df = table.to_dataframe(dense=True)
table_df.index.name = 'ASV'
table_df.reset_index(inplace=True)

# construct qsip object
with (ro.default_converter + pandas2ri.converter).context():
R_source_obj = qsip2.qsip_source_data(
source_df, source_mat_id=source_index_name
)
R_sample_obj = qsip2.qsip_sample_data(
sample_df, sample_id=sample_index_name,
)
R_feature_obj = qsip2.qsip_feature_data(
table_df,
feature_id='ASV',
type='relative' if relative else 'counts'
)
R_qsip_obj = qsip2.qsip_data(
source_data=R_source_obj,
sample_data=R_sample_obj,
feature_data=R_feature_obj
)

return R_qsip_obj


def _create_filtered_qsip_data(
table: biom.Table,
feature_wads: pd.DataFrame,
metadata: rachis.Metadata,
unlabeled_isotope: str,
labeled_isotope: str,
) -> RS4:
'''
Parameters
----------
table : biom.Table
The feature table.
feature_wads : pd.DataFrame
The per-feature weighted average density values.
metadata : rachis.Metadata
The standardized qSIP2 metadata.
unlabeled_isotope : str
The metadata value corresponding to the unlabeled isotope.
labeled_isotope : str
The metadata value corresponding to the labeled isotope.

Returns
-------
RS4
A qSIP2 data object with the necessary state to be considered to have
gone through filtering.
'''
R_qsip_object = _create_qsip_data(table, metadata, relative=True)

R_qsip_object.slots['filtered_feature_data'] = R_qsip_object.slots[
'feature_data'
]

with (ro.default_converter + pandas2ri.converter).context():
R_qsip_object.slots['filtered_wad_data'] = feature_wads

R_feature_wads = R_qsip_object.slots['filtered_wad_data']
R_feature_wads.rownames = ro.NULL
R_qsip_object.slots['filtered_wad_data'] = R_feature_wads

status = R_qsip_object.slots['status']
status.rx2['filtered'] = True
R_qsip_object.slots['status'] = status

unlabeled_sources, labeled_sources = _get_source_vectors(
table, metadata, unlabeled_isotope, labeled_isotope
)
retained_features = ro.vectors.StrVector(
list(table.ids(axis='observation'))
)
R_filter_results = R_qsip_object.slots['filter_results']
R_filter_results.rx2['unlabeled_source_mat_ids'] = unlabeled_sources
R_filter_results.rx2['labeled_source_mat_ids'] = labeled_sources
R_filter_results.rx2['retained_features'] = retained_features
R_qsip_object.slots['filter_results'] = R_filter_results

return R_qsip_object
Loading