Skip to content

Commit

Permalink
[api][desktop]: Finish Flat Wizard Job refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Oct 7, 2024
1 parent ce1bc89 commit ad04a34
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 387 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ data class TPPAJob(
get() = status.capture.savedPath

init {
status.capture.exposureAmount = 0

add(mountTrackTask)
add(mountMoveTask)
add(settleDelayTask)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ data class CameraCaptureEvent(
is CameraExposureElapsed -> handleCameraExposureElapsed(event)
is CameraExposureFinished -> handleCameraExposureFinished(event)
is CameraExposureStarted -> handleCameraExposureStarted(event)
is CameraExposureFailed -> handleTimedTaskEvent(event)
}

computeCaptureProgress()
Expand Down
14 changes: 14 additions & 0 deletions api/src/main/kotlin/nebulosa/api/cameras/CameraExposureFailed.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nebulosa.api.cameras

import nebulosa.job.manager.Job

data class CameraExposureFailed(
override val job: Job,
override val task: CameraExposureTask,
) : CameraExposureEvent {

override val elapsedTime = task.exposureTimeInMicroseconds
override val remainingTime = 0L
override val progress = 1.0
override val savedPath = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import nebulosa.indi.device.camera.*
import nebulosa.io.transferAndClose
import nebulosa.job.manager.Job
import nebulosa.job.manager.Task
import nebulosa.log.debug
import nebulosa.log.loggerFor
import nebulosa.util.concurrency.cancellation.CancellationSource
import nebulosa.util.concurrency.latch.CountUpDownLatch
Expand Down Expand Up @@ -39,9 +38,10 @@ data class CameraExposureTask(
save(event)
}
is CameraExposureAborted,
is CameraExposureFailed,
is nebulosa.indi.device.camera.CameraExposureFailed,
is CameraDetached -> {
latch.reset()
job.accept(CameraExposureFailed(job, this))
}
is CameraExposureProgressChanged -> {
// "min" fix possible bug on SVBony exposure time?
Expand All @@ -56,7 +56,7 @@ data class CameraExposureTask(

override fun run() {
if (camera.connected) {
LOG.debug { "Camera Exposure started. camera=$camera, request=$request" }
LOG.debug("Camera Exposure started. camera={}, request={}", camera, request)

latch.countUp()

Expand All @@ -79,7 +79,7 @@ data class CameraExposureTask(

latch.await()

LOG.debug { "Camera Exposure finished. camera=$camera, request=$request" }
LOG.debug("Camera Exposure finished. camera={}, request={}", camera, request)
} else {
LOG.warn("camera not connected. camera={}, request={}", camera, request)
}
Expand Down
4 changes: 4 additions & 0 deletions api/src/main/kotlin/nebulosa/api/sequencer/SequencerJob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ data class SequencerJob(
is CameraExposureEvent -> {
status.capture.handleCameraExposureEvent(event)

if (event is CameraExposureFailed) {
return stop()
}

if (event is CameraExposureStarted) {
captureStartElapsedTime = status.elapsedTime
} else {
Expand Down
84 changes: 50 additions & 34 deletions api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package nebulosa.api.wizard.flat

import nebulosa.api.cameras.AutoSubFolderMode
import nebulosa.api.cameras.CameraEventAware
import nebulosa.api.cameras.CameraExposureEvent
import nebulosa.api.cameras.CameraExposureFinished
import nebulosa.api.cameras.CameraExposureTask
import nebulosa.api.cameras.*
import nebulosa.api.message.MessageEvent
import nebulosa.fits.fits
import nebulosa.image.Image
Expand All @@ -14,11 +10,10 @@ import nebulosa.indi.device.camera.CameraEvent
import nebulosa.indi.device.camera.FrameType
import nebulosa.job.manager.AbstractJob
import nebulosa.job.manager.Task
import nebulosa.log.debug
import nebulosa.log.loggerFor
import nebulosa.util.concurrency.latch.CountUpDownLatch
import java.nio.file.Path
import java.time.Duration
import kotlin.use

data class FlatWizardJob(
@JvmField val flatWizardExecutor: FlatWizardExecutor,
Expand All @@ -34,20 +29,22 @@ data class FlatWizardJob(

@JvmField val status = FlatWizardEvent(camera)

@Volatile private var cameraRequest = request.capture.copy(
exposureTime = Duration.ZERO, frameType = FrameType.FLAT,
autoSave = false, autoSubFolderMode = AutoSubFolderMode.OFF,
@Volatile private var cameraExposureTask = CameraExposureTask(
this, camera, request.capture.copy(
exposureTime = Duration.ofNanos((exposureMin + exposureMax) / 2),
frameType = FrameType.FLAT,
autoSave = false, autoSubFolderMode = AutoSubFolderMode.OFF,
)
)

@Volatile private var cameraExposureTask = CameraExposureTask(this, camera, cameraRequest)
private val waitToComputeOptimalExposureTime = CountUpDownLatch()

inline val savedPath
get() = status.capture.savedPath

init {
status.capture.exposureAmount = 0

add(cameraExposureTask)
status.exposureTime = cameraExposureTask.request.exposureTime.toNanos() / 1000L
}

override fun handleCameraEvent(event: CameraEvent) {
Expand All @@ -62,57 +59,74 @@ data class FlatWizardJob(
if (event is CameraExposureFinished) {
status.capture.send()

status.state = FlatWizardState.CAPTURED
computeOptimalExposureTime(event.savedPath)
if (!computeOptimalExposureTime(event.savedPath)) {
val exposureTimeInNanos = (exposureMax + exposureMin) / 2L
val request = cameraExposureTask.request.copy(exposureTime = Duration.ofNanos(exposureTimeInNanos))
status.exposureTime = exposureTimeInNanos / 1000L
add(CameraExposureTask(this, camera, request).also { cameraExposureTask = it })
}

waitToComputeOptimalExposureTime.reset()
}

status.send()
}
}
}

private fun computeOptimalExposureTime(savedPath: Path) {
private fun computeOptimalExposureTime(savedPath: Path): Boolean {
val image = savedPath.fits().use { Image.open(it, false) }
val statistics = STATISTICS.compute(image)

LOG.debug { "flat frame computed. statistics=$statistics" }
LOG.debug("flat frame computed. statistics={}", statistics)

if (statistics.mean in meanRange) {
LOG.debug { "found an optimal exposure time. exposureTime=${status.exposureTime}, path=$savedPath" }
status.state = FlatWizardState.IDLE
return stop()
LOG.debug("found an optimal exposure time. exposureTime={}, path={}", status.exposureTime, savedPath)
status.state = FlatWizardState.CAPTURED
status.capture.state = CameraCaptureState.IDLE
return true
} else if (statistics.mean < meanRange.start) {
exposureMin = status.exposureTime
LOG.debug { "captured frame is below mean range. exposureTime=${status.exposureTime}, path=$savedPath" }
exposureMin = cameraExposureTask.request.exposureTime.toNanos()
LOG.debug("captured frame is below mean range. exposureTime={}, path={}", exposureMin, savedPath)
} else {
exposureMax = status.exposureTime
LOG.debug { "captured frame is above mean range. exposureTime=${status.exposureTime}, path=$savedPath" }
exposureMax = cameraExposureTask.request.exposureTime.toNanos()
LOG.debug("captured frame is above mean range. exposureTime={}, path={}", exposureMax, savedPath)
}

val delta = exposureMax - exposureMin

// 10ms
if (delta < 10000000) {
if (delta < MIN_DELTA_TIME) {
LOG.warn("Failed to find an optimal exposure time. exposureMin={}, exposureMax={}", exposureMin, exposureMax)
status.state = FlatWizardState.FAILED
return stop()
status.capture.state = CameraCaptureState.IDLE
return true
}

return false
}

override fun beforeStart() {
LOG.debug("Flat Wizard started. camera={}, request={}", camera, request)

status.state = FlatWizardState.EXPOSURING
status.send()
}

override fun beforeTask(task: Task) {
if (task === cameraExposureTask) {
val exposureTimeInNanos = (exposureMax + exposureMin) / 2L
cameraRequest = cameraRequest.copy(exposureTime = Duration.ofNanos(exposureTimeInNanos))
status.exposureTime = exposureTimeInNanos / 1000L
}
waitToComputeOptimalExposureTime.countUp()
}

override fun beforeStart() {
LOG.debug { "Flat Wizard started. camera=$camera, request=$request" }
override fun afterTask(task: Task, exception: Throwable?): Boolean {
if (exception == null) {
waitToComputeOptimalExposureTime.await()
}

return super.afterTask(task, exception)
}

override fun afterFinish() {
LOG.debug { "Flat Wizard finished. camera=$camera, request=$request, exposureTime=${status.exposureTime} µs" }
LOG.debug("Flat Wizard finished. camera={}, request={}, exposureTime={} µs", camera, request, status.exposureTime)
}

@Suppress("NOTHING_TO_INLINE")
Expand All @@ -122,6 +136,8 @@ data class FlatWizardJob(

companion object {

private const val MIN_DELTA_TIME = 10000000 // 10ms

@JvmStatic private val LOG = loggerFor<FlatWizardJob>()
@JvmStatic private val STATISTICS = Statistics(noMedian = true, noDeviation = true)
}
Expand Down
4 changes: 3 additions & 1 deletion desktop/src/app/flat-wizard/flat-wizard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,16 @@ export class FlatWizardComponent implements AfterViewInit, OnDestroy, Tickable {

electronService.on('FLAT_WIZARD.ELAPSED', (event) => {
ngZone.run(() => {
if (event.state === 'EXPOSURING' && event.capture && event.capture.camera.id === this.camera?.id) {
if (event.state === 'EXPOSURING' && event.capture && event.camera.id === this.camera?.id) {
this.running = true
this.cameraExposure.handleCameraCaptureEvent(event.capture, true)
} else if (event.state === 'CAPTURED') {
this.running = false
this.cameraExposure.reset()
this.angularService.message('Flat frame captured')
} else if (event.state === 'FAILED') {
this.running = false
this.cameraExposure.reset()
this.angularService.message('Failed to find an optimal exposure time from given parameters', 'error')
}
})
Expand Down
5 changes: 3 additions & 2 deletions desktop/src/shared/types/flat-wizard.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cameraStartCaptureWithDefault, DEFAULT_CAMERA_START_CAPTURE, type CameraCaptureEvent, type CameraStartCapture } from './camera.types'
import { cameraStartCaptureWithDefault, DEFAULT_CAMERA_START_CAPTURE, type Camera, type CameraCaptureEvent, type CameraStartCapture } from './camera.types'

export type FlatWizardState = 'EXPOSURING' | 'CAPTURED' | 'FAILED'

Expand All @@ -15,9 +15,10 @@ export interface FlatWizardRequest {
}

export interface FlatWizardEvent {
camera: Camera
state: FlatWizardState
exposureTime: number
capture?: CameraCaptureEvent
capture?: Omit<CameraCaptureEvent, 'camera'>
savedPath?: string
}

Expand Down
3 changes: 2 additions & 1 deletion desktop/src/shared/types/sequencer.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ export interface SequencerPlan {
}

export interface SequencerEvent extends MessageEvent {
camera: Camera
id: number
elapsedTime: number
remainingTime: number
progress: number
capture?: CameraCaptureEvent
capture?: Omit<CameraCaptureEvent, 'camera'>
state: SequencerState
}

Expand Down
Loading

0 comments on commit ad04a34

Please sign in to comment.