Skip to content

Migrate and update documentation to Google Doc style#395

Draft
kozlov721 wants to merge 13 commits into
mainfrom
docs/google-docstyle
Draft

Migrate and update documentation to Google Doc style#395
kozlov721 wants to merge 13 commits into
mainfrom
docs/google-docstyle

Conversation

@kozlov721

@kozlov721 kozlov721 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Migrate luxonis_train API documentation to Google-style docstrings and normalize node/attached-module documentation into a consistent, parseable schema for future docs frontend work.

This also preserves node variant information in human-readable nested lists so pydoctor output can later be consumed structurally without relying on raw Python dictionaries or compact encoded strings.

Specification

  • Converted docstrings across luxonis_train to Google style compatible with pydoctor.
  • Updated docformatter/pre-commit configuration for the migrated style.
  • Added consistent class-level sections for concrete nodes and attached modules:
    • Metadata
    • Provenance
    • Variants for nodes
    • optional Prediction format, Target format, and Formula for attached modules
  • Documented node variants with nested lists, including lengthy layer parameters.
  • Added placeholders such as Unknown, Internal, None, and Project license where source/license/task details are unclear or absent.
  • Added a local pre-commit AST check to enforce the documentation schema.

Dependencies & Potential Impact

No runtime dependency changes are expected.

Potential impact is limited to documentation generation, pre-commit behavior, and pydoctor parsing. The new local pre-commit hook may block future changes that add or modify node/attached-module docs without the required schema.

AI Usage

Assisted-by: OpenAI Codex:gpt-5

Submitted code was reviewed by a human: YES

The author is taking the responsibility for the contribution: YES

Summary by CodeRabbit

  • Documentation

    • Standardized and expanded docstrings across the project for clearer Args/Returns/Raises sections and richer module/class descriptions.
  • Tests

    • Added doctest execution with coverage reporting and result uploads.
  • Chores

    • Introduced a documentation schema validator and updated tooling/config to support intersphinx generation and stricter formatting.
    • Enhanced pre-commit hooks and CI to run new docs checks and doctests.

@kozlov721 kozlov721 requested a review from a team as a code owner June 4, 2026 17:09
@kozlov721 kozlov721 requested review from conorsim, klemen1999 and tersekmatija and removed request for a team June 4, 2026 17:09
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f994d529-27b7-4465-bc7d-7f3d366bbec7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds doc-schema validation and CI doctest steps; updates pre-commit and pyproject tooling; adds scripts to build intersphinx inventory and a comprehensive doc-schema checker; standardizes many docstrings across the codebase; small tweaks for variant alias robustness and a test fixture.

Changes

Documentation schema + tooling

Layer / File(s) Summary
All edits: tooling, scripts, docstrings, small fixes
/.github/workflows/ci.yaml, .pre-commit-config.yaml, pyproject.toml, scripts/check_doc_schema.py, scripts/build_luxonis_ml_intersphinx.py, luxonis_train/**
Adds doctest CI job, updates docs job to run pydoctor and build intersphinx inventory, adds pre-commit schema hook and pyproject pydoctor config, introduces intersphinx builder script, implements broad docstring standardization across modules and minor behavioral fixes (variant aliases, test fixture).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • klemen1999
  • tersekmatija
  • conorsim

Poem

In margins trimmed with careful paws, I hop through docs and laws;
New tests now nibble every line, while schema checks align.
Variants learn to skip a stone, no crashes in their zone.
With whiskers twitching, CI sings—
A warren tidy, ready for springs. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/google-docstyle

@github-actions github-actions Bot added the CLI Changes affecting the CLI label Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (2)
scripts/check_doc_schema.py (1)

14-14: 💤 Low value

Consider aliasing the rich print import.

The import shadows Python's builtin print, which can reduce clarity. While this is a common pattern with rich, an aliased import would be more explicit.

♻️ Optional refactor
-from rich import print
+from rich import print as rprint

Then update line 242:

-        print("Documentation schema check failed:", file=sys.stderr)
+        rprint("Documentation schema check failed:", file=sys.stderr)

And line 244:

-            print(f"  - {error}", file=sys.stderr)
+            rprint(f"  - {error}", file=sys.stderr)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_doc_schema.py` at line 14, Change the shadowing import "from
rich import print" to an aliased import (e.g., "from rich import print as
rich_print") and then replace uses of the builtin-shadowing print with
rich_print in the places noted (the call sites currently at the locations
referenced around lines where print is used, e.g., the two occurrences
mentioned). Ensure any other file-scope calls that expect the builtin print
remain using print or are updated to rich_print as appropriate so there is no
ambiguity between builtins and the rich printer.
luxonis_train/nodes/heads/transformer_segmentation_head.py (1)

74-86: ⚡ Quick win

Improve Notes section formatting for readability.

The Notes section on line 84 is a single long sentence listing 5 steps. Consider reformatting it as a numbered list for better readability.

📝 Suggested formatting improvement
-        Notes:
-            Steps: 1. Project each feature map to a channel dim of 256 using 1x1 convolutions. 2. Upsample the feature maps to 1/4 of the image size. 3. Fuse the projected feature maps through summation. 4. Apply segmentation head. 5. Upsample to original input resolution.
+        Notes:
+            Processing steps:
+            
+            1. Project each feature map to a channel dim of 256 using 1x1 convolutions.
+            2. Upsample the feature maps to 1/4 of the image size.
+            3. Fuse the projected feature maps through summation.
+            4. Apply segmentation head.
+            5. Upsample to original input resolution.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@luxonis_train/nodes/heads/transformer_segmentation_head.py` around lines 74 -
86, Update the forward method docstring in transformer_segmentation_head.py
(function forward) to reformat the Notes section into a clear numbered list
instead of a single long sentence: break the five steps into separate numbered
lines (1. project each feature map with 1x1 conv to 256 channels, 2. upsample to
1/4 image size, 3. fuse by summation, 4. apply segmentation head, 5. upsample to
original resolution) while keeping existing descriptions and argument/return
blocks intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yaml:
- Around line 136-183: Update the doctest CI job to harden action usage: replace
unpinned action versions used in steps named "Checkout", "Set up Python",
"Upload doctest results to Codecov", "Upload doctest coverage to Codecov", and
"Upload doctest coverage as artifact" by pinning each uses: to a specific commit
SHA (not just `@vN`) and add persist-credentials: false under the "Checkout" step;
keep the step names ("Checkout", "Set up Python", "Upload doctest results to
Codecov", "Upload doctest coverage to Codecov", "Upload doctest coverage as
artifact") and ensure the pinned SHAs correspond to the commits for
actions/checkout, actions/setup-python, codecov/test-results-action,
codecov/codecov-action, and actions/upload-artifact respectively.

In `@luxonis_train/attached_modules/losses/ctc_loss.py`:
- Line 24: The docstring incorrectly says "raw OCR labels encoded by the
attached node encoder" — update it to state that the target parameter is
provided unencoded (raw OCR labels) and will be encoded inside the forward pass
via self.node.encoder; specifically, revise the description near the target
parameter in the CTC loss docstring to mention that encoding is performed by
target = self.node.encoder(target) inside forward (or in the CTC_Loss.forward
method) so callers know to pass unencoded labels.
- Line 58: The docstring for the parameter "target" incorrectly says it's
"encoded" — update the parameter description in the CTCLoss docstring (the
target param in the class/method that calls node_encoder) to state that targets
are unencoded/raw on entry (e.g., token ids or label indices) and that they are
encoded by the node encoder at runtime (see the call to node_encoder around
where targets are passed/encoded). Ensure the wording matches the class name
CTCLoss and the method that performs encoding so readers know encoding happens
inside the implementation rather than beforehand.

In `@luxonis_train/attached_modules/losses/precision_dfl_detection_loss.py`:
- Around line 180-193: The docstring for decode_bbox is missing a proper Returns
description; update the Returns section of the decode_bbox(anchor_points:
Tensor, pred_dist: Tensor) method to clearly state that it returns the decoded
bounding boxes as a Tensor of shape [batch_size, N, 4] (or the correct dims used
in the implementation), where each 4-tuple is [x_min, y_min, x_max, y_max] or
the coordinate format used, and indicate the Tensor dtype and units if
applicable; ensure the wording matches the function behavior in decode_bbox and
replace the placeholder "Return value" with this precise description.

In `@luxonis_train/attached_modules/losses/precision_dfl_segmentation_loss.py`:
- Around line 195-206: Add a Returns section to the compute_segmentation_loss
docstring that documents the return value: a Tensor containing the total
segmentation loss for the batch (a scalar loss value), optionally noting its
shape (e.g., torch.Tensor scalar) and dtype; update the docstring immediately
after the Args block in the compute_segmentation_loss function in
precision_dfl_segmentation_loss.py so callers and docs clearly know what is
returned.

In
`@luxonis_train/attached_modules/visualizers/instance_seg_keypoint_visualizer.py`:
- Around line 24-29: The forward method in InstanceSegKeypointVisualizer has the
canvas parameters reversed relative to BaseVisualizer.run (which supplies
(target_canvas, prediction_canvas)); update the forward signature (and any
internal variable names) so the first canvas parameter is target_canvas and the
second is prediction_canvas, adjust the docstring to reflect the corrected input
order, and ensure all internal drawing calls that currently use the reversed
names (references to forward, target_canvas, prediction_canvas) are swapped
accordingly so targets are drawn on the target canvas and predictions on the
prediction canvas.

In `@luxonis_train/attached_modules/visualizers/segmentation_visualizer.py`:
- Around line 126-138: The Returns docstring is inaccurate: the function
sometimes returns a single Tensor when target is None but currently documents
tuple[Tensor, Tensor]; update the Returns section to reflect the actual return
type (e.g., Tensor | tuple[Tensor, Tensor] or tuple[Tensor, Optional[Tensor]]),
mentioning that a single Tensor is returned when target is None. Adjust the
return type wording in the docstring near the parameters (prediction_canvas,
target_canvas, predictions, target) so it matches the code path that checks
target is None and returns only the prediction visualization.

In `@luxonis_train/callbacks/gpu_stats_monitor.py`:
- Line 70: The docstring for the parameter intra_step_time in
luxonis_train.callbacks.gpu_stats_monitor (look for the GpuStatsMonitor/__init__
or method docstring that lists intra_step_time) uses "{False}" for the default;
update it to use the same backtick formatting as the other parameters
(``False``) so the default rendering is consistent with lines 71-73.

In `@luxonis_train/core/core.py`:
- Around line 707-709: The docstring for finalize_run uses old Sphinx
`@type/`@param tags; update it to the Google-style "Args:" section to match the
rest of the file—replace the `@type/`@param lines for parameters like status with
an Args: block listing each parameter name, its type, and a one-line
description, and keep the existing return/raises sections consistent with Google
style while leaving function signature and behavior unchanged.

In `@luxonis_train/nodes/heads/bisenet_head.py`:
- Line 24: The docstring license placeholders are inconsistent: class
BisenetHead (class docstring) uses "License: Unknown" while the
BisenetHead.__init__ docstring uses "License: NOT SPECIFIED"; standardize them
to a single placeholder (pick one, e.g., "License: Unknown") by updating the
license line in the BisenetHead class docstring and the BisenetHead.__init__
docstring so both use the exact same text.

In `@luxonis_train/nodes/heads/ddrnet_segmentation_head.py`:
- Around line 64-76: Update the broken URLs in the docstring Notes and See Also
sections of the DDRNet segmentation head (locate the DDRNetSegmentationHead
class or the module-level docstring in ddrnet_segmentation_head.py): remove the
stray spaces inside the GitHub URLs so "https://github.com/Deci-AI/super-
gradients/blob/master/LICENSE.md" becomes
"https://github.com/Deci-AI/super-gradients/blob/master/LICENSE.md" and
"https://github.com/Deci-AI/super-gradients/blob/master/src
/super_gradients/..." becomes
"https://github.com/Deci-AI/super-gradients/blob/master/src/super_gradients/..."
so both links work correctly.

---

Nitpick comments:
In `@luxonis_train/nodes/heads/transformer_segmentation_head.py`:
- Around line 74-86: Update the forward method docstring in
transformer_segmentation_head.py (function forward) to reformat the Notes
section into a clear numbered list instead of a single long sentence: break the
five steps into separate numbered lines (1. project each feature map with 1x1
conv to 256 channels, 2. upsample to 1/4 image size, 3. fuse by summation, 4.
apply segmentation head, 5. upsample to original resolution) while keeping
existing descriptions and argument/return blocks intact.

In `@scripts/check_doc_schema.py`:
- Line 14: Change the shadowing import "from rich import print" to an aliased
import (e.g., "from rich import print as rich_print") and then replace uses of
the builtin-shadowing print with rich_print in the places noted (the call sites
currently at the locations referenced around lines where print is used, e.g.,
the two occurrences mentioned). Ensure any other file-scope calls that expect
the builtin print remain using print or are updated to rich_print as appropriate
so there is no ambiguity between builtins and the rich printer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d95338b2-da9e-4457-8da1-d927ddc28418

📥 Commits

Reviewing files that changed from the base of the PR and between 5930f1c and 53220a0.

📒 Files selected for processing (142)
  • .github/workflows/ci.yaml
  • .pre-commit-config.yaml
  • luxonis_train/__main__.py
  • luxonis_train/assigners/__init__.py
  • luxonis_train/assigners/atss_assigner.py
  • luxonis_train/assigners/tal_assigner.py
  • luxonis_train/assigners/utils.py
  • luxonis_train/attached_modules/base_attached_module.py
  • luxonis_train/attached_modules/losses/adaptive_detection_loss.py
  • luxonis_train/attached_modules/losses/base_loss.py
  • luxonis_train/attached_modules/losses/bce_with_logits.py
  • luxonis_train/attached_modules/losses/cross_entropy.py
  • luxonis_train/attached_modules/losses/ctc_loss.py
  • luxonis_train/attached_modules/losses/efficient_keypoint_bbox_loss.py
  • luxonis_train/attached_modules/losses/embedding_losses.py
  • luxonis_train/attached_modules/losses/fomo_localization_loss.py
  • luxonis_train/attached_modules/losses/ohem_loss.py
  • luxonis_train/attached_modules/losses/precision_dfl_detection_loss.py
  • luxonis_train/attached_modules/losses/precision_dfl_segmentation_loss.py
  • luxonis_train/attached_modules/losses/reconstruction_segmentation_loss.py
  • luxonis_train/attached_modules/losses/sigmoid_focal_loss.py
  • luxonis_train/attached_modules/losses/smooth_bce_with_logits.py
  • luxonis_train/attached_modules/losses/softmax_focal_loss.py
  • luxonis_train/attached_modules/metrics/base_metric.py
  • luxonis_train/attached_modules/metrics/confusion_matrix/confusion_matrix.py
  • luxonis_train/attached_modules/metrics/confusion_matrix/detection_confusion_matrix.py
  • luxonis_train/attached_modules/metrics/confusion_matrix/fomo_confusion_matrix.py
  • luxonis_train/attached_modules/metrics/confusion_matrix/instance_segmentation_confusion_matrix.py
  • luxonis_train/attached_modules/metrics/confusion_matrix/recognition_confusion_matrix.py
  • luxonis_train/attached_modules/metrics/confusion_matrix/utils.py
  • luxonis_train/attached_modules/metrics/dice_coefficient.py
  • luxonis_train/attached_modules/metrics/embedding_metrics.py
  • luxonis_train/attached_modules/metrics/mean_average_precision/mean_average_precision.py
  • luxonis_train/attached_modules/metrics/mean_average_precision/mean_average_precision_bbox.py
  • luxonis_train/attached_modules/metrics/mean_average_precision/mean_average_precision_keypoints.py
  • luxonis_train/attached_modules/metrics/mean_average_precision/mean_average_precision_segmentation.py
  • luxonis_train/attached_modules/metrics/mean_iou.py
  • luxonis_train/attached_modules/metrics/object_keypoint_similarity.py
  • luxonis_train/attached_modules/metrics/ocr_accuracy.py
  • luxonis_train/attached_modules/metrics/torchmetrics.py
  • luxonis_train/attached_modules/metrics/utils.py
  • luxonis_train/attached_modules/visualizers/base_visualizer.py
  • luxonis_train/attached_modules/visualizers/bbox_visualizer.py
  • luxonis_train/attached_modules/visualizers/classification_visualizer.py
  • luxonis_train/attached_modules/visualizers/embeddings_visualizer.py
  • luxonis_train/attached_modules/visualizers/fomo_visualizer.py
  • luxonis_train/attached_modules/visualizers/instance_seg_keypoint_visualizer.py
  • luxonis_train/attached_modules/visualizers/instance_segmentation_visualizer.py
  • luxonis_train/attached_modules/visualizers/keypoint_visualizer.py
  • luxonis_train/attached_modules/visualizers/ocr_visualizer.py
  • luxonis_train/attached_modules/visualizers/segmentation_visualizer.py
  • luxonis_train/attached_modules/visualizers/utils.py
  • luxonis_train/callbacks/archive_on_train_end.py
  • luxonis_train/callbacks/convert_on_train_end.py
  • luxonis_train/callbacks/ema.py
  • luxonis_train/callbacks/export_on_train_end.py
  • luxonis_train/callbacks/gpu_stats_monitor.py
  • luxonis_train/callbacks/graceful_interrupt.py
  • luxonis_train/callbacks/gradcam_visualizer.py
  • luxonis_train/callbacks/luxonis_progress_bar.py
  • luxonis_train/callbacks/metadata_logger.py
  • luxonis_train/callbacks/test_on_train_end.py
  • luxonis_train/callbacks/training_manager.py
  • luxonis_train/callbacks/training_progress_callback.py
  • luxonis_train/callbacks/upload_checkpoint.py
  • luxonis_train/config/config.py
  • luxonis_train/config/predefined_models/base_predefined_model.py
  • luxonis_train/core/core.py
  • luxonis_train/core/utils/annotate_utils.py
  • luxonis_train/core/utils/archive_utils.py
  • luxonis_train/core/utils/export_utils.py
  • luxonis_train/core/utils/infer_utils.py
  • luxonis_train/core/utils/train_utils.py
  • luxonis_train/lightning/luxonis_lightning.py
  • luxonis_train/lightning/utils.py
  • luxonis_train/loaders/base_loader.py
  • luxonis_train/loaders/dummy_loader.py
  • luxonis_train/loaders/luxonis_loader_torch.py
  • luxonis_train/loaders/luxonis_perlin_loader_torch.py
  • luxonis_train/loaders/perlin.py
  • luxonis_train/nodes/backbones/contextspatial.py
  • luxonis_train/nodes/backbones/ddrnet/blocks.py
  • luxonis_train/nodes/backbones/ddrnet/ddrnet.py
  • luxonis_train/nodes/backbones/dinov3/dinov3.py
  • luxonis_train/nodes/backbones/efficientnet.py
  • luxonis_train/nodes/backbones/efficientrep/efficientrep.py
  • luxonis_train/nodes/backbones/efficientvit/blocks.py
  • luxonis_train/nodes/backbones/efficientvit/efficientvit.py
  • luxonis_train/nodes/backbones/ghostfacenet/ghostfacenet.py
  • luxonis_train/nodes/backbones/micronet/blocks.py
  • luxonis_train/nodes/backbones/micronet/micronet.py
  • luxonis_train/nodes/backbones/mobilenetv2.py
  • luxonis_train/nodes/backbones/mobileone/mobileone.py
  • luxonis_train/nodes/backbones/pplcnet_v3/pplcnet_v3.py
  • luxonis_train/nodes/backbones/recsubnet/recsubnet.py
  • luxonis_train/nodes/backbones/repvgg/repvgg.py
  • luxonis_train/nodes/backbones/resnet.py
  • luxonis_train/nodes/backbones/rexnetv1.py
  • luxonis_train/nodes/base_node.py
  • luxonis_train/nodes/blocks/blocks.py
  • luxonis_train/nodes/blocks/reparametrizable.py
  • luxonis_train/nodes/blocks/resnet.py
  • luxonis_train/nodes/blocks/unet.py
  • luxonis_train/nodes/blocks/utils.py
  • luxonis_train/nodes/heads/base_detection_head.py
  • luxonis_train/nodes/heads/base_head.py
  • luxonis_train/nodes/heads/bisenet_head.py
  • luxonis_train/nodes/heads/classification_head.py
  • luxonis_train/nodes/heads/ddrnet_segmentation_head.py
  • luxonis_train/nodes/heads/discsubnet_head/discsubnet_head.py
  • luxonis_train/nodes/heads/efficient_bbox_head.py
  • luxonis_train/nodes/heads/efficient_keypoint_bbox_head.py
  • luxonis_train/nodes/heads/fomo_head.py
  • luxonis_train/nodes/heads/ghostfacenet_head.py
  • luxonis_train/nodes/heads/ocr_ctc_head.py
  • luxonis_train/nodes/heads/precision_bbox_head.py
  • luxonis_train/nodes/heads/precision_seg_bbox_head.py
  • luxonis_train/nodes/heads/segmentation_head.py
  • luxonis_train/nodes/heads/transformer_classification_head.py
  • luxonis_train/nodes/heads/transformer_segmentation_head.py
  • luxonis_train/nodes/necks/reppan_neck/blocks.py
  • luxonis_train/nodes/necks/reppan_neck/reppan_neck.py
  • luxonis_train/nodes/necks/svtr_neck/svtr_neck.py
  • luxonis_train/registry.py
  • luxonis_train/strategies/triple_lr_sgd.py
  • luxonis_train/tasks.py
  • luxonis_train/typing.py
  • luxonis_train/utils/annotation.py
  • luxonis_train/utils/boundingbox.py
  • luxonis_train/utils/dataset_metadata.py
  • luxonis_train/utils/general.py
  • luxonis_train/utils/keypoints.py
  • luxonis_train/utils/ocr.py
  • luxonis_train/utils/tracker.py
  • luxonis_train/variants.py
  • pyproject.toml
  • scripts/check_doc_schema.py
  • tests/integration/test_bump_opset_version.py
  • tests/integration/test_export_unique_identifiers.py
  • tests/integration/test_overfit_batches.py
  • tests/integration/test_overfit_convergence.py
  • tests/unittests/test_callbacks/test_ema.py

Comment thread .github/workflows/ci.yaml
Comment thread luxonis_train/attached_modules/losses/ctc_loss.py
Comment thread luxonis_train/attached_modules/losses/ctc_loss.py Outdated
Comment thread luxonis_train/callbacks/gpu_stats_monitor.py Outdated
Comment thread luxonis_train/core/core.py Outdated
Comment thread luxonis_train/nodes/heads/bisenet_head.py
Comment thread luxonis_train/nodes/heads/ddrnet_segmentation_head.py
@kozlov721 kozlov721 marked this pull request as draft June 5, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLI Changes affecting the CLI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant