diff --git a/desktop/src/app/framing/framing.component.ts b/desktop/src/app/framing/framing.component.ts index 390825b78..8e6936846 100644 --- a/desktop/src/app/framing/framing.component.ts +++ b/desktop/src/app/framing/framing.component.ts @@ -96,11 +96,12 @@ export class FramingComponent implements AfterViewInit, OnDestroy { } private frameFromData(data: FramingData) { + console.info(data) this.rightAscension = data.rightAscension ?? this.rightAscension this.declination = data.declination ?? this.declination - this.width = data.width ?? this.width - this.height = data.height ?? this.height - this.fov = data.fov ?? this.fov + this.width = data.width || this.width + this.height = data.height || this.height + this.fov = data.fov || this.fov if (data.rotation === 0 || data.rotation) this.rotation = data.rotation if (data.rightAscension && data.declination) { diff --git a/desktop/src/app/image/image.component.ts b/desktop/src/app/image/image.component.ts index 9b748a35c..0453116f8 100644 --- a/desktop/src/app/image/image.component.ts +++ b/desktop/src/app/image/image.component.ts @@ -5,10 +5,10 @@ import hotkeys from 'hotkeys-js' import interact from 'interactjs' import createPanZoom, { PanZoom } from 'panzoom' import { basename, dirname, extname } from 'path' -import { MenuItem } from 'primeng/api' import { ContextMenu } from 'primeng/contextmenu' import { DeviceListMenuComponent } from '../../shared/components/device-list-menu/device-list-menu.component' import { HistogramComponent } from '../../shared/components/histogram/histogram.component' +import { ExtendedMenuItem } from '../../shared/components/menu-item/menu-item.component' import { SEPARATOR_MENU_ITEM } from '../../shared/constants' import { ApiService } from '../../shared/services/api.service' import { BrowserWindowService } from '../../shared/services/browser-window.service' @@ -85,7 +85,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { scnr: this.scnr } - calibrationGroup?: string | true // true == 'None' + calibrationViaCamera = true showAnnotationDialog = false annotateWithStarsAndDSOs = true @@ -164,7 +164,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { transformation: this.transformation } - private readonly saveAsMenuItem: MenuItem = { + private readonly saveAsMenuItem: ExtendedMenuItem = { label: 'Save as...', icon: 'mdi mdi-content-save', command: async () => { @@ -186,7 +186,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly plateSolveMenuItem: MenuItem = { + private readonly plateSolveMenuItem: ExtendedMenuItem = { label: 'Plate Solve', icon: 'mdi mdi-sigma', command: () => { @@ -194,7 +194,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly stretchMenuItem: MenuItem = { + private readonly stretchMenuItem: ExtendedMenuItem = { label: 'Stretch', icon: 'mdi mdi-chart-histogram', command: () => { @@ -212,7 +212,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly scnrMenuItem: MenuItem = { + private readonly scnrMenuItem: ExtendedMenuItem = { label: 'SCNR', icon: 'mdi mdi-palette', disabled: true, @@ -252,13 +252,13 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly calibrationMenuItem: MenuItem = { + private readonly calibrationMenuItem: ExtendedMenuItem = { label: 'Calibration', icon: 'mdi mdi-wrench', items: [], } - private readonly statisticsMenuItem: MenuItem = { + private readonly statisticsMenuItem: ExtendedMenuItem = { icon: 'mdi mdi-chart-histogram', label: 'Statistics', command: () => { @@ -267,7 +267,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly fitsHeaderMenuItem: MenuItem = { + private readonly fitsHeaderMenuItem: ExtendedMenuItem = { icon: 'mdi mdi-list-box', label: 'FITS Header', command: () => { @@ -275,7 +275,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly pointMountHereMenuItem: MenuItem = { + private readonly pointMountHereMenuItem: ExtendedMenuItem = { label: 'Point mount here', icon: 'mdi mdi-telescope', disabled: true, @@ -286,7 +286,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly frameAtThisCoordinateMenuItem: MenuItem = { + private readonly frameAtThisCoordinateMenuItem: ExtendedMenuItem = { label: 'Frame at this coordinate', icon: 'mdi mdi-image', disabled: true, @@ -294,7 +294,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { const coordinate = this.mouseCoordinateInterpolation?.interpolateAsText(this.imageMouseX, this.imageMouseY, false, false, false) if (coordinate) { - this.browserWindow.openFraming({ data: { rightAscension: coordinate.alpha, declination: coordinate.delta } }) + this.browserWindow.openFraming({ data: { rightAscension: coordinate.alpha, declination: coordinate.delta, fov: this.solver.solved!.width / 60, rotation: this.solver.solved!.orientation } }) } }, } @@ -381,7 +381,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly fovMenuItem: MenuItem = { + private readonly fovMenuItem: ExtendedMenuItem = { label: 'Field of View', icon: 'mdi mdi-camera-metering-spot', command: () => { @@ -393,7 +393,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { }, } - private readonly overlayMenuItem: MenuItem = { + private readonly overlayMenuItem: ExtendedMenuItem = { label: 'Overlay', icon: 'mdi mdi-layers', items: [ @@ -517,9 +517,13 @@ export class ImageComponent implements AfterViewInit, OnDestroy { this.roiInteractable?.unset() } - private markCalibrationGroupItem(name: string) { - for (const item of this.calibrationMenuItem.items!) { - (item as CheckableMenuItem).checked = item.label === name + private markCalibrationGroupItem(name?: string) { + this.calibrationMenuItem.items![1].checked = this.calibrationViaCamera + + for (let i = 3; i < this.calibrationMenuItem.items!.length; i++) { + const item = this.calibrationMenuItem.items![i] + item.checked = item.label === (name ?? 'None') + item.disabled = this.calibrationViaCamera } } @@ -531,10 +535,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { if (!found) { reloadImage = !!this.transformation.calibrationGroup this.transformation.calibrationGroup = undefined - - if (this.calibrationGroup) { - this.calibrationGroup = true // None - } + this.calibrationViaCamera = true } const makeItem = (name?: string) => { @@ -542,17 +543,18 @@ export class ImageComponent implements AfterViewInit, OnDestroy { const icon = name ? 'mdi mdi-wrench' : 'mdi mdi-close' return { - label, icon, checked: this.transformation.calibrationGroup === name, + label, icon, + checked: this.transformation.calibrationGroup === name, + disabled: this.calibrationViaCamera, command: async () => { this.transformation.calibrationGroup = name - this.calibrationGroup = name ?? true this.markCalibrationGroupItem(label) await this.loadImage() }, } } - const menu: MenuItem[] = [] + const menu: ExtendedMenuItem[] = [] menu.push({ label: 'Open', @@ -560,6 +562,16 @@ export class ImageComponent implements AfterViewInit, OnDestroy { command: () => this.browserWindow.openCalibration() }) + menu.push({ + label: 'Camera', + icon: 'mdi mdi-camera-iris', + checked: this.calibrationViaCamera, + command: () => { + this.calibrationViaCamera = !this.calibrationViaCamera + this.markCalibrationGroupItem(this.transformation.calibrationGroup) + } + }) + menu.push(SEPARATOR_MENU_ITEM) menu.push(makeItem()) @@ -636,14 +648,17 @@ export class ImageComponent implements AfterViewInit, OnDestroy { this.imageData = data // Not clicked on menu item. - if (!this.calibrationGroup && this.transformation.calibrationGroup !== data.capture?.calibrationGroup) { + if (this.calibrationViaCamera && this.transformation.calibrationGroup !== data.capture?.calibrationGroup) { this.transformation.calibrationGroup = data.capture?.calibrationGroup - this.markCalibrationGroupItem(this.transformation.calibrationGroup ?? 'None') + this.markCalibrationGroupItem(this.transformation.calibrationGroup) } if (data.source === 'FRAMING') { this.disableAutoStretch() - this.resetStretch(false) + + if (this.transformation.stretch.auto) { + this.resetStretch(false) + } } else if (data.source === 'FLAT_WIZARD') { this.disableCalibration(false) } @@ -694,7 +709,9 @@ export class ImageComponent implements AfterViewInit, OnDestroy { private async loadImageFromPath(path: string) { const image = this.image.nativeElement - const { info, blob } = await this.api.openImage(path, this.transformation, this.imageData.camera) + const transformation = structuredClone(this.transformation) + if (this.calibrationViaCamera) transformation.calibrationGroup = this.imageData.capture?.calibrationGroup + const { info, blob } = await this.api.openImage(path, transformation, this.imageData.camera) this.imageInfo = info this.scnrMenuItem.disabled = info.mono @@ -717,6 +734,11 @@ export class ImageComponent implements AfterViewInit, OnDestroy { this.imageURL = window.URL.createObjectURL(blob) image.src = this.imageURL + if (!info.camera?.id) { + this.calibrationViaCamera = false + this.markCalibrationGroupItem(this.transformation.calibrationGroup) + } + this.retrieveCoordinateInterpolation() } @@ -775,7 +797,7 @@ export class ImageComponent implements AfterViewInit, OnDestroy { private disableCalibration(canEnable: boolean = true) { this.transformation.calibrationGroup = undefined - this.markCalibrationGroupItem('None') + this.markCalibrationGroupItem(undefined) this.calibrationMenuItem.disabled = !canEnable } diff --git a/desktop/src/shared/services/browser-window.service.ts b/desktop/src/shared/services/browser-window.service.ts index a41bff26f..a88625b40 100644 --- a/desktop/src/shared/services/browser-window.service.ts +++ b/desktop/src/shared/services/browser-window.service.ts @@ -69,7 +69,7 @@ export class BrowserWindowService { async openImage(data: Omit & { id?: string, path: string }) { const hash = data.id || uuidv4() const id = `image.${hash}` - await this.openWindow({ id, path: 'image', icon: 'image', width: '50%', height: `0.9w`, resizable: true, data }) + await this.openWindow({ id, path: 'image', icon: 'image', width: '50%', height: `0.9w`, resizable: true, data, autoResizable: false }) return id } diff --git a/desktop/src/shared/types/image.types.ts b/desktop/src/shared/types/image.types.ts index b1acbbb52..067e71352 100644 --- a/desktop/src/shared/types/image.types.ts +++ b/desktop/src/shared/types/image.types.ts @@ -20,7 +20,7 @@ export interface FITSHeaderItem { } export interface ImageInfo { - camera: Camera + camera?: Camera path: string width: number height: number @@ -258,4 +258,4 @@ export interface ImageTransformation { mirrorVertical: boolean invert: boolean scnr: Pick -} \ No newline at end of file +} diff --git a/nebulosa-fits/src/main/kotlin/nebulosa/fits/FitsPath.kt b/nebulosa-fits/src/main/kotlin/nebulosa/fits/FitsPath.kt index b7d91f2c6..497ded614 100644 --- a/nebulosa-fits/src/main/kotlin/nebulosa/fits/FitsPath.kt +++ b/nebulosa-fits/src/main/kotlin/nebulosa/fits/FitsPath.kt @@ -1,15 +1,17 @@ package nebulosa.fits -import nebulosa.io.seekableSink -import nebulosa.io.seekableSource +import nebulosa.io.sink +import nebulosa.io.source import java.io.Closeable import java.io.File +import java.io.RandomAccessFile import java.nio.file.Path data class FitsPath(val path: Path) : Fits(), Closeable { - private val source = path.seekableSource() - private val sink = path.seekableSink() + private val file = RandomAccessFile(path.toFile(), "rw") + private val source = file.source() + private val sink = file.sink() constructor(file: File) : this(file.toPath()) @@ -24,5 +26,6 @@ data class FitsPath(val path: Path) : Fits(), Closeable { override fun close() { source.close() sink.close() + file.close() } } diff --git a/nebulosa-xisf/src/main/kotlin/nebulosa/xisf/XisfPath.kt b/nebulosa-xisf/src/main/kotlin/nebulosa/xisf/XisfPath.kt index f8d115156..587f6fa44 100644 --- a/nebulosa-xisf/src/main/kotlin/nebulosa/xisf/XisfPath.kt +++ b/nebulosa-xisf/src/main/kotlin/nebulosa/xisf/XisfPath.kt @@ -1,15 +1,17 @@ package nebulosa.xisf -import nebulosa.io.seekableSink -import nebulosa.io.seekableSource +import nebulosa.io.sink +import nebulosa.io.source import java.io.Closeable import java.io.File +import java.io.RandomAccessFile import java.nio.file.Path data class XisfPath(val path: Path) : Xisf(), Closeable { - private val source = path.seekableSource() - private val sink = path.seekableSink() + private val file = RandomAccessFile(path.toFile(), "rw") + private val source = file.source() + private val sink = file.sink() constructor(file: File) : this(file.toPath()) @@ -24,5 +26,6 @@ data class XisfPath(val path: Path) : Xisf(), Closeable { override fun close() { source.close() sink.close() + file.close() } }