Skip to content

Commit

Permalink
[api][desktop]: Refactor Camera Exposure Job
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Oct 7, 2024
1 parent ae36729 commit 264013c
Show file tree
Hide file tree
Showing 20 changed files with 200 additions and 379 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nebulosa.api.alignment.polar.darv

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import nebulosa.api.cameras.CameraCaptureEvent
import nebulosa.api.message.MessageEvent
import nebulosa.guiding.GuideDirection
Expand All @@ -9,7 +10,7 @@ data class DARVEvent(
@JvmField val camera: Camera,
@JvmField var state: DARVState = DARVState.IDLE,
@JvmField var direction: GuideDirection? = null,
@JvmField val capture: CameraCaptureEvent = CameraCaptureEvent(camera),
@JvmField @field:JsonIgnoreProperties("camera") val capture: CameraCaptureEvent = CameraCaptureEvent(camera),
) : MessageEvent {

override val eventName = "DARV.ELAPSED"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ package nebulosa.api.alignment.polar.darv
import nebulosa.api.cameras.AutoSubFolderMode
import nebulosa.api.cameras.CameraCaptureState
import nebulosa.api.cameras.CameraEventAware
import nebulosa.api.cameras.CameraExposureElapsed
import nebulosa.api.cameras.CameraExposureEvent
import nebulosa.api.cameras.CameraExposureFinished
import nebulosa.api.cameras.CameraExposureStarted
import nebulosa.api.cameras.CameraExposureTask
import nebulosa.api.guiding.GuidePulseRequest
import nebulosa.api.guiding.GuidePulseTask
Expand Down Expand Up @@ -69,12 +66,7 @@ data class DARVJob(
status.capture.handleCameraDelayEvent(event, CameraCaptureState.EXPOSURING)
}
is CameraExposureEvent -> {
when (event) {
is CameraExposureStarted -> status.capture.handleCameraExposureStarted(event)
is CameraExposureFinished -> status.capture.handleCameraExposureFinished(event)
is CameraExposureElapsed -> status.capture.handleCameraExposureElapsed(event)
}

status.capture.handleCameraExposureEvent(event)
status.capture.captureRemainingTime = status.capture.stepRemainingTime
status.capture.captureElapsedTime = status.capture.stepElapsedTime
status.capture.captureProgress = status.capture.stepProgress
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nebulosa.api.alignment.polar.tppa

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import nebulosa.api.beans.converters.angle.DeclinationSerializer
import nebulosa.api.beans.converters.angle.RightAscensionSerializer
Expand All @@ -18,7 +19,7 @@ data class TPPAEvent(
@JvmField @JsonSerialize(using = DeclinationSerializer::class) var totalError: Angle = 0.0,
@JvmField var azimuthErrorDirection: String = "",
@JvmField var altitudeErrorDirection: String = "",
@JvmField val capture: CameraCaptureEvent = CameraCaptureEvent(camera),
@JvmField @field:JsonIgnoreProperties("camera") val capture: CameraCaptureEvent = CameraCaptureEvent(camera),
@JvmField var pausing: Boolean = false,
) : MessageEvent {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ data class TPPAJob(
private val alignment = ThreePointPolarAlignment(solver, longitude, latitude)
private val cameraExposureTask = CameraExposureTask(this, camera, cameraRequest)
private val settleDelayTask = DelayTask(this, SETTLE_TIME)
private val alignmentTask = TPPAAlignmentTask(this, alignment)
private val tppaTask = TPPATask(this, alignment)
private val mountTrackTask = MountTrackTask(this, mount, true)
private val mountMoveTask = MountMoveTask(this, mount, mountMoveRequest)
private val mountMoveState = BooleanArray(3)
Expand All @@ -74,7 +74,7 @@ data class TPPAJob(
add(mountMoveTask)
add(settleDelayTask)
add(cameraExposureTask)
add(alignmentTask)
add(tppaTask)
}

override fun handleCameraEvent(event: CameraEvent) {
Expand Down Expand Up @@ -107,7 +107,7 @@ data class TPPAJob(
status.capture.savedPath = null
status.state = TPPAState.EXPOSURING
tppaExecutor.accept(status)
} else if (task === alignmentTask) {
} else if (task === tppaTask) {
status.state = TPPAState.SOLVING
tppaExecutor.accept(status)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package nebulosa.api.alignment.polar.tppa
import nebulosa.alignment.polar.point.three.ThreePointPolarAlignment
import nebulosa.job.manager.Task

data class TPPAAlignmentTask(
data class TPPATask(
@JvmField val job: TPPAJob,
@JvmField val alignment: ThreePointPolarAlignment,
) : Task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ data class CameraCaptureEvent(
@JvmField var stepProgress: Double = 0.0,
@JvmField var savedPath: Path? = null,
@JvmField var liveStackedPath: Path? = null,
@JvmField val capture: CameraStartCaptureRequest? = null,
) : MessageEvent {

override val eventName = "CAMERA.CAPTURE_ELAPSED"
Expand Down Expand Up @@ -47,6 +46,14 @@ data class CameraCaptureEvent(
state = CameraCaptureState.EXPOSURING
}

fun handleCameraExposureEvent(event: CameraExposureEvent) {
when (event) {
is CameraExposureElapsed -> handleCameraExposureElapsed(event)
is CameraExposureFinished -> handleCameraExposureFinished(event)
is CameraExposureStarted -> handleCameraExposureStarted(event)
}
}

fun handleCameraDelayEvent(event: DelayEvent, newState: CameraCaptureState = CameraCaptureState.WAITING) {
handleTimedTaskEvent(event)
state = newState
Expand Down
29 changes: 12 additions & 17 deletions api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureExecutor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.greenrobot.eventbus.ThreadMode
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import org.springframework.stereotype.Component
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor

@Component
@Subscriber
Expand All @@ -26,18 +27,18 @@ class CameraCaptureExecutor(
private val guider: Guider,
private val threadPoolTaskExecutor: ThreadPoolTaskExecutor,
private val calibrationFrameService: CalibrationFrameService,
) : Consumer<CameraCaptureEvent>, CameraEventAware, WheelEventAware {
) : Consumer<CameraCaptureEvent>, CameraEventAware, WheelEventAware, Executor by threadPoolTaskExecutor {

private val jobs = ConcurrentHashMap.newKeySet<CameraCaptureJob>(2)

@Subscribe(threadMode = ThreadMode.ASYNC)
override fun handleCameraEvent(event: CameraEvent) {
jobs.find { it.task.camera === event.device }?.handleCameraEvent(event)
jobs.find { it.camera === event.device }?.handleCameraEvent(event)
}

@Subscribe(threadMode = ThreadMode.ASYNC)
override fun handleFilterWheelEvent(event: FilterWheelEvent) {
jobs.find { it.task.wheel === event.device }?.handleFilterWheelEvent(event)
jobs.find { it.wheel === event.device }?.handleFilterWheelEvent(event)
}

override fun accept(event: CameraCaptureEvent) {
Expand All @@ -50,36 +51,30 @@ class CameraCaptureExecutor(
mount: Mount? = null, wheel: FilterWheel? = null, focuser: Focuser? = null, rotator: Rotator? = null
) {
check(camera.connected) { "${camera.name} Camera is not connected" }
check(jobs.none { it.task.camera === camera }) { "${camera.name} Camera Capture is already in progress" }
check(jobs.none { it.camera === camera }) { "${camera.name} Camera Capture is already in progress" }

val liveStackingManager = CameraLiveStackingManager(calibrationFrameService)
val task = CameraCaptureTask(
camera, request, guider, false, threadPoolTaskExecutor,
liveStackingManager, mount, wheel, focuser, rotator
)

task.subscribe(this)

with(CameraCaptureJob(task)) {
with(CameraCaptureJob(this, camera, request, guider, liveStackingManager, mount, wheel, focuser, rotator)) {
val completable = runAsync(threadPoolTaskExecutor)
jobs.add(this)
whenComplete { _, _ -> jobs.remove(this); liveStackingManager.close() }
start()
completable.whenComplete { _, _ -> jobs.remove(this); liveStackingManager.close() }
}
}

fun pause(camera: Camera) {
jobs.find { it.task.camera === camera }?.pause()
jobs.find { it.camera === camera }?.pause()
}

fun unpause(camera: Camera) {
jobs.find { it.task.camera === camera }?.unpause()
jobs.find { it.camera === camera }?.unpause()
}

fun stop(camera: Camera) {
jobs.find { it.task.camera === camera }?.stop()
jobs.find { it.camera === camera }?.stop()
}

fun status(camera: Camera): CameraCaptureEvent? {
return jobs.find { it.task.camera === camera }?.task?.get()
return jobs.find { it.camera === camera }?.status
}
}
159 changes: 154 additions & 5 deletions api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureJob.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,168 @@
package nebulosa.api.cameras

import nebulosa.api.tasks.Job
import nebulosa.api.guiding.DitherAfterExposureEvent
import nebulosa.api.guiding.DitherAfterExposureTask
import nebulosa.api.guiding.WaitForSettleTask
import nebulosa.api.wheels.WheelEventAware
import nebulosa.api.wheels.WheelMoveTask
import nebulosa.guiding.Guider
import nebulosa.indi.device.camera.Camera
import nebulosa.indi.device.camera.CameraEvent
import nebulosa.indi.device.camera.FrameType
import nebulosa.indi.device.filterwheel.FilterWheel
import nebulosa.indi.device.filterwheel.FilterWheelEvent
import nebulosa.indi.device.focuser.Focuser
import nebulosa.indi.device.mount.Mount
import nebulosa.indi.device.rotator.Rotator
import nebulosa.job.manager.AbstractJob
import nebulosa.job.manager.SplitTask
import nebulosa.job.manager.Task
import nebulosa.job.manager.delay.DelayEvent
import nebulosa.job.manager.delay.DelayTask
import nebulosa.log.debug
import nebulosa.log.loggerFor
import java.nio.file.Path

data class CameraCaptureJob(override val task: CameraCaptureTask) : Job(), CameraEventAware, WheelEventAware {
data class CameraCaptureJob(
@JvmField val cameraCaptureExecutor: CameraCaptureExecutor,
@JvmField val camera: Camera,
@JvmField val request: CameraStartCaptureRequest,
@JvmField val guider: Guider? = null,
@JvmField val liveStackerManager: CameraLiveStackingManager? = null,
@JvmField val mount: Mount? = null,
@JvmField val wheel: FilterWheel? = null,
@JvmField val focuser: Focuser? = null,
@JvmField val rotator: Rotator? = null,
) : AbstractJob(), CameraEventAware, WheelEventAware {

override val name = "${task.camera.name} Camera Capture Job"
private val delayTask = DelayTask(this, request.exposureDelay)
private val waitForSettleTask = WaitForSettleTask(this, guider)
private val delayAndWaitForSettleSplitTask = SplitTask(listOf(delayTask, waitForSettleTask), cameraCaptureExecutor)
private val cameraExposureTask = CameraExposureTask(this, camera, request)
private val ditherAfterExposureTask = DitherAfterExposureTask(this, guider, request.dither)
private val shutterWheelMoveTask = if (wheel != null && request.shutterPosition > 0) WheelMoveTask(this, wheel, request.shutterPosition) else null

@JvmField val status = CameraCaptureEvent(camera)
@JvmField val estimatedCaptureTime =
(request.exposureTime.toNanos() * request.exposureAmount + request.exposureDelay.toNanos() * (request.exposureAmount - 1)) / 1000L

@Volatile private var startCaptureElapsedTime = 0L

init {
status.exposureAmount = request.exposureAmount

add(delayTask)
add(delayAndWaitForSettleSplitTask)
add(cameraExposureTask)
add(ditherAfterExposureTask)
}

override fun handleCameraEvent(event: CameraEvent) {
task.handleCameraEvent(event)
cameraExposureTask.handleCameraEvent(event)
}

override fun handleFilterWheelEvent(event: FilterWheelEvent) {
task.handleFilterWheelEvent(event)
shutterWheelMoveTask?.handleFilterWheelEvent(event)
}

override fun beforeStart() {
LOG.debug { "Camera Capture started. request=$request, camera=$camera, mount=$mount, wheel=$wheel, focuser=$focuser" }

camera.snoop(listOf(mount, wheel, focuser, rotator))

status.state = CameraCaptureState.CAPTURE_STARTED
status.captureRemainingTime = estimatedCaptureTime
status.captureElapsedTime = 0L
status.captureProgress = 0.0
cameraCaptureExecutor.accept(status)

if (request.frameType == FrameType.DARK) {
shutterWheelMoveTask?.run()
}
}

override fun afterFinish() {
status.state = CameraCaptureState.CAPTURE_FINISHED
status.captureRemainingTime = 0L
status.captureElapsedTime = estimatedCaptureTime
status.captureProgress = 1.0
cameraCaptureExecutor.accept(status)

liveStackerManager?.stop(request)

LOG.debug { "Camera Capture finished. request=$request, status=$status, mount=$mount, wheel=$wheel, focuser=$focuser" }
}

override fun isLoop(): Boolean {
return request.isLoop
}

override fun canRun(prev: Task?, current: Task): Boolean {
if (current === ditherAfterExposureTask) {
return !isCancelled && guider != null
&& status.exposureCount >= 1 && request.dither.afterExposures > 0
&& status.exposureCount % request.dither.afterExposures == 0
} else if (current === delayTask) {
return status.exposureCount == 0
} else if (current === delayAndWaitForSettleSplitTask) {
return status.exposureCount > 0
}

return super.canRun(prev, current)
}

override fun accept(event: Any) {
when (event) {
is DelayEvent -> {
if (event.task === delayTask) {
status.handleCameraDelayEvent(event)
status.captureElapsedTime += event.waitTime
status.captureRemainingTime -= event.waitTime
}
}
is CameraExposureEvent -> {
status.handleCameraExposureEvent(event)

if (event is CameraExposureStarted) {
startCaptureElapsedTime = status.captureElapsedTime
} else {
status.captureElapsedTime = startCaptureElapsedTime + event.elapsedTime

if (event is CameraExposureFinished) {
status.liveStackedPath = addFrameToLiveStacker(status.savedPath)
}
}

if (estimatedCaptureTime > 0L) {
status.captureProgress = (estimatedCaptureTime - status.captureRemainingTime) / estimatedCaptureTime.toDouble()
}

cameraCaptureExecutor.accept(status)
}
is DitherAfterExposureEvent -> {
status.state = CameraCaptureState.DITHERING
cameraCaptureExecutor.accept(status)
}
}
}

private fun addFrameToLiveStacker(path: Path?): Path? {
return if (path != null && liveStackerManager?.start(request, path) == true) {
try {
status.state = CameraCaptureState.STACKING
cameraCaptureExecutor.accept(status)

liveStackerManager.stack(request, path)
} catch (_: Throwable) {
null
}
} else {
null
}
}

companion object {

@JvmStatic private val LOG = loggerFor<CameraCaptureJob>()
}
}
Loading

0 comments on commit 264013c

Please sign in to comment.