From dfc2a74091626670b260fa59a9ef19124fe512b4 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Thu, 13 Jun 2024 18:30:25 -0300 Subject: [PATCH 1/3] [api][desktop]: Support Siril Star Detector --- .../api/stardetector/StarDetectionRequest.kt | 3 + .../api/stardetector/StarDetectorType.kt | 3 +- .../src/app/alignment/alignment.component.ts | 4 +- desktop/src/app/image/image.component.html | 9 +- desktop/src/app/image/image.component.ts | 10 +- .../src/app/settings/settings.component.ts | 6 +- desktop/src/shared/pipes/dropdown-options.ts | 2 +- desktop/src/shared/services/api.service.ts | 6 +- .../src/shared/services/preference.service.ts | 6 +- desktop/src/shared/types/alignment.types.ts | 4 +- desktop/src/shared/types/autofocus.type.ts | 6 +- desktop/src/shared/types/image.types.ts | 12 +- desktop/src/shared/types/settings.types.ts | 12 +- .../nebulosa/adql/ConstellationBoundary.kt | 26 ++-- .../nebulosa/common/exec/CommandLine.kt | 2 +- nebulosa-siril/build.gradle.kts | 1 + .../kotlin/nebulosa/siril/command/FindStar.kt | 111 ++++++++++++++++++ .../siril/stardetector/SirilStarDetector.kt | 22 ++++ .../src/test/kotlin/SirilLiveStackerTest.kt | 7 ++ 19 files changed, 207 insertions(+), 45 deletions(-) create mode 100644 nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt create mode 100644 nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt diff --git a/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectionRequest.kt b/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectionRequest.kt index d5af3d394..c43e42c1f 100644 --- a/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectionRequest.kt +++ b/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectionRequest.kt @@ -5,6 +5,7 @@ import nebulosa.pixinsight.script.PixInsightIsRunning import nebulosa.pixinsight.script.PixInsightScriptRunner import nebulosa.pixinsight.script.PixInsightStartup import nebulosa.pixinsight.stardetector.PixInsightStarDetector +import nebulosa.siril.stardetector.SirilStarDetector import nebulosa.stardetector.StarDetector import java.nio.file.Path import java.time.Duration @@ -15,11 +16,13 @@ data class StarDetectionRequest( @JvmField val executablePath: Path? = null, @JvmField val timeout: Duration = Duration.ZERO, @JvmField val minSNR: Double = 0.0, + @JvmField val maxStars: Int = 0, @JvmField val slot: Int = 1, ) : Supplier> { override fun get() = when (type) { StarDetectorType.ASTAP -> AstapStarDetector(executablePath!!, minSNR) + StarDetectorType.SIRIL -> SirilStarDetector(executablePath!!, maxStars) StarDetectorType.PIXINSIGHT -> { val runner = PixInsightScriptRunner(executablePath!!) diff --git a/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectorType.kt b/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectorType.kt index b69842640..56f51327e 100644 --- a/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectorType.kt +++ b/api/src/main/kotlin/nebulosa/api/stardetector/StarDetectorType.kt @@ -2,5 +2,6 @@ package nebulosa.api.stardetector enum class StarDetectorType { ASTAP, - PIXINSIGHT + PIXINSIGHT, + SIRIL, } diff --git a/desktop/src/app/alignment/alignment.component.ts b/desktop/src/app/alignment/alignment.component.ts index e0808fd88..4aa54e0ce 100644 --- a/desktop/src/app/alignment/alignment.component.ts +++ b/desktop/src/app/alignment/alignment.component.ts @@ -10,7 +10,7 @@ import { Angle } from '../../shared/types/atlas.types' import { Camera, EMPTY_CAMERA, EMPTY_CAMERA_START_CAPTURE, ExposureTimeUnit, updateCameraStartCaptureFromCamera } from '../../shared/types/camera.types' import { EMPTY_GUIDE_OUTPUT, GuideDirection, GuideOutput } from '../../shared/types/guider.types' import { EMPTY_MOUNT, Mount } from '../../shared/types/mount.types' -import { EMPTY_PLATE_SOLVER_OPTIONS } from '../../shared/types/settings.types' +import { EMPTY_PLATE_SOLVER_REQUEST } from '../../shared/types/settings.types' import { deviceComparator } from '../../shared/utils/comparators' import { AppComponent } from '../app.component' import { CameraComponent } from '../camera/camera.component' @@ -40,7 +40,7 @@ export class AlignmentComponent implements AfterViewInit, OnDestroy, Pingable { readonly tppaRequest: TPPAStart = { capture: structuredClone(EMPTY_CAMERA_START_CAPTURE), - plateSolver: structuredClone(EMPTY_PLATE_SOLVER_OPTIONS), + plateSolver: structuredClone(EMPTY_PLATE_SOLVER_REQUEST), startFromCurrentPosition: true, stepDirection: 'EAST', compensateRefraction: true, diff --git a/desktop/src/app/image/image.component.html b/desktop/src/app/image/image.component.html index 3e0ee793c..f24ba2d51 100644 --- a/desktop/src/app/image/image.component.html +++ b/desktop/src/app/image/image.component.html @@ -451,13 +451,20 @@ -
+
+
+ + + + +
COMPUTED
diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 30cb5d0bd..e66504012 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -110,6 +110,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { running: false, type: 'ASTAP', minSNR: 0, + maxStars: 0, visible: false, stars: [], computed: { @@ -799,6 +800,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { async detectStars() { const options = this.preference.starDetectionRequest(this.starDetection.type).get() options.minSNR = this.starDetection.minSNR + options.maxStars = this.starDetection.maxStars try { this.starDetection.running = true @@ -1275,8 +1277,9 @@ export class ImageComponent implements AfterViewInit, OnDestroy { const preference = this.preference.imagePreference.get() this.solver.radius = preference.solverRadius ?? this.solver.radius this.solver.type = preference.solverType ?? 'ASTAP' - this.starDetection.type = preference.starDetectionType ?? this.starDetection.type - this.starDetection.minSNR = preference.starDetectionMinSNR ?? this.preference.starDetectionRequest(this.starDetection.type).get().minSNR ?? this.starDetection.minSNR + this.starDetection.type = preference.starDetection?.type ?? this.starDetection.type + this.starDetection.minSNR = preference.starDetection?.minSNR ?? this.preference.starDetectionRequest(this.starDetection.type).get().minSNR ?? this.starDetection.minSNR + this.starDetection.maxStars = preference.starDetection?.maxStars ?? this.starDetection.maxStars this.fov.fovs = this.preference.imageFOVs.get() this.fov.fovs.forEach(e => { e.enabled = false; e.computed = undefined }) @@ -1288,8 +1291,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { preference.solverType = this.solver.type preference.solverPixelSize = this.solver.pixelSize preference.solverFocalLength = this.solver.focalLength - preference.starDetectionType = this.starDetection.type - preference.starDetectionMinSNR = this.starDetection.minSNR + preference.starDetection = this.starDetection this.preference.imagePreference.set(preference) } diff --git a/desktop/src/app/settings/settings.component.ts b/desktop/src/app/settings/settings.component.ts index a444ddcd8..5802796bb 100644 --- a/desktop/src/app/settings/settings.component.ts +++ b/desktop/src/app/settings/settings.component.ts @@ -6,7 +6,7 @@ import { PreferenceService } from '../../shared/services/preference.service' import { PrimeService } from '../../shared/services/prime.service' import { EMPTY_LOCATION, Location } from '../../shared/types/atlas.types' import { LiveStackerType, LiveStackingRequest } from '../../shared/types/camera.types' -import { PlateSolverOptions, PlateSolverType, StarDetectionOptions, StarDetectorType } from '../../shared/types/settings.types' +import { PlateSolverRequest, PlateSolverType, StarDetectionRequest, StarDetectorType } from '../../shared/types/settings.types' import { AppComponent } from '../app.component' @Component({ @@ -40,10 +40,10 @@ export class SettingsComponent implements AfterViewInit, OnDestroy { location: Location plateSolverType: PlateSolverType = 'ASTAP' - readonly plateSolvers = new Map() + readonly plateSolvers = new Map() starDetectorType: StarDetectorType = 'ASTAP' - readonly starDetectors = new Map() + readonly starDetectors = new Map() liveStackerType: LiveStackerType = 'SIRIL' readonly liveStackers = new Map() diff --git a/desktop/src/shared/pipes/dropdown-options.ts b/desktop/src/shared/pipes/dropdown-options.ts index 717c70fcc..36b62d3cc 100644 --- a/desktop/src/shared/pipes/dropdown-options.ts +++ b/desktop/src/shared/pipes/dropdown-options.ts @@ -23,7 +23,7 @@ export class DropdownOptionsPipe implements PipeTransform { transform(type: K): DropdownOptions[K] { switch (type) { - case 'STAR_DETECTOR': return ['ASTAP', 'PIXINSIGHT'] as DropdownOptions[K] + case 'STAR_DETECTOR': return ['ASTAP', 'PIXINSIGHT', 'SIRIL'] as DropdownOptions[K] case 'PLATE_SOLVER': return ['ASTAP', 'ASTROMETRY_NET_ONLINE', 'SIRIL'] as DropdownOptions[K] case 'AUTO_FOCUS_FITTING_MODE': return ['TRENDLINES', 'PARABOLIC', 'TREND_PARABOLIC', 'HYPERBOLIC', 'TREND_HYPERBOLIC'] as DropdownOptions[K] case 'AUTO_FOCUS_BACKLASH_COMPENSATION_MODE': return ['NONE', 'ABSOLUTE', 'OVERSHOOT'] as DropdownOptions[K] diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts index c55a3ebe6..b0c3b308a 100644 --- a/desktop/src/shared/services/api.service.ts +++ b/desktop/src/shared/services/api.service.ts @@ -15,7 +15,7 @@ import { CoordinateInterpolation, DetectedStar, FOVCamera, FOVTelescope, ImageAn import { CelestialLocationType, Mount, MountRemoteControl, MountRemoteControlType, SlewRate, TrackMode } from '../types/mount.types' import { Rotator } from '../types/rotator.types' import { SequencePlan } from '../types/sequencer.types' -import { PlateSolverOptions, StarDetectionOptions } from '../types/settings.types' +import { PlateSolverRequest, StarDetectionRequest } from '../types/settings.types' import { FilterWheel } from '../types/wheel.types' import { HttpService } from './http.service' @@ -563,7 +563,7 @@ export class ApiService { return this.http.get(`image/coordinate-interpolation?${query}`) } - detectStars(path: string, starDetector: StarDetectionOptions) { + detectStars(path: string, starDetector: StarDetectionRequest) { const query = this.http.query({ path }) return this.http.put(`star-detection?${query}`, starDetector) } @@ -672,7 +672,7 @@ export class ApiService { // SOLVER solveImage( - solver: PlateSolverOptions, path: string, blind: boolean, + solver: PlateSolverRequest, path: string, blind: boolean, centerRA: Angle, centerDEC: Angle, radius: Angle, ) { const query = this.http.query({ path, blind, centerRA, centerDEC, radius }) diff --git a/desktop/src/shared/services/preference.service.ts b/desktop/src/shared/services/preference.service.ts index c30a99f8d..872b021c5 100644 --- a/desktop/src/shared/services/preference.service.ts +++ b/desktop/src/shared/services/preference.service.ts @@ -10,7 +10,7 @@ import { Focuser, FocuserPreference } from '../types/focuser.types' import { ConnectionDetails, Equipment, HomePreference } from '../types/home.types' import { EMPTY_IMAGE_PREFERENCE, FOV, ImagePreference } from '../types/image.types' import { Rotator, RotatorPreference } from '../types/rotator.types' -import { EMPTY_PLATE_SOLVER_OPTIONS, EMPTY_STAR_DETECTION_OPTIONS, PlateSolverOptions as PlateSolverRequest, PlateSolverType, StarDetectionOptions as StarDetectionRequest, StarDetectorType } from '../types/settings.types' +import { EMPTY_PLATE_SOLVER_REQUEST, EMPTY_STAR_DETECTION_REQUEST, PlateSolverRequest, PlateSolverType, StarDetectionRequest, StarDetectorType } from '../types/settings.types' import { FilterWheel, WheelPreference } from '../types/wheel.types' import { LocalStorageService } from './local-storage.service' @@ -69,11 +69,11 @@ export class PreferenceService { } plateSolverRequest(type: PlateSolverType) { - return new PreferenceData(this.storage, `plateSolver.${type}`, () => { ...EMPTY_PLATE_SOLVER_OPTIONS, type }) + return new PreferenceData(this.storage, `plateSolver.${type}`, () => { ...EMPTY_PLATE_SOLVER_REQUEST, type }) } starDetectionRequest(type: StarDetectorType) { - return new PreferenceData(this.storage, `starDetection.${type}`, () => { ...EMPTY_STAR_DETECTION_OPTIONS, type }) + return new PreferenceData(this.storage, `starDetection.${type}`, () => { ...EMPTY_STAR_DETECTION_REQUEST, type }) } liveStackingRequest(type: LiveStackerType) { diff --git a/desktop/src/shared/types/alignment.types.ts b/desktop/src/shared/types/alignment.types.ts index 19b4ddfd0..afc3fef7a 100644 --- a/desktop/src/shared/types/alignment.types.ts +++ b/desktop/src/shared/types/alignment.types.ts @@ -1,7 +1,7 @@ import { Angle } from './atlas.types' import { Camera, CameraCaptureEvent, CameraStartCapture } from './camera.types' import { GuideDirection } from './guider.types' -import { PlateSolverOptions, PlateSolverType } from './settings.types' +import { PlateSolverRequest, PlateSolverType } from './settings.types' export type Hemisphere = 'NORTHERN' | 'SOUTHERN' @@ -52,7 +52,7 @@ export interface DARVEvent extends MessageEvent { export interface TPPAStart { capture: CameraStartCapture - plateSolver: PlateSolverOptions + plateSolver: PlateSolverRequest startFromCurrentPosition: boolean compensateRefraction: boolean stopTrackingWhenDone: boolean diff --git a/desktop/src/shared/types/autofocus.type.ts b/desktop/src/shared/types/autofocus.type.ts index 4b84c4c4a..b17c56a8e 100644 --- a/desktop/src/shared/types/autofocus.type.ts +++ b/desktop/src/shared/types/autofocus.type.ts @@ -1,6 +1,6 @@ import { Point } from 'electron' import { CameraCaptureEvent, CameraStartCapture } from './camera.types' -import { EMPTY_STAR_DETECTION_OPTIONS, StarDetectionOptions } from './settings.types' +import { EMPTY_STAR_DETECTION_REQUEST, StarDetectionRequest } from './settings.types' export type AutoFocusState = 'IDLE' | 'MOVING' | 'EXPOSURING' | 'EXPOSURED' | 'ANALYSING' | 'ANALYSED' | 'CURVE_FITTED' | 'FAILED' | 'FINISHED' @@ -22,7 +22,7 @@ export interface AutoFocusRequest { initialOffsetSteps: number stepSize: number totalNumberOfAttempts: number - starDetector: StarDetectionOptions + starDetector: StarDetectionRequest } export interface AutoFocusPreference extends Omit { } @@ -38,7 +38,7 @@ export const EMPTY_AUTO_FOCUS_PREFERENCE: AutoFocusPreference = { backlashIn: 0, backlashOut: 0 }, - starDetector: EMPTY_STAR_DETECTION_OPTIONS, + starDetector: EMPTY_STAR_DETECTION_REQUEST, } export interface Curve { diff --git a/desktop/src/shared/types/image.types.ts b/desktop/src/shared/types/image.types.ts index a28a2a2b6..6f9b0aed7 100644 --- a/desktop/src/shared/types/image.types.ts +++ b/desktop/src/shared/types/image.types.ts @@ -1,7 +1,7 @@ import { Point, Size } from 'electron' import { Angle, AstronomicalObject, DeepSkyObject, EquatorialCoordinateJ2000, Star } from './atlas.types' import { Camera, CameraStartCapture } from './camera.types' -import { PlateSolverType, StarDetectorType } from './settings.types' +import { PlateSolverType, StarDetectionRequest, StarDetectorType } from './settings.types' export type ImageChannel = 'RED' | 'GREEN' | 'BLUE' | 'GRAY' @@ -117,14 +117,17 @@ export interface ImagePreference { solverFocalLength?: number solverPixelSize?: number savePath?: string - starDetectionType?: StarDetectorType - starDetectionMinSNR?: number + starDetection?: Pick } export const EMPTY_IMAGE_PREFERENCE: ImagePreference = { solverRadius: 4, solverType: 'ASTAP', - starDetectionType: 'ASTAP' + starDetection: { + type: 'ASTAP', + minSNR: 0, + maxStars: 0, + } } export interface ImageData { @@ -286,6 +289,7 @@ export interface StarDetectionDialog { running: boolean type: StarDetectorType minSNR: number + maxStars: number visible: boolean stars: DetectedStar[] computed: Omit & { minFlux: number, maxFlux: number } diff --git a/desktop/src/shared/types/settings.types.ts b/desktop/src/shared/types/settings.types.ts index 85f5c84a1..201c3193c 100644 --- a/desktop/src/shared/types/settings.types.ts +++ b/desktop/src/shared/types/settings.types.ts @@ -1,6 +1,6 @@ export type PlateSolverType = 'ASTROMETRY_NET' | 'ASTROMETRY_NET_ONLINE' | 'ASTAP' | 'SIRIL' -export interface PlateSolverOptions { +export interface PlateSolverRequest { type: PlateSolverType executablePath: string downsampleFactor: number @@ -9,7 +9,7 @@ export interface PlateSolverOptions { timeout: number } -export const EMPTY_PLATE_SOLVER_OPTIONS: PlateSolverOptions = { +export const EMPTY_PLATE_SOLVER_REQUEST: PlateSolverRequest = { type: 'ASTAP', executablePath: '', downsampleFactor: 0, @@ -18,20 +18,22 @@ export const EMPTY_PLATE_SOLVER_OPTIONS: PlateSolverOptions = { timeout: 300, } -export type StarDetectorType = 'ASTAP' | 'PIXINSIGHT' +export type StarDetectorType = 'ASTAP' | 'PIXINSIGHT' | 'SIRIL' -export interface StarDetectionOptions { +export interface StarDetectionRequest { type: StarDetectorType executablePath: string timeout: number minSNR: number + maxStars: number slot: number } -export const EMPTY_STAR_DETECTION_OPTIONS: StarDetectionOptions = { +export const EMPTY_STAR_DETECTION_REQUEST: StarDetectionRequest = { type: 'ASTAP', executablePath: '', timeout: 300, minSNR: 0, + maxStars: 0, slot: 1, } diff --git a/nebulosa-adql/src/main/kotlin/nebulosa/adql/ConstellationBoundary.kt b/nebulosa-adql/src/main/kotlin/nebulosa/adql/ConstellationBoundary.kt index b04471769..1bd488f1c 100644 --- a/nebulosa-adql/src/main/kotlin/nebulosa/adql/ConstellationBoundary.kt +++ b/nebulosa-adql/src/main/kotlin/nebulosa/adql/ConstellationBoundary.kt @@ -6,7 +6,7 @@ import nebulosa.io.resource import nebulosa.math.hours import nebulosa.math.toDegrees -class ConstellationBoundary internal constructor(override val operand: PolygonFunction) : Region { +data class ConstellationBoundary(override val operand: PolygonFunction) : Region { constructor(constellationName: String) : this(PolygonFunction(Region.ICRS, BOUNDARY[constellationName.uppercase()])) @@ -15,17 +15,19 @@ class ConstellationBoundary internal constructor(override val operand: PolygonFu private val BOUNDARY = HashMap>(88) init { - for (line in resource("constellations_bound_in_20.txt")!!.bufferedReader().lines()) { - if (line.isEmpty() || line.startsWith('#')) continue - - val parts = line.split(" ") - val rightAscension = parts[0].hours.toDegrees - val declination = parts[1].toDouble() - val name = parts[2].trim() - - with(BOUNDARY.getOrPut(name) { ArrayList(150) }) { - add(NumericConstant(rightAscension)) - add(NumericConstant(declination)) + resource("constellations_bound_in_20.txt")!!.use { + for (line in it.bufferedReader().lines()) { + if (line.isEmpty() || line.startsWith('#')) continue + + val parts = line.split(" ") + val rightAscension = parts[0].hours.toDegrees + val declination = parts[1].toDouble() + val name = parts[2].trim() + + with(BOUNDARY.getOrPut(name) { ArrayList(150) }) { + add(NumericConstant(rightAscension)) + add(NumericConstant(declination)) + } } } } diff --git a/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt b/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt index 0a27196d5..ec0912b99 100644 --- a/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt +++ b/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt @@ -180,7 +180,7 @@ data class CommandLine internal constructor( } catch (e: InterruptedException) { LOG.error("command line interrupted") } catch (e: Throwable) { - LOG.error("command line failed", e) + LOG.error("command line failed: {}", e.message) } finally { completable.complete(Unit) reader.close() diff --git a/nebulosa-siril/build.gradle.kts b/nebulosa-siril/build.gradle.kts index 848424aef..eb0f4ddb0 100644 --- a/nebulosa-siril/build.gradle.kts +++ b/nebulosa-siril/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { api(project(":nebulosa-math")) api(project(":nebulosa-livestacker")) api(project(":nebulosa-platesolver")) + api(project(":nebulosa-stardetector")) implementation(project(":nebulosa-log")) testImplementation(project(":nebulosa-test")) } diff --git a/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt b/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt new file mode 100644 index 000000000..cdcf1fd5c --- /dev/null +++ b/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt @@ -0,0 +1,111 @@ +package nebulosa.siril.command + +import nebulosa.common.concurrency.latch.CountUpDownLatch +import nebulosa.common.exec.CommandLineListener +import nebulosa.fits.height +import nebulosa.log.debug +import nebulosa.log.loggerFor +import nebulosa.stardetector.StarPoint +import java.io.Closeable +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.TimeUnit +import kotlin.io.path.bufferedReader + +/** + * Detects stars in the currently loaded image. + */ +data class FindStar( + @JvmField val path: Path, + @JvmField val maxStars: Int = 0, +) : SirilCommand>, CommandLineListener, Closeable { + + data class Star( + override val x: Double, + override val y: Double, + override val hfd: Double, + override val snr: Double, + override val flux: Double, + ) : StarPoint + + private val outputPath by lazy { Files.createTempFile("siril-", ".txt") } + + private val command by lazy { + buildString(256) { + append("findstar \"-out=$outputPath\"") + if (maxStars > 0) append(" -maxstars=$maxStars") + } + } + + private val latch = CountUpDownLatch(0) + + override fun onLineRead(line: String) { + LOG.debug { line } + + if (line.startsWith("log: The file") && line.endsWith("has been created.")) { + latch.reset() + } + } + + override fun onExit(exitCode: Int, exception: Throwable?) { + latch.reset() + } + + override fun write(commandLine: SirilCommandLine): List { + if (commandLine.execute(Load(path))) { + try { + latch.countUp() + + commandLine.registerCommandLineListener(this) + commandLine.write(command) + + if (!latch.await(15, TimeUnit.SECONDS)) { + return emptyList() + } + } finally { + commandLine.unregisterCommandLineListener(this) + close() + } + + val header = commandLine.execute(DumpHeader()) + Thread.sleep(1000) + return outputPath.parseStars(header.height) + } else { + return emptyList() + } + } + + override fun close() { + // outputPath.deleteIfExists() + } + + companion object { + + const val FWHM = 1.1774100225154747 // sqrt(2 * ln(2)) + + @JvmStatic private val LOG = loggerFor() + + @JvmStatic + private fun Path.parseStars(height: Int): List { + val stars = ArrayList(256) + + bufferedReader().use { + for (line in it.lines()) { + if (line.startsWith('#')) continue + + val columns = line.split('\t') + val x = columns[5].trim().toDouble() + val y = columns[6].trim().toDouble() + val fwhmx = columns[7].trim().toDouble() + val fwhmy = columns[8].trim().toDouble() + val fwhm = (fwhmx + fwhmy) / 2.0 + val hfd = fwhm / FWHM + + stars.add(Star(x, height - y, hfd, 0.0, 0.0)) + } + } + + return stars + } + } +} diff --git a/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt b/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt new file mode 100644 index 000000000..0aa2bee55 --- /dev/null +++ b/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt @@ -0,0 +1,22 @@ +package nebulosa.siril.stardetector + +import nebulosa.siril.command.FindStar +import nebulosa.siril.command.SirilCommandLine +import nebulosa.stardetector.StarDetector +import nebulosa.stardetector.StarPoint +import java.nio.file.Path + +data class SirilStarDetector( + private val executablePath: Path, + private val maxStars: Int = 0, +) : StarDetector { + + override fun detect(input: Path): List { + val commandLine = SirilCommandLine(executablePath) + + return commandLine.use { + commandLine.run() + commandLine.execute(FindStar(input, maxStars)) + } + } +} diff --git a/nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt b/nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt index 1bc2cc7b1..6cafb84aa 100644 --- a/nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt +++ b/nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt @@ -10,6 +10,7 @@ import nebulosa.math.* import nebulosa.platesolver.Parity import nebulosa.siril.livestacker.SirilLiveStacker import nebulosa.siril.platesolver.SirilPlateSolver +import nebulosa.siril.stardetector.SirilStarDetector import nebulosa.test.AbstractFitsAndXisfTest import nebulosa.test.NonGitHubOnlyCondition import java.nio.file.Path @@ -59,5 +60,11 @@ class SirilLiveStackerTest : AbstractFitsAndXisfTest() { solution.widthInPixels shouldBeExactly 1280.0 solution.heightInPixels shouldBeExactly 1024.0 } + "star detector" { + val detector = SirilStarDetector(executablePath) + val stars = detector.detect(PI_01_LIGHT) + stars shouldHaveSize 126 + println(stars) + } } } From 6682e5c5671a4eb98c3c65eb42ae4459f17256c9 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Thu, 13 Jun 2024 19:13:32 -0300 Subject: [PATCH 2/3] [api][desktop]: Support Siril Star Detector --- desktop/src/app/image/image.component.ts | 28 +++++++++----- desktop/src/shared/types/image.types.ts | 37 ++++++++++--------- .../nebulosa/common/exec/CommandLine.kt | 4 +- .../kotlin/nebulosa/siril/command/FindStar.kt | 12 +++--- .../nebulosa/siril/command/SirilCommand.kt | 3 ++ .../siril/stardetector/SirilStarDetector.kt | 2 + .../{SirilLiveStackerTest.kt => SirilTest.kt} | 20 ++++++++-- 7 files changed, 69 insertions(+), 37 deletions(-) rename nebulosa-siril/src/test/kotlin/{SirilLiveStackerTest.kt => SirilTest.kt} (81%) diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index e66504012..3e7841167 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -921,8 +921,8 @@ export class ImageComponent implements AfterViewInit, OnDestroy { } } - this.solver.focalLength ||= imagePreference.solverFocalLength || 0 - this.solver.pixelSize ||= imagePreference.solverPixelSize || 0 + this.solver.focalLength ||= imagePreference.solver?.focalLength || 0 + this.solver.pixelSize ||= imagePreference.solver?.pixelSize || 0 } imageClicked(event: MouseEvent, contextMenu: boolean) { @@ -1275,8 +1275,10 @@ export class ImageComponent implements AfterViewInit, OnDestroy { private loadPreference() { const preference = this.preference.imagePreference.get() - this.solver.radius = preference.solverRadius ?? this.solver.radius - this.solver.type = preference.solverType ?? 'ASTAP' + this.solver.radius = preference.solver?.radius ?? this.solver.radius + this.solver.type = preference.solver?.type ?? 'ASTAP' + this.solver.focalLength = preference.solver?.focalLength ?? 0 + this.solver.pixelSize = preference.solver?.pixelSize ?? 0 this.starDetection.type = preference.starDetection?.type ?? this.starDetection.type this.starDetection.minSNR = preference.starDetection?.minSNR ?? this.preference.starDetectionRequest(this.starDetection.type).get().minSNR ?? this.starDetection.minSNR this.starDetection.maxStars = preference.starDetection?.maxStars ?? this.starDetection.maxStars @@ -1287,11 +1289,19 @@ export class ImageComponent implements AfterViewInit, OnDestroy { private savePreference() { const preference = this.preference.imagePreference.get() - preference.solverRadius = this.solver.radius - preference.solverType = this.solver.type - preference.solverPixelSize = this.solver.pixelSize - preference.solverFocalLength = this.solver.focalLength - preference.starDetection = this.starDetection + + preference.solver = { + type: this.solver.type, + focalLength: this.solver.focalLength, + pixelSize: this.solver.pixelSize, + radius: this.solver.radius, + } + preference.starDetection = { + type: this.starDetection.type, + maxStars: this.starDetection.maxStars, + minSNR: this.starDetection.minSNR, + } + this.preference.imagePreference.set(preference) } diff --git a/desktop/src/shared/types/image.types.ts b/desktop/src/shared/types/image.types.ts index 6f9b0aed7..f7bb8688d 100644 --- a/desktop/src/shared/types/image.types.ts +++ b/desktop/src/shared/types/image.types.ts @@ -1,7 +1,7 @@ import { Point, Size } from 'electron' import { Angle, AstronomicalObject, DeepSkyObject, EquatorialCoordinateJ2000, Star } from './atlas.types' import { Camera, CameraStartCapture } from './camera.types' -import { PlateSolverType, StarDetectionRequest, StarDetectorType } from './settings.types' +import { PlateSolverRequest, StarDetectionRequest } from './settings.types' export type ImageChannel = 'RED' | 'GREEN' | 'BLUE' | 'GRAY' @@ -111,18 +111,28 @@ export interface ImageStatistics { maximum: number } +export interface StarDetectionImagePreference extends Pick { +} + +export interface PlateSolverImagePreference extends Pick { + radius: number + focalLength: number + pixelSize: number +} + export interface ImagePreference { - solverRadius?: number - solverType?: PlateSolverType - solverFocalLength?: number - solverPixelSize?: number savePath?: string - starDetection?: Pick + solver?: PlateSolverImagePreference + starDetection?: StarDetectionImagePreference } export const EMPTY_IMAGE_PREFERENCE: ImagePreference = { - solverRadius: 4, - solverType: 'ASTAP', + solver: { + type: 'ASTAP', + radius: 4, + focalLength: 0, + pixelSize: 0, + }, starDetection: { type: 'ASTAP', minSNR: 0, @@ -213,17 +223,13 @@ export interface ImageStretchDialog { midtone: number } -export interface ImageSolverDialog { +export interface ImageSolverDialog extends PlateSolverImagePreference { showDialog: boolean running: boolean blind: boolean centerRA: Angle centerDEC: Angle - radius: number - focalLength: number - pixelSize: number readonly solved: ImageSolved - type: PlateSolverType } export interface ImageFOVDialog extends FOV { @@ -284,12 +290,9 @@ export interface ROISelected { height: number } -export interface StarDetectionDialog { +export interface StarDetectionDialog extends StarDetectionImagePreference { showDialog: boolean running: boolean - type: StarDetectorType - minSNR: number - maxStars: number visible: boolean stars: DetectedStar[] computed: Omit & { minFlux: number, maxFlux: number } diff --git a/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt b/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt index ec0912b99..73a923280 100644 --- a/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt +++ b/nebulosa-common/src/main/kotlin/nebulosa/common/exec/CommandLine.kt @@ -178,9 +178,9 @@ data class CommandLine internal constructor( } } } catch (e: InterruptedException) { - LOG.error("command line interrupted") + LOG.warn("command line interrupted") } catch (e: Throwable) { - LOG.error("command line failed: {}", e.message) + LOG.warn("command line exited: {}", e.message) } finally { completable.complete(Unit) reader.close() diff --git a/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt b/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt index cdcf1fd5c..5f2fc4e50 100644 --- a/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt +++ b/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/FindStar.kt @@ -94,14 +94,16 @@ data class FindStar( if (line.startsWith('#')) continue val columns = line.split('\t') - val x = columns[5].trim().toDouble() - val y = columns[6].trim().toDouble() - val fwhmx = columns[7].trim().toDouble() - val fwhmy = columns[8].trim().toDouble() + val flux = columns[3].trim().toDouble() // A ??? + val x = columns[5].trim().toDouble() // X + val y = columns[6].trim().toDouble() // Y + val fwhmx = columns[7].trim().toDouble() // FWHMx [px] + val fwhmy = columns[8].trim().toDouble() // FWHMy [px] + val snr = columns[12].trim().toDouble() // RMSE ??? val fwhm = (fwhmx + fwhmy) / 2.0 val hfd = fwhm / FWHM - stars.add(Star(x, height - y, hfd, 0.0, 0.0)) + stars.add(Star(x, height - y, hfd, snr, flux)) } } diff --git a/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/SirilCommand.kt b/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/SirilCommand.kt index a7b25f10d..ad6bf8b7a 100644 --- a/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/SirilCommand.kt +++ b/nebulosa-siril/src/main/kotlin/nebulosa/siril/command/SirilCommand.kt @@ -1,5 +1,8 @@ package nebulosa.siril.command +/** + * @see Commands + */ sealed interface SirilCommand { fun write(commandLine: SirilCommandLine): T diff --git a/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt b/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt index 0aa2bee55..78880f873 100644 --- a/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt +++ b/nebulosa-siril/src/main/kotlin/nebulosa/siril/stardetector/SirilStarDetector.kt @@ -6,6 +6,8 @@ import nebulosa.stardetector.StarDetector import nebulosa.stardetector.StarPoint import java.nio.file.Path +// https://gitlab.com/free-astro/siril/-/blob/master/src/algos/star_finder.c + data class SirilStarDetector( private val executablePath: Path, private val maxStars: Int = 0, diff --git a/nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt b/nebulosa-siril/src/test/kotlin/SirilTest.kt similarity index 81% rename from nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt rename to nebulosa-siril/src/test/kotlin/SirilTest.kt index 6cafb84aa..7da243822 100644 --- a/nebulosa-siril/src/test/kotlin/SirilLiveStackerTest.kt +++ b/nebulosa-siril/src/test/kotlin/SirilTest.kt @@ -18,7 +18,7 @@ import kotlin.io.path.copyTo import kotlin.io.path.listDirectoryEntries @EnabledIf(NonGitHubOnlyCondition::class) -class SirilLiveStackerTest : AbstractFitsAndXisfTest() { +class SirilTest : AbstractFitsAndXisfTest() { init { val executablePath = Path.of("siril-cli") @@ -62,9 +62,21 @@ class SirilLiveStackerTest : AbstractFitsAndXisfTest() { } "star detector" { val detector = SirilStarDetector(executablePath) - val stars = detector.detect(PI_01_LIGHT) - stars shouldHaveSize 126 - println(stars) + + with(detector.detect(PI_FOCUS_0)) { + this shouldHaveSize 307 + map { it.hfd }.average() shouldBe (7.9 plusOrMinus 1e-1) + } + + with(detector.detect(PI_FOCUS_30000)) { + this shouldHaveSize 258 + map { it.hfd }.average() shouldBe (1.1 plusOrMinus 1e-1) + } + + with(detector.detect(PI_FOCUS_100000)) { + this shouldHaveSize 82 + map { it.hfd }.average() shouldBe (22.4 plusOrMinus 1e-1) + } } } } From e6658bf5255ee5269b26fc3b36a5976c82ead9b2 Mon Sep 17 00:00:00 2001 From: tiagohm Date: Thu, 13 Jun 2024 20:01:56 -0300 Subject: [PATCH 3/3] [desktop]: Allow set maxStars on Settings for Siril --- desktop/src/app/image/image.component.ts | 2 +- desktop/src/app/settings/settings.component.html | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 3e7841167..742f6bc7f 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -1281,7 +1281,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { this.solver.pixelSize = preference.solver?.pixelSize ?? 0 this.starDetection.type = preference.starDetection?.type ?? this.starDetection.type this.starDetection.minSNR = preference.starDetection?.minSNR ?? this.preference.starDetectionRequest(this.starDetection.type).get().minSNR ?? this.starDetection.minSNR - this.starDetection.maxStars = preference.starDetection?.maxStars ?? this.starDetection.maxStars + this.starDetection.maxStars = preference.starDetection?.maxStars ?? this.preference.starDetectionRequest(this.starDetection.type).get().maxStars ?? this.starDetection.maxStars this.fov.fovs = this.preference.imageFOVs.get() this.fov.fovs.forEach(e => { e.enabled = false; e.computed = undefined }) diff --git a/desktop/src/app/settings/settings.component.html b/desktop/src/app/settings/settings.component.html index e3ba8e467..2e45ca29f 100644 --- a/desktop/src/app/settings/settings.component.html +++ b/desktop/src/app/settings/settings.component.html @@ -88,7 +88,7 @@ [path]="starDetectors.get(starDetectorType)!.executablePath" class="w-full" (pathChange)="starDetectors.get(starDetectorType)!.executablePath = $event; save()" />
-
+
Min SNR
+
+ + + + +