Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion conf/test.config
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ params {
config_profile_name = 'Test profile'
config_profile_description = 'Minimal test dataset to check pipeline function'
input = params.pipelines_testdata_base_path + '/test_data/samplesheets/sample_sheet.csv'
stage = 'align_stitch'
stage = 'int_align_stitch'
nuclei_quantification = true
}
2 changes: 1 addition & 1 deletion conf/test_full.config
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ params {
config_profile_name = 'Full test profile'
config_profile_description = 'Full test dataset to check pipeline function'
input = params.pipelines_testdata_base_path + '/test_data/samplesheets/sample_sheet.csv'
stage = 'align_stitch'
stage = 'int_align_stitch'
nuclei_quantification = true
}
34 changes: 18 additions & 16 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,22 +229,19 @@ Update sample orientation
| --------------------------- | -------------- |
| `model_file` | Model file name. **Default: ''** |
| `gpu` | Cuda visible device index. **Default: 0** |

| | |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `chunk_size` | 'Chunk size in voxels. **Default: [112, 112, 32]** |
| `chunk_overlap` | Overlap between chunks in voxels. **Default: [16, 16, 8]** |
| `pred_threshold` | Prediction threshold. **Default: 0.5** |
| `normalize_intensity` | Whether to normalize intensities using min/max. **Default: true** |
| `resample_chunks` | Whether to resample image to match trained image resolution. Note: increases computation time. **Default: false** |
| `tree_radius` | Pixel radius for removing centroids near each other. **Default: 2** |
| `acquired_img_resolution` | Resolution of acquired images. **Default: [0.75, 0.75, 4]** |
| `trained_img_resolution` | Resolution of images the model was trained on. **Default: [0.75, 0.75, 2.5]** |
| `measure_coloc` | Measure intensity of co-localized channels. **Default: false** |
| `n_channels` | Number of channels. **Default: ''** |
| `use_mask` | Use mask. **Default: false** |
| `mask_file` | Mask file. **Default: ''** |
| `resample_resolution` | Resolution of resampled images. **Default: 25** |
| `chunk_size` | 'Chunk size in voxels. **Default: [112, 112, 32]** |
| `chunk_overlap` | Overlap between chunks in voxels. **Default: [16, 16, 8]** |
| `pred_threshold` | Prediction threshold. **Default: 0.5** |
| `normalize_intensity` | Whether to normalize intensities using min/max. **Default: true** |
| `resample_chunks` | Whether to resample image to match trained image resolution. Note: increases computation time. **Default: false** |
| `tree_radius` | Pixel radius for removing centroids near each other. **Default: 2** |
| `acquired_img_resolution` | Resolution of acquired images. **Default: [0.75, 0.75, 4]** |
| `trained_img_resolution` | Resolution of images the model was trained on. **Default: [0.75, 0.75, 2.5]** |
| `measure_coloc` | Measure intensity of co-localized channels. **Default: false** |
| `n_channels` | Number of channels. **Default: ''** |
| `use_mask` | Use mask. **Default: false** |
| `mask_file` | Mask file. **Default: ''** |
| `resample_resolution` | Resolution of resampled images. **Default: 25** |

## Running the pipeline

Expand Down Expand Up @@ -486,3 +483,8 @@ To determine optimal z correspondence for adjacent tiles, a sample of evenly spa
Finally this results in 4 matrices for a stack representing pairwise horizontal and vertical z displacements and their corresponding weights. To calculate the final z displacement for each tile a minimum spanning tree is used, where displacements are used as vertices and their weights as edges.

**Iterative xy alignment and stitching**
Stitching process proceeds with iterative alignment in the x–y plane. The starting point for the iterative stitching along the stack is chosen near the middle of the volume. At this position, all tiles contained sufficient signal above background, defined as at least one standard deviation above the darkfield intensity. The initial translations for each tile are computed using phase correlation, providing a robust estimate of relative positioning. These translations are then refined using the Scale-Invariant Feature Transform (SIFT) algorithm, which improves accuracy by matching distinctive image features across overlapping regions. Stitching begins from the top-left tile to maintain consistent positioning and prevent cumulative shifts along the z-axis. Overlapping areas between tiles are blended using a sigmoidal function, ensuring smooth transitions and preserving image contrast. To handle cases where slices lack sufficient tissue content, shifts greater than five pixels compared to the previous iteration are replaced with the previous iteration’s values.

### Nuclei quantification

Images are subdivided into patches of 112 × 112 × 32 voxels with an overlap of 16 × 16 × 8 voxels to reduce boundary artifacts. Each patch is then passed to a modified pretrained 3D‑UNet (based on Çiçek et al., 2016 and Isensee et al., 2018) to predict binary nuclei masks. Individual nuclei are obtained via connected‑component analysis, and centroid coordinates are extracted from these components. To prevent duplicate detections introduced by overlapping patches, centroids located closer than half the overlap to a patch border (< 8 pixels in x/y or < 4 pixels in z) are removed under the assumption they will be captured by the neighboring patch. Remaining centroids across all patches are merged using a kd‑tree nearest‑neighbor search, eliminating duplicates within 1.5 voxels of each other to ensure each nucleus is counted exactly once.
31 changes: 22 additions & 9 deletions modules/local/numorphstitch/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,16 @@ process NUMORPHSTITCH {
def prefix = task.ext.prefix ?: "${meta.id}"
def alignment_table = alignment_table_mat.name ? alignment_table_mat : ''
def z_displacement_align = z_displacement_align_mat ? z_displacement_align_mat : ''
def path_table = path_table_mat ? path_table_mat : ''
def thresholds = thresholds_mat ? thresholds_mat : ''
def adj_params = adj_params_mat ? adj_params_mat : ''
def NM_var = NM_variables ? NM_variables : ''
"""

mkdir -p results/variables/
img_dir=\$(readlink -f ${img_directory})
parameter_file=\$(readlink -f ${parameter_file})
results_dir=\$(readlink -f ./results)

if [ -n "${alignment_table}" ]; then
ln -sr ${alignment_table} results/variables/
Expand All @@ -39,18 +46,24 @@ process NUMORPHSTITCH {
ln -sr ${z_displacement_align} results/variables
fi

ln -sr ${thresholds_mat} results/variables
ln -sr ${adj_params_mat} results/variables
ln -sr ${path_table_mat} results/variables
if [ -n "${path_table}" ]; then
ln -sr ${path_table} results/variables
fi

# resolve symlinks and paths
img_dir=\$(readlink -f ${img_directory})
parameter_file=\$(readlink -f ${parameter_file})
results_dir=\$(readlink -f ./results)
NM_variables=\$(readlink -f ${NM_variables})
if [ -n "${thresholds}" ]; then
ln -sr ${thresholds} results/variables
fi

numorph_preprocessing 'input_dir' \$img_dir 'output_dir' \$results_dir 'parameter_file' \$parameter_file 'sample_name' ${meta.id} 'stage' 'stitch' 'NM_variables' \$NM_variables
if [ -n "${adj_params}" ]; then
ln -sr ${adj_params} results/variables
fi

if [ -n "${NM_var}" ]; then
NM_variables=\$(readlink -f ${NM_var})
numorph_preprocessing 'input_dir' \$img_dir 'output_dir' \$results_dir 'parameter_file' \$parameter_file 'sample_name' ${meta.id} 'stage' 'stitch' 'NM_variables' \$NM_variables
fi

numorph_preprocessing 'input_dir' \$img_dir 'output_dir' \$results_dir 'parameter_file' \$parameter_file 'sample_name' ${meta.id} 'stage' 'stitch'

cat <<-END_VERSIONS > versions.yml
"${task.process}":
Expand Down
2 changes: 1 addition & 1 deletion nextflow.config
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ params {

// Input options
input = null
stage = 'align_stitch'
stage = 'int_align_stitch'
ara_registration = false
nuclei_quantification = false
model_file = 'https://zenodo.org/records/16893708/files/075_121_model.h5' // Path to the pre-trained model file
Expand Down
2 changes: 1 addition & 1 deletion nextflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"stage": {
"type": "string",
"description": "The stage of the pipeline to run.",
"help_text": "The stage of the pipeline to run. The pipeline has multiple stages that can be run separately or the complete pipeline can be run. Stages are: `stitch`, `align_stitch`",
"help_text": "The stage of the pipeline to run. The pipeline has multiple stages that can be run separately or the complete pipeline can be run. Stages are: `int_stitch`, `int_align_stitch`, `stitch_only`",
"fa_icon": "fas fa-cogs"
},
"ara_registration": {
Expand Down
38 changes: 35 additions & 3 deletions workflows/lsmquant.nf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ include { UNZIPFILES } from '../modules/nf-core/unzipfiles'
include { UNZIP } from '../modules/nf-core/unzip'
include { STAGEFILES } from '../modules/local/stagefiles'
include { MULTIQC } from '../modules/nf-core/multiqc'
include { NUMORPHSTITCH } from '../modules/local/numorphstitch'

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -86,8 +87,39 @@ workflow LSMQUANT {
ch_samplesheet = ch_unzipped.mix(ch_stagedfiles)


// run single channel preprocessing by stitching only
if (params.stage == 'stitch') {
// run only stitching
if (params.stage == 'stitch_only') {
// create empty channels for intensity adjustment outputs
empty_adj_params_mat = samplesheet.map {[]}
empty_path_table_mat = samplesheet.map {[]}
empty_thresholds_mat = samplesheet.map {[]}
empty_NM_variables = samplesheet.map {[]}
empty_align_table_mat = samplesheet.map {[]}
empty_z_displacement_align_mat = samplesheet.map {[]}

NUMORPHSTITCH (
ch_samplesheet,
empty_align_table_mat,
empty_z_displacement_align_mat,
empty_path_table_mat,
empty_thresholds_mat,
empty_adj_params_mat,
empty_NM_variables
)
ch_versions = ch_versions.mix(NUMORPHSTITCH.out.versions)

def stitched_output = NUMORPHSTITCH.out.stitched

stitched_output
.join(samplesheet)
.map { meta, stitched, raw_img_directory, parameter_file ->
tuple(meta, stitched, parameter_file)
}
.set { stitched_data }
}

// run single channel preprocessing by intensity and stitching
if (params.stage == 'int_stitch') {
NUMORPH_STITCH (ch_samplesheet)
ch_versions = ch_versions.mix(NUMORPH_STITCH.out.versions)

Expand All @@ -102,7 +134,7 @@ workflow LSMQUANT {
}

// run preprocessing with multi channel alignment and stitching
if (params.stage == 'align_stitch') {
if (params.stage == 'int_align_stitch') {
NUMORPH_PREPROCESSING (ch_samplesheet)
ch_versions = ch_versions.mix(NUMORPH_PREPROCESSING.out.versions)

Expand Down
Loading