Skip to content

Commit

Permalink
[api][desktop]: Remove image automatically after a time
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Jun 3, 2024
1 parent e8172c4 commit adb831f
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ data class CameraCaptureEvent(
@JvmField val stepElapsedTime: Duration = Duration.ZERO,
@JvmField val stepProgress: Double = 0.0,
@JvmField val savedPath: Path? = null,
@JvmField val liveStackedSavedPath: Path? = null,
@JvmField val liveStackedPath: Path? = null,
@JvmField val capture: CameraStartCaptureRequest? = null,
) : MessageEvent {

Expand Down
10 changes: 5 additions & 5 deletions api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ data class CameraCaptureTask(
@Volatile private var stepElapsedTime = Duration.ZERO
@Volatile private var stepProgress = 0.0
@Volatile private var savedPath: Path? = null
@Volatile private var liveStackedSavedPath: Path? = null
@Volatile private var liveStackedPath: Path? = null

@JvmField @JsonIgnore val estimatedCaptureTime: Duration = if (request.isLoop) Duration.ZERO
else Duration.ofNanos(request.exposureTime.toNanos() * request.exposureAmount + request.exposureDelay.toNanos() * (request.exposureAmount - if (useFirstExposure) 0 else 1))
Expand Down Expand Up @@ -82,7 +82,7 @@ data class CameraCaptureTask(
val gain = request.gain.toDouble()

val wheel = camera.snoopedDevices.firstOrNull { it is FilterWheel } as? FilterWheel
val filter = wheel?.let { it.names[it.position] }
val filter = wheel?.let { it.names.getOrNull(it.position - 1) }

val newDark = dark ?: calibrationFrameProvider
.findBestDarkFrames(calibrationGroup, temperature, width, height, binX, binY, exposureTime, gain)
Expand Down Expand Up @@ -196,7 +196,7 @@ data class CameraCaptureTask(
CameraExposureState.FINISHED -> {
captureElapsedTime = prevCaptureElapsedTime + request.exposureTime
savedPath = event.savedPath
liveStackedSavedPath = addFrameToLiveStacker(savedPath)
liveStackedPath = addFrameToLiveStacker(savedPath)
CameraCaptureState.EXPOSURE_FINISHED
}
CameraExposureState.IDLE -> {
Expand All @@ -220,7 +220,7 @@ data class CameraCaptureTask(
this, camera, state, request.exposureAmount, exposureCount,
captureRemainingTime, captureElapsedTime, captureProgress,
stepRemainingTime, stepElapsedTime, stepProgress,
savedPath, liveStackedSavedPath,
savedPath, liveStackedPath,
if (state == CameraCaptureState.EXPOSURE_FINISHED) request else null
)

Expand Down Expand Up @@ -250,7 +250,7 @@ data class CameraCaptureTask(
stepElapsedTime = Duration.ZERO
stepProgress = 0.0
savedPath = null
liveStackedSavedPath = null
liveStackedPath = null

delayTask.reset()
cameraExposureTask.reset()
Expand Down
57 changes: 47 additions & 10 deletions api/src/main/kotlin/nebulosa/api/image/ImageBucket.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,33 @@ package nebulosa.api.image

import nebulosa.fits.fits
import nebulosa.image.Image
import nebulosa.log.loggerFor
import nebulosa.plate.solving.PlateSolution
import nebulosa.xisf.xisf
import org.springframework.stereotype.Component
import java.io.Closeable
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.timer
import kotlin.io.path.extension

@Component
class ImageBucket {
class ImageBucket : Closeable {

data class OpenedImage(
@JvmField val image: Image,
@JvmField var image: Image? = null,
@JvmField var solution: PlateSolution? = null,
@JvmField val debayer: Boolean = true,
@JvmField var openedAt: Long = System.currentTimeMillis(),
)

private val bucket = HashMap<Path, OpenedImage>(256)
private val bucket = ConcurrentHashMap<Path, OpenedImage>(8)
private val timer = timer("Image Bucket Timer", true, IMAGES_MAX_TIME, IMAGES_MAX_TIME, ::deleteUnusedImages)

@Synchronized
fun put(path: Path, image: Image, solution: PlateSolution? = null, debayer: Boolean = true) {
bucket[path] = OpenedImage(image, solution ?: PlateSolution.from(image.header), debayer)
fun put(path: Path, image: Image, solution: PlateSolution? = null, debayer: Boolean = true): OpenedImage {
return OpenedImage(image, solution ?: PlateSolution.from(image.header), debayer).also { bucket[path] = it }
}

@Synchronized
Expand All @@ -32,11 +39,17 @@ class ImageBucket {
}

@Synchronized
fun open(path: Path, debayer: Boolean = true, solution: PlateSolution? = null, force: Boolean = false): Image {
fun open(
path: Path, debayer: Boolean = this[path]?.debayer ?: true,
solution: PlateSolution? = null, force: Boolean = false
): OpenedImage {
val openedImage = this[path]

if (openedImage != null && !force && debayer == openedImage.debayer) {
return openedImage.image
if (openedImage.image != null && solution == null) {
openedImage.openedAt = System.currentTimeMillis()
return openedImage
}
}

val representation = when (path.extension.lowercase()) {
Expand All @@ -46,8 +59,7 @@ class ImageBucket {
}

val image = representation.use { Image.open(it, debayer) }
put(path, image, solution, debayer)
return image
return put(path, image, solution ?: openedImage?.solution, debayer)
}

@Synchronized
Expand All @@ -60,7 +72,7 @@ class ImageBucket {
}

operator fun contains(path: Path): Boolean {
return path in bucket
return bucket.containsKey(path)
}

operator fun contains(image: Image): Boolean {
Expand All @@ -70,4 +82,29 @@ class ImageBucket {
operator fun contains(solution: PlateSolution): Boolean {
return bucket.any { it.value.solution === solution }
}

override fun close() {
timer.cancel()
}

@Suppress("UNUSED_PARAMETER")
private fun deleteUnusedImages(task: TimerTask) {
val currentTime = System.currentTimeMillis()

synchronized(this) {
for ((path, image) in bucket) {
if (currentTime - image.openedAt >= IMAGES_MAX_TIME) {
image.image = null
LOG.info("image at {} has been disposed", path)
}
}
}
}

companion object {

private const val IMAGES_MAX_TIME = 1000 * 60 * 5L // 5 min

@JvmStatic private val LOG = loggerFor<ImageBucket>()
}
}
21 changes: 10 additions & 11 deletions api/src/main/kotlin/nebulosa/api/image/ImageService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,16 @@ class ImageService(
path: Path, camera: Camera?, transformation: ImageTransformation,
output: HttpServletResponse,
) {
val image = imageBucket.open(path, transformation.debayer, force = transformation.force)
val (transformedImage, statistics, stretchParams, instrument) = image.transform(true, transformation, ImageOperation.OPEN, camera)
val (image, calibration) = imageBucket.open(path, transformation.debayer, force = transformation.force)
val (transformedImage, statistics, stretchParams, instrument) = image!!.transform(true, transformation, ImageOperation.OPEN, camera)

val info = ImageInfo(
path,
transformedImage.width, transformedImage.height, transformedImage.mono,
stretchParams!!.shadow, stretchParams.highlight, stretchParams.midtone,
transformedImage.header.rightAscension.takeIf { it.isFinite() },
transformedImage.header.declination.takeIf { it.isFinite() },
imageBucket[path]?.solution?.let(::ImageSolved),
calibration?.let(::ImageSolved),
transformedImage.header.mapNotNull { if (it.isCommentStyle) null else ImageHeaderItem(it.key, it.value) },
transformedImage.header.bitpix, instrument, statistics,
)
Expand Down Expand Up @@ -170,9 +170,9 @@ class ImageService(
minorPlanetMagLimit: Double = 12.0, useSimbad: Boolean = false,
location: Location? = null,
): List<ImageAnnotation> {
val (image, calibration) = imageBucket[path] ?: return emptyList()
val (image, calibration) = imageBucket.open(path)

if (calibration.isNullOrEmpty() || !calibration.solved) {
if (image == null || calibration.isNullOrEmpty() || !calibration.solved) {
return emptyList()
}

Expand Down Expand Up @@ -267,8 +267,8 @@ class ImageService(
return annotations
}

fun saveImageAs(inputPath: Path, save: SaveImage, camera: Camera?) {
val (image) = imageBucket[inputPath]?.image?.transform(save.shouldBeTransformed, save.transformation, ImageOperation.SAVE)
fun saveImageAs(path: Path, save: SaveImage, camera: Camera?) {
val (image) = imageBucket.open(path).image?.transform(save.shouldBeTransformed, save.transformation, ImageOperation.SAVE)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Image not found")

require(save.path != null)
Expand All @@ -295,9 +295,9 @@ class ImageService(
}

fun coordinateInterpolation(path: Path): CoordinateInterpolation? {
val (image, calibration) = imageBucket[path] ?: return null
val (image, calibration) = imageBucket.open(path)

if (calibration.isNullOrEmpty() || !calibration.solved) {
if (image == null || calibration.isNullOrEmpty() || !calibration.solved) {
return null
}

Expand Down Expand Up @@ -331,8 +331,7 @@ class ImageService(
}

fun histogram(path: Path, bitLength: Int = 16): IntArray {
val (image) = imageBucket[path] ?: return IntArray(0)
return image.compute(Histogram(bitLength = bitLength))
return imageBucket.open(path).image?.compute(Histogram(bitLength = bitLength)) ?: IntArray(0)
}

companion object {
Expand Down
5 changes: 2 additions & 3 deletions api/src/main/kotlin/nebulosa/api/mounts/MountService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,10 @@ class MountService(
}

fun pointMountHere(mount: Mount, path: Path, x: Double, y: Double) {
val calibration = imageBucket[path]?.solution ?: return
val calibration = imageBucket.open(path).solution ?: return

if (calibration.isNotEmpty() && calibration.solved) {
val wcs = WCS(calibration)
val (rightAscension, declination) = wcs.use { it.pixToSky(x, y) } // J2000
val (rightAscension, declination) = WCS(calibration).use { it.pixToSky(x, y) } // J2000

val icrf = ICRF.equatorial(calibration.rightAscension, calibration.declination)
val (calibratedRA, calibratedDEC) = icrf.equatorialAtDate()
Expand Down
6 changes: 3 additions & 3 deletions desktop/src/app/image/image.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,19 +528,19 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
if (event.state === 'EXPOSURE_FINISHED' && event.camera.id === this.imageData.camera?.id) {
ngZone.run(() => {
if (this.showLiveStackedImage === undefined) {
if (event.liveStackedSavedPath) {
if (event.liveStackedPath) {
this.showLiveStackedImage = true
this.app.topMenu[0].toggled = true
this.app.topMenu[0].visible = true
}
} else if (!event.liveStackedSavedPath) {
} else if (!event.liveStackedPath) {
this.showLiveStackedImage = undefined
this.app.topMenu[0].toggled = false
this.app.topMenu[0].visible = false
}

this.imageData.path = event.savedPath
this.imageData.liveStackedPath = event.liveStackedSavedPath
this.imageData.liveStackedPath = event.liveStackedPath
this.imageData.capture = event.capture

this.clearOverlay()
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/shared/types/camera.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export interface CameraCaptureEvent extends MessageEvent {
stepProgress: number
stepRemainingTime: number
savedPath?: string
liveStackedSavedPath?: string
liveStackedPath?: string
state: CameraCaptureState
capture?: CameraStartCapture
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ data class CommandLine internal constructor(
private inner class ProcessWaiter(
private val process: Process,
private val timeout: Long,
) : Thread() {
) : Thread("Command Line Process Waiter") {

init {
isDaemon = false
isDaemon = true
}

override fun run() {
Expand Down Expand Up @@ -139,13 +139,13 @@ data class CommandLine internal constructor(
private inner class StreamLineReader(
stream: InputStream,
private val isError: Boolean,
) : Thread() {
) : Thread("Command Line Stream Line Reader") {

private val reader = stream.bufferedReader()
private val completable = CompletableFuture<Unit>()

init {
isDaemon = false
isDaemon = true
}

override fun run() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal open class INDIFilterWheel(
@Volatile final override var position = 0
@Volatile final override var moving = false

final override val names = ArrayList<String>(12)
final override val names = ArrayList<String>(8)

override fun handleMessage(message: INDIProtocol) {
when (message) {
Expand Down

0 comments on commit adb831f

Please sign in to comment.