Skip to content

Commit

Permalink
[api][desktop]: Support Siril Plate Solver
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Jun 12, 2024
1 parent 17f5b9f commit 0b76e71
Show file tree
Hide file tree
Showing 17 changed files with 89 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import nebulosa.astap.platesolver.AstapPlateSolver
import nebulosa.astrometrynet.nova.NovaAstrometryNetService
import nebulosa.astrometrynet.platesolver.LocalAstrometryNetPlateSolver
import nebulosa.astrometrynet.platesolver.NovaAstrometryNetPlateSolver
import nebulosa.siril.platesolver.SirilPlateSolver
import okhttp3.OkHttpClient
import org.hibernate.validator.constraints.time.DurationMax
import org.hibernate.validator.constraints.time.DurationMin
Expand All @@ -16,6 +17,8 @@ data class PlateSolverRequest(
@JvmField val type: PlateSolverType = PlateSolverType.ASTROMETRY_NET_ONLINE,
@JvmField val executablePath: Path? = null,
@JvmField val downsampleFactor: Int = 0,
@JvmField val focalLength: Double = 0.0,
@JvmField val pixelSize: Double = 0.0,
@JvmField val apiUrl: String = "",
@JvmField val apiKey: String = "",
@field:DurationMin(seconds = 0) @field:DurationMax(minutes = 5) @field:DurationUnit(ChronoUnit.SECONDS)
Expand All @@ -31,6 +34,7 @@ data class PlateSolverRequest(
val service = NOVA_ASTROMETRY_NET_CACHE.getOrPut(key) { NovaAstrometryNetService(apiUrl, httpClient) }
NovaAstrometryNetPlateSolver(service, apiKey)
}
PlateSolverType.SIRIL -> SirilPlateSolver(executablePath!!, focalLength, pixelSize)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,5 @@ class PlateSolverService(
fun solve(
options: PlateSolverRequest, path: Path,
centerRA: Angle = 0.0, centerDEC: Angle = 0.0, radius: Angle = 0.0,
) = options.get(httpClient)
.solve(path, null, centerRA, centerDEC, radius, 1, options.timeout.takeIf { it.toSeconds() > 0 })
) = options.get(httpClient).solve(path, null, centerRA, centerDEC, radius, options.downsampleFactor, options.timeout)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ enum class PlateSolverType {
ASTAP,
ASTROMETRY_NET,
ASTROMETRY_NET_ONLINE,
SIRIL,
}
35 changes: 27 additions & 8 deletions desktop/src/app/image/image.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,34 +165,52 @@
<div class="col-12 gap-2 align-items-center">
<span class="p-float-label">
<p-dropdown [options]="'PLATE_SOLVER' | dropdownOptions" [(ngModel)]="solver.type" styleClass="p-inputtext-sm border-0"
[autoDisplayFirst]="false" />
[autoDisplayFirst]="false" appendTo="body" />
<label>Type</label>
</span>
<p-checkbox [binary]="true" label="Blind" [(ngModel)]="solver.blind" />
<p-checkbox [binary]="true" [disabled]="solver.type === 'SIRIL'" label="Blind" [(ngModel)]="solver.blind" />
</div>
<div class="col-8">
<div class="grid">
<div class="col-6">
<span class="p-float-label">
<input pInputText [disabled]="solver.blind" class="p-inputtext-sm border-0 w-full" [(ngModel)]="solver.centerRA" />
<input pInputText [disabled]="solver.blind && solver.type !== 'SIRIL'" class="p-inputtext-sm border-0 w-full"
[(ngModel)]="solver.centerRA" />
<label>Center RA (h)</label>
</span>
</div>
<div class="col-6">
<span class="p-float-label">
<input pInputText [disabled]="solver.blind" class="p-inputtext-sm border-0 w-full" [(ngModel)]="solver.centerDEC" />
<input pInputText [disabled]="solver.blind && solver.type !== 'SIRIL'" class="p-inputtext-sm border-0 w-full"
[(ngModel)]="solver.centerDEC" />
<label>Center DEC (°)</label>
</span>
</div>
</div>
</div>
<div class="col-4 flex flex-row align-items-center gap-2">
<div class="col-4 flex flex-row align-items-center">
<span class="p-float-label">
<p-inputNumber [disabled]="solver.blind" [min]="1" [max]="180" styleClass="p-inputtext-sm border-0 w-full" [showButtons]="true"
[(ngModel)]="solver.radius" scrollableNumber />
<p-inputNumber [disabled]="solver.blind && solver.type !== 'SIRIL'" [min]="1" [max]="180" styleClass="p-inputtext-sm border-0 w-full"
[showButtons]="true" [(ngModel)]="solver.radius" scrollableNumber />
<label>Radius (°)</label>
</span>
</div>
@if (solver.type === 'SIRIL') {
<div class="col-6 flex flex-row align-items-center">
<span class="p-float-label">
<p-inputNumber [min]="0" [max]="10000" styleClass="p-inputtext-sm border-0 w-full" [showButtons]="true"
[(ngModel)]="solver.focalLength" [allowEmpty]="false" locale="en" scrollableNumber />
<label>Focal length (mm)</label>
</span>
</div>
<div class="col-6 flex flex-row align-items-center">
<span class="p-float-label">
<p-inputNumber [min]="0" [max]="100" [step]="0.01" styleClass="p-inputtext-sm border-0 w-full" [showButtons]="true"
[(ngModel)]="solver.pixelSize" [allowEmpty]="false" locale="en" scrollableNumber />
<label>Pixel size (µm)</label>
</span>
</div>
}
</div>
<ng-template pTemplate="footer">
<div class="grid pt-2">
Expand Down Expand Up @@ -247,7 +265,8 @@
</div>
</div>

<p-button [disabled]="solver.running" icon="mdi mdi-sigma" label="Solve" (onClick)="solveImage()" [text]="true" size="small" />
<p-button [disabled]="solver.running || !canPlateSolve" icon="mdi mdi-sigma" label="Solve" (onClick)="solveImage()" [text]="true"
size="small" />
</ng-template>
</p-dialog>

Expand Down
29 changes: 27 additions & 2 deletions desktop/src/app/image/image.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { PreferenceService } from '../../shared/services/preference.service'
import { PrimeService } from '../../shared/services/prime.service'
import { Angle, EquatorialCoordinateJ2000 } from '../../shared/types/atlas.types'
import { Camera } from '../../shared/types/camera.types'
import { AnnotationInfoDialog, DEFAULT_FOV, DetectedStar, EMPTY_IMAGE_SOLVED, FOV, IMAGE_STATISTICS_BIT_OPTIONS, ImageAnnotation, ImageAnnotationDialog, ImageChannel, ImageData, ImageFITSHeadersDialog, ImageFOVDialog, ImageInfo, ImageROI, ImageSCNRDialog, ImageSaveDialog, ImageSolved, ImageSolverDialog, ImageStatisticsBitOption, ImageStretchDialog, ImageTransformation, StarDetectionDialog } from '../../shared/types/image.types'
import { AnnotationInfoDialog, DEFAULT_FOV, DetectedStar, EMPTY_IMAGE_SOLVED, FITSHeaderItem, FOV, IMAGE_STATISTICS_BIT_OPTIONS, ImageAnnotation, ImageAnnotationDialog, ImageChannel, ImageData, ImageFITSHeadersDialog, ImageFOVDialog, ImageInfo, ImageROI, ImageSCNRDialog, ImageSaveDialog, ImageSolved, ImageSolverDialog, ImageStatisticsBitOption, ImageStretchDialog, ImageTransformation, StarDetectionDialog } from '../../shared/types/image.types'
import { Mount } from '../../shared/types/mount.types'
import { CoordinateInterpolator, InterpolatedCoordinate } from '../../shared/utils/coordinate-interpolation'
import { AppComponent } from '../app.component'
Expand Down Expand Up @@ -130,12 +130,14 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
readonly solver: ImageSolverDialog = {
showDialog: false,
running: false,
type: 'ASTAP',
blind: true,
centerRA: '',
centerDEC: '',
radius: 4,
focalLength: 0,
pixelSize: 0,
solved: structuredClone(EMPTY_IMAGE_SOLVED),
type: 'ASTAP'
}

crossHair = false
Expand Down Expand Up @@ -459,6 +461,10 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
return (this.showLiveStackedImage && this.imageData.liveStackedPath) || this.imageData.path
}

get canPlateSolve() {
return this.solver.type !== 'SIRIL' || (this.solver.focalLength > 0 && this.solver.pixelSize > 0)
}

constructor(
private app: AppComponent,
private route: ActivatedRoute,
Expand Down Expand Up @@ -879,6 +885,8 @@ export class ImageComponent implements AfterViewInit, OnDestroy {

this.fitsHeaders.headers = info.headers

this.retrieveInfoFromImageHeaders(info.headers)

if (this.imageURL) window.URL.revokeObjectURL(this.imageURL)
this.imageURL = window.URL.createObjectURL(blob)
image.src = this.imageURL
Expand All @@ -891,6 +899,21 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
this.retrieveCoordinateInterpolation()
}

private retrieveInfoFromImageHeaders(headers: FITSHeaderItem[]) {
const imagePreference = this.preference.imagePreference.get()

for (const item of headers) {
if (item.name === 'FOCALLEN') {
this.solver.focalLength = parseFloat(item.value)
} else if (item.name === 'XPIXSZ') {
this.solver.pixelSize = parseFloat(item.value)
}
}

this.solver.focalLength ||= imagePreference.solverFocalLength || 0
this.solver.pixelSize ||= imagePreference.solverPixelSize || 0
}

imageClicked(event: MouseEvent, contextMenu: boolean) {
this.imageMouseX = event.offsetX
this.imageMouseY = event.offsetY
Expand Down Expand Up @@ -1254,6 +1277,8 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
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.starDetectionType = this.starDetection.type
preference.starDetectionMinSNR = this.starDetection.minSNR
this.preference.imagePreference.set(preference)
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/app/settings/settings.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<label>Timeout (s)</label>
</span>
</div>
<div class="col-12" *ngIf="plateSolverType === 'ASTAP'">
<div class="col-12" *ngIf="plateSolverType !== 'ASTROMETRY_NET_ONLINE'">
<neb-path-chooser key="PLATE_SOLVER_EXECUTABLE_PATH" [directory]="false" label="Executable path"
[path]="plateSolvers.get(plateSolverType)!.executablePath" class="w-full"
(pathChange)="plateSolvers.get(plateSolverType)!.executablePath = $event; save()" />
Expand Down
2 changes: 2 additions & 0 deletions desktop/src/app/settings/settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class SettingsComponent implements AfterViewInit, OnDestroy {

this.plateSolvers.set('ASTAP', preference.plateSolverRequest('ASTAP').get())
this.plateSolvers.set('ASTROMETRY_NET_ONLINE', preference.plateSolverRequest('ASTROMETRY_NET_ONLINE').get())
this.plateSolvers.set('SIRIL', preference.plateSolverRequest('SIRIL').get())

this.starDetectors.set('ASTAP', preference.starDetectionRequest('ASTAP').get())
this.starDetectors.set('PIXINSIGHT', preference.starDetectionRequest('PIXINSIGHT').get())
Expand Down Expand Up @@ -130,6 +131,7 @@ export class SettingsComponent implements AfterViewInit, OnDestroy {
save() {
this.preference.plateSolverRequest('ASTAP').set(this.plateSolvers.get('ASTAP'))
this.preference.plateSolverRequest('ASTROMETRY_NET_ONLINE').set(this.plateSolvers.get('ASTROMETRY_NET_ONLINE'))
this.preference.plateSolverRequest('SIRIL').set(this.plateSolvers.get('SIRIL'))

this.preference.starDetectionRequest('ASTAP').set(this.starDetectors.get('ASTAP'))
this.preference.starDetectionRequest('PIXINSIGHT').set(this.starDetectors.get('PIXINSIGHT'))
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/shared/pipes/dropdown-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class DropdownOptionsPipe implements PipeTransform {
transform(type: DropdownOptionType): DropdownOptionReturnType | undefined {
switch (type) {
case 'STAR_DETECTOR': return ['ASTAP', 'PIXINSIGHT']
case 'PLATE_SOLVER': return ['ASTAP', 'ASTROMETRY_NET_ONLINE']
case 'PLATE_SOLVER': return ['ASTAP', 'ASTROMETRY_NET_ONLINE', 'SIRIL']
case 'AUTO_FOCUS_FITTING_MODE': return ['TRENDLINES', 'PARABOLIC', 'TREND_PARABOLIC', 'HYPERBOLIC', 'TREND_HYPERBOLIC']
case 'AUTO_FOCUS_BACKLASH_COMPENSATION_MODE': return ['NONE', 'ABSOLUTE', 'OVERSHOOT']
case 'LIVE_STACKER': return ['SIRIL', 'PIXINSIGHT']
Expand Down
4 changes: 4 additions & 0 deletions desktop/src/shared/types/image.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export interface ImageStatistics {
export interface ImagePreference {
solverRadius?: number
solverType?: PlateSolverType
solverFocalLength?: number
solverPixelSize?: number
savePath?: string
starDetectionType?: StarDetectorType
starDetectionMinSNR?: number
Expand Down Expand Up @@ -215,6 +217,8 @@ export interface ImageSolverDialog {
centerRA: Angle
centerDEC: Angle
radius: number
focalLength: number
pixelSize: number
readonly solved: ImageSolved
type: PlateSolverType
}
Expand Down
6 changes: 3 additions & 3 deletions desktop/src/shared/types/settings.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type PlateSolverType = 'ASTROMETRY_NET' | 'ASTROMETRY_NET_ONLINE' | 'ASTAP'
export type PlateSolverType = 'ASTROMETRY_NET' | 'ASTROMETRY_NET_ONLINE' | 'ASTAP' | 'SIRIL'

export interface PlateSolverOptions {
type: PlateSolverType
Expand All @@ -15,7 +15,7 @@ export const EMPTY_PLATE_SOLVER_OPTIONS: PlateSolverOptions = {
downsampleFactor: 0,
apiUrl: 'https://nova.astrometry.net/',
apiKey: '',
timeout: 600,
timeout: 300,
}

export type StarDetectorType = 'ASTAP' | 'PIXINSIGHT'
Expand All @@ -31,7 +31,7 @@ export interface StarDetectionOptions {
export const EMPTY_STAR_DETECTION_OPTIONS: StarDetectionOptions = {
type: 'ASTAP',
executablePath: '',
timeout: 600,
timeout: 300,
minSNR: 0,
slot: 1,
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ data class AstapPlateSolver(private val executablePath: Path) : PlateSolver {
override fun solve(
path: Path?, image: Image?,
centerRA: Angle, centerDEC: Angle, radius: Angle,
downsampleFactor: Int, timeout: Duration?,
downsampleFactor: Int, timeout: Duration,
cancellationToken: CancellationToken,
): PlateSolution {
requireNotNull(path) { "path is required" }
Expand Down Expand Up @@ -61,7 +61,7 @@ data class AstapPlateSolver(private val executablePath: Path) : PlateSolver {
LOG.info("ASTAP solving. command={}", cmd.command)

try {
val timeoutOrDefault = timeout?.takeIf { it.toSeconds() > 0 } ?: Duration.ofMinutes(5)
val timeoutOrDefault = timeout.takeIf { it.toSeconds() > 0 } ?: Duration.ofMinutes(5)
cancellationToken.listen(cmd)
cmd.start(timeoutOrDefault)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class LibAstrometryNetPlateSolver(private val solver: LibAstrometryNet) : P
override fun solve(
path: Path?, image: Image?,
centerRA: Angle, centerDEC: Angle, radius: Angle,
downsampleFactor: Int, timeout: Duration?,
downsampleFactor: Int, timeout: Duration,
cancellationToken: CancellationToken,
): PlateSolution {
return PlateSolution.NO_SOLUTION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ data class LocalAstrometryNetPlateSolver(private val executablePath: Path) : Pla
override fun solve(
path: Path?, image: Image?,
centerRA: Angle, centerDEC: Angle, radius: Angle,
downsampleFactor: Int, timeout: Duration?,
downsampleFactor: Int, timeout: Duration,
cancellationToken: CancellationToken,
): PlateSolution {
requireNotNull(path) { "path is required" }
Expand All @@ -39,7 +39,7 @@ data class LocalAstrometryNetPlateSolver(private val executablePath: Path) : Pla

putArg("--dir", outFolder)

putArg("--cpulimit", timeout?.takeIf { it.toSeconds() > 0 }?.toSeconds() ?: 300)
putArg("--cpulimit", timeout.takeIf { it.toSeconds() > 0 }?.toSeconds() ?: 300)
putArg("--scale-units", "degwidth")
putArg("--guess-scale")
putArg("--crpix-center")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ data class NovaAstrometryNetPlateSolver(
override fun solve(
path: Path?, image: Image?,
centerRA: Angle, centerDEC: Angle, radius: Angle,
downsampleFactor: Int, timeout: Duration?,
downsampleFactor: Int, timeout: Duration,
cancellationToken: CancellationToken,
): PlateSolution {
renewSession()
Expand All @@ -69,7 +69,7 @@ data class NovaAstrometryNetPlateSolver(
throw PlateSolverException(submission.errorMessage)
}

var timeLeft = timeout?.takeIf { it.toSeconds() > 0 }?.toMillis() ?: 300000L
var timeLeft = timeout.takeIf { it.toSeconds() > 0 }?.toMillis() ?: 300000L

while (timeLeft >= 0L && !cancellationToken.isCancelled) {
val startTime = System.currentTimeMillis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface PlateSolver {
fun solve(
path: Path?, image: Image?,
centerRA: Angle = 0.0, centerDEC: Angle = 0.0, radius: Angle = 0.0,
downsampleFactor: Int = 0, timeout: Duration? = null,
downsampleFactor: Int = 0, timeout: Duration = Duration.ZERO,
cancellationToken: CancellationToken = CancellationToken.NONE,
): PlateSolution
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean
data class PlateSolve(
@JvmField val path: Path,
@JvmField val focalLength: Double = 0.0,
@JvmField val pixelSize: Double = 0.0,
@JvmField val useCenterCoordinates: Boolean = false,
@JvmField val rightAscension: Angle = 0.0, val declination: Angle = 0.0,
@JvmField val downsampleFactor: Int = 0,
Expand All @@ -39,7 +40,8 @@ data class PlateSolve(
if (useCenterCoordinates) append(" ${rightAscension.toHours},${declination.toDegrees}")
append(" -platesolve -noflip")
if (focalLength > 0.0) append(" -focal=$focalLength")
if (downsampleFactor > 0) append(" -downscale=$downsampleFactor")
if (pixelSize > 0.0) append(" -pixelsize=$pixelSize")
if (downsampleFactor > 1) append(" -downscale")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ import nebulosa.siril.command.SirilCommandLine
import java.nio.file.Path
import java.time.Duration

data class SirilPlateSolver(private val executablePath: Path) : PlateSolver {
data class SirilPlateSolver(
private val executablePath: Path,
private val focalLength: Double = 0.0,
private val pixelSize: Double = 0.0,
) : PlateSolver {

override fun solve(
path: Path?, image: Image?,
centerRA: Angle, centerDEC: Angle, radius: Angle,
downsampleFactor: Int, timeout: Duration?, cancellationToken: CancellationToken
downsampleFactor: Int, timeout: Duration, cancellationToken: CancellationToken
): PlateSolution {
val commandLine = SirilCommandLine(executablePath)

return try {
commandLine.run()
cancellationToken.listen(commandLine)
val useCenterCoordinates = radius > 0.0 && radius.toDegrees >= 0.1 && centerRA.isFinite() && centerDEC.isFinite()
commandLine.execute(PlateSolve(path!!, 0.0, useCenterCoordinates, centerRA, centerDEC, downsampleFactor, timeout ?: Duration.ZERO))
commandLine.execute(PlateSolve(path!!, focalLength, pixelSize, useCenterCoordinates, centerRA, centerDEC, downsampleFactor, timeout))
} finally {
cancellationToken.unlisten(commandLine)
commandLine.close()
Expand Down

0 comments on commit 0b76e71

Please sign in to comment.