Skip to content

Commit

Permalink
Merge pull request #22 from ksugar/dev
Browse files Browse the repository at this point in the history
0.6.0 -> 0.7.0
  • Loading branch information
ksugar authored Sep 3, 2024
2 parents 9d40bec + deff51b commit 7848e9e
Show file tree
Hide file tree
Showing 15 changed files with 1,425 additions and 20 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# QuPath extension SAM

### New release v0.7: SAM2-based 2D+T tracking and 3D segmentation are supported now!
<img src="https://github.com/ksugar/qupath-extension-sam/releases/download/assets/sam2-sequence-demo.gif" width="768">

<img src="https://github.com/ksugar/samapi/releases/download/assets/qupath-samapi.gif" width="768">

<img src="https://github.com/ksugar/samapi/releases/download/assets/qupath-sam-multipoint-live.gif" width="768">
Expand All @@ -12,7 +15,7 @@ This is a part of the following paper. Please [cite](#citation) it when you use

## Install

Drag and drop [the extension file](https://github.com/ksugar/qupath-extension-sam/releases/download/v0.6.0/qupath-extension-sam-0.6.0.jar) to [QuPath](https://qupath.github.io) and restart it.
Drag and drop [the extension file](https://github.com/ksugar/qupath-extension-sam/releases/download/v0.7.0/qupath-extension-sam-0.7.0.jar) to [QuPath](https://qupath.github.io) and restart it.

Since QuPath v0.5.0, you can install the extension from the extension manager dialog by specifying `Owner` and `Repository` as shown below.

Expand All @@ -30,7 +33,7 @@ To update the `qupath-extension-sam`, follow the following instructions.

1. Open extensions directory from `Extensions` > `Installed extensions` > `Open extensions directory`
![](https://github.com/ksugar/qupath-extension-sam/releases/download/assets/open-extensions-directory.png)
2. Replace `qupath-extension-sam-x.y.z.jar` with [the latest version of the extension file](https://github.com/ksugar/qupath-extension-sam/releases/download/v0.6.0/qupath-extension-sam-0.6.0.jar). If you are using QuPath v0.4.x, you need to install [the extension file for QuPath v0.4.x](https://github.com/ksugar/qupath-extension-sam/releases/download/v0.4.1/qupath-extension-sam-0.4.1.jar), which is now deprecated.
2. Replace `qupath-extension-sam-x.y.z.jar` with [the latest version of the extension file](https://github.com/ksugar/qupath-extension-sam/releases/download/v0.7.0/qupath-extension-sam-0.7.0.jar). If you are using QuPath v0.4.x, you need to install [the extension file for QuPath v0.4.x](https://github.com/ksugar/qupath-extension-sam/releases/download/v0.4.1/qupath-extension-sam-0.4.1.jar), which is now deprecated.
3. Restart QuPath application.

Please note that you need to also update the [samapi](https://github.com/ksugar/samapi/tree/v0.5.0) server.
Expand Down Expand Up @@ -173,8 +176,11 @@ If you select a class in `Auto set` in the Annotations tab, it is used for a new

## Updates

### v0.7.0
- Support 2D+T tracking and 3D segmentation with [SAM2](https://ai.meta.com/sam2/) models, available with the [samapi](https://github.com/ksugar/samapi) server `v0.6.0` and above.

### v0.6.0
- Support [SAM2](https://ai.meta.com/sam2/) models. The SAM2 models are available from the [samapi](https://github.com/ksugar/samapi) server `v0.5.0` and above.
- Support [SAM2](https://ai.meta.com/sam2/) models, available with the [samapi](https://github.com/ksugar/samapi) server `v0.5.0` and above.
- Use the current view for the encoder input in the rectangle mode.

### v0.5.0
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
ext.moduleName = 'org.elephant.sam.qupath'

// TODO: Define the extension version & provide a short description
version = "0.6.0"
version = "0.7.0"
description = 'QuPath extension for Segment Anything Model (SAM)'

// TODO: Specify the QuPath version, compatible with the extension.
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/elephant/sam/SAMExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public GitHubRepo getRepository() {

@Override
public Version getQuPathVersion() {
return Version.parse("0.6.0");
return Version.parse("0.7.0");
}

}
79 changes: 77 additions & 2 deletions src/main/java/org/elephant/sam/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import com.google.gson.JsonObject;

import org.elephant.sam.entities.SAMOutput;
import org.elephant.sam.http.MultipartBodyBuilder;
import org.locationtech.jts.geom.Coordinate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import qupath.lib.awt.common.AwtTools;
import qupath.lib.common.GeneralTools;
import qupath.lib.geom.Point2;
import qupath.lib.gui.images.servers.RenderedImageServer;
Expand All @@ -18,6 +21,7 @@
import qupath.lib.objects.PathObjectTools;
import qupath.lib.objects.classes.PathClass;
import qupath.lib.regions.ImagePlane;
import qupath.lib.regions.ImageRegion;
import qupath.lib.regions.RegionRequest;
import qupath.lib.roi.RectangleROI;
import qupath.lib.roi.interfaces.ROI;
Expand Down Expand Up @@ -92,10 +96,14 @@ private static PathObject parsePathObject(Gson gson, JsonElement element) {
if (jsonObj.has("properties")) {
// Set quality from properties if we can
JsonObject properties = jsonObj.get("properties").getAsJsonObject();
JsonElement quality = properties.has("quality") ? properties.get("quality") : null;
JsonElement quality = properties.get("quality");
if (quality != null && quality.isJsonPrimitive()) {
pathObject.getMeasurementList().put(SAM_QUALITY_MEASUREMENT, quality.getAsDouble());
}
JsonElement objectId = properties.get("object_idx");
if (objectId != null && objectId.isJsonPrimitive()) {
pathObject.setPathClass(PathClass.getInstance(objectId.getAsString()));
}
}
return pathObject;
}
Expand Down Expand Up @@ -154,7 +162,11 @@ public static PathObject applyTransformAndClassification(PathObject pathObject,
}
if (plane != null && !Objects.equals(plane, pathObject.getROI().getImagePlane()))
pathObject = PathObjectTools.updatePlane(pathObject, plane, true, false);
pathObject.setPathClass(pathClass);
if (pathClass == PathClass.NULL_CLASS) {
pathObject.resetPathClass();
} else if (pathClass != null) {
pathObject.setPathClass(pathClass);
}
return pathObject;
}

Expand Down Expand Up @@ -214,6 +226,43 @@ public static ImageServer<BufferedImage> createRenderedServer(QuPathViewer viewe
.build();
}

/**
* Get a region request for the viewer, intersected with the rendered server.
*
* @param viewer
* @param renderedServer
* @return the region request
*/
public static RegionRequest getViewerRegion(QuPathViewer viewer, ImageServer<BufferedImage> renderedServer) {
return getViewerRegion(viewer, renderedServer, viewer.getZPosition(), viewer.getTPosition());
}

/**
* Get a region request for the viewer, intersected with the rendered server.
*
* @param viewer
* @param renderedServer
* @param z
* @param t
* @return the region request
*/
public static RegionRequest getViewerRegion(QuPathViewer viewer, ImageServer<BufferedImage> renderedServer, int z,
int t) {
if (renderedServer == null) {
try {
renderedServer = Utils.createRenderedServer(viewer);
} catch (IOException e) {
logger.error("Failed to create rendered server", e);
}
}
// Find the region and downsample currently used within the viewer
ImageRegion region = AwtTools.getImageRegion(viewer.getDisplayedRegionShape(), z, t);
RegionRequest viewerRegion = RegionRequest.createInstance(renderedServer.getPath(),
viewer.getDownsampleFactor(), region);
viewerRegion = viewerRegion.intersect2D(0, 0, renderedServer.getWidth(), renderedServer.getHeight());
return viewerRegion;
}

/**
* Encode a BufferedImage as a base64-encoded PNG.
*
Expand Down Expand Up @@ -333,4 +382,30 @@ public static boolean isPotentialPromptObject(PathObject pathObject) {
return pathObject.isAnnotation() && pathObject.hasROI() && !pathObject.getROI().isEmpty();
}

/**
* Converts a BufferedImage to a JPEG byte array.
*
* @param image
* The BufferedImage to convert.
* @return A byte array containing the JPEG data.
* @throws IOException
* If an error occurs during writing.
*/
public static byte[] bufferedImageToJpegBytes(BufferedImage image) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(image, "jpg", baos);
return baos.toByteArray();
}
}

public static byte[] createImageUploadMultipartBody(String boundary, String dirname, String filename,
BufferedImage image)
throws IOException {
byte[] imageBytes = bufferedImageToJpegBytes(image);
final MultipartBodyBuilder builder = new MultipartBodyBuilder(boundary);
builder.addFormField("dirname", dirname);
builder.addFilePart("file", filename, "image/jpeg", imageBytes);
return builder.build();
}

}
Loading

0 comments on commit 7848e9e

Please sign in to comment.