Skip to content

Commit f247305

Browse files
committed
Refactor template to xgboost extension
1 parent bed7c08 commit f247305

16 files changed

Lines changed: 1410 additions & 478 deletions

README.md

Lines changed: 99 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,205 +1,170 @@
1-
# QuPath extension template
1+
# QuPath XGBoost Classifier Extension
22

3-
This repo contains a template and instructions to help create a new extension for [QuPath](https://qupath.github.io).
3+
A [QuPath](https://qupath.github.io) extension that brings [XGBoost](https://xgboost.readthedocs.io/) gradient-boosted tree classification to object classification workflows.
4+
Train a model from point or area annotations across multiple project images, then run inference across any selection of images — all from a GUI, no scripting required.
45

5-
It already contains two minimal extensions - one using Java, one using Groovy - so the first task is to make sure that they work.
6-
Then, it's a matter of customizing the code to make it more useful.
6+
---
77

8-
> **Update!**
9-
> For QuPath v0.6.0 this repo switched to use Kotlin DSL for Gradle build files -
10-
> and also to use the [QuPath Gradle Plugin](https://github.com/qupath/qupath-gradle-plugin).
11-
>
12-
> The outcome is that the build files are _much_ simpler.
8+
## How it works
139

10+
### Training
1411

15-
## Build the extension
12+
The extension looks for **annotated regions** (point or area ROIs) on top of existing detections to build training data.
1613

17-
Building the extension with Gradle should be pretty easy - you don't even need to install Gradle separately, because the
18-
[Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) will take care of that.
14+
1. For each selected project entry it reads the object hierarchy.
15+
2. Every detection that falls inside an annotation whose class is one of the selected output classes becomes a training sample. If a detection is covered by annotations of more than one class, it is discarded to avoid label ambiguity.
16+
3. Measurement values for the selected features are extracted from each training detection into a feature matrix.
17+
4. An initial XGBoost model is trained on the full feature set to rank features by **gain importance**.
18+
5. A final model is retrained using only the top-N selected features (configurable; 0 = keep all).
19+
6. Feature names and class names are embedded directly in the saved model JSON so no external metadata file is needed at inference time.
1920

20-
Open a command prompt, navigate to where the code lives, and use
21-
```bash
22-
gradlew build
23-
```
21+
### Inference
2422

25-
The built extension should be found inside `build/libs`.
26-
You can drag this onto QuPath to install it.
27-
You'll be prompted to create a user directory if you don't already have one.
23+
1. The saved model JSON is loaded; feature names and class names are read from its metadata.
24+
2. For each selected project entry, measurements are extracted from all detections using the embedded feature list.
25+
3. Each detection is assigned the class with the highest predicted probability.
26+
4. Image data is saved back to the project.
2827

29-
The minimal extension here doesn't do much, but it should at least install a new command under the 'Extensions' menu in
30-
QuPath.
28+
---
3129

32-
> In case your extension contains external dependencies beyond what QuPath already includes, you can create a
33-
> [single jar file](https://imperceptiblethoughts.com/shadow/introduction/#benefits-of-shadow) that bundles these along
34-
> with your extension by using
35-
> ```bash
36-
> gradlew shadowJar
37-
> ```
38-
> If you don't do that, you'll need to drag *all* the extra dependences onto QuPath to install them as well.
30+
## Requirements
3931

32+
- **QuPath** 0.7.0 or later
33+
- **Java** 25
34+
- Detections must already exist in the project images (e.g. from cell detection or tile classification)
35+
- Training images must have point or area annotations placed on top of detections, with a `PathClass` set to one of the desired output classes
4036

41-
## Configure the extension
37+
---
4238

43-
Edit `settings.gradle.kts` to specify which version of QuPath your extension should be compatible with, e.g.
39+
## Build
4440

45-
```kotlin
46-
qupath {
47-
version = "0.6.0"
48-
}
49-
```
41+
### Standalone shadow jar (recommended for distribution)
5042

51-
Edit `build.gradle.kts` to specify the details of your extension
52-
53-
```kotlin
54-
qupathExtension {
55-
name = "qupath-extension-template"
56-
group = "io.github.qupath"
57-
version = "0.1.0-SNAPSHOT"
58-
description = "A simple QuPath extension"
59-
automaticModule = "io.github.qupath.extension.template"
60-
}
43+
```bash
44+
gradlew shadowJar
6145
```
6246

47+
The output is in `build/libs/` with `-all` in the filename.
48+
Drag this onto QuPath to install it — you will be prompted to create a user directory if you don't have one yet.
6349

64-
## Run QuPath + the extension
50+
### During development (composite build)
6551

66-
During development, your probably want to run QuPath easily with your extension installed for debugging.
52+
Create a file called `include-extra` in the **root of the QuPath source directory** (not this repo):
6753

68-
### 0. Make sure you have Java installed
69-
You'll need to install Java first.
70-
71-
At the time of writing, we use a Java 21 JDK downloaded from https://adoptium.net/
72-
73-
> Java 21 is a 'Long Term Support' release - which is why we use it instead of the very latest version.
74-
75-
### 1. Get QuPath's source code
76-
You can find instructions at https://qupath.readthedocs.io/en/stable/docs/reference/building.html
77-
78-
### 2. Create an `include-extra` file
79-
Create a file called `include-extra` in the root directory of the QuPath source code (*not* the extension code!).
80-
81-
Set the contents of this file to:
8254
```
8355
[includeBuild]
84-
/path/to/your/extension
56+
../qupath-extension-xgboost
8557
8658
[dependencies]
87-
extension-group:extension-name
59+
io.github.qupath:qupath-extension-xgboost
8860
```
89-
replacing the default lines where needed.
9061

91-
For example, to build the extension with the names given above you'd use
92-
```
93-
[includeBuild]
94-
../qupath-extension-template
95-
96-
[dependencies]
97-
io.github.qupath:qupath-extension-template
98-
```
62+
Then run QuPath from the QuPath source directory:
9963

100-
### 3. Run QuPath
101-
Run QuPath from the command line using
102-
```
64+
```bash
10365
gradlew run
10466
```
105-
If all goes well, QuPath should launch and you can check the *Extensions* mention to confirm the extension is installed.
10667

68+
When using composite build, add `implementation` alongside `shadow` for XGBoost4J in `build.gradle.kts` so the dependency is available on the runtime classpath:
10769

108-
## Set up in an IDE (optional)
109-
110-
During development, things are likely to be much easier if you work within an IDE.
111-
112-
QuPath itself is developed using IntelliJ, and you can import the extension template there.
113-
114-
The setup process is as above, and you'll need a a [Run configuration](https://www.jetbrains.com/help/idea/run-debug-configuration.html)
115-
to call `gradlew run`.
70+
```kotlin
71+
implementation("ml.dmlc:xgboost4j_2.13:3.2.0")
72+
shadow("ml.dmlc:xgboost4j_2.13:3.2.0")
73+
```
11674

75+
---
11776

118-
## Customize the extension
77+
## Usage
11978

120-
Now you're ready for the creative part.
79+
After installation the extension adds two commands under **Extensions → XGBoost Classifier**.
12180

122-
You can develop the extension using either Java or Groovy - the template includes examples of both.
81+
---
12382

124-
### Create the extension Java or Groovy file(s)
83+
### Train XGBoost Classifier
12584

126-
For the extension to work, you need to create at least one file that extends `qupath.lib.gui.extensions.QuPathExtension`.
85+
Opens the training dialog.
12786

128-
There are two examples in the template, in two languages:
129-
* **Java:** `qupath.ext.template.DemoExtension.java`.
130-
* **Groovy:** `qupath.ext.template.DemoGroovyExtension.java`.
87+
#### Project Entries
13188

132-
You can pick the one that corresponds to the language you want to use, and delete the other.
89+
Select which project images to use for training. Only images with annotations on top of detections will contribute training data.
13390

134-
Then take your chosen file and rename it, edit it, move it to another package... basically, make it your own.
91+
- **Filter field** — type to search image names
92+
- **Available / Selected / Both** — toggle which side of the list the filter applies to
93+
- **With data file only** — hide images that have no saved data file
13594

136-
> Please **don't neglect this step!**
137-
> If you do, there's a chance of multiple extensions being created with the same class names... and causing confusion later.
95+
#### Features
13896

139-
### Update the `META-INF/services` file
97+
Select which object measurements to offer to the model. Use the **↺ Refresh** button to discover available measurements from the currently open image.
14098

141-
For QuPath to *find* the extension later, the full class name needs to be available in `resources/META-INFO/services/qupath.lib.gui.extensions.QuPathExtensions`.
99+
- **Filter field** — type to narrow the list; toggle **Available / Selected / Both** to target either side
100+
- All features moved to the right will be used as the initial feature set; the model then ranks and optionally reduces them further by gain importance
142101

143-
So remember to edit that file to include the class name that you actually used for your extension.
102+
#### Output Classes
144103

145-
### Specify your license
104+
Select which annotation classes define the training labels. At least two classes are required.
146105

147-
Add a license file to your GitHub repo so that others know what they can and can't do with your extension.
106+
- **Filter field** — works the same as the features filter
148107

149-
This should be compatible with QuPath's license -- see https://github.com/qupath/qupath
108+
#### Parameters
150109

151-
## Repository configuration
110+
| Parameter | Default | Description |
111+
|-----------|---------|-------------|
112+
| Model name | `my_xgboost` | Base filename; saved as `<name>.json` |
113+
| Output dir | Project `classifiers/object_classifiers/` | Directory for the saved model |
114+
| Rounds | 150 | Number of boosting rounds |
115+
| Max depth | 5 | Maximum depth of each tree |
116+
| Learning rate (η) | 0.2 | Step size shrinkage; lower = more conservative |
117+
| Subsample | 0.8 | Fraction of training rows sampled per round |
118+
| Top-N features | 10 | Keep only the N highest-gain features after the first pass; set to 0 to keep all |
152119

153-
### Easy install
120+
The **Log** panel streams progress during training, including per-image object counts, the top-20 feature importances, and the final saved model path.
154121

155-
If you follow some conventions in naming your extension and making releases, then other QuPath users will find it easy to automatically
156-
install and update your extension!
122+
---
157123

158-
First, we suggest you name your extension `qupath-extension-[something]`, and keep it in its own repository (named the same as the extension),
159-
separate from other projects.
124+
### Run XGBoost Classifier
160125

161-
Next, when you want to publish a new version of your extension, use the `github_release.yml` workflow included in this repository.
126+
Opens the inference dialog.
162127

163-
To do so, you'd need to navigate to `Actions -> Make draft release -> Run workflow -> Run workflow` as shown in the following screenshot:
128+
#### Project Entries
164129

165-
![Screenshot from 2024-03-14 18-44-42](https://github.com/alanocallaghan/qupath-extension-template/assets/10779688/4712a209-eda7-4f80-8bed-bbab20e4f50a)
130+
Same filter controls as the training dialog. Select the images you want to classify.
166131

167-
This will automatically build the extension, and create a draft release containing the extension jar (and its associated sources and javadoc).
168-
You can then navigate to `Releases` and fill out information about the release --- the version, any significant changes, etc.
169-
Once published, users will be able to automatically install the extension as described here:
170-
https://qupath.readthedocs.io/en/0.5/docs/intro/extensions.html#installing-extensions
132+
#### Model
171133

172-
### Catalogs
134+
Pick the `.json` model file produced by the training step.
135+
The file chooser defaults to the project's `classifiers/object_classifiers/` folder.
173136

174-
QuPath's extension manager can easily install an extension if it is referenced in a **catalog**.
175-
A catalog is a JSON file hosted on a GitHub repository containing information about extensions, making it possible to easily manage them from QuPath.
137+
The **Log** panel reports the number of detections classified per image.
176138

177-
To create a catalog, follow the [extension catalog model documentation](https://qupath.github.io/extension-catalog-model/).
178-
You will need to create a JSON file containing specific information about your extension and host it on a dedicated GitHub repository.
179-
Once the catalog is created, any user will be able to easily install your catalog by:
139+
---
180140

181-
* Opening QuPath's extension manager by clicking on `Extensions` -> `Manage extensions` in QuPath.
182-
* Adding the URL to your catalog by clicking on `Manage extension catalogs` -> `Add` in the extension manager.
183-
* Clicking on the `+` symbol next to your extension in the extension manager.
141+
## Model file format
184142

185-
QuPath will then make it easy to manage your extension and automatically inform users when an update is available.
143+
Models are saved as XGBoost native JSON. Feature names and class names are embedded as model attributes:
186144

187-
### Replace this readme
145+
- `feature_names` — the ordered list of measurement names the model expects
146+
- `class_names` — comma-separated list of output class names in label index order
188147

189-
Don't forget to replace the contents of this readme with your own!
148+
This makes a model file fully self-contained — it can be shared across projects or used in scripts as long as the same measurements exist in the target images.
190149

150+
---
191151

192-
## Getting help
152+
## Tips
193153

194-
For questions about QuPath and/or creating new extensions, please use the forum at https://forum.image.sc/tag/qupath
154+
- **More training data is better.** Collect annotations across multiple images to cover the range of imaging conditions in your dataset.
155+
- **Start with Top-N = 0** to see the full importance ranking in the log, then re-train with a smaller Top-N to reduce overfitting on noisy measurements.
156+
- **Conflicting annotations** (a detection covered by two different classes) are silently discarded. If you see unexpectedly low training counts, check for overlapping annotations of different classes.
157+
- The final model is trained from scratch on the reduced feature set, so Rounds and Max depth apply to both the ranking pass and the final pass.
195158

196-
------
159+
---
197160

198161
## License
199162

200-
This is just a template, you're free to use it however you like.
201-
You can treat the contents of *this repository only* as being under [the Unlicense](https://unlicense.org) (except for the Gradle wrapper, which has its own license included).
163+
GPL v3 — consistent with QuPath's own license.
164+
165+
---
202166

203-
If you use it to create a new QuPath extension, I'd strongly encourage you to select a suitable open-source license for the extension.
167+
## Acknowledgements
204168

205-
Note that *QuPath itself* is available under the GPL, so you do have to abide by those terms: see https://github.com/qupath/qupath for more.
169+
- [QuPath](https://github.com/qupath/qupath) — Pete Bankhead and contributors
170+
- [XGBoost4J](https://xgboost.readthedocs.io/en/stable/jvm/index.html) — DMLC / XGBoost contributors

build.gradle.kts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
plugins {
2-
// Support writing the extension in Groovy (remove this if you don't want to)
3-
groovy
42
// To optionally create a shadow/fat jar that bundle up any non-core dependencies
53
id("com.gradleup.shadow") version "8.3.5"
64
// QuPath Gradle extension convention plugin
75
id("qupath-conventions")
86
}
97

10-
// TODO: Configure your extension here (please change the defaults!)
8+
119
qupathExtension {
12-
name = "qupath-extension-template"
10+
name = "qupath-extension-xgboost"
1311
group = "io.github.qupath"
1412
version = "0.1.0-SNAPSHOT"
15-
description = "A simple QuPath extension"
16-
automaticModule = "io.github.qupath.extension.template"
13+
description = "XGBoost object classifier extension for QuPath"
14+
automaticModule = "io.github.qupath.extension.xgboost"
1715
}
1816

19-
// TODO: Define your dependencies here
2017
dependencies {
2118

2219
// Main dependencies for most QuPath extensions
2320
shadow(libs.bundles.qupath)
2421
shadow(libs.bundles.logging)
2522
shadow(libs.qupath.fxtras)
2623

27-
// If you aren't using Groovy, this can be removed
28-
shadow(libs.bundles.groovy)
24+
// Available at runtime for both composite-build AND shadow jar
25+
implementation("ml.dmlc:xgboost4j_2.13:3.2.0")
26+
shadow("ml.dmlc:xgboost4j_2.13:3.2.0")
2927

3028
// For testing
3129
testImplementation(libs.bundles.qupath)

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pluginManagement {
99

1010
// TODO: Specify which version of QuPath the extension is targeting here
1111
qupath {
12-
version = "0.6.0"
12+
version = "0.7.0"
1313
}
1414

1515
// Apply QuPath Gradle settings plugin to handle configuration

0 commit comments

Comments
 (0)