Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0a94a89
Add API for setting custom window dimensions
kephale Aug 25, 2025
8da7fc0
Add static factory method for creating SciView with custom dimensions
kephale Aug 25, 2025
031ac58
Add demo for custom window sizing API
kephale Aug 25, 2025
c25ab12
Add unit test for window sizing API
kephale Aug 25, 2025
e48be69
Update README with documentation for window sizing API
kephale Aug 25, 2025
2fb6b36
Update scijava-common version to 2.99.0 to fix CI build
kephale Aug 25, 2025
55b5451
Fix code review issues: Add test documentation and use specific excep…
kephale Aug 25, 2025
8aea71f
Add SciView.create(width, height) overload for custom window dimensions
kephale Aug 25, 2025
d3e0615
SciView: remove duplicated create method
smlpt Aug 26, 2025
49f5772
build: revert to existing scijava 2.98
smlpt Aug 26, 2025
890048b
Fix menu weights for CustomWindowSizeDemo
smlpt Aug 26, 2025
667837f
build: fix missing SL4J provider
smlpt Aug 26, 2025
37463ba
removed WindowSizingTest because of missing test pipeline in sciview …
smlpt Aug 26, 2025
a39245a
Revert "build: fix missing SL4J provider"
smlpt Sep 2, 2025
1c57564
Merge branch 'main' into add-window-size-api
smlpt Oct 28, 2025
3a2fea9
build: update to kotlin 2.2.10
smlpt Oct 28, 2025
15527d8
Merge branch 'main' into add-window-size-api
smlpt Dec 16, 2025
da55899
bump to pom-scijava 43 to fix missing slf4j deps
smlpt Dec 18, 2025
1fa5f89
SwingMainWindow: add component listener that updates sciview's window…
smlpt Dec 18, 2025
f31ff72
SciView: add window resizing functionality when toggling VR
smlpt Dec 19, 2025
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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ Should you experience any issues, [please try the latest development version](ht

![Overview of sciview's user interface](https://gblobscdn.gitbook.com/assets%2F-LqBCy3SBefXis0YnrcI%2F-MK5WLQvMLIvw2GF6Rn2%2F-MK5WMGzmSavDTwlGro2%2Fmain-cheatsheet.jpg?alt=media&token=70c82549-e939-4752-af12-1756492a5f01)

## API Features

### Custom Window Dimensions

SciView now supports setting custom window dimensions via API, which is essential for VR headsets that require specific resolutions:

```kotlin
// Create SciView with custom dimensions
val sciview = SciView.create(1920, 1080)

// Or resize an existing instance
sciview.setWindowSize(2880, 1700) // Example: Oculus Quest 2 resolution

// Query current dimensions
val (width, height) = sciview.getWindowSize()
```

This feature is particularly useful for:
- VR headset integration requiring exact resolutions
- Multi-monitor setups
- Creating screenshots or recordings at specific resolutions
- Kiosk or presentation modes

## Developers

[Kyle Harrington](https://kyleharrington.com), University of Idaho & [Ulrik Guenther](https://ulrik.is/writing), MPI-CBG
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jvmTarget=21
#useLocalScenery=true
kotlinVersion=2.2.10
dokkaVersion=1.9.20
scijavaParentPOMVersion=40.0.0
scijavaParentPOMVersion=43.0.0
version=0.4.1-SNAPSHOT

# update site configuration
Expand Down
103 changes: 94 additions & 9 deletions src/main/kotlin/sc/iview/SciView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ import graphics.scenery.controls.TrackerInput
import graphics.scenery.primitives.*
import graphics.scenery.proteins.Protein
import graphics.scenery.proteins.RibbonDiagram
import graphics.scenery.utils.ExtractsNatives
import graphics.scenery.utils.ExtractsNatives.Companion.getPlatform
import graphics.scenery.utils.LogbackUtils
import graphics.scenery.utils.SceneryPanel
import graphics.scenery.utils.Statistics
Expand Down Expand Up @@ -84,8 +82,8 @@ import net.imglib2.type.numeric.RealType
import net.imglib2.type.numeric.integer.UnsignedByteType
import net.imglib2.view.Views
import org.joml.Quaternionf
import org.joml.Vector2f
import org.joml.Vector3f
import org.joml.Vector4f
import org.scijava.Context
import org.scijava.`object`.ObjectService
import org.scijava.display.Display
Expand All @@ -100,8 +98,6 @@ import org.scijava.service.SciJavaService
import org.scijava.thread.ThreadService
import org.scijava.util.ColorRGB
import org.scijava.util.Colors
import org.scijava.util.VersionUtils
import sc.iview.commands.demo.animation.ParticleDemo
import sc.iview.commands.edit.InspectorInteractiveCommand
import sc.iview.event.NodeActivatedEvent
import sc.iview.event.NodeAddedEvent
Expand Down Expand Up @@ -131,12 +127,10 @@ import java.util.function.Predicate
import java.util.stream.Collectors
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.LinkedHashMap
import kotlin.concurrent.thread
import javax.swing.JOptionPane
import kotlin.math.cos
import kotlin.math.sin
import kotlin.system.measureTimeMillis

/**
* Main SciView class.
Expand Down Expand Up @@ -1689,22 +1683,28 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
}

private var originalFOV = camera?.fov
private var originalWinSize = getWindowSize()

/**
* Enable VR rendering
* Enable or disable VR rendering. Automatically stores the original controls and FOV and restores them
* after VR is toggled off again.
* @param resizeWindow changes the window resolution to match the stereo rendering of the selected headset.
* @param resolutionScale Factor that allows changing the VR resolution
*/
fun toggleVRRendering() {
fun toggleVRRendering(resizeWindow: Boolean = true, resolutionScale: Float = 1f) {
var renderer = renderer ?: return

// Save camera's original settings if we switch from 2D to VR
if (!vrActive) {
originalFOV = camera?.fov
originalWinSize = getWindowSize()
}

// If turning off VR, store the controls state before deactivating
if (vrActive) {
// We're about to turn off VR
controls.stashControls()
setWindowSize(originalWinSize.first, originalWinSize.second)
}

vrActive = !vrActive
Expand All @@ -1721,6 +1721,20 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
if (hmd.initializedAndWorking()) {
hub.add(SceneryElement.HMDInput, hmd)
ti = hmd
// Disable the sidebar if it was still open
if ((mainWindow as SwingMainWindow).sidebarOpen) {
toggleSidebar()
}
if (resizeWindow) {
val perEyeResolution = hmd.getRenderTargetSize()
// Recommended resolution is about x1.33 larger than the actual headset resolution
// due to distortion compensation.
// Too high resolution gets in the way of volume rendering, so we scale it down a bit again
setWindowSize(
(perEyeResolution.x * 2f / 1.33f * resolutionScale).toInt(),
(perEyeResolution.y / 1.33f * resolutionScale).toInt()
)
}
} else {
logger.warn("Could not initialise VR headset, just activating stereo rendering.")
}
Expand Down Expand Up @@ -1916,6 +1930,57 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
println(scijavaContext!!.serviceIndex)
}

/**
* Set the window dimensions of the sciview rendering window.
* This is essential for VR headsets that require specific resolutions.
*
* @param width The desired width of the window in pixels
* @param height The desired height of the window in pixels
* @return true if the window was successfully resized, false otherwise
*/
fun setWindowSize(width: Int, height: Int): Boolean {
if (width <= 0 || height <= 0) {
log.error("Window dimensions must be positive: width=$width, height=$height")
return false
}

try {
// Update internal dimensions
windowWidth = width
windowHeight = height

// Update the main window frame if it exists
if (mainWindow is SwingMainWindow) {
val swingWindow = mainWindow as SwingMainWindow
val scale = getScenerySettings().get("Renderer.SurfaceScale") ?: Vector2f(1f)
val scaledWidth = (width / scale.x()).toInt()
val scaleHeight = (height / scale.y()).toInt()
// We need to scale the swing window with taking the surface scale into account
swingWindow.frame.setSize(scaledWidth, scaleHeight)

// Update the renderer dimensions
// TODO Is this even needed? Since outdated semaphores will trigger a swapchain recreation anyway
renderer?.reshape(width, height)
}

log.info("Window resized to ${width}x${height}")
return true
} catch (e: Exception) {
log.error("Failed to resize window: ${e.message}")
e.printStackTrace()
return false
}
}

/**
* Get the current window dimensions.
*
* @return a Pair containing the width and height of the window
*/
fun getWindowSize(): Pair<Int, Int> {
return Pair(windowWidth, windowHeight)
}

/**
* Return the color table corresponding to the [lutName]
* @param lutName a String represening an ImageJ style LUT name, like Fire.lut
Expand Down Expand Up @@ -2002,6 +2067,26 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
val sciViewService = context.service(SciViewService::class.java)
return sciViewService.orCreateActiveSciView
}

/**
* Static launching method with custom window dimensions
*
* @param width The desired width of the window in pixels
* @param height The desired height of the window in pixels
* @return a newly created SciView with specified dimensions
*/
@JvmStatic
@Throws(Exception::class)
fun create(width: Int, height: Int): SciView {
xinitThreads()
val context = Context(ImageJService::class.java, SciJavaService::class.java, SCIFIOService::class.java)
val objectService = context.service(ObjectService::class.java)
objectService.addObject(Utils.SciviewStandalone())
val sciViewService = context.service(SciViewService::class.java)
val sciView = sciViewService.orCreateActiveSciView
sciView.setWindowSize(width, height)
return sciView
}

/**
* Static launching method
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/sc/iview/commands/MenuWeights.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ object MenuWeights {
const val DEMO_BASIC_IMAGEPLANE = 4.0
const val DEMO_BASIC_VOLUME = 6.0
const val DEMO_BASIC_POINTCLOUD = 7.0
const val DEMO_BASIC_CUSTOM_WINDOW = 8.0
// Demo/Animation
const val DEMO_ANIMATION_PARTICLE = 0.0
const val DEMO_ANIMATION_VOLUMETIMESERIES = 1.0
Expand Down
129 changes: 129 additions & 0 deletions src/main/kotlin/sc/iview/commands/demo/basic/CustomWindowSizeDemo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*-
* #%L
* Scenery-backed 3D visualization package for ImageJ.
* %%
* Copyright (C) 2016 - 2024 sciview developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package sc.iview.commands.demo.basic

import org.joml.Vector3f
import org.scijava.command.Command
import org.scijava.plugin.Menu
import org.scijava.plugin.Parameter
import org.scijava.plugin.Plugin
import org.scijava.util.ColorRGB
import sc.iview.SciView
import sc.iview.commands.MenuWeights

/**
* Demo to test custom window sizing API.
* Shows how to set custom window dimensions for VR or other specific display requirements.
*
* @author Kyle Harrington
*/
@Plugin(type = Command::class,
label = "Custom Window Size Demo",
menuRoot = "SciView",
menu = [Menu(label = "Demo", weight = MenuWeights.DEMO),
Menu(label = "Basic", weight = MenuWeights.DEMO_BASIC),
Menu(label = "Custom Window Size", weight = MenuWeights.DEMO_BASIC_CUSTOM_WINDOW)])
class CustomWindowSizeDemo : Command {
@Parameter
private lateinit var sciview: SciView

@Parameter(label = "Window Width", min = "100", max = "3840")
private var width: Int = 1920

@Parameter(label = "Window Height", min = "100", max = "2160")
private var height: Int = 1080

override fun run() {
// Get current window size
val (currentWidth, currentHeight) = sciview.getWindowSize()
println("Current window size: ${currentWidth}x${currentHeight}")

// Set new window size
println("Setting window size to ${width}x${height}...")
val success = sciview.setWindowSize(width, height)

if (success) {
println("Window successfully resized to ${width}x${height}")

// Add some demo content to visualize the new dimensions
sciview.addSphere(
position = Vector3f(0f, 0f, 0f),
radius = 1f,
color = ColorRGB(128, 255, 128)
) {
name = "Center Sphere"
}

// Add corner markers to show the viewport
val aspectRatio = width.toFloat() / height.toFloat()
val markerSize = 0.2f

// Top-left
sciview.addBox(
position = Vector3f(-aspectRatio * 2, 2f, -5f),
size = Vector3f(markerSize, markerSize, markerSize),
color = ColorRGB(255, 0, 0)
) {
name = "Top-Left Marker"
}

// Top-right
sciview.addBox(
position = Vector3f(aspectRatio * 2, 2f, -5f),
size = Vector3f(markerSize, markerSize, markerSize),
color = ColorRGB(0, 255, 0)
) {
name = "Top-Right Marker"
}

// Bottom-left
sciview.addBox(
position = Vector3f(-aspectRatio * 2, -2f, -5f),
size = Vector3f(markerSize, markerSize, markerSize),
color = ColorRGB(0, 0, 255)
) {
name = "Bottom-Left Marker"
}

// Bottom-right
sciview.addBox(
position = Vector3f(aspectRatio * 2, -2f, -5f),
size = Vector3f(markerSize, markerSize, markerSize),
color = ColorRGB(255, 255, 0)
) {
name = "Bottom-Right Marker"
}

// Center the camera
sciview.centerOnScene()
} else {
println("Failed to resize window")
}
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/sc/iview/ui/SwingMainWindow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import java.util.*
import javax.script.ScriptException
import javax.swing.*
import kotlin.concurrent.thread
import kotlin.math.log
import kotlin.math.roundToInt


Expand Down Expand Up @@ -235,6 +236,12 @@ class SwingMainWindow(val sciview: SciView) : MainWindow() {
frame.add(mainSplitPane, BorderLayout.CENTER)
frame.add(toolbar, BorderLayout.EAST)
frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE
frame.addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
sciview.windowWidth = e.component.width
sciview.windowHeight = e.component.height
}
})
frame.addWindowListener(object : WindowAdapter() {
override fun windowClosing(e: WindowEvent) {
logger.debug("Closing SciView window.")
Expand Down
Loading