Skip to content

Commit

Permalink
Live Stacking (#431)
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm authored Jun 3, 2024
2 parents bc81dd8 + adb831f commit 9df5cdd
Show file tree
Hide file tree
Showing 73 changed files with 1,508 additions and 492 deletions.
1 change: 1 addition & 0 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
implementation(project(":nebulosa-nova"))
implementation(project(":nebulosa-sbd"))
implementation(project(":nebulosa-simbad"))
implementation(project(":nebulosa-siril"))
implementation(project(":nebulosa-stellarium-protocol"))
implementation(project(":nebulosa-wcs"))
implementation(project(":nebulosa-xisf"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import jakarta.validation.Valid
import jakarta.validation.constraints.NotNull
import nebulosa.api.cameras.CameraStartCaptureRequest
import nebulosa.api.solver.PlateSolverOptions
import nebulosa.api.solver.PlateSolverRequest
import nebulosa.guiding.GuideDirection
import org.hibernate.validator.constraints.time.DurationMin
import org.springframework.boot.convert.DurationUnit
Expand All @@ -13,7 +13,7 @@ import java.time.temporal.ChronoUnit

data class TPPAStartRequest(
@JsonIgnoreProperties("camera", "focuser", "wheel") @JvmField val capture: CameraStartCaptureRequest = CameraStartCaptureRequest.EMPTY,
@field:NotNull @Valid @JvmField val plateSolver: PlateSolverOptions = PlateSolverOptions.EMPTY,
@field:NotNull @Valid @JvmField val plateSolver: PlateSolverRequest = PlateSolverRequest.EMPTY,
@JvmField val startFromCurrentPosition: Boolean = true,
@JvmField val compensateRefraction: Boolean = false,
@JvmField val stopTrackingWhenDone: Boolean = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ data class TPPATask(
captureEvent = event

if (event.state == CameraCaptureState.EXPOSURE_FINISHED) {
savedImage = event.savePath!!
savedImage = event.savedPath!!
}

if (!finished.get()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package nebulosa.api.autofocus

import nebulosa.api.cameras.CameraStartCaptureRequest
import nebulosa.api.focusers.BacklashCompensation
import nebulosa.api.stardetection.StarDetectionOptions
import nebulosa.api.stardetection.StarDetectionRequest

data class AutoFocusRequest(
@JvmField val fittingMode: AutoFocusFittingMode = AutoFocusFittingMode.HYPERBOLIC,
Expand All @@ -12,5 +12,5 @@ data class AutoFocusRequest(
@JvmField val initialOffsetSteps: Int = 4,
@JvmField val stepSize: Int = 50,
@JvmField val totalNumberOfAttempts: Int = 1,
@JvmField val starDetector: StarDetectionOptions = StarDetectionOptions.EMPTY,
@JvmField val starDetector: StarDetectionRequest = StarDetectionRequest.EMPTY,
)
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ data class AutoFocusTask(
if (event.state == CameraCaptureState.EXPOSURE_FINISHED) {
sendEvent(AutoFocusState.EXPOSURED, event)
sendEvent(AutoFocusState.ANALYSING)
val detectedStars = starDetection.detect(event.savePath!!)
val detectedStars = starDetection.detect(event.savedPath!!)
starCount = detectedStars.size
LOG.info("detected $starCount stars")
starHFD = detectedStars.measureDetectedStars()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package nebulosa.api.beans.converters.angle

class DeclinationDeserializer : AngleDeserializer(true)
class DeclinationDeserializer : AngleDeserializer(false)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package nebulosa.api.beans.converters.angle

class DegreesDeserializer : AngleDeserializer(false, defaultValue = 0.0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package nebulosa.api.calibration

interface CalibrationFrameProvider {

fun findBestDarkFrames(
name: String, temperature: Double, width: Int, height: Int,
binX: Int, binY: Int = binX, exposureTimeInMicroseconds: Long = 0L,
gain: Double = 0.0,
): List<CalibrationFrameEntity>

fun findBestFlatFrames(
name: String, width: Int, height: Int,
binX: Int, binY: Int = binX, filter: String? = null
): List<CalibrationFrameEntity>

fun findBestBiasFrames(
name: String, width: Int, height: Int,
binX: Int, binY: Int = binX, gain: Double = 0.0,
): List<CalibrationFrameEntity>
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import kotlin.math.roundToInt
@Service
class CalibrationFrameService(
private val calibrationFrameRepository: CalibrationFrameRepository,
) {
) : CalibrationFrameProvider {

fun calibrate(name: String, image: Image, createNew: Boolean = false): Image {
return synchronized(image) {
Expand Down Expand Up @@ -146,13 +146,13 @@ class CalibrationFrameService(
calibrationFrameRepository.delete(frame)
}

// exposureTime, temperature, width, height, binX, binY, gain.
fun findBestDarkFrames(name: String, image: Image): List<CalibrationFrameEntity> {
val header = image.header
val temperature = header.temperature

override fun findBestDarkFrames(
name: String, temperature: Double, width: Int, height: Int,
binX: Int, binY: Int, exposureTimeInMicroseconds: Long,
gain: Double,
): List<CalibrationFrameEntity> {
val frames = calibrationFrameRepository
.darkFrames(name, image.width, image.height, header.binX, header.exposureTimeInMicroseconds, header.gain)
.darkFrames(name, width, height, binX, exposureTimeInMicroseconds, gain)

if (frames.isEmpty()) return emptyList()

Expand All @@ -164,20 +164,46 @@ class CalibrationFrameService(
return groupedFrames.firstEntry().value
}

// filter, width, height, binX, binY.
fun findBestFlatFrames(name: String, image: Image): List<CalibrationFrameEntity> {
val filter = image.header.filter
fun findBestDarkFrames(name: String, image: Image): List<CalibrationFrameEntity> {
val header = image.header
val temperature = header.temperature
val binX = header.binX
val exposureTime = header.exposureTimeInMicroseconds

return findBestDarkFrames(name, temperature, image.width, image.height, binX, binX, exposureTime, header.gain)
}

override fun findBestFlatFrames(
name: String, width: Int, height: Int,
binX: Int, binY: Int, filter: String?
): List<CalibrationFrameEntity> {
// TODO: Generate master from matched frames.
return calibrationFrameRepository
.flatFrames(name, filter, image.width, image.height, image.header.binX)
.flatFrames(name, filter, width, height, binX)
}

// width, height, binX, binY, gain.
fun findBestBiasFrames(name: String, image: Image): List<CalibrationFrameEntity> {
fun findBestFlatFrames(name: String, image: Image): List<CalibrationFrameEntity> {
val header = image.header
val filter = header.filter
val binX = header.binX

return findBestFlatFrames(name, image.width, image.height, binX, binX, filter)
}

override fun findBestBiasFrames(
name: String, width: Int, height: Int,
binX: Int, binY: Int, gain: Double,
): List<CalibrationFrameEntity> {
// TODO: Generate master from matched frames.
return calibrationFrameRepository
.biasFrames(name, image.width, image.height, image.header.binX, image.header.gain)
.biasFrames(name, width, height, binX, gain)
}

fun findBestBiasFrames(name: String, image: Image): List<CalibrationFrameEntity> {
val header = image.header
val binX = header.binX

return findBestBiasFrames(name, image.width, image.height, binX, binX, image.header.gain)
}

companion object {
Expand Down
15 changes: 6 additions & 9 deletions api/src/main/kotlin/nebulosa/api/cameras/CameraCaptureEvent.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package nebulosa.api.cameras

import com.fasterxml.jackson.annotation.JsonIgnore
import nebulosa.api.messages.MessageEvent
import nebulosa.indi.device.camera.Camera
import java.nio.file.Path
import java.time.Duration

data class CameraCaptureEvent(
@JvmField val camera: Camera,
@JvmField @field:JsonIgnore val task: CameraCaptureTask,
@JvmField val camera: Camera = task.camera,
@JvmField val state: CameraCaptureState = CameraCaptureState.IDLE,
@JvmField val exposureAmount: Int = 0,
@JvmField val exposureCount: Int = 0,
Expand All @@ -16,15 +18,10 @@ data class CameraCaptureEvent(
@JvmField val stepRemainingTime: Duration = Duration.ZERO,
@JvmField val stepElapsedTime: Duration = Duration.ZERO,
@JvmField val stepProgress: Double = 0.0,
@JvmField val savePath: Path? = null,
@JvmField val savedPath: Path? = null,
@JvmField val liveStackedPath: Path? = null,
@JvmField val capture: CameraStartCaptureRequest? = null,
) : MessageEvent {

override val eventName = "CAMERA.CAPTURE_ELAPSED"

companion object {

@JvmStatic
fun exposureFinished(camera: Camera, savePath: Path) =
CameraCaptureEvent(camera, CameraCaptureState.EXPOSURE_FINISHED, savePath = savePath)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nebulosa.api.cameras

import io.reactivex.rxjava3.functions.Consumer
import nebulosa.api.beans.annotations.Subscriber
import nebulosa.api.calibration.CalibrationFrameService
import nebulosa.api.messages.MessageService
import nebulosa.guiding.Guider
import nebulosa.indi.device.camera.Camera
Expand All @@ -18,6 +19,7 @@ class CameraCaptureExecutor(
private val messageService: MessageService,
private val guider: Guider,
private val threadPoolTaskExecutor: ThreadPoolTaskExecutor,
private val calibrationFrameService: CalibrationFrameService,
) : Consumer<CameraCaptureEvent>, CameraEventAware {

private val jobs = ConcurrentHashMap.newKeySet<CameraCaptureJob>(2)
Expand All @@ -36,7 +38,7 @@ class CameraCaptureExecutor(
check(camera.connected) { "${camera.name} Camera is not connected" }
check(jobs.none { it.task.camera === camera }) { "${camera.name} Camera Capture is already in progress" }

val task = CameraCaptureTask(camera, request, guider, executor = threadPoolTaskExecutor)
val task = CameraCaptureTask(camera, request, guider, executor = threadPoolTaskExecutor, calibrationFrameProvider = calibrationFrameService)
task.subscribe(this)

with(CameraCaptureJob(task)) {
Expand Down
Loading

0 comments on commit 9df5cdd

Please sign in to comment.