Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XISF Support #362

Merged
merged 16 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
*.jar binary
*.fit binary
*.fits binary
*.xisf binary
*.ttf binary
*.xcf binary
*.png binary
*.db binary
*.gz binary
*.dll binary
*.so binary
*.zip binary
*.zip binary
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,3 @@ libobjectbox*.dylib
.kotlin

# End of https://www.toptal.com/developers/gitignore/api/intellij,kotlin,java,gradle

**/saved/*.png
**/saved/*.jpg
2 changes: 1 addition & 1 deletion GitHub CI.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<option name="externalSystemIdString"
value="GRADLE"/>
<option name="scriptParameters"
value="-Dgithub=true"/>
value="-Dgithub=true --continue"/>
<option name="taskDescriptions">
<list/>
</option>
Expand Down
3 changes: 2 additions & 1 deletion api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies {
implementation(project(":nebulosa-guiding-phd2"))
implementation(project(":nebulosa-hips2fits"))
implementation(project(":nebulosa-horizons"))
implementation(project(":nebulosa-imaging"))
implementation(project(":nebulosa-image"))
implementation(project(":nebulosa-indi-client"))
implementation(project(":nebulosa-log"))
implementation(project(":nebulosa-lx200-protocol"))
Expand All @@ -29,6 +29,7 @@ dependencies {
implementation(project(":nebulosa-stellarium-protocol"))
implementation(project(":nebulosa-watney"))
implementation(project(":nebulosa-wcs"))
implementation(project(":nebulosa-xisf"))
implementation(libs.apache.codec)
implementation(libs.csv)
implementation(libs.eventbus)
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/kotlin/nebulosa/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ URL: `localhost:{PORT}/ws`
"offset": 0,
"offsetMin": 0,
"offsetMax": 0,
"hasGuiderHead": false,
"hasGuideHead": false,
"pixelSizeX": 0,
"pixelSizeY": 0,
"capturesPath": "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import nebulosa.batch.processing.ExecutionContext.Companion.getDouble
import nebulosa.batch.processing.ExecutionContext.Companion.getDuration
import nebulosa.batch.processing.delay.DelayStep
import nebulosa.batch.processing.delay.DelayStepListener
import nebulosa.image.format.ImageRepresentation
import nebulosa.indi.device.camera.Camera
import nebulosa.indi.device.camera.FrameType
import nebulosa.indi.device.guide.GuideOutput
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration

data class DARVJob(
Expand Down Expand Up @@ -65,8 +67,8 @@ data class DARVJob(
onNext(DARVEvent.Finished(id))
}

override fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution) {
onNext(CameraExposureFinished(stepExecution.jobExecution, step.camera, 1, 1, Duration.ZERO, 1.0, Duration.ZERO, step.savedPath!!))
override fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution, image: ImageRepresentation?, savedPath: Path) {
onNext(CameraExposureFinished(stepExecution.jobExecution, step.camera, 1, 1, Duration.ZERO, 1.0, Duration.ZERO, savedPath))
}

override fun onGuidePulseElapsed(step: GuidePulseStep, stepExecution: StepExecution) {
Expand Down
21 changes: 13 additions & 8 deletions api/src/main/kotlin/nebulosa/api/alignment/polar/tppa/TPPAStep.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import nebulosa.batch.processing.StepExecution
import nebulosa.batch.processing.StepResult
import nebulosa.common.concurrency.latch.Pauseable
import nebulosa.common.time.Stopwatch
import nebulosa.fits.fits
import nebulosa.imaging.Image
import nebulosa.image.format.ImageRepresentation
import nebulosa.indi.device.camera.Camera
import nebulosa.indi.device.mount.Mount
import nebulosa.log.debug
import nebulosa.log.loggerFor
import nebulosa.math.Angle
import nebulosa.math.deg
import nebulosa.plate.solving.PlateSolver
import java.nio.file.Path

data class TPPAStep(
@JvmField val camera: Camera,
Expand All @@ -30,18 +30,18 @@ data class TPPAStep(
private val longitude: Angle = mount!!.longitude,
private val latitude: Angle = mount!!.latitude,
private val cameraRequest: CameraStartCaptureRequest = request.capture,
) : Step, Pauseable {
) : Step, Pauseable, CameraCaptureListener {

private val cameraExposureStep = CameraExposureStep(camera, cameraRequest)
private val alignment = ThreePointPolarAlignment(solver, longitude, latitude)
private val listeners = LinkedHashSet<TPPAListener>()
private val stopwatch = Stopwatch()
private val stepDistances = DoubleArray(2) { if (request.eastDirection) request.stepDistance else -request.stepDistance }

@Volatile private var image: Image? = null
@Volatile private var mountSlewStep: MountSlewStep? = null
@Volatile private var noSolutionAttempts = 0
@Volatile private var stepExecution: StepExecution? = null
@Volatile private var savedImage: Pair<ImageRepresentation?, Path>? = null

val stepCount
get() = alignment.state
Expand All @@ -65,20 +65,26 @@ data class TPPAStep(
return listeners.remove(listener)
}

override fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution, image: ImageRepresentation?, savedPath: Path) {
savedImage = image to savedPath
}

override fun beforeJob(jobExecution: JobExecution) {
cameraExposureStep.registerCameraCaptureListener(this)
cameraExposureStep.beforeJob(jobExecution)
mount?.tracking(true)
}

override fun afterJob(jobExecution: JobExecution) {
cameraExposureStep.afterJob(jobExecution)
cameraExposureStep.unregisterCameraCaptureListener(this)

if (mount != null && request.stopTrackingWhenDone) {
mount.tracking(false)
}

savedImage = null
stopwatch.stop()

listeners.forEach { it.polarAlignmentFinished(this, jobExecution.cancellationToken.isCancelled) }
}

Expand Down Expand Up @@ -119,14 +125,13 @@ data class TPPAStep(
cameraExposureStep.execute(stepExecution)

if (!cancellationToken.isCancelled) {
val savedPath = cameraExposureStep.savedPath ?: return StepResult.FINISHED
image = savedPath.fits().use { image?.load(it, false) ?: Image.open(it, false) }
val saved = savedImage ?: return StepResult.FINISHED

val radius = if (mount == null) 0.0 else ThreePointPolarAlignment.DEFAULT_RADIUS

// Polar alignment step.
val result = alignment.align(
savedPath, image!!, mount?.rightAscension ?: 0.0, mount?.declination ?: 0.0, radius,
saved.second, mount?.rightAscension ?: 0.0, mount?.declination ?: 0.0, radius,
request.compensateRefraction, cancellationToken
)

Expand Down
10 changes: 6 additions & 4 deletions api/src/main/kotlin/nebulosa/api/atlas/SkyAtlasUpdateTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ class SkyAtlasUpdateTask(

httpClient.newCall(request).execute().use {
if (it.isSuccessful) {
val reader = SimbadDatabaseReader(it.body!!.byteStream().source())

for (entity in reader) {
simbadEntityRepository.save(entity)
it.body!!.byteStream().source().use { source ->
SimbadDatabaseReader(source).use { reader ->
for (entity in reader) {
simbadEntityRepository.save(entity)
}
}
}
} else if (it.code == 404) {
finished = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import nebulosa.guiding.Guider
import nebulosa.guiding.phd2.PHD2Guider
import nebulosa.hips2fits.Hips2FitsService
import nebulosa.horizons.HorizonsService
import nebulosa.imaging.Image
import nebulosa.image.Image
import nebulosa.log.loggerFor
import nebulosa.phd2.client.PHD2Client
import nebulosa.sbd.SmallBodyDatabaseService
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package nebulosa.api.calibration

import nebulosa.fits.*
import nebulosa.imaging.Image
import nebulosa.imaging.algorithms.transformation.correction.BiasSubtraction
import nebulosa.imaging.algorithms.transformation.correction.DarkSubtraction
import nebulosa.imaging.algorithms.transformation.correction.FlatCorrection
import nebulosa.image.Image
import nebulosa.image.algorithms.transformation.correction.BiasSubtraction
import nebulosa.image.algorithms.transformation.correction.DarkSubtraction
import nebulosa.image.algorithms.transformation.correction.FlatCorrection
import nebulosa.image.format.Header
import nebulosa.image.format.ImageHdu
import nebulosa.image.format.ReadableHeader
import nebulosa.indi.device.camera.FrameType
import nebulosa.log.loggerFor
import org.springframework.stereotype.Service
Expand All @@ -31,7 +34,7 @@ class CalibrationFrameService(

return if (darkFrame != null || biasFrame != null || flatFrame != null) {
var transformedImage = if (createNew) image.clone() else image
var calibrationImage = Image(transformedImage.width, transformedImage.height, Header.EMPTY, transformedImage.mono)
var calibrationImage = Image(transformedImage.width, transformedImage.height, Header.Empty, transformedImage.mono)

if (biasFrame != null) {
calibrationImage = biasFrame.path!!.fits().use(calibrationImage::load)!!
Expand Down Expand Up @@ -98,7 +101,8 @@ class CalibrationFrameService(

try {
file.fits().use { fits ->
val (header) = fits.filterIsInstance<ImageHdu>().firstOrNull() ?: return@use
val hdu = fits.filterIsInstance<ImageHdu>().firstOrNull() ?: return@use
val header = hdu.header
val frameType = header.frameType?.takeIf { it != FrameType.LIGHT } ?: return@use

val exposureTime = if (frameType == FrameType.DARK) header.exposureTimeInMicroseconds else 0L
Expand Down Expand Up @@ -174,7 +178,7 @@ class CalibrationFrameService(

@JvmStatic private val LOG = loggerFor<CalibrationFrameService>()

@JvmStatic val Header.frameType
@JvmStatic val ReadableHeader.frameType
get() = frame?.uppercase()?.let {
if ("LIGHT" in it) FrameType.LIGHT
else if ("DARK" in it) FrameType.DARK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import nebulosa.batch.processing.JobExecution
import nebulosa.batch.processing.JobStatus
import nebulosa.batch.processing.StepExecution
import nebulosa.batch.processing.delay.DelayStep
import nebulosa.image.format.ImageRepresentation
import java.nio.file.Path

data class CameraCaptureEventHandler(private val observer: Observer<MessageEvent>) : CameraCaptureListener {

Expand All @@ -31,8 +33,8 @@ data class CameraCaptureEventHandler(private val observer: Observer<MessageEvent
sendCameraExposureEvent(step, stepExecution, state)
}

override fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution) {
sendCameraExposureEvent(step, stepExecution, CameraCaptureState.EXPOSURE_FINISHED)
override fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution, image: ImageRepresentation?, savedPath: Path) {
sendCameraExposureEvent(step, stepExecution, CameraCaptureState.EXPOSURE_FINISHED, savedPath)
}

override fun onCaptureFinished(step: CameraExposureStep, jobExecution: JobExecution) {
Expand All @@ -41,7 +43,7 @@ data class CameraCaptureEventHandler(private val observer: Observer<MessageEvent
observer.onNext(CameraCaptureFinished(jobExecution, step.camera, step.exposureAmount, captureElapsedTime, aborted))
}

fun sendCameraExposureEvent(step: CameraExposureStep, stepExecution: StepExecution, state: CameraCaptureState) {
fun sendCameraExposureEvent(step: CameraExposureStep, stepExecution: StepExecution, state: CameraCaptureState, savedPath: Path? = null) {
val exposureCount = stepExecution.context.getInt(CameraExposureStep.EXPOSURE_COUNT)
val captureElapsedTime = stepExecution.context.getDuration(CameraExposureStep.CAPTURE_ELAPSED_TIME)
val captureProgress = stepExecution.context.getDouble(CameraExposureStep.CAPTURE_PROGRESS)
Expand Down Expand Up @@ -83,7 +85,7 @@ data class CameraCaptureEventHandler(private val observer: Observer<MessageEvent
stepExecution.jobExecution, step.camera,
step.exposureAmount, exposureCount,
captureElapsedTime, captureProgress, captureRemainingTime,
step.savedPath!!
savedPath!!,
)
}
else -> return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package nebulosa.api.cameras

import nebulosa.batch.processing.JobExecution
import nebulosa.batch.processing.StepExecution
import nebulosa.image.format.ImageRepresentation
import java.nio.file.Path

interface CameraCaptureListener {

Expand All @@ -11,7 +13,7 @@ interface CameraCaptureListener {

fun onExposureElapsed(step: CameraExposureStep, stepExecution: StepExecution) = Unit

fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution) = Unit
fun onExposureFinished(step: CameraExposureStep, stepExecution: StepExecution, image: ImageRepresentation?, savedPath: Path) = Unit

fun onCaptureFinished(step: CameraExposureStep, jobExecution: JobExecution) = Unit
}
32 changes: 15 additions & 17 deletions api/src/main/kotlin/nebulosa/api/cameras/CameraExposureStep.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import nebulosa.batch.processing.StepResult
import nebulosa.batch.processing.delay.DelayStep
import nebulosa.batch.processing.delay.DelayStepListener
import nebulosa.common.concurrency.latch.CountUpDownLatch
import nebulosa.fits.Fits
import nebulosa.indi.device.camera.*
import nebulosa.io.transferAndClose
import nebulosa.log.debug
Expand All @@ -20,7 +19,6 @@ import okio.sink
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.InputStream
import java.nio.file.Path
import java.time.Duration
import java.time.LocalDateTime
Expand Down Expand Up @@ -50,9 +48,6 @@ data class CameraExposureStep(

@Volatile private var stepExecution: StepExecution? = null

@Volatile override var savedPath: Path? = null
private set

override fun registerCameraCaptureListener(listener: CameraCaptureListener): Boolean {
return listeners.add(listener)
}
Expand All @@ -66,7 +61,7 @@ data class CameraExposureStep(
if (event.device === camera) {
when (event) {
is CameraFrameCaptured -> {
save(event.stream, event.fits)
save(event)
}
is CameraExposureAborted,
is CameraExposureFailed,
Expand Down Expand Up @@ -168,25 +163,26 @@ data class CameraExposureStep(
}
}

private fun save(stream: InputStream?, fits: Fits?) {
private fun save(event: CameraFrameCaptured) {
try {
savedPath = request.makeSavePath(camera)
val savedPath = request.makeSavePath(camera)

LOG.info("saving FITS. path={}", savedPath)
LOG.info("saving FITS image at {}", savedPath)

savedPath!!.createParentDirectories()
savedPath.createParentDirectories()

if (stream != null) {
stream.transferAndClose(savedPath!!.outputStream())
} else if (fits != null) {
savedPath!!.outputStream().use { fits.writeTo(it.sink()) }
if (event.stream != null) {
event.stream!!.transferAndClose(savedPath.outputStream())
} else if (event.image != null) {
savedPath.sink().use(event.image!!::write)
} else {
LOG.warn("invalid event. camera={}", event.device)
return
}

listeners.forEach { it.onExposureFinished(this, stepExecution!!) }
listeners.forEach { it.onExposureFinished(this, stepExecution!!, event.image, savedPath) }
} catch (e: Throwable) {
LOG.error("failed to save FITS", e)
LOG.error("failed to save FITS image", e)
aborted = true
} finally {
latch.countDown()
Expand Down Expand Up @@ -230,7 +226,9 @@ data class CameraExposureStep(
@JvmStatic private val DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd.HHmmssSSS")

@JvmStatic
fun CameraStartCaptureRequest.makeSavePath(camera: Camera, autoSave: Boolean = this.autoSave): Path {
internal fun CameraStartCaptureRequest.makeSavePath(
camera: Camera, autoSave: Boolean = this.autoSave,
): Path {
return if (autoSave) {
val now = LocalDateTime.now()
val savePath = autoSubFolderMode.pathFor(savePath!!, now)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ data class CameraLoopExposureStep(
private val cameraExposureStep = CameraExposureStep(camera, request)
private val delayStep = DelayStep(request.exposureDelay)

override val savedPath
get() = cameraExposureStep.savedPath

init {
delayStep.registerDelayStepListener(cameraExposureStep)
}
Expand Down
Loading