Skip to content

Commit

Permalink
[api][desktop]: Implement PixInsight Pixel Math
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Jun 5, 2024
1 parent 353d288 commit 96a1e6e
Show file tree
Hide file tree
Showing 18 changed files with 460 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,37 @@ class CalibrationFrameService(
fun calibrate(name: String, image: Image, createNew: Boolean = false): Image {
return synchronized(image) {
val darkFrame = findBestDarkFrames(name, image).firstOrNull()
val biasFrame = findBestBiasFrames(name, image).firstOrNull()
val biasFrame = if (darkFrame == null) findBestBiasFrames(name, image).firstOrNull() else null
val flatFrame = findBestFlatFrames(name, image).firstOrNull()

if (darkFrame != null || biasFrame != null || flatFrame != null) {
val darkImage = darkFrame?.path?.fits()?.use(Image::open)
val biasImage = biasFrame?.path?.fits()?.use(Image::open)
var flatImage = flatFrame?.path?.fits()?.use(Image::open)

if (darkImage != null || biasImage != null || flatImage != null) {
var transformedImage = if (createNew) image.clone() else image

if (biasFrame != null) {
val calibrationImage = biasFrame.path!!.fits().use(Image::open)
transformedImage = transformedImage.transform(BiasSubtraction(calibrationImage))
// If not using dark frames.
if (biasImage != null) {
// Subtract Master Bias from Flat Frames.
if (flatImage != null) {
flatImage = flatImage.transform(BiasSubtraction(biasImage))
LOG.info("bias frame subtraction applied to flat frame. frame={}", biasFrame)
}

// Subtract the Master Bias frame.
transformedImage = transformedImage.transform(BiasSubtraction(biasImage))
LOG.info("bias frame subtraction applied. frame={}", biasFrame)
} else {
} else if (darkFrame == null) {
LOG.info(
"no bias frames found. width={}, height={}, bin={}, gain={}",
image.width, image.height, image.header.binX, image.header.gain
)
}

if (darkFrame != null) {
val calibrationImage = darkFrame.path!!.fits().use(Image::open)
transformedImage = transformedImage.transform(DarkSubtraction(calibrationImage))
// Subtract Master Dark frame.
if (darkImage != null) {
transformedImage = transformedImage.transform(DarkSubtraction(darkImage))
LOG.info("dark frame subtraction applied. frame={}", darkFrame)
} else {
LOG.info(
Expand All @@ -59,9 +70,9 @@ class CalibrationFrameService(
)
}

if (flatFrame != null) {
val calibrationImage = flatFrame.path!!.fits().use(Image::open)
transformedImage = transformedImage.transform(FlatCorrection(calibrationImage))
// Divide the Dark-subtracted Light frame by the Master Flat frame to correct for variations in the optical path.
if (flatImage != null) {
transformedImage = transformedImage.transform(FlatCorrection(flatImage))
LOG.info("flat frame correction applied. frame={}", flatFrame)
} else {
LOG.info(
Expand Down Expand Up @@ -177,7 +188,7 @@ class CalibrationFrameService(
name: String, width: Int, height: Int,
binX: Int, binY: Int, filter: String?
): List<CalibrationFrameEntity> {
// TODO: Generate master from matched frames.
// TODO: Generate master from matched frames. (Subtract the master bias frame from each flat frame)
return calibrationFrameRepository
.flatFrames(name, filter, width, height, binX)
}
Expand Down
61 changes: 33 additions & 28 deletions desktop/src/app/image/image.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<circle cx="50%" cy="50%" r="32%" stroke="#E53935" stroke-width="3" fill="transparent"></circle>
</svg>

<svg *ngIf="!transformation.mirrorHorizontal && !transformation.mirrorVertical && annotationIsVisible"
<svg *ngIf="!transformation.mirrorHorizontal && !transformation.mirrorVertical && annotation.visible"
class="absolute left-0 top-0 w-full h-full pointer-events-none select-none">
<g *ngFor="let a of annotations" (dblclick)="showAnnotationInfo(a)" class="pointer-events-auto cursor-pointer">
<g *ngFor="let a of annotation.data" (dblclick)="showAnnotationInfo(a)" class="pointer-events-auto cursor-pointer">
<circle [attr.cx]="a.x" [attr.cy]="a.y" r="4" stroke="#FDD835" stroke-width="1" fill="transparent"></circle>
<text [attr.x]="a.x" [attr.y]="a.y" fill="#00897B" style="font-size: 5px" class="pointer-events-none select-none">
{{ (a.star ?? a.dso ?? a.minorPlanet) | skyObject:'name' }}
Expand Down Expand Up @@ -85,71 +85,76 @@
</div>
</div>
<ng-template pTemplate="footer">
<p-button [disabled]="annotating" icon="mdi mdi-check" label="Annotate" (onClick)="annotateImage()" [text]="true" size="small" />
<p-button [disabled]="annotation.running" icon="mdi mdi-check" label="Annotate" (onClick)="annotateImage()" [text]="true" size="small" />
</ng-template>
</p-dialog>

<p-dialog header="Astronomical Object" [draggable]="true" [(visible)]="showAnnotationInfoDialog" [modal]="false"
<p-dialog header="Astronomical Object" [draggable]="true" [(visible)]="annotation.showDialog" [modal]="false"
[style]="{width: 'min-content', minWidth: '325px'}">
<div class="grid mt-2">
<ng-container *ngIf="annotationInfo">
<ng-container *ngIf="annotationInfo.info">
<div class="col-12">
<span class="p-float-label">
<input pInputText readonly [value]="annotationInfo | skyObject:'name'" class="border-0 p-inputtext-sm" />
<input pInputText readonly [value]="annotationInfo.info | skyObject:'name'" class="border-0 p-inputtext-sm" />
<label>Name</label>
</span>
</div>
<div class="col-6">
<span class="p-float-label">
<input pInputText readonly [value]="annotationInfo.rightAscensionJ2000 | angle:true:24"
<input pInputText readonly [value]="annotationInfo.info.rightAscensionJ2000 | angle:true:24"
class="border-0 p-inputtext-sm" />
<label>RA (J2000)</label>
</span>
</div>
<div class="col-6">
<span class="p-float-label">
<input pInputText readonly [value]="annotationInfo.declinationJ2000 | angle:true:360"
<input pInputText readonly [value]="annotationInfo.info.declinationJ2000 | angle:true:360"
class="border-0 p-inputtext-sm" />
<label>DEC (J2000)</label>
</span>
</div>
<div class="col-6" *ngIf="annotationInfo.magnitude">
<div class="col-6" *ngIf="annotationInfo.info.magnitude">
<span class="p-float-label">
<input pInputText readonly value="{{ annotationInfo.magnitude < 30.0 ? annotationInfo.magnitude.toFixed(2) : '-' }}"
<input pInputText readonly value="{{ annotationInfo.info.magnitude < 30.0 ? annotationInfo.info.magnitude.toFixed(2) : '-' }}"
class="border-0 p-inputtext-sm" />
<label>Magnitude</label>
</span>
</div>
<div class="col-6" *ngIf="annotationInfo.constellation">
<div class="col-6" *ngIf="annotationInfo.info.constellation">
<span class="p-float-label">
<input pInputText readonly [value]="annotationInfo.constellation" class="border-0 p-inputtext-sm" />
<input pInputText readonly [value]="annotationInfo.info.constellation" class="border-0 p-inputtext-sm" />
<label>Constellation</label>
</span>
</div>
<div class="col-6" *ngIf="annotationInfo.type">
<div class="col-6" *ngIf="annotationInfo.info.type">
<span class="p-float-label">
<input pInputText readonly value="{{ annotationInfo.type | enum }}" class="border-0 p-inputtext-sm" />
<input pInputText readonly value="{{ annotationInfo.info.type | enum }}" class="border-0 p-inputtext-sm" />
<label>Type</label>
</span>
</div>
<div class="col-6" *ngIf="annotationInfo.distance">
<div class="col-6" *ngIf="annotationInfo.info.distance">
<span class="p-float-label">
<input pInputText readonly value="{{ annotationInfo.distance <= 0 ? '-' : annotationInfo.distance }}"
<input pInputText readonly value="{{ annotationInfo.info.distance <= 0 ? '-' : annotationInfo.info.distance }}"
class="border-0 p-inputtext-sm" />
<label>Distance (ly)</label>
</span>
</div>
</ng-container>
<div class="col-12 justify-content-center text-sm" *ngIf="annotationInfo?.type">
<a target="_blank" href="{{ 'https://simbad.cds.unistra.fr/simbad/sim-id?Ident=' + (annotationInfo | skyObject:'firstName') }}">Simbad</a>
<div class="col-12 justify-content-center text-sm" *ngIf="annotationInfo.info?.type">
<a target="_blank"
href="{{ 'https://simbad.cds.unistra.fr/simbad/sim-id?Ident=' + (annotationInfo.info | skyObject:'firstName') }}">Simbad</a>
</div>
</div>
<ng-template pTemplate="footer">
<div class="col-12 flex justify-content-center">
<p-button (onClick)="mountSync(annotationInfo!)" icon="mdi mdi-sync" label="Sync" [text]="true" severity="info" size="small" />
<p-button (onClick)="mountGoTo(annotationInfo!)" icon="mdi mdi-telescope" label="Go To" [text]="true" severity="success" size="small" />
<p-button (onClick)="mountSlew(annotationInfo!)" icon="mdi mdi-telescope" label="Slew" [text]="true" severity="success" size="small" />
<p-button (onClick)="frame(annotationInfo!)" icon="mdi mdi-image" label="Frame" [text]="true" size="small" />
<p-button [disabled]="!annotationInfo.info" (onClick)="mountSync(annotationInfo.info!)" icon="mdi mdi-sync" label="Sync" [text]="true"
severity="info" size="small" />
<p-button [disabled]="!annotationInfo.info" (onClick)="mountGoTo(annotationInfo.info!)" icon="mdi mdi-telescope" label="Go To"
[text]="true" severity="success" size="small" />
<p-button [disabled]="!annotationInfo.info" (onClick)="mountSlew(annotationInfo.info!)" icon="mdi mdi-telescope" label="Slew"
[text]="true" severity="success" size="small" />
<p-button [disabled]="!annotationInfo.info" (onClick)="frame(annotationInfo.info!)" icon="mdi mdi-image" label="Frame" [text]="true"
size="small" />
</div>
</ng-template>
</p-dialog>
Expand Down Expand Up @@ -231,18 +236,18 @@
</div>
<div class="grid mb-2 mt-2">
<div class="col-12 flex justify-content-center">
<p-button [disabled]="solver.solving || !solver.solved.solved" (onClick)="mountSync(solver.solved)" icon="mdi mdi-sync" label="Sync"
<p-button [disabled]="solver.running || !solver.solved.solved" (onClick)="mountSync(solver.solved)" icon="mdi mdi-sync" label="Sync"
[text]="true" severity="info" size="small" />
<p-button [disabled]="solver.solving || !solver.solved.solved" (onClick)="mountGoTo(solver.solved)" icon="mdi mdi-telescope"
<p-button [disabled]="solver.running || !solver.solved.solved" (onClick)="mountGoTo(solver.solved)" icon="mdi mdi-telescope"
label="Go To" [text]="true" severity="success" size="small" />
<p-button [disabled]="solver.solving || !solver.solved.solved" (onClick)="mountSlew(solver.solved)" icon="mdi mdi-telescope"
<p-button [disabled]="solver.running || !solver.solved.solved" (onClick)="mountSlew(solver.solved)" icon="mdi mdi-telescope"
label="Slew" [text]="true" severity="success" size="small" />
<p-button [disabled]="solver.solving || !solver.solved.solved" (onClick)="frame(solver.solved)" icon="mdi mdi-image" label="Frame"
<p-button [disabled]="solver.running || !solver.solved.solved" (onClick)="frame(solver.solved)" icon="mdi mdi-image" label="Frame"
[text]="true" size="small" />
</div>
</div>

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

Expand Down Expand Up @@ -499,7 +504,7 @@
</div>
</div>
<ng-template pTemplate="footer">
<p-button [disabled]="detecting" icon="mdi mdi-check" label="Detect" (onClick)="detectStars()" [text]="true" size="small" />
<p-button [disabled]="starDetection.running" icon="mdi mdi-check" label="Detect" (onClick)="detectStars()" [text]="true" size="small" />
</ng-template>
</p-dialog>

Expand Down
53 changes: 27 additions & 26 deletions desktop/src/app/image/image.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { BrowserWindowService } from '../../shared/services/browser-window.servi
import { ElectronService } from '../../shared/services/electron.service'
import { PreferenceService } from '../../shared/services/preference.service'
import { PrimeService } from '../../shared/services/prime.service'
import { Angle, AstronomicalObject, DeepSkyObject, EquatorialCoordinateJ2000, Star } from '../../shared/types/atlas.types'
import { Angle, EquatorialCoordinateJ2000 } from '../../shared/types/atlas.types'
import { Camera } from '../../shared/types/camera.types'
import { 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, 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 @@ -92,15 +92,22 @@ export class ImageComponent implements AfterViewInit, OnDestroy {

readonly annotation: ImageAnnotationDialog = {
showDialog: false,
running: false,
visible: false,
useStarsAndDSOs: true,
useMinorPlanets: false,
minorPlanetsMagLimit: 18.0,
useSimbad: false
useSimbad: false,
data: []
}

readonly annotationInfo: AnnotationInfoDialog = {
showDialog: false
}

detecting = false
readonly starDetection: StarDetectionDialog = {
showDialog: false,
running: false,
type: 'ASTAP',
minSNR: 0,
visible: false,
Expand All @@ -122,7 +129,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy {

readonly solver: ImageSolverDialog = {
showDialog: false,
solving: false,
running: false,
blind: true,
centerRA: '',
centerDEC: '',
Expand All @@ -132,11 +139,6 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
}

crossHair = false
annotations: ImageAnnotation[] = []
annotating = false
showAnnotationInfoDialog = false
annotationInfo?: AstronomicalObject & Partial<Star & DeepSkyObject>
annotationIsVisible = false

readonly fitsHeaders: ImageFITSHeadersDialog = {
showDialog: false,
Expand Down Expand Up @@ -341,7 +343,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
},
toggle: (event) => {
event.originalEvent?.stopImmediatePropagation()
this.annotationIsVisible = event.checked
this.annotation.visible = event.checked
},
}

Expand Down Expand Up @@ -752,8 +754,8 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
}

private clearOverlay() {
this.annotations = []
this.annotationIsVisible = false
this.annotation.data = []
this.annotation.visible = false
this.annotationMenuItem.toggleable = false

this.starDetection.stars = []
Expand All @@ -775,15 +777,14 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
}

async detectStars() {
this.detecting = true

const options = this.preference.starDetectionRequest(this.starDetection.type).get()
options.minSNR = this.starDetection.minSNR

try {
this.starDetection.running = true
this.starDetection.stars = await this.api.detectStars(this.imagePath!, options)
} finally {
this.detecting = false
this.starDetection.running = false
}

let hfd = 0
Expand Down Expand Up @@ -906,21 +907,21 @@ export class ImageComponent implements AfterViewInit, OnDestroy {

async annotateImage() {
try {
this.annotating = true
this.annotations = await this.api.annotationsOfImage(this.imagePath!, this.annotation.useStarsAndDSOs,
this.annotation.running = true
this.annotation.data = await this.api.annotationsOfImage(this.imagePath!, this.annotation.useStarsAndDSOs,
this.annotation.useMinorPlanets, this.annotation.minorPlanetsMagLimit, this.annotation.useSimbad)
this.annotationIsVisible = true
this.annotationMenuItem.toggleable = this.annotations.length > 0
this.annotationMenuItem.toggled = this.annotationMenuItem.toggleable
this.annotation.visible = this.annotation.data.length > 0
this.annotationMenuItem.toggleable = this.annotation.visible
this.annotationMenuItem.toggled = this.annotation.visible
this.annotation.showDialog = false
} finally {
this.annotating = false
this.annotation.running = false
}
}

showAnnotationInfo(annotation: ImageAnnotation) {
this.annotationInfo = annotation.star ?? annotation.dso ?? annotation.minorPlanet
this.showAnnotationInfoDialog = true
this.annotationInfo.info = annotation.star ?? annotation.dso ?? annotation.minorPlanet
this.annotationInfo.showDialog = true
}

private disableAutoStretch() {
Expand Down Expand Up @@ -1037,7 +1038,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
}

async solveImage() {
this.solver.solving = true
this.solver.running = true

try {
const solver = this.preference.plateSolverRequest(this.solver.type).get()
Expand All @@ -1049,7 +1050,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy {
} catch {
this.updateImageSolved(this.imageInfo?.solved)
} finally {
this.solver.solving = false
this.solver.running = false

if (this.solver.solved.solved) {
this.retrieveCoordinateInterpolation()
Expand Down
Loading

0 comments on commit 96a1e6e

Please sign in to comment.