Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Oct 16, 2024
2 parents cc17d07 + dc14a18 commit fd15b3f
Show file tree
Hide file tree
Showing 126 changed files with 1,156 additions and 2,756 deletions.
2 changes: 1 addition & 1 deletion api/src/main/kotlin/nebulosa/api/Nebulosa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import io.javalin.Javalin
import io.javalin.http.Context
import io.javalin.http.HttpStatus.BAD_REQUEST
import io.javalin.json.JavalinJackson
import nebulosa.api.converters.modules.DeviceModule
import nebulosa.api.converters.DeviceModule
import nebulosa.api.core.ErrorResponse
import nebulosa.api.inject.*
import nebulosa.json.PathModule
Expand Down
6 changes: 3 additions & 3 deletions api/src/main/kotlin/nebulosa/api/atlas/LibWCSDownloadTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ class LibWCSDownloadTask(

companion object {

const val VERSION_URL = "https://raw.githubusercontent.com/tiagohm/nebulosa.data/main/wcs/VERSION.txt"
const val VERSION_URL = "https://raw.githubusercontent.com/tiagohm/nebulosa.data/main/libs/wcs/VERSION.txt"
const val VERSION_KEY = "LIBWCS.VERSION"

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

@JvmStatic private val LIBRARY_URLS = mapOf(
"linux-x86-64" to "https://raw.githubusercontent.com/tiagohm/nebulosa.data/main/wcs/linux-x86-64/libwcs.so",
"win32-x86-64" to "https://raw.githubusercontent.com/tiagohm/nebulosa.data/main/wcs/win32-x86-64/libwcs.dll",
"linux-x86-64" to "https://raw.githubusercontent.com/tiagohm/nebulosa.data/main/libs/wcs/linux-x86-64/libwcs.so",
"win32-x86-64" to "https://raw.githubusercontent.com/tiagohm/nebulosa.data/main/libs/wcs/win32-x86-64/libwcs.dll",
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package nebulosa.api.cameras

import nebulosa.api.calibration.CalibrationFrameService
import nebulosa.api.inject.Named
import nebulosa.api.message.MessageService
import nebulosa.api.wheels.WheelEventAware
import nebulosa.guiding.Guider
Expand All @@ -14,6 +15,8 @@ import nebulosa.indi.device.rotator.Rotator
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
Expand All @@ -25,7 +28,7 @@ class CameraCaptureExecutor(
private val guider: Guider,
private val executorService: ExecutorService,
eventBus: EventBus,
) : Consumer<CameraCaptureEvent>, CameraEventAware, WheelEventAware, Executor by executorService {
) : Consumer<CameraCaptureEvent>, CameraEventAware, WheelEventAware, KoinComponent, Executor by executorService {

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

Expand Down Expand Up @@ -55,7 +58,7 @@ class CameraCaptureExecutor(
check(camera.connected) { "${camera.name} Camera is not connected" }
check(jobs.none { it.camera === camera }) { "${camera.name} Camera Capture is already in progress" }

val liveStackingManager = CameraLiveStackingManager(calibrationFrameService)
val liveStackingManager = CameraLiveStackingManager(get(Named.liveStackingDir), calibrationFrameService)

with(CameraCaptureJob(this, camera, request, guider, liveStackingManager, mount, wheel, focuser, rotator)) {
val completable = runAsync(executorService)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package nebulosa.api.cameras

import nebulosa.api.calibration.CalibrationFrameProvider
import nebulosa.api.image.ImageFilterType
import nebulosa.api.livestacker.LiveStackingRequest
import nebulosa.api.stacker.StackerGroupType
import nebulosa.fits.*
import nebulosa.image.format.ImageHdu
import nebulosa.livestacker.LiveStacker
Expand All @@ -17,19 +17,21 @@ import java.util.*
import kotlin.io.path.*

data class CameraLiveStackingManager(
private val liveStackingDir: Path,
private val calibrationFrameProvider: CalibrationFrameProvider? = null,
) : AutoCloseable {

private val liveStackers = EnumMap<StackerGroupType, LiveStacker>(StackerGroupType::class.java)
private val liveStackers = EnumMap<ImageFilterType, LiveStacker>(ImageFilterType::class.java)
private val workingDirectories = HashSet<Path>()

@Volatile private var referencePath: Path? = null
@Volatile private var stackedPath: Path? = null

@Synchronized
fun start(request: CameraStartCaptureRequest, path: Path): Boolean {
if (request.stackerGroupType in liveStackers) {
return true
} else if (request.stackerGroupType != StackerGroupType.NONE && request.liveStacking.enabled) {
} else if (request.stackerGroupType != ImageFilterType.NONE && request.liveStacking.enabled) {
try {
val workingDirectory = Files.createTempDirectory("ls-${request.stackerGroupType}-")
workingDirectories.add(workingDirectory)
Expand All @@ -50,7 +52,7 @@ data class CameraLiveStackingManager(

@Synchronized
fun stack(request: CameraStartCaptureRequest, path: Path?): Path? {
if (path == null || request.stackerGroupType == StackerGroupType.NONE) return null
if (path == null || request.stackerGroupType == ImageFilterType.NONE) return null

val stackerGroupType = request.stackerGroupType
val liveStacker = liveStackers[stackerGroupType] ?: return null
Expand All @@ -65,10 +67,10 @@ data class CameraLiveStackingManager(
}

val combinedPath = Path.of("${path.parent}", "STACKED.fits")
val luminancePath = liveStackers[StackerGroupType.LUMINANCE]?.stackedPath
val redPath = liveStackers[StackerGroupType.RED]?.stackedPath
val greenPath = liveStackers[StackerGroupType.GREEN]?.stackedPath
val bluePath = liveStackers[StackerGroupType.BLUE]?.stackedPath
val luminancePath = liveStackers[ImageFilterType.LUMINANCE]?.stackedPath
val redPath = liveStackers[ImageFilterType.RED]?.stackedPath
val greenPath = liveStackers[ImageFilterType.GREEN]?.stackedPath
val bluePath = liveStackers[ImageFilterType.BLUE]?.stackedPath

if (stackerGroupType.isLRGB && (luminancePath != null || redPath != null || greenPath != null || bluePath != null)) {
if (stacker.combineLRGB(combinedPath, luminancePath, redPath, greenPath, bluePath)) {
Expand All @@ -77,13 +79,19 @@ data class CameraLiveStackingManager(
} else if (luminancePath != null) {
stacker.align(luminancePath, stackedPath, stackedPath)

if (stacker.combineLuminance(combinedPath, luminancePath, stackedPath, stackerGroupType == StackerGroupType.MONO)) {
if (stacker.combineLuminance(combinedPath, luminancePath, stackedPath, stackerGroupType == ImageFilterType.MONO)) {
stackedPath = combinedPath
}
}
}

return stackedPath ?: liveStacker.stackedPath
if (stackedPath != null && this.stackedPath == null) {
this.stackedPath = Path("$liveStackingDir", "${System.currentTimeMillis()}.${stackedPath.extension}")
}

this.stackedPath?.also { stackedPath?.copyTo(it, true) }

return this.stackedPath
}

fun stop(request: CameraStartCaptureRequest) {
Expand Down Expand Up @@ -147,7 +155,7 @@ data class CameraLiveStackingManager(

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

private inline val Path?.isCalibrationFrame
get() = this != null && exists() && isRegularFile() && (isFits() || isXisf())
private inline val Path.isCalibrationFrame
get() = exists() && isRegularFile() && (isFits() || isXisf())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package nebulosa.api.cameras

import nebulosa.api.converters.time.DurationUnit
import nebulosa.api.guiding.DitherAfterExposureRequest
import nebulosa.api.image.ImageFilterType
import nebulosa.api.livestacker.LiveStackingRequest
import nebulosa.api.stacker.StackerGroupType
import nebulosa.api.validators.*
import nebulosa.indi.device.camera.FrameType
import java.nio.file.Path
Expand Down Expand Up @@ -35,7 +35,7 @@ data class CameraStartCaptureRequest(
@JvmField val dither: DitherAfterExposureRequest = DitherAfterExposureRequest.DISABLED,
// Stacking.
@JvmField val liveStacking: LiveStackingRequest = LiveStackingRequest.DISABLED,
@JvmField val stackerGroupType: StackerGroupType = StackerGroupType.MONO,
@JvmField val stackerGroupType: ImageFilterType = ImageFilterType.MONO,
// Filter Wheel.
@JvmField val filterPosition: Int = 0,
@JvmField val shutterPosition: Int = 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nebulosa.api.converters.modules
package nebulosa.api.converters

import com.fasterxml.jackson.databind.module.SimpleDeserializers
import com.fasterxml.jackson.databind.module.SimpleModule
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package nebulosa.api.stacker
package nebulosa.api.image

import nebulosa.fits.*
import nebulosa.image.format.ReadableHeader
import nebulosa.indi.device.camera.FrameType
import nebulosa.indi.device.camera.FrameType.Companion.frameType

data class AnalyzedTarget(
data class ImageAnalyzed(
@JvmField val width: Int,
@JvmField val height: Int,
@JvmField val binX: Int,
@JvmField val binY: Int,
@JvmField val gain: Double,
@JvmField val exposureTime: Long,
@JvmField val type: FrameType,
@JvmField val group: StackerGroupType,
@JvmField val filter: ImageFilterType,
) {

constructor(header: ReadableHeader) : this(
header.width, header.height, header.binX, header.binY, header.gain, header.exposureTimeInMicroseconds,
header.frameType ?: FrameType.LIGHT, StackerGroupType.from(header)
header.frameType ?: FrameType.LIGHT, ImageFilterType.from(header)
)
}
6 changes: 6 additions & 0 deletions api/src/main/kotlin/nebulosa/api/image/ImageController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ImageController(
app.post("image", ::openImage)
app.delete("image", ::closeImage)
app.put("image/save-as", ::saveImageAs)
app.put("image/analyze", ::analyze)
app.put("image/annotations", ::annotations)
app.get("image/coordinate-interpolation", ::coordinateInterpolation)
app.get("image/histogram", ::histogram)
Expand All @@ -47,6 +48,11 @@ class ImageController(
imageService.saveImageAs(path, save)
}

private fun analyze(ctx: Context) {
val path = ctx.queryParam("path").notNull().path().exists()
imageService.analyze(path)?.also(ctx::json)
}

private fun annotations(ctx: Context) {
val path = ctx.queryParam("path").notNull().path().exists()
val request = ctx.bodyAsClass<AnnotateImageRequest>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package nebulosa.api.stacker
package nebulosa.api.image

import nebulosa.fits.filter
import nebulosa.fits.naxis
import nebulosa.image.format.ReadableHeader

enum class StackerGroupType {
enum class ImageFilterType {
NONE,
LUMINANCE,
RED,
Expand Down
2 changes: 2 additions & 0 deletions api/src/main/kotlin/nebulosa/api/image/ImageInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import nebulosa.api.converters.angle.DeclinationSerializer
import nebulosa.api.converters.angle.RightAscensionSerializer
import nebulosa.fits.Bitpix
import nebulosa.image.algorithms.computation.Statistics
import nebulosa.image.algorithms.transformation.CfaPattern
import nebulosa.indi.device.camera.Camera
import java.nio.file.Path

Expand All @@ -14,6 +15,7 @@ data class ImageInfo(
@JvmField val width: Int,
@JvmField val height: Int,
@JvmField val mono: Boolean,
@JvmField val bayer: CfaPattern?,
@JvmField val stretch: ImageTransformation.Stretch,
@field:JsonSerialize(using = RightAscensionSerializer::class) @JvmField val rightAscension: Double? = null,
@field:JsonSerialize(using = DeclinationSerializer::class) @JvmField val declination: Double? = null,
Expand Down
18 changes: 17 additions & 1 deletion api/src/main/kotlin/nebulosa/api/image/ImageService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import nebulosa.image.Image
import nebulosa.image.algorithms.computation.Histogram
import nebulosa.image.algorithms.computation.Statistics
import nebulosa.image.algorithms.transformation.*
import nebulosa.image.format.ImageHdu
import nebulosa.image.format.ImageModifier
import nebulosa.indi.device.camera.Camera
import nebulosa.log.*
Expand All @@ -31,6 +32,8 @@ import nebulosa.time.UTC
import nebulosa.wcs.WCS
import nebulosa.wcs.WCSException
import nebulosa.xisf.XisfFormat
import nebulosa.xisf.isXisf
import nebulosa.xisf.xisf
import okio.sink
import java.net.URI
import java.nio.file.Path
Expand All @@ -39,6 +42,8 @@ import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService
import javax.imageio.ImageIO
import kotlin.io.path.exists
import kotlin.io.path.isRegularFile
import kotlin.io.path.outputStream
import kotlin.math.roundToInt

Expand Down Expand Up @@ -86,7 +91,7 @@ class ImageService(

val info = ImageInfo(
path,
transformedImage.width, transformedImage.height, transformedImage.mono,
transformedImage.width, transformedImage.height, transformedImage.mono, CfaPattern.from(image.header),
transformation.stretch.copy(
shadow = (stretchParameters!!.shadow * 65536f).roundToInt(),
highlight = (stretchParameters.highlight * 65536f).roundToInt(),
Expand Down Expand Up @@ -299,6 +304,17 @@ class ImageService(
}
}

fun analyze(path: Path): ImageAnalyzed? {
if (!path.exists() || !path.isRegularFile()) return null

val image = if (path.isFits()) path.fits()
else if (path.isXisf()) path.xisf()
else return null

return image.use { it.firstOrNull { hdu -> hdu is ImageHdu }?.header }
?.let(::ImageAnalyzed)
}

fun frame(
rightAscension: Angle, declination: Angle,
width: Int, height: Int, fov: Angle,
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/kotlin/nebulosa/api/image/ImageSolved.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ data class ImageSolved(
solution.width.toArcmin, solution.height.toArcmin,
solution.radius.toDegrees,
)

companion object {

@JvmStatic val NO_SOLUTION = ImageSolved(PlateSolution.NO_SOLUTION)
}
}
8 changes: 3 additions & 5 deletions api/src/main/kotlin/nebulosa/api/inject/Inject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ import nebulosa.api.rotators.RotatorService
import nebulosa.api.sequencer.SequencerController
import nebulosa.api.sequencer.SequencerExecutor
import nebulosa.api.sequencer.SequencerService
import nebulosa.api.stacker.StackerController
import nebulosa.api.stacker.StackerService
import nebulosa.api.stardetector.StarDetectionController
import nebulosa.api.stardetector.StarDetectionService
import nebulosa.api.wheels.WheelController
Expand Down Expand Up @@ -118,6 +116,7 @@ object Named {
val sequencesDir = named("sequencesDir")
val cacheDir = named("cacheDir")
val libsDir = named("libsDir")
val liveStackingDir = named("liveStackingDir")
val defaultHttpClient = named("defaultHttpClient")
val alpacaHttpClient = named("alpacaHttpClient")
val mainBoxStore = named("mainBoxStore")
Expand Down Expand Up @@ -156,6 +155,7 @@ fun pathModule(root: Path = Path(requireNotNull(System.getProperty(APP_DIR_KEY))
single(Named.sequencesDir) { Path("$root", "sequences").createDirectories() }
single(Named.cacheDir) { Path("$root", "cache").createDirectories() }
single(Named.libsDir) { Path("$root", "libs").createDirectories() }
single(Named.liveStackingDir) { Path("$root", "live-stacking").createDirectories() }
}

// CORE
Expand Down Expand Up @@ -296,14 +296,13 @@ fun servicesModule() = module {
single { CalibrationFrameService(get()) }
single { FramingService(get(), get()) }
single { ImageService(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
single { PlateSolverService(get()) }
single { PlateSolverService(get(), get()) }
single { FlatWizardExecutor(get(), get(), get()) }
single { FlatWizardService(get(Named.capturesDir), get()) }
single { StarDetectionService() }
single { AutoFocusExecutor(get(), get(), get()) }
single { AutoFocusService(get()) }
single { LiveStackingService() }
single { StackerService(get()) }
single { FramingService(get(), get()) }
single { INDIService(get()) }
single { DARVExecutor(get(), get(), get()) }
Expand Down Expand Up @@ -341,7 +340,6 @@ fun controllersModule() = module(true) {
single { StarDetectionController(get(), get()) }
single { AutoFocusController(get(), get(), get()) }
single { LiveStackingController(get(), get(), get()) }
single { StackerController(get(), get()) }
single { FramingController(get(), get(), get()) }
single { INDIController(get(), get(), get()) }
single { PolarAlignmentController(get(), get(), get()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ class PlateSolverController(

private fun start(ctx: Context) {
val path = ctx.queryParam("path").notNullOrBlank().path().exists()
val key = ctx.queryParam("key").notNullOrBlank()
val solver = ctx.bodyAsClass<PlateSolverRequest>().valid()
ctx.json(plateSolverService.solveImage(solver, path))
ctx.json(plateSolverService.start(solver, path, key))
}

@Suppress("UNUSED_PARAMETER")
private fun stop(ctx: Context) {
plateSolverService.stopSolver()
plateSolverService.stop(ctx.queryParam("key").notNullOrBlank())
}
}
Loading

0 comments on commit fd15b3f

Please sign in to comment.