diff --git a/kstars/CMakeLists.txt b/kstars/CMakeLists.txt index b2526c918..657c713ab 100644 --- a/kstars/CMakeLists.txt +++ b/kstars/CMakeLists.txt @@ -1,1206 +1,1207 @@ add_subdirectory( data ) add_subdirectory( icons ) add_subdirectory( htmesh ) if (${KF5_VERSION} VERSION_EQUAL 5.18.0 OR ${KF5_VERSION} VERSION_GREATER 5.18.0) SET(HAVE_KF5WIT 1) # if(NOT BUILD_KSTARS_LITE) # add_subdirectory( tools/whatsinteresting/qml) # endif(NOT BUILD_KSTARS_LITE) else() SET(HAVE_KF5WIT 0) endif() if (ANDROID AND CMAKE_TOOLCHAIN_FILE) include(${CMAKE_TOOLCHAIN_FILE}) endif () if (NOT ANDROID) find_package(ZLIB REQUIRED) find_package(Threads REQUIRED) endif () if(MSVC) add_definitions(-D_USE_MATH_DEFINES=1) add_definitions(-DNOMINMAX) endif() include_directories( ${kstars_SOURCE_DIR}/kstars ${kstars_SOURCE_DIR}/kstars/skyobjects ${kstars_SOURCE_DIR}/kstars/skycomponents ${kstars_SOURCE_DIR}/kstars/auxiliary ${kstars_SOURCE_DIR}/kstars/time ${kstars_SOURCE_DIR}/kstars/tools ) if (INDI_FOUND) if(BUILD_KSTARS_LITE) set (fits_klite_SRCS fitsviewer/fitsdata.cpp ) set (fits2_klite_SRCS fitsviewer/bayer.c fitsviewer/fpack.c fitsviewer/fpackutil.c ) include_directories(${CFITSIO_INCLUDE_DIR}) include_directories(${NOVA_INCLUDE_DIR}) set (indi_klite_SRCS indi/clientmanagerlite.cpp indi/inditelescopelite.cpp kstarslite/skyitems/skynodes/crosshairnode.cpp kstarslite/skyitems/telescopesymbolsitem.cpp ) endif () set(indiui_SRCS indi/streamform.ui indi/drivermanager.ui indi/opsindi.ui indi/indihostconf.ui indi/customdrivers.ui #indi/telescopewizard.ui ) set(indi_SRCS indi/drivermanager.cpp indi/servermanager.cpp indi/clientmanager.cpp indi/blobmanager.cpp indi/guimanager.cpp indi/driverinfo.cpp indi/deviceinfo.cpp indi/indidevice.cpp indi/indigroup.cpp indi/indiproperty.cpp indi/indielement.cpp indi/indistd.cpp indi/indilistener.cpp indi/inditelescope.cpp indi/indiccd.cpp indi/wsmedia.cpp indi/indifocuser.cpp indi/indifilter.cpp indi/indidome.cpp indi/indiweather.cpp indi/indicap.cpp indi/indilightbox.cpp indi/indidbus.cpp indi/opsindi.cpp #indi/telescopewizardprocess.cpp indi/streamwg.cpp indi/videowg.cpp indi/indiwebmanager.cpp indi/customdrivers.cpp ) if (CFITSIO_FOUND) set(ekosui_SRCS ekos/opsekos.ui ekos/manager.ui ekos/profileeditor.ui ekos/profilewizard.ui # Scheduler ekos/scheduler/scheduler.ui ekos/scheduler/mosaic.ui # Capture ekos/capture/capture.ui ekos/capture/calibrationoptions.ui ekos/capture/dslrinfo.ui ekos/capture/rotatorsettings.ui ekos/capture/customproperties.ui # Align ekos/align/align.ui ekos/align/opsastrometry.ui ekos/align/opsalign.ui ekos/align/opsastrometrycfg.ui ekos/align/opsastrometryindexfiles.ui ekos/align/mountmodel.ui ekos/align/opsastap.ui # Focus ekos/focus/focus.ui # Mount ekos/mount/mount.ui # Guide ekos/guide/guide.ui ekos/guide/opscalibration.ui ekos/guide/opsguide.ui ekos/guide/manualdither.ui ekos/observatory/observatory.ui #TODO remove from GIT #ekos/guide/guider.ui #ekos/guide/rcalibration.ui # Auxiliary ekos/auxiliary/filtersettings.ui ekos/auxiliary/opslogs.ui ekos/auxiliary/serialportassistant.ui # Ekos Live ekos/ekoslive/ekoslivedialog.ui # INDI Hub ekos/indihub.ui ) set(ekos_SRCS ekos/ekos.cpp ekos/manager.cpp ekos/profileeditor.cpp ekos/profilewizard.cpp ekos/qMDNS.cpp ekos/opsekos.cpp # Auxiliary ekos/auxiliary/dome.cpp ekos/auxiliary/weather.cpp ekos/auxiliary/dustcap.cpp ekos/auxiliary/darklibrary.cpp ekos/auxiliary/filtermanager.cpp ekos/auxiliary/filterdelegate.cpp ekos/auxiliary/opslogs.cpp ekos/auxiliary/serialportassistant.cpp # Capture ekos/capture/capture.cpp ekos/capture/sequencejob.cpp ekos/capture/dslrinfodialog.cpp ekos/capture/rotatorsettings.cpp ekos/capture/customproperties.cpp # Scheduler ekos/scheduler/schedulerjob.cpp ekos/scheduler/scheduler.cpp ekos/scheduler/mosaic.cpp # Focus ekos/focus/focus.cpp ekos/focus/focusalgorithms.cpp ekos/focus/polynomialfit.cpp # Mount ekos/mount/mount.cpp # Align ekos/align/align.cpp ekos/align/alignview.cpp ekos/align/astrometryparser.cpp ekos/align/opsastrometry.cpp ekos/align/opsalign.cpp ekos/align/opsastrometrycfg.cpp ekos/align/opsastrometryindexfiles.cpp ekos/align/opsastap.cpp ekos/align/offlineastrometryparser.cpp ekos/align/onlineastrometryparser.cpp ekos/align/remoteastrometryparser.cpp ekos/align/astapastrometryparser.cpp # Guide ekos/guide/guide.cpp ekos/guide/guideinterface.cpp ekos/guide/opscalibration.cpp ekos/guide/opsguide.cpp # Internal Guide ekos/guide/internalguide/gmath.cpp ekos/guide/internalguide/internalguider.cpp #ekos/guide/internalguide/guider.cpp ekos/guide/internalguide/matr.cpp #ekos/guide/internalguide/rcalibration.cpp ekos/guide/internalguide/vect.cpp ekos/guide/internalguide/imageautoguiding.cpp # External Guide ekos/guide/externalguide/phd2.cpp ekos/guide/externalguide/linguider.cpp #Observatory ekos/observatory/observatory.cpp ekos/observatory/observatorymodel.cpp ekos/observatory/observatorydomemodel.cpp ekos/observatory/observatoryweathermodel.cpp # Ekos Live ekos/ekoslive/ekosliveclient.cpp ekos/ekoslive/message.cpp ekos/ekoslive/media.cpp ekos/ekoslive/cloud.cpp ) endif(CFITSIO_FOUND) include_directories(${INDI_INCLUDE_DIR}) endif (INDI_FOUND) if (CFITSIO_FOUND) set (sep_SRCS fitsviewer/sep/analyse.c fitsviewer/sep/aperture.c fitsviewer/sep/background.c fitsviewer/sep/convolve.c fitsviewer/sep/deblend.c fitsviewer/sep/extract.c fitsviewer/sep/lutz.c fitsviewer/sep/util.c ) set (fits_SRCS fitsviewer/fitslabel.cpp fitsviewer/fitsviewer.cpp fitsviewer/stretch.cpp fitsviewer/fitstab.cpp fitsviewer/fitsdebayer.cpp fitsviewer/opsfits.cpp ) if (Qt5DataVisualization_FOUND) set(fits_SRCS ${fits_SRCS} fitsviewer/starprofileviewer.cpp) endif() set (fits2_SRCS fitsviewer/bayer.c fitsviewer/fpack.c fitsviewer/fpackutil.c fitsviewer/fitshistogram.cpp fitsviewer/fitsview.cpp fitsviewer/fitsdata.cpp fitsviewer/fitsstardetector.cpp fitsviewer/fitsthresholddetector.cpp fitsviewer/fitsgradientdetector.cpp fitsviewer/fitscentroiddetector.cpp fitsviewer/fitssepdetector.cpp + fitsviewer/fitsskyobject.cpp ) set (fitsui_SRCS fitsviewer/fitsheaderdialog.ui fitsviewer/statform.ui fitsviewer/fitsdebayer.ui indi/streamform.ui indi/recordingoptions.ui fitsviewer/fitshistogramui.ui fitsviewer/opsfits.ui ) include_directories(${CFITSIO_INCLUDE_DIR}) endif(CFITSIO_FOUND) IF (CFITSIO_FOUND) IF (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) IF (SANITIZERS) SET_SOURCE_FILES_PROPERTIES(fitsviewer/bayer.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align -fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fitsdata.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fitshistogram.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fitsview.cpp PROPERTIES COMPILE_FLAGS "-fno-sanitize=address,undefined -fomit-frame-pointer") ELSE () SET_SOURCE_FILES_PROPERTIES(fitsviewer/bayer.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") ENDIF () SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/analyse.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/aperture.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align -Wno-pointer-arith") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/background.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/deblend.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align -Wno-incompatible-pointer-types-discards-qualifiers") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/extract.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/lutz.c PROPERTIES COMPILE_FLAGS "-Wno-cast-align") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/util.c PROPERTIES COMPILE_FLAGS "-Wno-incompatible-pointer-types-discards-qualifiers") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpack.c PROPERTIES COMPILE_FLAGS "-Wno-error") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpackutil.c PROPERTIES COMPILE_FLAGS "-Wno-error") ELSEIF (NOT WIN32) SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpack.c PROPERTIES COMPILE_FLAGS "-Wno-error") SET_SOURCE_FILES_PROPERTIES(fitsviewer/fpackutil.c PROPERTIES COMPILE_FLAGS "-Wno-error") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/aperture.c PROPERTIES COMPILE_FLAGS "-Wno-pointer-arith") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/deblend.c PROPERTIES COMPILE_FLAGS "-Wno-discarded-qualifiers") SET_SOURCE_FILES_PROPERTIES(fitsviewer/sep/util.c PROPERTIES COMPILE_FLAGS "-Wno-discarded-qualifiers") ENDIF () ENDIF () if(WCSLIB_FOUND) include_directories( ${WCSLIB_INCLUDE_DIR} ) endif(WCSLIB_FOUND) set(xplanet_SRCS xplanet/opsxplanet.cpp ) set(xplanetui_SRCS xplanet/opsxplanet.ui ) ########### next target ############### set(libkstarstools_SRCS tools/altvstime.cpp tools/avtplotwidget.cpp tools/calendarwidget.cpp tools/conjunctions.cpp tools/eclipsetool.cpp tools/eclipsehandler.cpp tools/eclipsetool/lunareclipsehandler.cpp tools/jmoontool.cpp tools/approachsolver.cpp tools/ksconjunct.cpp tools/eqplotwidget.cpp tools/astrocalc.cpp tools/modcalcangdist.cpp tools/modcalcapcoord.cpp tools/modcalcaltaz.cpp tools/modcalcdaylength.cpp tools/modcalceclipticcoords.cpp tools/modcalcvizequinox.cpp tools/modcalcgalcoord.cpp tools/modcalcgeodcoord.cpp tools/modcalcjd.cpp tools/modcalcplanets.cpp tools/modcalcsidtime.cpp tools/modcalcvlsr.cpp tools/observinglist.cpp tools/obslistpopupmenu.cpp tools/sessionsortfilterproxymodel.cpp tools/obslistwizard.cpp tools/planetviewer.cpp tools/pvplotwidget.cpp tools/scriptargwidgets.cpp tools/scriptbuilder.cpp tools/scriptfunction.cpp tools/skycalendar.cpp tools/wutdialog.cpp tools/flagmanager.cpp tools/horizonmanager.cpp tools/nameresolver.cpp tools/polarishourangle.cpp #FIXME Port to KF5 #tools/moonphasetool.cpp tools/starhopper.cpp tools/eyepiecefield.cpp tools/exporteyepieceview.cpp tools/starhopperdialog.cpp tools/adddeepskyobject.cpp ) if(${KF5_VERSION} VERSION_EQUAL 5.18.0 OR ${KF5_VERSION} VERSION_GREATER 5.18.0) set(libkstarstools_SRCS ${libkstarstools_SRCS} tools/whatsinteresting/skyobjlistmodel.cpp tools/whatsinteresting/wiview.cpp tools/whatsinteresting/modelmanager.cpp tools/whatsinteresting/skyobjitem.cpp tools/whatsinteresting/wilpsettings.cpp tools/whatsinteresting/wiequipsettings.cpp tools/whatsinteresting/obsconditions.cpp tools/whatsinteresting/skyobjdescription.cpp ) endif() ki18n_wrap_ui(libkstarstools_ui_SRCS tools/altvstime.ui tools/argchangeviewoption.ui tools/argexportimage.ui tools/argloadcolorscheme.ui tools/arglooktoward.ui tools/argfindobject.ui tools/argprintimage.ui tools/argsetaltaz.ui tools/argsetcolor.ui tools/argsetgeolocation.ui tools/argsetlocaltime.ui tools/argsetradec.ui tools/argsettrack.ui tools/argtimescale.ui tools/argwaitfor.ui tools/argwaitforkey.ui tools/argzoom.ui tools/conjunctions.ui tools/eclipsetool.ui tools/modcalcangdist.ui tools/modcalcapcoord.ui tools/modcalcaltaz.ui tools/modcalcdaylength.ui tools/modcalceclipticcoords.ui tools/modcalcvizequinox.ui tools/modcalcgalcoord.ui tools/modcalcgeod.ui tools/modcalcjd.ui tools/modcalcplanets.ui tools/modcalcsidtime.ui tools/modcalcvlsr.ui tools/observinglist.ui tools/obslistwizard.ui tools/optionstreeview.ui tools/planetviewer.ui tools/scriptbuilder.ui tools/scriptnamedialog.ui tools/skycalendar.ui tools/wutdialog.ui tools/flagmanager.ui tools/starhopperdialog.ui tools/horizonmanager.ui tools/adddeepskyobject.ui tools/polarishourangle.ui ) if (${KF5_VERSION} VERSION_EQUAL 5.18.0 OR ${KF5_VERSION} VERSION_GREATER 5.18.0) ki18n_wrap_ui(libkstarstools_ui_SRCS tools/whatsinteresting/wilpsettings.ui tools/whatsinteresting/wiequipsettings.ui ) endif() set(libkstarswidgets_SRCS widgets/clicklabel.cpp widgets/dmsbox.cpp widgets/draglistbox.cpp widgets/fovwidget.cpp widgets/logedit.cpp widgets/magnitudespinbox.cpp widgets/mapcanvas.cpp widgets/thumbimage.cpp widgets/timespinbox.cpp widgets/timestepbox.cpp widgets/timeunitbox.cpp widgets/infoboxwidget.cpp # widgets/genericcalendarwidget.cpp # widgets/moonphasecalendarwidget.cpp widgets/kshelplabel.cpp widgets/unitspinboxwidget.cpp ) ki18n_wrap_ui(libkstarswidgets_ui_SRCS # widgets/genericcalendarwidget.ui widgets/unitspinboxwidget.ui ) set(kstars_KCFG_SRCS Options.kcfgc) set(kstars_options_SRCS options/opsadvanced.cpp options/opscatalog.cpp options/opscolors.cpp options/opsguides.cpp options/opssolarsystem.cpp options/opssatellites.cpp options/opssupernovae.cpp ) set(kstars_optionsui_SRCS options/opsadvanced.ui options/opscatalog.ui options/opscolors.ui options/opsguides.ui options/opssolarsystem.ui options/opssatellites.ui options/opssupernovae.ui ) set(kstars_dialogs_SRCS dialogs/addcatdialog.cpp dialogs/addlinkdialog.cpp dialogs/detaildialog.cpp dialogs/finddialog.cpp dialogs/focusdialog.cpp dialogs/fovdialog.cpp dialogs/locationdialog.cpp dialogs/timedialog.cpp dialogs/exportimagedialog.cpp ) set(kstars_dialogsui_SRCS dialogs/addcatdialog.ui dialogs/addlinkdialog.ui dialogs/details_database.ui dialogs/details_data.ui dialogs/details_data_comet.ui dialogs/details_links.ui dialogs/details_log.ui dialogs/details_position.ui dialogs/finddialog.ui dialogs/focusdialog.ui dialogs/fovdialog.ui dialogs/locationdialog.ui dialogs/wizwelcome.ui dialogs/wizlocation.ui dialogs/wizdownload.ui dialogs/wizdata.ui dialogs/newfov.ui dialogs/exportimagedialog.ui ) set(hips_SRCS hips/healpix.cpp hips/hipsrenderer.cpp hips/scanrender.cpp hips/pixcache.cpp hips/urlfiledownload.cpp hips/opships.cpp ) set(hips_manager_SRCS hips/hipsmanager.cpp ) set(oal_SRCS oal/log.cpp oal/observer.cpp oal/site.cpp oal/session.cpp oal/scope.cpp oal/eyepiece.cpp oal/filter.cpp oal/observation.cpp oal/lens.cpp oal/equipmentwriter.cpp oal/observeradd.cpp oal/execute.cpp ) set(printing_SRCS printing/detailstable.cpp printing/finderchart.cpp printing/foveditordialog.cpp printing/fovsnapshot.cpp printing/kstarsdocument.cpp printing/legend.cpp printing/loggingform.cpp printing/printingwizard.cpp printing/pwizchartconfig.cpp printing/pwizchartcontents.cpp printing/pwizfovbrowse.cpp printing/pwizfovconfig.cpp printing/pwizfovmanual.cpp printing/pwizfovsh.cpp printing/pwizfovtypeselection.cpp printing/pwizobjectselection.cpp printing/pwizprint.cpp printing/shfovexporter.cpp printing/simplefovexporter.cpp ) set(printingui_SRCS printing/foveditordialog.ui printing/pwizchartconfig.ui printing/pwizchartcontents.ui printing/pwizfovbrowse.ui printing/pwizfovconfig.ui printing/pwizfovmanual.ui printing/pwizfovsh.ui printing/pwizfovtypeselection.ui printing/pwizobjectselection.ui printing/pwizprint.ui printing/pwizwelcome.ui ) set( kstars_KCFG_SRCS Options.kcfgc ) set(libkstarscomponents_SRCS skycomponents/skylabeler.cpp skycomponents/highpmstarlist.cpp skycomponents/skymapcomposite.cpp skycomponents/skymesh.cpp skycomponents/linelistindex.cpp skycomponents/linelistlabel.cpp skycomponents/noprecessindex.cpp skycomponents/listcomponent.cpp skycomponents/pointlistcomponent.cpp skycomponents/solarsystemsinglecomponent.cpp skycomponents/solarsystemlistcomponent.cpp skycomponents/earthshadowcomponent.cpp skycomponents/asteroidscomponent.cpp skycomponents/cometscomponent.cpp skycomponents/planetmoonscomponent.cpp skycomponents/solarsystemcomposite.cpp skycomponents/satellitescomponent.cpp skycomponents/starcomponent.cpp skycomponents/deepstarcomponent.cpp skycomponents/deepskycomponent.cpp skycomponents/catalogcomponent.cpp skycomponents/syncedcatalogcomponent.cpp skycomponents/constellationartcomponent.cpp skycomponents/constellationboundarylines.cpp skycomponents/constellationlines.cpp skycomponents/constellationnamescomponent.cpp skycomponents/supernovaecomponent.cpp skycomponents/coordinategrid.cpp skycomponents/equatorialcoordinategrid.cpp skycomponents/horizontalcoordinategrid.cpp skycomponents/localmeridiancomponent.cpp skycomponents/ecliptic.cpp skycomponents/equator.cpp skycomponents/artificialhorizoncomponent.cpp skycomponents/hipscomponent.cpp skycomponents/horizoncomponent.cpp skycomponents/milkyway.cpp skycomponents/skycomponent.cpp skycomponents/skycomposite.cpp skycomponents/starblock.cpp skycomponents/starblocklist.cpp skycomponents/starblockfactory.cpp skycomponents/culturelist.cpp skycomponents/flagcomponent.cpp skycomponents/targetlistcomponent.cpp ) #LIST(APPEND libkstarscomponents_SRCS # #skycomponents/notifyupdatesui.cpp # ) IF (BUILD_KSTARS_LITE) set(libkstarstools_ui_klite_SRCS tools/nameresolver.cpp ) ENDIF () set(kstars_skyobjects_SRCS skyobjects/constellationsart.cpp skyobjects/deepskyobject.cpp skyobjects/jupitermoons.cpp skyobjects/planetmoons.cpp skyobjects/ksasteroid.cpp skyobjects/kscomet.cpp skyobjects/ksmoon.cpp skyobjects/ksearthshadow.cpp skyobjects/ksplanetbase.cpp skyobjects/ksplanet.cpp #skyobjects/kspluto.cpp skyobjects/kssun.cpp skyobjects/skyline.cpp skyobjects/skyobject.cpp skyobjects/skypoint.cpp skyobjects/starobject.cpp skyobjects/trailobject.cpp skyobjects/satellite.cpp skyobjects/satellitegroup.cpp skyobjects/supernova.cpp ) set(kstars_projection_SRCS projections/projector.cpp projections/lambertprojector.cpp projections/gnomonicprojector.cpp projections/stereographicprojector.cpp projections/orthographicprojector.cpp projections/azimuthalequidistantprojector.cpp projections/equirectangularprojector.cpp ) set(kstars_extra_SRCS auxiliary/colorscheme.cpp auxiliary/dms.cpp auxiliary/cachingdms.cpp auxiliary/geolocation.cpp auxiliary/ksfilereader.cpp auxiliary/ksuserdb.cpp auxiliary/binfilehelper.cpp auxiliary/ksutils.cpp auxiliary/ksdssimage.cpp auxiliary/ksdssdownloader.cpp auxiliary/nonlineardoublespinbox.cpp auxiliary/profileinfo.cpp auxiliary/filedownloader.cpp auxiliary/kspaths.cpp auxiliary/QRoundProgressBar.cpp auxiliary/skyobjectlistmodel.cpp auxiliary/ksnotification.cpp auxiliary/ksmessagebox.cpp auxiliary/QProgressIndicator.cpp auxiliary/ctkrangeslider.cpp time/simclock.cpp time/kstarsdatetime.cpp time/timezonerule.cpp ksnumbers.cpp kstarsdata.cpp texturemanager.cpp #to minimize number of indef KSTARS_LITE skypainter.cpp ) SET(kstars_extra_kstars_SRCS auxiliary/thememanager.cpp auxiliary/schememanager.cpp auxiliary/imageviewer.cpp auxiliary/xplanetimageviewer.cpp auxiliary/fov.cpp auxiliary/thumbnailpicker.cpp auxiliary/thumbnaileditor.cpp auxiliary/imageexporter.cpp auxiliary/kswizard.cpp auxiliary/qcustomplot.cpp kstarsdbus.cpp kspopupmenu.cpp ksalmanac.cpp kstarsactions.cpp kstarsinit.cpp kstars.cpp kstarssplash.cpp skymap.cpp skymapdrawabstract.cpp skymapqdraw.cpp skymapevents.cpp skyqpainter.cpp ) # Temporary solution to allow use of qml files from source dir DELETE SET(KSTARSLITE_CPP_OPTIONS -DSOURCE_DIR=\"${kstars_SOURCE_DIR}\" -DQML_IMPORT="${CMAKE_CURRENT_SOURCE_DIR}") set(klite_SRCS kstarslite.cpp kstarsliteinit.cpp skymaplite.cpp skymapliteevents.cpp #Wrappers kstarslite/skypointlite.cpp kstarslite/skyobjectlite.cpp #ImageProvider kstarslite/imageprovider.cpp #Dialogs kstarslite/dialogs/detaildialoglite.cpp kstarslite/dialogs/finddialoglite.cpp kstarslite/dialogs/locationdialoglite.cpp #RootNode kstarslite/skyitems/rootnode.cpp kstarslite/skyitems/skyopacitynode.cpp kstarslite/skyitems/typedeflite.h #SkyItems kstarslite/skyitems/skyitem.cpp kstarslite/skyitems/planetsitem.cpp kstarslite/skyitems/asteroidsitem.cpp kstarslite/skyitems/cometsitem.cpp kstarslite/skyitems/horizonitem.cpp kstarslite/skyitems/labelsitem.cpp kstarslite/skyitems/constellationnamesitem.cpp kstarslite/skyitems/staritem.cpp kstarslite/skyitems/deepstaritem.cpp kstarslite/skyitems/deepskyitem.cpp kstarslite/skyitems/constellationartitem.cpp kstarslite/skyitems/satellitesitem.cpp kstarslite/skyitems/supernovaeitem.cpp kstarslite/skyitems/fovitem.cpp kstarslite/skyitems/syncedcatalogitem.cpp #Line kstarslite/skyitems/lines/linesitem.cpp kstarslite/skyitems/lines/equatoritem.cpp kstarslite/skyitems/lines/eclipticitem.cpp kstarslite/skyitems/lines/milkywayitem.cpp #SkyNodes kstarslite/skyitems/skynodes/planetnode.cpp kstarslite/skyitems/skynodes/skynode.cpp kstarslite/skyitems/skynodes/pointsourcenode.cpp kstarslite/skyitems/skynodes/planetmoonsnode.cpp kstarslite/skyitems/skynodes/horizonnode.cpp kstarslite/skyitems/skynodes/labelnode.cpp kstarslite/skyitems/skynodes/guidelabelnode.cpp kstarslite/skyitems/skynodes/deepskynode.cpp kstarslite/skyitems/skynodes/dsosymbolnode.cpp kstarslite/skyitems/skynodes/skypolygonnode.cpp kstarslite/skyitems/skynodes/constellationartnode.cpp kstarslite/skyitems/skynodes/satellitenode.cpp kstarslite/skyitems/skynodes/supernovanode.cpp kstarslite/skyitems/skynodes/trixelnode.cpp kstarslite/skyitems/skynodes/fovsymbolnode.cpp #Nodes kstarslite/skyitems/skynodes/nodes/pointnode.cpp kstarslite/skyitems/skynodes/nodes/polynode.cpp kstarslite/skyitems/skynodes/nodes/linenode.cpp kstarslite/skyitems/skynodes/nodes/ellipsenode.cpp kstarslite/skyitems/skynodes/nodes/rectnode.cpp #Other kstarslite/deviceorientation.cpp ) set(kstarslite_libtess_SRC #libtess libtess/gluos.h libtess/priorityq-sort.h libtess/sweep.c libtess/tessmono.c libtess/dict-list.h libtess/glu.h libtess/tessellate.c libtess/dict.c libtess/geom.c libtess/memalloc.c libtess/mesh.c libtess/normal.c libtess/priorityq.c libtess/priorityq-heap.c libtess/render.c libtess/tess.c ) IF (BUILD_KSTARS_LITE) ADD_CUSTOM_TARGET(convert_translations ${CMAKE_SOURCE_DIR}/tools/convert_translations.sh ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) ADD_DEPENDENCIES(convert_translations fetch-translations) IF (ANDROID) ADD_CUSTOM_TARGET(convert_translations_to_android ${CMAKE_SOURCE_DIR}/tools/convert_translations.sh ${CMAKE_BINARY_DIR}/packaging/android/export/share/kstars WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) ADD_DEPENDENCIES(convert_translations_to_android fetch-translations) ENDIF () ENDIF () IF ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") SET_SOURCE_FILES_PROPERTIES(${kstarslite_libtess_SRC} PROPERTIES COMPILE_FLAGS "-Wno-error") ENDIF () #Qml files will be probably moved to user's data dir, but for use #with QtCreator it is more convenient to have them here set(kstarsliteqml_SRCS kstarslite/qml/main.qml kstarslite/qml/constants/Constants.qml kstarslite/qml/modules/SkyMapLiteWrapper.qml kstarslite/qml/modules/BottomMenu.qml kstarslite/qml/modules/KSPage.qml kstarslite/qml/modules/KSListView.qml kstarslite/qml/modules/KSLabel.qml kstarslite/qml/modules/KSText.qml kstarslite/qml/modules/KSTabButton.qml kstarslite/qml/modules/KSTab.qml kstarslite/qml/modules/KSTabBarArrow.qml kstarslite/qml/modules/KSTextField.qml kstarslite/qml/modules/KSButton.qml kstarslite/qml/modules/TopMenu.qml kstarslite/qml/modules/helpers/TopMenuButton.qml kstarslite/qml/modules/helpers/BottomMenuButton.qml kstarslite/qml/modules/Splash.qml kstarslite/qml/modules/helpers/TimeSpinBox.qml kstarslite/qml/modules/TimePage.qml #Popups kstarslite/qml/modules/popups/ProjectionsPopup.qml kstarslite/qml/modules/popups/FOVPopup.qml kstarslite/qml/modules/popups/ColorSchemePopup.qml #Menus kstarslite/qml/modules/menus/ContextMenu.qml #Helpers kstarslite/qml/modules/helpers/PassiveNotification.qml kstarslite/qml/modules/helpers/KSMenuItem.qml kstarslite/qml/modules/helpers/TelescopeControl.qml #Dialogs kstarslite/qml/dialogs/FindDialog.qml kstarslite/qml/dialogs/LocationDialog.qml kstarslite/qml/dialogs/DetailsDialog.qml kstarslite/qml/dialogs/AboutDialog.qml kstarslite/qml/dialogs/helpers/DetailsItem.qml kstarslite/qml/dialogs/helpers/DetailsAddLink.qml kstarslite/qml/dialogs/helpers/LocationEdit.qml kstarslite/qml/dialogs/helpers/LocationLoading.qml kstarslite/qml/dialogs/menus/DetailsLinkMenu.qml kstarslite/qml/dialogs/menus/LocationsGeoMenu.qml #INDI kstarslite/qml/indi/INDIControlPanel.qml kstarslite/qml/indi/DevicePanel.qml kstarslite/qml/indi/ImagePreview.qml kstarslite/qml/indi/modules/MotionControl.qml kstarslite/qml/indi/modules/Led.qml kstarslite/qml/indi/modules/KSLed.qml kstarslite/qml/indi/modules/Property.qml kstarslite/qml/indi/modules/KSComboBox.qml kstarslite/qml/indi/modules/KSButtonSwitch.qml kstarslite/qml/indi/modules/KSCheckBox.qml kstarslite/qml/indi/modules/KSINDIText.qml kstarslite/qml/indi/modules/KSINDITextField.qml kstarslite/qml/indi/modules/KSButtonsSwitchRow.qml #Tutorial kstarslite/qml/modules/tutorial/TutorialPopup.qml kstarslite/qml/modules/tutorial/TutorialExitPopup.qml kstarslite/qml/modules/tutorial/TutorialStep1.qml kstarslite/qml/modules/tutorial/TutorialStep2.qml kstarslite/qml/modules/tutorial/TutorialStep3.qml kstarslite/qml/modules/tutorial/TutorialStep4.qml kstarslite/qml/modules/tutorial/TutorialStep5.qml kstarslite/qml/modules/tutorial/TutorialPane.qml ) add_subdirectory(kstarslite/qml) ADD_CUSTOM_TARGET(kstarsliteqml SOURCES ${kstarsliteqml_SRCS}) if(ANDROID) add_subdirectory(kstarslite/res) endif(ANDROID) set(kstars_SRCS ${indi_SRCS} ${fits_SRCS} ${ekos_SRCS} ${libkstarswidgets_SRCS} ${libkstarscomponents_SRCS} ${libkstarstools_SRCS} ${kstars_extra_SRCS} ${kstars_extra_kstars_SRCS} ${kstars_projection_SRCS} ${xplanet_SRCS} ${kstars_options_SRCS} ${kstars_skyobjects_SRCS} ${kstars_dialogs_SRCS} ${hips_SRCS} ${oal_SRCS} ${printing_SRCS} #KStars Lite ${kstarslite_SRCS} # Generated files ${libkstarstools_ui_SRCS} ${libkstarswidgets_ui_SRCS} ) set(kstarslite_SRCS ${indi_klite_SRCS} ${libkstarscomponents_SRCS} ${kstars_extra_SRCS} ${kstars_projection_SRCS} ${kstars_skyobjects_SRCS} # KStars Lite sources ${klite_SRCS} # Generated files ${libkstarstools_ui_klite_SRCS} ) # Generate all the necessary QLoggingCategory files ecm_qt_declare_logging_category(kstars_SRCS HEADER kstars_debug.h IDENTIFIER KSTARS CATEGORY_NAME org.kde.kstars) ecm_qt_declare_logging_category(kstars_SRCS HEADER indi_debug.h IDENTIFIER KSTARS_INDI CATEGORY_NAME org.kde.kstars.indi) ecm_qt_declare_logging_category(kstars_SRCS HEADER fits_debug.h IDENTIFIER KSTARS_FITS CATEGORY_NAME org.kde.kstars.fits) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_debug.h IDENTIFIER KSTARS_EKOS CATEGORY_NAME org.kde.kstars.ekos) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_capture_debug.h IDENTIFIER KSTARS_EKOS_CAPTURE CATEGORY_NAME org.kde.kstars.ekos.capture) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_focus_debug.h IDENTIFIER KSTARS_EKOS_FOCUS CATEGORY_NAME org.kde.kstars.ekos.focus) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_align_debug.h IDENTIFIER KSTARS_EKOS_ALIGN CATEGORY_NAME org.kde.kstars.ekos.align) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_guide_debug.h IDENTIFIER KSTARS_EKOS_GUIDE CATEGORY_NAME org.kde.kstars.ekos.guide) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_mount_debug.h IDENTIFIER KSTARS_EKOS_MOUNT CATEGORY_NAME org.kde.kstars.ekos.mount) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_scheduler_debug.h IDENTIFIER KSTARS_EKOS_SCHEDULER CATEGORY_NAME org.kde.kstars.ekos.scheduler) ecm_qt_declare_logging_category(kstars_SRCS HEADER ekos_observatory_debug.h IDENTIFIER KSTARS_EKOS_OBSERVATORY CATEGORY_NAME org.kde.kstars.ekos.observatory) kconfig_add_kcfg_files(kstars_SRCS ${kstars_KCFG_SRCS}) ecm_qt_declare_logging_category(kstarslite_SRCS HEADER kstars_debug.h IDENTIFIER KSTARS CATEGORY_NAME org.kde.kstars) ecm_qt_declare_logging_category(kstarslite_SRCS HEADER fits_debug.h IDENTIFIER KSTARS_FITS CATEGORY_NAME org.kde.kstars.fits) kconfig_add_kcfg_files(kstarslite_SRCS ${kstars_KCFG_SRCS}) IF (UNITY_BUILD) ENABLE_UNITY_BUILD(kstars kstars_SRCS 10 cpp) ENABLE_UNITY_BUILD(kstarslite kstarslite_SRCS 10 cpp) ENDIF () set(kstars_SRCS ${kstars_SRCS} ${fits2_SRCS} ${sep_SRCS} ${hips_manager_SRCS}) set(kstarslite_SRCS ${kstarslite_SRCS} ${fits_klite_SRCS} ${sep_SRCS} ${fits2_klite_SRCS} ${kstarslite_libtess_SRC}) IF (NOT ANDROID) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.xml kstars.h KStars) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.SimClock.xml simclock.h SimClock) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.FOV.xml fov.h FOV) IF (INDI_FOUND) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.INDI.xml indi/indidbus.h INDIDBus) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.xml ekos/manager.h Ekos::Manager) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Capture.xml ekos/capture/capture.h Ekos::Capture) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Focus.xml ekos/focus/focus.h Ekos::Focus) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Guide.xml ekos/guide/guide.h Ekos::Guide) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Align.xml ekos/align/align.h Ekos::Align) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Mount.xml ekos/mount/mount.h Ekos::Mount) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Dome.xml ekos/auxiliary/dome.h Ekos::Dome) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Weather.xml ekos/auxiliary/weather.h Ekos::Weather) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.DustCap.xml ekos/auxiliary/dustcap.h Ekos::DustCap) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Scheduler.xml ekos/scheduler/scheduler.h Ekos::Scheduler) qt5_add_dbus_adaptor(kstars_SRCS org.kde.kstars.Ekos.Observatory.xml ekos/observatory/observatory.h Ekos::Observatory) ENDIF () ki18n_wrap_ui(kstars_SRCS ${indiui_SRCS} ${ui_SRCS} ${fitsui_SRCS} ${ekosui_SRCS} ${xplanetui_SRCS} ${kstars_optionsui_SRCS} ${kstars_dialogsui_SRCS} ${printingui_SRCS} auxiliary/thumbnailpicker.ui auxiliary/thumbnaileditor.ui oal/observeradd.ui oal/equipmentwriter.ui oal/execute.ui hips/opships.ui hips/opshipsdisplay.ui hips/opshipscache.ui #skycomponents/notifyupdatesui.ui ) add_library(KStarsLib STATIC ${kstars_SRCS}) include(GenerateExportHeader) generate_export_header(KStarsLib) target_link_libraries(KStarsLib LibKSDataHandlers htmesh KF5::Crash KF5::I18n KF5::NewStuff KF5::KIOFileWidgets KF5::WidgetsAddons KF5::Plotting KF5::Notifications Qt5::Gui Qt5::PrintSupport Qt5::Sql Qt5::Svg Qt5::Qml Qt5::Quick Qt5::Network #Qt5::Positioning Qt5::Concurrent Qt5::WebSockets ${ZLIB_LIBRARIES} ) if (Qt5Keychain_FOUND) target_include_directories(KStarsLib PUBLIC ${QTKEYCHAIN_INCLUDE_DIRS}) target_link_libraries(KStarsLib ${QTKEYCHAIN_LIBRARIES}) endif(Qt5Keychain_FOUND) if (Qt5DataVisualization_FOUND) target_link_libraries(KStarsLib Qt5::DataVisualization) endif(Qt5DataVisualization_FOUND) if (KF5NotifyConfig_FOUND) target_link_libraries(KStarsLib KF5::NotifyConfig) endif(KF5NotifyConfig_FOUND) if(NOT WIN32) target_link_libraries(KStarsLib m) endif(NOT WIN32) ENDIF () if (BUILD_KSTARS_LITE) add_library(KStarsLiteLib STATIC ${kstarslite_SRCS}) target_link_libraries(KStarsLiteLib LibKSDataHandlers htmesh KF5::I18n KF5::Plotting KF5::ConfigGui Qt5::Gui Qt5::Sql Qt5::Qml Qt5::Quick Qt5::QuickControls2 Qt5::Positioning Qt5::PositioningQuick Qt5::Concurrent ${ZLIB_LIBRARIES} ) if (ANDROID) target_link_libraries(KStarsLiteLib Qt5::AndroidExtras) endif () endif () if (CFITSIO_FOUND) if (NOT ANDROID) target_include_directories(KStarsLib PUBLIC ${CFITSIO_INCLUDE_DIR}) target_link_libraries(KStarsLib ${CFITSIO_LIBRARIES}) endif() if (BUILD_KSTARS_LITE) target_include_directories(KStarsLiteLib PUBLIC ${CFITSIO_INCLUDE_DIR}) target_link_libraries(KStarsLiteLib ${CFITSIO_LIBRARIES}) endif() endif(CFITSIO_FOUND) if(INDI_FOUND) if (NOT ANDROID) find_package(Nova REQUIRED) include_directories(${NOVA_INCLUDE_DIR}) endif () ## Support Multiple Platforms. All Require INDI ## WIN32 Desktop: Requires INDI Qt5 Client + GSL ## WIN32 Lite: Requires INDI Qt5 Client ## Linux + MacOS Desktop: Requires INDI Client + GSL ## Linux + MacOS Lite: Requires INDI Qt5 Client ## Android: Requires INDI Qt5 Client built for Android if (NOT ANDROID) find_package(GSL REQUIRED) include_directories(${GSL_INCLUDE_DIRS}) target_link_libraries(KStarsLib ${GSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} KF5::Notifications) endif () if(WIN32 OR ANDROID) if(ANDROID) target_link_libraries(KStarsLiteLib ${INDI_CLIENT_ANDROID_LIBRARIES} ${CFITSIO_LIBRARIES} ${LIBRAW_LIBRARIES}) target_compile_options(KStarsLiteLib PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) else(ANDROID) target_link_libraries(KStarsLib ${INDI_CLIENT_LIBRARIES} ${NOVA_LIBRARIES}) endif(ANDROID) else(WIN32 OR ANDROID) if (BUILD_KSTARS_LITE) target_link_libraries(KStarsLiteLib ${INDI_CLIENT_QT_LIBRARIES} ${NOVA_LIBRARIES} z) target_compile_options(KStarsLiteLib PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) endif(BUILD_KSTARS_LITE) target_link_libraries(KStarsLib ${INDI_CLIENT_LIBRARIES} ${NOVA_LIBRARIES} z) endif(WIN32 OR ANDROID) endif(INDI_FOUND) if(WCSLIB_FOUND) target_link_libraries(KStarsLib ${WCSLIB_LIBRARIES}) if (BUILD_KSTARS_LITE) target_link_libraries(KStarsLiteLib ${WCSLIB_LIBRARIES}) endif() endif (WCSLIB_FOUND) if(LibRaw_FOUND) if (NOT ANDROID) target_link_libraries(KStarsLib ${LibRaw_LIBRARIES}) endif() if (BUILD_KSTARS_LITE) target_link_libraries(KStarsLiteLib ${LibRaw_LIBRARIES}) endif() endif (LibRaw_FOUND) #FIXME Enable OpenGL Later #if( OPENGL_FOUND ) # target_link_libraries(KStarsLib # ${OPENGL_LIBRARIES} # ${QT_QTOPENGL_LIBRARY} # ) #endif( OPENGL_FOUND ) set (KSTARS_APP_SRCS main.cpp ) # add icon to application sources ecm_add_app_icon(KSTARS_APP_SRCS ICONS ${CMAKE_CURRENT_SOURCE_DIR}/icons/16-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/32-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/48-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/64-apps-kstars.png ${CMAKE_CURRENT_SOURCE_DIR}/icons/128-apps-kstars.png ) qt5_add_resources(KSTARS_APP_SRCS data/kstars.qrc) if (ANDROID) add_library(kstars SHARED ${KSTARS_APP_SRCS}) target_compile_options(kstars PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) add_dependencies(KStarsLiteLib cfitsio indi raw) target_link_libraries(kstars KStarsLiteLib) else () if (BUILD_KSTARS_LITE) add_executable(kstars_lite ${KSTARS_APP_SRCS}) target_compile_options(kstars_lite PRIVATE ${KSTARSLITE_CPP_OPTIONS} -DUSE_QT5_INDI -DKSTARS_LITE) target_link_libraries(kstars_lite KStarsLiteLib) endif() add_executable(kstars ${KSTARS_APP_SRCS}) target_link_libraries(kstars KStarsLib) endif () install(TARGETS kstars ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.kstars.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES kstars.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(FILES kstars.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) if(INDI_FOUND) #install(FILES ekos/mount/mountbox.qml DESTINATION ${KDE_INSTALL_DATADIR}/kstars/ekos/mount/qml) #install(DIRECTORY ekos/mount/ DESTINATION ${KDE_INSTALL_DATADIR}/kstars/ekos/mount/qml # FILES_MATCHING PATTERN "*.png") endif() if (NOT ANDROID AND BUILD_KSTARS_LITE) install(TARGETS kstars_lite ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) endif() diff --git a/kstars/fitsviewer/fitscentroiddetector.cpp b/kstars/fitsviewer/fitscentroiddetector.cpp index 54b7c69bc..80730ea2b 100644 --- a/kstars/fitsviewer/fitscentroiddetector.cpp +++ b/kstars/fitsviewer/fitscentroiddetector.cpp @@ -1,468 +1,459 @@ /*************************************************************************** fitscentroiddetector.cpp - FITS Image ------------------- begin : Sat March 28 2020 copyright : (C) 2004 by Jasem Mutlaq, (C) 2020 by Eric Dejouhanet email : eric.dejouhanet@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* * See http://members.aol.com/pkirchg for more details. * ***************************************************************************/ #include #include "fitscentroiddetector.h" #include "fits_debug.h" FITSStarDetector& FITSCentroidDetector::configure(const QString &setting, const QVariant &value) { - if (!setting.compare("initStdDev", Qt::CaseInsensitive)) - { - bool ok = false; - double const result = value.toDouble(&ok); - if (ok) - m_initStdDev = result; - } - if (!setting.compare("minEdgeWidth", Qt::CaseInsensitive)) - { - bool ok = false; - int const result = value.toInt(&ok); - if (ok) - m_initStdDev = result; - } + if (!setting.compare("MINIMUM_STDVAR", Qt::CaseInsensitive)) + if (value.canConvert ()) + MINIMUM_STDVAR = value.value (); + + if (!setting.compare("MINIMUM_PIXEL_RANGE", Qt::CaseInsensitive)) + if (value.canConvert ()) + MINIMUM_PIXEL_RANGE = value.value (); + + if (!setting.compare("JMINDEX", Qt::CaseInsensitive)) + if (value.canConvert ()) + JMINDEX = value.value (); return *this; } bool FITSCentroidDetector::checkCollision(Edge * s1, Edge * s2) const { int dis; //distance int diff_x = s1->x - s2->x; int diff_y = s1->y - s2->y; dis = std::abs(sqrt(diff_x * diff_x + diff_y * diff_y)); dis -= s1->width / 2; dis -= s2->width / 2; if (dis <= 0) //collision return true; //no collision return false; } /*** Find center of stars and calculate Half Flux Radius */ int FITSCentroidDetector::findSources(QList &starCenters, const QRect &boundary) { switch (parent()->property("dataType").toInt()) { case TBYTE: return findSources(starCenters, boundary); case TSHORT: return findSources(starCenters, boundary); case TUSHORT: return findSources(starCenters, boundary); case TLONG: return findSources(starCenters, boundary); case TULONG: return findSources(starCenters, boundary); case TFLOAT: return findSources(starCenters, boundary); case TLONGLONG: return findSources(starCenters, boundary); case TDOUBLE: return findSources(starCenters, boundary); default: return -1; } } template int FITSCentroidDetector::findSources(QList &starCenters, const QRect &boundary) { FITSData const * const image_data = reinterpret_cast(parent()); if (image_data == nullptr) return 0; FITSData::Statistic const &stats = image_data->getStatistics(); FITSMode const m_Mode = static_cast (parent()->property("mode").toInt()); + int initStdDev = MINIMUM_STDVAR; + int minEdgeWidth = MINIMUM_PIXEL_RANGE; + double threshold = 0, sum = 0, avg = 0, min = 0; int starDiameter = 0; int pixVal = 0; int minimumEdgeCount = MINIMUM_EDGE_LIMIT; auto * buffer = reinterpret_cast(image_data->getImageBuffer()); - double JMIndex = 100; - -#if 0//ndef KSTARS_LITE - if (histogram) - { - if (!histogram->isConstructed()) - histogram->constructHistogram(); - JMIndex = histogram->getJMIndex(); - } -#endif + double JMIndex = JMINDEX; float dispersion_ratio = 1.5; QList edges; if (JMIndex < DIFFUSE_THRESHOLD) { - m_minEdgeWidth = JMIndex * 35 + 1; - minimumEdgeCount = m_minEdgeWidth - 1; + minEdgeWidth = JMIndex * 35 + 1; + minimumEdgeCount = minEdgeWidth - 1; } else { - m_minEdgeWidth = 6; + minEdgeWidth = 6; minimumEdgeCount = 4; } - while (m_initStdDev >= 1) + while (initStdDev >= 1) { - m_minEdgeWidth--; + minEdgeWidth--; minimumEdgeCount--; - m_minEdgeWidth = qMax(3, m_minEdgeWidth); + minEdgeWidth = qMax(3, minEdgeWidth); minimumEdgeCount = qMax(3, minimumEdgeCount); if (JMIndex < DIFFUSE_THRESHOLD) { // Taking the average out seems to have better result for noisy images - threshold = stats.max[0] - stats.mean[0] * ((MINIMUM_STDVAR - m_initStdDev) * 0.5 + 1); + threshold = stats.max[0] - stats.mean[0] * ((MINIMUM_STDVAR - initStdDev) * 0.5 + 1); min = stats.min[0]; if (threshold - min < 0) { - threshold = stats.mean[0] * ((MINIMUM_STDVAR - m_initStdDev) * 0.5 + 1); + threshold = stats.mean[0] * ((MINIMUM_STDVAR - initStdDev) * 0.5 + 1); min = 0; } - dispersion_ratio = 1.4 - (MINIMUM_STDVAR - m_initStdDev) * 0.08; + dispersion_ratio = 1.4 - (MINIMUM_STDVAR - initStdDev) * 0.08; } else { - threshold = stats.mean[0] + stats.stddev[0] * m_initStdDev * (0.3 - (MINIMUM_STDVAR - m_initStdDev) * 0.05); + threshold = stats.mean[0] + stats.stddev[0] * initStdDev * (0.3 - (MINIMUM_STDVAR - initStdDev) * 0.05); min = stats.min[0]; // Ratio between centeroid center and edge - dispersion_ratio = 1.8 - (MINIMUM_STDVAR - m_initStdDev) * 0.2; + dispersion_ratio = 1.8 - (MINIMUM_STDVAR - initStdDev) * 0.2; } qCDebug(KSTARS_FITS) << "SNR: " << stats.SNR; qCDebug(KSTARS_FITS) << "The threshold level is " << threshold << "(actual " << threshold - min - << ") minimum edge width" << m_minEdgeWidth << " minimum edge limit " << minimumEdgeCount; + << ") minimum edge width" << minEdgeWidth << " minimum edge limit " << minimumEdgeCount; threshold -= min; int subX, subY, subW, subH; if (boundary.isNull()) { if (m_Mode == FITS_GUIDE || m_Mode == FITS_FOCUS) { // Only consider the central 70% subX = round(stats.width * 0.15); subY = round(stats.height * 0.15); subW = stats.width - subX; subH = stats.height - subY; } else { // Consider the complete area 100% subX = 0; subY = 0; subW = stats.width; subH = stats.height; } } else { subX = boundary.x(); subY = boundary.y(); subW = subX + boundary.width(); subH = subY + boundary.height(); } // Detect "edges" that are above threshold for (int i = subY; i < subH; i++) { starDiameter = 0; for (int j = subX; j < subW; j++) { pixVal = buffer[j + (i * stats.width)] - min; // If pixel value > threshold, let's get its weighted average if (pixVal >= threshold) { avg += j * pixVal; sum += pixVal; starDiameter++; } // Value < threshold but avg exists else if (sum > 0) { // We found a potential centroid edge - if (starDiameter >= m_minEdgeWidth) + if (starDiameter >= minEdgeWidth) { float center = avg / sum + 0.5; if (center > 0) { int i_center = std::floor(center); // Check if center is 10% or more brighter than edge, if not skip if (((buffer[i_center + (i * stats.width)] - min) / (buffer[i_center + (i * stats.width) - starDiameter / 2] - min) >= dispersion_ratio) && ((buffer[i_center + (i * stats.width)] - min) / (buffer[i_center + (i * stats.width) + starDiameter / 2] - min) >= dispersion_ratio)) { qCDebug(KSTARS_FITS) << "Edge center is " << buffer[i_center + (i * stats.width)] - min << " Edge is " << buffer[i_center + (i * stats.width) - starDiameter / 2] - min << " and ratio is " << ((buffer[i_center + (i * stats.width)] - min) / (buffer[i_center + (i * stats.width) - starDiameter / 2] - min)) << " located at X: " << center << " Y: " << i + 0.5; auto * newEdge = new Edge(); newEdge->x = center; newEdge->y = i + 0.5; newEdge->scanned = 0; newEdge->val = buffer[i_center + (i * stats.width)] - min; newEdge->width = starDiameter; newEdge->HFR = 0; newEdge->sum = sum; edges.append(newEdge); } } } // Reset avg = sum = starDiameter = 0; } } } qCDebug(KSTARS_FITS) << "Total number of edges found is: " << edges.count(); // In case of hot pixels - if (edges.count() == 1 && m_initStdDev > 1) + if (edges.count() == 1 && initStdDev > 1) { - m_initStdDev--; + initStdDev--; continue; } if (edges.count() >= MAX_EDGE_LIMIT) { qCWarning(KSTARS_FITS) << "Too many edges, aborting... " << edges.count(); qDeleteAll(edges); return -1; } if (edges.count() >= minimumEdgeCount) break; qDeleteAll(edges); edges.clear(); - m_initStdDev--; + initStdDev--; } int cen_count = 0; int cen_x = 0; int cen_y = 0; int cen_v = 0; int cen_w = 0; int width_sum = 0; // Let's sort edges, starting with widest auto const greaterThan = [](Edge const *a, Edge const *b) { return a->sum > b->sum; }; std::sort(edges.begin(), edges.end(), greaterThan); // Now, let's scan the edges and find the maximum centroid vertically for (int i = 0; i < edges.count(); i++) { qCDebug(KSTARS_FITS) << "# " << i << " Edge at (" << edges[i]->x << "," << edges[i]->y << ") With a value of " << edges[i]->val << " and width of " << edges[i]->width << " pixels. with sum " << edges[i]->sum; // If edge scanned already, skip if (edges[i]->scanned == 1) { qCDebug(KSTARS_FITS) << "Skipping check for center " << i << " because it was already counted"; continue; } qCDebug(KSTARS_FITS) << "Investigating edge # " << i << " now ..."; // Get X, Y, and Val of edge cen_x = edges[i]->x; cen_y = edges[i]->y; cen_v = edges[i]->sum; cen_w = edges[i]->width; float avg_x = 0; float avg_y = 0; sum = 0; cen_count = 0; // Now let's compare to other edges until we hit a maxima for (int j = 0; j < edges.count(); j++) { if (edges[j]->scanned) continue; if (checkCollision(edges[j], edges[i])) { if (edges[j]->sum >= cen_v) { cen_v = edges[j]->sum; cen_w = edges[j]->width; } edges[j]->scanned = 1; cen_count++; avg_x += edges[j]->x * edges[j]->val; avg_y += edges[j]->y * edges[j]->val; sum += edges[j]->val; continue; } } - int cen_limit = (MINIMUM_ROWS_PER_CENTER - (MINIMUM_STDVAR - m_initStdDev)); + int cen_limit = (MINIMUM_ROWS_PER_CENTER - (MINIMUM_STDVAR - initStdDev)); if (edges.count() < LOW_EDGE_CUTOFF_1) { if (edges.count() < LOW_EDGE_CUTOFF_2) cen_limit = 1; else cen_limit = 2; } - qCDebug(KSTARS_FITS) << "center_count: " << cen_count << " and initstdDev= " << m_initStdDev << " and limit is " + qCDebug(KSTARS_FITS) << "center_count: " << cen_count << " and initstdDev= " << initStdDev << " and limit is " << cen_limit; if (cen_limit < 1) continue; // If centroid count is within acceptable range //if (cen_limit >= 2 && cen_count >= cen_limit) if (cen_count >= cen_limit) { // We detected a centroid, let's init it auto * rCenter = new Edge(); rCenter->x = avg_x / sum; rCenter->y = avg_y / sum; width_sum += rCenter->width; rCenter->width = cen_w; qCDebug(KSTARS_FITS) << "Found a real center with number with (" << rCenter->x << "," << rCenter->y << ")"; // Calculate Total Flux From Center, Half Flux, Full Summation double TF = 0; double HF = 0; double FSum = 0; cen_x = (int)std::floor(rCenter->x); cen_y = (int)std::floor(rCenter->y); if (cen_x < 0 || cen_x > stats.width || cen_y < 0 || cen_y > stats.height) { delete rCenter; continue; } // Complete sum along the radius //for (int k=0; k < rCenter->width; k++) for (int k = rCenter->width / 2; k >= -(rCenter->width / 2); k--) { FSum += buffer[cen_x - k + (cen_y * stats.width)] - min; //qDebug() << image_buffer[cen_x-k+(cen_y*stats.width)] - min; } // Half flux HF = FSum / 2.0; // Total flux starting from center TF = buffer[cen_y * stats.width + cen_x] - min; int pixelCounter = 1; // Integrate flux along radius axis until we reach half flux for (int k = 1; k < rCenter->width / 2; k++) { if (TF >= HF) { qCDebug(KSTARS_FITS) << "Stopping at TF " << TF << " after #" << k << " pixels."; break; } TF += buffer[cen_y * stats.width + cen_x + k] - min; TF += buffer[cen_y * stats.width + cen_x - k] - min; pixelCounter++; } // Calculate weighted Half Flux Radius rCenter->HFR = pixelCounter * (HF / TF); // Store full flux rCenter->val = FSum; qCDebug(KSTARS_FITS) << "HFR for this center is " << rCenter->HFR << " pixels and the total flux is " << FSum; starCenters.append(rCenter); } } if (starCenters.count() > 1 && m_Mode != FITS_FOCUS) { float width_avg = (float)width_sum / starCenters.count(); float lsum = 0, sdev = 0; for (auto ¢er : starCenters) lsum += (center->width - width_avg) * (center->width - width_avg); sdev = (std::sqrt(lsum / (starCenters.count() - 1))) * 4; // Reject stars > 4 * stddev foreach (Edge * center, starCenters) if (center->width > sdev) starCenters.removeOne(center); //foreach(Edge *center, starCenters) //qDebug() << center->x << "," << center->y << "," << center->width << "," << center->val << endl; } // Release memory qDeleteAll(edges); return starCenters.count(); } diff --git a/kstars/fitsviewer/fitscentroiddetector.h b/kstars/fitsviewer/fitscentroiddetector.h index 7af17c1d7..38d5c5e9a 100644 --- a/kstars/fitsviewer/fitscentroiddetector.h +++ b/kstars/fitsviewer/fitscentroiddetector.h @@ -1,78 +1,82 @@ /*************************************************************************** fitscentroiddetector.h - FITS Image ------------------- begin : Sat March 28 2020 copyright : (C) 2004 by Jasem Mutlaq, (C) 2020 by Eric Dejouhanet email : eric.dejouhanet@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* * See http://members.aol.com/pkirchg for more details. * ***************************************************************************/ #ifndef FITSCENTROIDDETECTOR_H #define FITSCENTROIDDETECTOR_H #include #include "fitsstardetector.h" class FITSCentroidDetector: public FITSStarDetector { Q_OBJECT public: explicit FITSCentroidDetector(FITSData *parent): FITSStarDetector(parent) {}; public: /** @brief Find sources in the parent FITS data file. * @see FITSStarDetector::findSources(). */ int findSources(QList &starCenters, QRect const &boundary = QRect()) override; /** @brief Configure the detection method. * @see FITSStarDetector::configure(). - * @note Parameter "initStdDev" defaults to MINIMUM_STDVAR. - * @note Parameter "minEdgeWidth" defaults to MINIMUM_PIXEL_RANGE. - * @todo Provide all constants of this class as parameters, and explain their use. + * @see Detection parameters. */ FITSStarDetector & configure(const QString &setting, const QVariant &value) override; -public: - /** @group Detection internals +protected: + /** @group Detection parameters. Use the names as strings for FITSStarDetector::configure(). * @{ */ - static constexpr int MINIMUM_STDVAR { 5 }; - static constexpr int MINIMUM_PIXEL_RANGE { 5 }; - static constexpr int MINIMUM_EDGE_LIMIT { 2 }; - static constexpr int MAX_EDGE_LIMIT { 10000 }; - static constexpr double DIFFUSE_THRESHOLD { 0.15 }; - static constexpr int MINIMUM_ROWS_PER_CENTER { 3 }; - static constexpr int LOW_EDGE_CUTOFF_1 { 50 }; - static constexpr int LOW_EDGE_CUTOFF_2 { 10 }; + /** @brief Initial variation, decreasing as search progresses. Configurable. */ + int MINIMUM_STDVAR { 5 }; + /** @brief Initial source width, decreasing as search progresses. Configurable. */ + int MINIMUM_PIXEL_RANGE { 5 }; + /** @brief Custom image contrast index from the frame histogram. Configurable. */ + double JMINDEX { 100 }; + /** @brief Initial source count over which search stops. */ + int MINIMUM_EDGE_LIMIT { 2 }; + /** @brief Maximum source count over which search aborts. */ + int MAX_EDGE_LIMIT { 10000 }; + /** @brief Minimum value of JMINDEX under which the custom image contrast index from the histogram is used to redefine edge width and count. */ + double DIFFUSE_THRESHOLD { 0.15 }; + /** @brief */ + int MINIMUM_ROWS_PER_CENTER { 3 }; + /** @brief */ + int LOW_EDGE_CUTOFF_1 { 50 }; + /** @brief */ + int LOW_EDGE_CUTOFF_2 { 10 }; /** @} */ protected: /** @internal Find sources in the parent FITS data file, dependent of the pixel depth. * @see FITSGradientDetector::findSources. */ template int findSources(QList &starCenters, const QRect &boundary); /** @internal Check whether two sources overlap. * @param s1, s2 are the two sources to check collision on. * @return true if the sources collide, else false. */ bool checkCollision(Edge * s1, Edge * s2) const; - -protected: - int m_initStdDev { MINIMUM_STDVAR }; - int m_minEdgeWidth { MINIMUM_PIXEL_RANGE }; }; #endif // FITSCENTROIDDETECTOR_H diff --git a/kstars/fitsviewer/fitsdata.cpp b/kstars/fitsviewer/fitsdata.cpp index 31ab36781..a745dcb19 100644 --- a/kstars/fitsviewer/fitsdata.cpp +++ b/kstars/fitsviewer/fitsdata.cpp @@ -1,3196 +1,3174 @@ /*************************************************************************** FITSImage.cpp - FITS Image ------------------- begin : Thu Jan 22 2004 copyright : (C) 2004 by Jasem Mutlaq email : mutlaqja@ikarustech.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* * See http://members.aol.com/pkirchg for more details. * ***************************************************************************/ #include "fitsdata.h" #include "fitsthresholddetector.h" #include "fitsgradientdetector.h" #include "fitscentroiddetector.h" #include "fitssepdetector.h" #include "fpack.h" #include "kstarsdata.h" #include "ksutils.h" #include "kspaths.h" #include "Options.h" #include "skymapcomposite.h" #include "auxiliary/ksnotification.h" #include #include #include #include #include #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) #include #include #endif #ifndef KSTARS_LITE #include "fitshistogram.h" #endif #include #include #include #define ZOOM_DEFAULT 100.0 #define ZOOM_MIN 10 #define ZOOM_MAX 400 #define ZOOM_LOW_INCR 10 #define ZOOM_HIGH_INCR 50 const QString FITSData::m_TemporaryPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation); FITSData::FITSData(FITSMode fitsMode): m_Mode(fitsMode) { debayerParams.method = DC1394_BAYER_METHOD_NEAREST; debayerParams.filter = DC1394_COLOR_FILTER_RGGB; debayerParams.offsetX = debayerParams.offsetY = 0; } FITSData::FITSData(const FITSData * other) { debayerParams.method = DC1394_BAYER_METHOD_NEAREST; debayerParams.filter = DC1394_COLOR_FILTER_RGGB; debayerParams.offsetX = debayerParams.offsetY = 0; this->m_Mode = other->m_Mode; this->m_DataType = other->m_DataType; this->m_Channels = other->m_Channels; memcpy(&stats, &(other->stats), sizeof(stats)); m_ImageBuffer = new uint8_t[stats.samples_per_channel * m_Channels * stats.bytesPerPixel]; memcpy(m_ImageBuffer, other->m_ImageBuffer, stats.samples_per_channel * m_Channels * stats.bytesPerPixel); } FITSData::~FITSData() { int status = 0; clearImageBuffers(); #ifdef HAVE_WCSLIB if (m_wcs != nullptr) wcsvfree(&m_nwcs, &m_wcs); #endif if (starCenters.count() > 0) qDeleteAll(starCenters); delete[] wcs_coord; if (objList.count() > 0) qDeleteAll(objList); if (fptr != nullptr) { fits_flush_file(fptr, &status); fits_close_file(fptr, &status); fptr = nullptr; if (m_isTemporary && autoRemoveTemporaryFITS) QFile::remove(m_Filename); } qDeleteAll(records); } void FITSData::loadCommon(const QString &inFilename) { int status = 0; qDeleteAll(starCenters); starCenters.clear(); if (fptr != nullptr) { fits_flush_file(fptr, &status); fits_close_file(fptr, &status); fptr = nullptr; // If current file is temporary AND // Auto Remove Temporary File is Set AND // New filename is different from existing filename // THen remove it. We have to check for name since we cannot delete // the same filename and try to open it below! if (m_isTemporary && autoRemoveTemporaryFITS && inFilename != m_Filename) QFile::remove(m_Filename); } m_Filename = inFilename; } bool FITSData::loadFITSFromMemory(const QString &inFilename, void *fits_buffer, size_t fits_buffer_size, bool silent) { loadCommon(inFilename); qCDebug(KSTARS_FITS) << "Reading FITS file buffer (" << KFormat().formatByteSize(fits_buffer_size) << ")"; return privateLoad(fits_buffer, fits_buffer_size, silent); } QFuture FITSData::loadFITS(const QString &inFilename, bool silent) { loadCommon(inFilename); qCInfo(KSTARS_FITS) << "Loading FITS file " << m_Filename; QFuture result = QtConcurrent::run( this, &FITSData::privateLoad, nullptr, 0, silent); return result; } namespace { // Common code for reporting fits read errors. Always returns false. bool fitsOpenError(int status, const QString &message, bool silent) { char error_status[512]; fits_report_error(stderr, status); fits_get_errstatus(status, error_status); QString errMessage = message; errMessage.append(i18n(" Error: %1", QString::fromUtf8(error_status))); if (!silent) KSNotification::error(errMessage, i18n("FITS Open")); qCCritical(KSTARS_FITS) << errMessage; return false; } } bool FITSData::privateLoad(void *fits_buffer, size_t fits_buffer_size, bool silent) { int status = 0, anynull = 0; long naxes[3]; QString errMessage; m_isTemporary = m_Filename.startsWith(m_TemporaryPath); if (fits_buffer == nullptr && m_Filename.endsWith(".fz")) { // Store so we don't lose. m_compressedFilename = m_Filename; QString uncompressedFile = QDir::tempPath() + QString("/%1").arg(QUuid::createUuid().toString().remove( QRegularExpression("[-{}]"))); fpstate fpvar; fp_init (&fpvar); if (fp_unpack(m_Filename.toLatin1().data(), uncompressedFile.toLatin1().data(), fpvar) < 0) { errMessage = i18n("Failed to unpack compressed fits"); if (!silent) KSNotification::error(errMessage, i18n("FITS Open")); qCCritical(KSTARS_FITS) << errMessage; return false; } // Remove compressed .fz if it was temporary if (m_isTemporary && autoRemoveTemporaryFITS) QFile::remove(m_Filename); m_Filename = uncompressedFile; m_isTemporary = true; m_isCompressed = true; } if (fits_buffer == nullptr) { // Use open diskfile as it does not use extended file names which has problems opening // files with [ ] or ( ) in their names. if (fits_open_diskfile(&fptr, m_Filename.toLatin1(), READONLY, &status)) return fitsOpenError(status, i18n("Error opening fits file %1", m_Filename), silent); else stats.size = QFile(m_Filename).size(); } else { // Read the FITS file from a memory buffer. void *temp_buffer = fits_buffer; size_t temp_size = fits_buffer_size; if (fits_open_memfile(&fptr, m_Filename.toLatin1().data(), READONLY, &temp_buffer, &temp_size, 0, nullptr, &status)) return fitsOpenError(status, i18n("Error reading fits buffer."), silent); else stats.size = fits_buffer_size; } if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status)) return fitsOpenError(status, i18n("Could not locate image HDU."), silent); if (fits_get_img_param(fptr, 3, &(stats.bitpix), &(stats.ndim), naxes, &status)) return fitsOpenError(status, i18n("FITS file open error (fits_get_img_param)."), silent); if (stats.ndim < 2) { errMessage = i18n("1D FITS images are not supported in KStars."); if (!silent) KSNotification::error(errMessage, i18n("FITS Open")); qCCritical(KSTARS_FITS) << errMessage; return false; } switch (stats.bitpix) { case BYTE_IMG: m_DataType = TBYTE; stats.bytesPerPixel = sizeof(uint8_t); break; case SHORT_IMG: // Read SHORT image as USHORT m_DataType = TUSHORT; stats.bytesPerPixel = sizeof(int16_t); break; case USHORT_IMG: m_DataType = TUSHORT; stats.bytesPerPixel = sizeof(uint16_t); break; case LONG_IMG: // Read LONG image as ULONG m_DataType = TULONG; stats.bytesPerPixel = sizeof(int32_t); break; case ULONG_IMG: m_DataType = TULONG; stats.bytesPerPixel = sizeof(uint32_t); break; case FLOAT_IMG: m_DataType = TFLOAT; stats.bytesPerPixel = sizeof(float); break; case LONGLONG_IMG: m_DataType = TLONGLONG; stats.bytesPerPixel = sizeof(int64_t); break; case DOUBLE_IMG: m_DataType = TDOUBLE; stats.bytesPerPixel = sizeof(double); break; default: errMessage = i18n("Bit depth %1 is not supported.", stats.bitpix); if (!silent) KSNotification::error(errMessage, i18n("FITS Open")); qCCritical(KSTARS_FITS) << errMessage; return false; } if (stats.ndim < 3) naxes[2] = 1; if (naxes[0] == 0 || naxes[1] == 0) { errMessage = i18n("Image has invalid dimensions %1x%2", naxes[0], naxes[1]); if (!silent) KSNotification::error(errMessage, i18n("FITS Open")); qCCritical(KSTARS_FITS) << errMessage; return false; } stats.width = naxes[0]; stats.height = naxes[1]; stats.samples_per_channel = stats.width * stats.height; clearImageBuffers(); m_Channels = naxes[2]; // Channels always set to #1 if we are not required to process 3D Cubes // Or if mode is not FITS_NORMAL (guide, focus..etc) if (m_Mode != FITS_NORMAL || !Options::auto3DCube()) m_Channels = 1; m_ImageBufferSize = stats.samples_per_channel * m_Channels * stats.bytesPerPixel; m_ImageBuffer = new uint8_t[m_ImageBufferSize]; if (m_ImageBuffer == nullptr) { qCWarning(KSTARS_FITS) << "FITSData: Not enough memory for image_buffer channel. Requested: " << m_ImageBufferSize << " bytes."; clearImageBuffers(); return false; } rotCounter = 0; flipHCounter = 0; flipVCounter = 0; long nelements = stats.samples_per_channel * m_Channels; if (fits_read_img(fptr, m_DataType, 1, nelements, nullptr, m_ImageBuffer, &anynull, &status)) return fitsOpenError(status, i18n("Error reading image."), silent); parseHeader(); if (Options::autoDebayer() && checkDebayer()) { //m_BayerBuffer = m_ImageBuffer; if (debayer()) calculateStats(); } else calculateStats(); WCSLoaded = false; if (m_Mode == FITS_NORMAL || m_Mode == FITS_ALIGN) checkForWCS(); starsSearched = false; return true; } int FITSData::saveFITS(const QString &newFilename) { if (newFilename == m_Filename) return 0; if (m_isCompressed) { KSNotification::error(i18n("Saving compressed files is not supported.")); return -1; } int status = 0, exttype = 0; long nelements; fitsfile * new_fptr; if (HasDebayer) { fits_flush_file(fptr, &status); /* close current file */ if (fits_close_file(fptr, &status)) { fits_report_error(stderr, status); return status; } // Skip "!" in the beginning of the new file name QString finalFileName(newFilename); finalFileName.remove('!'); // Remove first otherwise copy will fail below if file exists QFile::remove(finalFileName); if (!QFile::copy(m_Filename, finalFileName)) { qCCritical(KSTARS_FITS()) << "FITS: Failed to copy " << m_Filename << " to " << finalFileName; fptr = nullptr; return -1; } if (m_isTemporary && autoRemoveTemporaryFITS) { QFile::remove(m_Filename); m_isTemporary = false; } m_Filename = finalFileName; // Use open diskfile as it does not use extended file names which has problems opening // files with [ ] or ( ) in their names. fits_open_diskfile(&fptr, m_Filename.toLatin1(), READONLY, &status); fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status); return 0; } nelements = stats.samples_per_channel * m_Channels; /* Create a new File, overwriting existing*/ if (fits_create_file(&new_fptr, newFilename.toLatin1(), &status)) { fits_report_error(stderr, status); return status; } // if (fits_movabs_hdu(fptr, 1, &exttype, &status)) // { // fits_report_error(stderr, status); // return status; // } if (fits_copy_header(fptr, new_fptr, &status)) { fits_report_error(stderr, status); return status; } fits_flush_file(fptr, &status); /* close current file */ if (fits_close_file(fptr, &status)) { fits_report_error(stderr, status); return status; } status = 0; fptr = new_fptr; if (fits_movabs_hdu(fptr, 1, &exttype, &status)) { fits_report_error(stderr, status); return status; } /* Write Data */ if (fits_write_img(fptr, m_DataType, 1, nelements, m_ImageBuffer, &status)) { fits_report_error(stderr, status); return status; } /* Write keywords */ // Minimum if (fits_update_key(fptr, TDOUBLE, "DATAMIN", &(stats.min), "Minimum value", &status)) { fits_report_error(stderr, status); return status; } // Maximum if (fits_update_key(fptr, TDOUBLE, "DATAMAX", &(stats.max), "Maximum value", &status)) { fits_report_error(stderr, status); return status; } // NAXIS1 if (fits_update_key(fptr, TUSHORT, "NAXIS1", &(stats.width), "length of data axis 1", &status)) { fits_report_error(stderr, status); return status; } // NAXIS2 if (fits_update_key(fptr, TUSHORT, "NAXIS2", &(stats.height), "length of data axis 2", &status)) { fits_report_error(stderr, status); return status; } // ISO Date if (fits_write_date(fptr, &status)) { fits_report_error(stderr, status); return status; } QString history = QString("Modified by KStars on %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss")); // History if (fits_write_history(fptr, history.toLatin1(), &status)) { fits_report_error(stderr, status); return status; } int rot = 0, mirror = 0; if (rotCounter > 0) { rot = (90 * rotCounter) % 360; if (rot < 0) rot += 360; } if (flipHCounter % 2 != 0 || flipVCounter % 2 != 0) mirror = 1; if ((rot != 0) || (mirror != 0)) rotWCSFITS(rot, mirror); rotCounter = flipHCounter = flipVCounter = 0; if (m_isTemporary && autoRemoveTemporaryFITS) { QFile::remove(m_Filename); m_isTemporary = false; } m_Filename = newFilename; fits_flush_file(fptr, &status); qCInfo(KSTARS_FITS) << "Saved FITS file:" << m_Filename; return status; } void FITSData::clearImageBuffers() { delete[] m_ImageBuffer; m_ImageBuffer = nullptr; //m_BayerBuffer = nullptr; } void FITSData::calculateStats(bool refresh) { // Calculate min max calculateMinMax(refresh); // Get standard deviation and mean in one run switch (m_DataType) { case TBYTE: runningAverageStdDev(); break; case TSHORT: runningAverageStdDev(); break; case TUSHORT: runningAverageStdDev(); break; case TLONG: runningAverageStdDev(); break; case TULONG: runningAverageStdDev(); break; case TFLOAT: runningAverageStdDev(); break; case TLONGLONG: runningAverageStdDev(); break; case TDOUBLE: runningAverageStdDev(); break; default: return; } // FIXME That's not really SNR, must implement a proper solution for this value stats.SNR = stats.mean[0] / stats.stddev[0]; if (refresh && markStars) // Let's try to find star positions again after transformation starsSearched = false; } int FITSData::calculateMinMax(bool refresh) { int status, nfound = 0; status = 0; if ((fptr != nullptr) && !refresh) { if (fits_read_key_dbl(fptr, "DATAMIN", &(stats.min[0]), nullptr, &status) == 0) nfound++; if (fits_read_key_dbl(fptr, "DATAMAX", &(stats.max[0]), nullptr, &status) == 0) nfound++; // If we found both keywords, no need to calculate them, unless they are both zeros if (nfound == 2 && !(stats.min[0] == 0 && stats.max[0] == 0)) return 0; } stats.min[0] = 1.0E30; stats.max[0] = -1.0E30; stats.min[1] = 1.0E30; stats.max[1] = -1.0E30; stats.min[2] = 1.0E30; stats.max[2] = -1.0E30; switch (m_DataType) { case TBYTE: calculateMinMax(); break; case TSHORT: calculateMinMax(); break; case TUSHORT: calculateMinMax(); break; case TLONG: calculateMinMax(); break; case TULONG: calculateMinMax(); break; case TFLOAT: calculateMinMax(); break; case TLONGLONG: calculateMinMax(); break; case TDOUBLE: calculateMinMax(); break; default: break; } //qDebug() << "DATAMIN: " << stats.min << " - DATAMAX: " << stats.max; return 0; } template QPair FITSData::getParitionMinMax(uint32_t start, uint32_t stride) { auto * buffer = reinterpret_cast(m_ImageBuffer); T min = std::numeric_limits::max(); T max = std::numeric_limits::min(); uint32_t end = start + stride; for (uint32_t i = start; i < end; i++) { if (buffer[i] < min) min = buffer[i]; else if (buffer[i] > max) max = buffer[i]; } return qMakePair(min, max); } template void FITSData::calculateMinMax() { T min = std::numeric_limits::max(); T max = std::numeric_limits::min(); // Create N threads const uint8_t nThreads = 16; for (int n = 0; n < m_Channels; n++) { uint32_t cStart = n * stats.samples_per_channel; // Calculate how many elements we process per thread uint32_t tStride = stats.samples_per_channel / nThreads; // Calculate the final stride since we can have some left over due to division above uint32_t fStride = tStride + (stats.samples_per_channel - (tStride * nThreads)); // Start location for inspecting elements uint32_t tStart = cStart; // List of futures QList>> futures; for (int i = 0; i < nThreads; i++) { // Run threads futures.append(QtConcurrent::run(this, &FITSData::getParitionMinMax, tStart, (i == (nThreads - 1)) ? fStride : tStride)); tStart += tStride; } // Now wait for results for (int i = 0; i < nThreads; i++) { QPair result = futures[i].result(); if (result.first < min) min = result.first; if (result.second > max) max = result.second; } stats.min[n] = min; stats.max[n] = max; } } template QPair FITSData::getSquaredSumAndMean(uint32_t start, uint32_t stride) { uint32_t m_n = 2; double m_oldM = 0, m_newM = 0, m_oldS = 0, m_newS = 0; auto * buffer = reinterpret_cast(m_ImageBuffer); uint32_t end = start + stride; for (uint32_t i = start; i < end; i++) { m_newM = m_oldM + (buffer[i] - m_oldM) / m_n; m_newS = m_oldS + (buffer[i] - m_oldM) * (buffer[i] - m_newM); m_oldM = m_newM; m_oldS = m_newS; m_n++; } return qMakePair(m_newM, m_newS); } template void FITSData::runningAverageStdDev() { // Create N threads const uint8_t nThreads = 16; for (int n = 0; n < m_Channels; n++) { uint32_t cStart = n * stats.samples_per_channel; // Calculate how many elements we process per thread uint32_t tStride = stats.samples_per_channel / nThreads; // Calculate the final stride since we can have some left over due to division above uint32_t fStride = tStride + (stats.samples_per_channel - (tStride * nThreads)); // Start location for inspecting elements uint32_t tStart = cStart; // List of futures QList>> futures; for (int i = 0; i < nThreads; i++) { // Run threads futures.append(QtConcurrent::run(this, &FITSData::getSquaredSumAndMean, tStart, (i == (nThreads - 1)) ? fStride : tStride)); tStart += tStride; } double mean = 0, squared_sum = 0; // Now wait for results for (int i = 0; i < nThreads; i++) { QPair result = futures[i].result(); mean += result.first; squared_sum += result.second; } double variance = squared_sum / stats.samples_per_channel; stats.mean[n] = mean / nThreads; stats.stddev[n] = sqrt(variance); } } void FITSData::setMinMax(double newMin, double newMax, uint8_t channel) { stats.min[channel] = newMin; stats.max[channel] = newMax; } bool FITSData::parseHeader() { char * header = nullptr; int status = 0, nkeys = 0; if (fits_hdr2str(fptr, 0, nullptr, 0, &header, &nkeys, &status)) { fits_report_error(stderr, status); free(header); return false; } QString recordList = QString(header); for (int i = 0; i < nkeys; i++) { Record * oneRecord = new Record; // Quotes cause issues for simplified below so we're removing them. QString record = recordList.mid(i * 80, 80).remove("'"); QStringList properties = record.split(QRegExp("[=/]")); // If it is only a comment if (properties.size() == 1) { oneRecord->key = properties[0].mid(0, 7); oneRecord->comment = properties[0].mid(8).simplified(); } else { oneRecord->key = properties[0].simplified(); oneRecord->value = properties[1].simplified(); if (properties.size() > 2) oneRecord->comment = properties[2].simplified(); // Try to guess the value. // Test for integer & double. If neither, then leave it as "string". bool ok = false; // Is it Integer? oneRecord->value.toInt(&ok); if (ok) oneRecord->value.convert(QMetaType::Int); else { // Is it double? oneRecord->value.toDouble(&ok); if (ok) oneRecord->value.convert(QMetaType::Double); } } records.append(oneRecord); } free(header); return true; } bool FITSData::getRecordValue(const QString &key, QVariant &value) const { for (Record * oneRecord : records) { if (oneRecord->key == key) { value = oneRecord->value; return true; } } return false; } int FITSData::findStars(StarAlgorithm algorithm, const QRect &trackingBox) { int count = 0; starAlgorithm = algorithm; qDeleteAll(starCenters); starCenters.clear(); switch (algorithm) { case ALGORITHM_SEP: count = FITSSEPDetector(this) .findSources(starCenters, trackingBox); break; case ALGORITHM_GRADIENT: count = FITSGradientDetector(this) .findSources(starCenters, trackingBox); break; case ALGORITHM_CENTROID: +#ifndef KSTARS_LITE + if (histogram) + if (!histogram->isConstructed()) + histogram->constructHistogram(); + + count = FITSCentroidDetector(this) + .configure("JMINDEX", histogram? histogram->getJMIndex() : 100) + .findSources(starCenters, trackingBox); +#else count = FITSCentroidDetector(this) .findSources(starCenters, trackingBox); +#endif break; case ALGORITHM_THRESHOLD: count = FITSThresholdDetector(this) - .configure("Threshold", Options::focusThreshold()) + .configure("THRESHOLD_PERCENTAGE", Options::focusThreshold()) .findSources(starCenters, trackingBox); break; } starsSearched = true; return count; } int FITSData::filterStars(const float innerRadius, const float outerRadius) { long const sqDiagonal = this->width() * this->width() / 4 + this->height() * this->height() / 4; long const sqInnerRadius = std::lround(sqDiagonal * innerRadius * innerRadius); long const sqOuterRadius = std::lround(sqDiagonal * outerRadius * outerRadius); starCenters.erase(std::remove_if(starCenters.begin(), starCenters.end(), [&](Edge * edge) { long const x = edge->x - this->width() / 2; long const y = edge->y - this->height() / 2; long const sqRadius = x * x + y * y; return sqRadius < sqInnerRadius || sqOuterRadius < sqRadius; }), starCenters.end()); return starCenters.count(); } double FITSData::getHFR(HFRType type) { // This method is less susceptible to noise // Get HFR for the brightest star only, instead of averaging all stars // It is more consistent. // TODO: Try to test this under using a real CCD. if (starCenters.empty()) return -1; if (type == HFR_MAX) { maxHFRStar = nullptr; int maxVal = 0; int maxIndex = 0; for (int i = 0; i < starCenters.count(); i++) { if (starCenters[i]->HFR > maxVal) { maxIndex = i; maxVal = starCenters[i]->HFR; } } maxHFRStar = starCenters[maxIndex]; return static_cast(starCenters[maxIndex]->HFR); } QVector HFRs; for (auto center : starCenters) HFRs << center->HFR; std::sort(HFRs.begin(), HFRs.end()); double sum = std::accumulate(HFRs.begin(), HFRs.end(), 0.0); double m = sum / HFRs.size(); if (HFRs.size() > 3) { double accum = 0.0; std::for_each (HFRs.begin(), HFRs.end(), [&](const double d) { accum += (d - m) * (d - m); }); double stddev = sqrt(accum / (HFRs.size() - 1)); // Remove stars over 2 standard deviations away. auto end1 = std::remove_if(HFRs.begin(), HFRs.end(), [m, stddev](const double hfr) { return hfr > (m + stddev * 2); }); auto end2 = std::remove_if(HFRs.begin(), end1, [m, stddev](const double hfr) { return hfr < (m - stddev * 2); }); // New mean sum = std::accumulate(HFRs.begin(), end2, 0.0); const int num_remaining = std::distance(HFRs.begin(), end2); if (num_remaining > 0) m = sum / num_remaining; } return m; } double FITSData::getHFR(int x, int y) { if (starCenters.empty()) return -1; for (int i = 0; i < starCenters.count(); i++) { if (std::fabs(starCenters[i]->x - x) <= starCenters[i]->width / 2 && std::fabs(starCenters[i]->y - y) <= starCenters[i]->width / 2) { return starCenters[i]->HFR; } } return -1; } void FITSData::applyFilter(FITSScale type, uint8_t * image, QVector * min, QVector * max) { if (type == FITS_NONE) return; QVector dataMin(3); QVector dataMax(3); if (min) dataMin = *min; if (max) dataMax = *max; switch (type) { case FITS_AUTO_STRETCH: { for (int i = 0; i < 3; i++) { dataMin[i] = stats.mean[i] - stats.stddev[i]; dataMax[i] = stats.mean[i] + stats.stddev[i] * 3; } } break; case FITS_HIGH_CONTRAST: { for (int i = 0; i < 3; i++) { dataMin[i] = stats.mean[i] + stats.stddev[i]; dataMax[i] = stats.mean[i] + stats.stddev[i] * 3; } } break; case FITS_HIGH_PASS: { for (int i = 0; i < 3; i++) { dataMin[i] = stats.mean[i]; } } break; default: break; } switch (m_DataType) { case TBYTE: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i]; dataMax[i] = dataMax[i] > UINT8_MAX ? UINT8_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TSHORT: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < INT16_MIN ? INT16_MIN : dataMin[i]; dataMax[i] = dataMax[i] > INT16_MAX ? INT16_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TUSHORT: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i]; dataMax[i] = dataMax[i] > UINT16_MAX ? UINT16_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TLONG: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < INT_MIN ? INT_MIN : dataMin[i]; dataMax[i] = dataMax[i] > INT_MAX ? INT_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TULONG: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < 0 ? 0 : dataMin[i]; dataMax[i] = dataMax[i] > UINT_MAX ? UINT_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TFLOAT: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < FLT_MIN ? FLT_MIN : dataMin[i]; dataMax[i] = dataMax[i] > FLT_MAX ? FLT_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TLONGLONG: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < LLONG_MIN ? LLONG_MIN : dataMin[i]; dataMax[i] = dataMax[i] > LLONG_MAX ? LLONG_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; case TDOUBLE: { for (int i = 0; i < 3; i++) { dataMin[i] = dataMin[i] < DBL_MIN ? DBL_MIN : dataMin[i]; dataMax[i] = dataMax[i] > DBL_MAX ? DBL_MAX : dataMax[i]; } applyFilter(type, image, &dataMin, &dataMax); } break; default: return; } if (min != nullptr) *min = dataMin; if (max != nullptr) *max = dataMax; } template void FITSData::applyFilter(FITSScale type, uint8_t * targetImage, QVector * targetMin, QVector * targetMax) { bool calcStats = false; T * image = nullptr; if (targetImage) image = reinterpret_cast(targetImage); else { image = reinterpret_cast(m_ImageBuffer); calcStats = true; } T min[3], max[3]; for (int i = 0; i < 3; i++) { min[i] = (*targetMin)[i] < std::numeric_limits::min() ? std::numeric_limits::min() : (*targetMin)[i]; max[i] = (*targetMax)[i] > std::numeric_limits::max() ? std::numeric_limits::max() : (*targetMax)[i]; } // Create N threads const uint8_t nThreads = 16; uint32_t width = stats.width; uint32_t height = stats.height; //QTime timer; //timer.start(); switch (type) { case FITS_AUTO: case FITS_LINEAR: case FITS_AUTO_STRETCH: case FITS_HIGH_CONTRAST: case FITS_LOG: case FITS_SQRT: case FITS_HIGH_PASS: { // List of futures QList> futures; QVector coeff(3); if (type == FITS_LOG) { for (int i = 0; i < 3; i++) coeff[i] = max[i] / std::log(1 + max[i]); } else if (type == FITS_SQRT) { for (int i = 0; i < 3; i++) coeff[i] = max[i] / sqrt(max[i]); } for (int n = 0; n < m_Channels; n++) { if (type == FITS_HIGH_PASS) min[n] = stats.mean[n]; uint32_t cStart = n * stats.samples_per_channel; // Calculate how many elements we process per thread uint32_t tStride = stats.samples_per_channel / nThreads; // Calculate the final stride since we can have some left over due to division above uint32_t fStride = tStride + (stats.samples_per_channel - (tStride * nThreads)); T * runningBuffer = image + cStart; if (type == FITS_LOG) { for (int i = 0; i < nThreads; i++) { // Run threads futures.append(QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max, coeff, n](T & a) { a = qBound(min[n], static_cast(round(coeff[n] * std::log(1 + qBound(min[n], a, max[n])))), max[n]); })); runningBuffer += tStride; } } else if (type == FITS_SQRT) { for (int i = 0; i < nThreads; i++) { // Run threads futures.append(QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max, coeff, n](T & a) { a = qBound(min[n], static_cast(round(coeff[n] * a)), max[n]); })); } runningBuffer += tStride; } else { for (int i = 0; i < nThreads; i++) { // Run threads futures.append(QtConcurrent::map(runningBuffer, (runningBuffer + ((i == (nThreads - 1)) ? fStride : tStride)), [min, max, n](T & a) { a = qBound(min[n], a, max[n]); })); runningBuffer += tStride; } } } for (int i = 0; i < nThreads * m_Channels; i++) futures[i].waitForFinished(); if (calcStats) { for (int i = 0; i < 3; i++) { stats.min[i] = min[i]; stats.max[i] = max[i]; } //if (type != FITS_AUTO && type != FITS_LINEAR) runningAverageStdDev(); //QtConcurrent::run(this, &FITSData::runningAverageStdDev); } } break; case FITS_EQUALIZE: { #ifndef KSTARS_LITE if (histogram == nullptr) return; if (!histogram->isConstructed()) histogram->constructHistogram(); T bufferVal = 0; QVector cumulativeFreq = histogram->getCumulativeFrequency(); double coeff = 255.0 / (height * width); uint32_t row = 0; uint32_t index = 0; for (int i = 0; i < m_Channels; i++) { uint32_t offset = i * stats.samples_per_channel; for (uint32_t j = 0; j < height; j++) { row = offset + j * width; for (uint32_t k = 0; k < width; k++) { index = k + row; bufferVal = (image[index] - min[i]) / histogram->getBinWidth(i); if (bufferVal >= cumulativeFreq.size()) bufferVal = cumulativeFreq.size() - 1; image[index] = qBound(min[i], static_cast(round(coeff * cumulativeFreq[bufferVal])), max[i]); } } } #endif } if (calcStats) calculateStats(true); break; // Based on http://www.librow.com/articles/article-1 case FITS_MEDIAN: { uint8_t BBP = stats.bytesPerPixel; auto * extension = new T[(width + 2) * (height + 2)]; // Check memory allocation if (!extension) return; // Create image extension for (uint32_t ch = 0; ch < m_Channels; ch++) { uint32_t offset = ch * stats.samples_per_channel; uint32_t N = width, M = height; for (uint32_t i = 0; i < M; ++i) { memcpy(extension + (N + 2) * (i + 1) + 1, image + (N * i) + offset, N * BBP); extension[(N + 2) * (i + 1)] = image[N * i + offset]; extension[(N + 2) * (i + 2) - 1] = image[N * (i + 1) - 1 + offset]; } // Fill first line of image extension memcpy(extension, extension + N + 2, (N + 2) * BBP); // Fill last line of image extension memcpy(extension + (N + 2) * (M + 1), extension + (N + 2) * M, (N + 2) * BBP); // Call median filter implementation N = width + 2; M = height + 2; // Move window through all elements of the image for (uint32_t m = 1; m < M - 1; ++m) for (uint32_t n = 1; n < N - 1; ++n) { // Pick up window elements int k = 0; float window[9]; memset(&window[0], 0, 9 * sizeof(float)); for (uint32_t j = m - 1; j < m + 2; ++j) for (uint32_t i = n - 1; i < n + 2; ++i) window[k++] = extension[j * N + i]; // Order elements (only half of them) for (uint32_t j = 0; j < 5; ++j) { // Find position of minimum element int mine = j; for (uint32_t l = j + 1; l < 9; ++l) if (window[l] < window[mine]) mine = l; // Put found minimum element in its place const float temp = window[j]; window[j] = window[mine]; window[mine] = temp; } // Get result - the middle element image[(m - 1) * (N - 2) + n - 1 + offset] = window[4]; } } // Free memory delete[] extension; if (calcStats) runningAverageStdDev(); } break; case FITS_ROTATE_CW: rotFITS(90, 0); rotCounter++; break; case FITS_ROTATE_CCW: rotFITS(270, 0); rotCounter--; break; case FITS_FLIP_H: rotFITS(0, 1); flipHCounter++; break; case FITS_FLIP_V: rotFITS(0, 2); flipVCounter++; break; default: break; } } QList FITSData::getStarCentersInSubFrame(QRect subFrame) const { QList starCentersInSubFrame; for (int i = 0; i < starCenters.count(); i++) { int x = static_cast(starCenters[i]->x); int y = static_cast(starCenters[i]->y); if(subFrame.contains(x, y)) { starCentersInSubFrame.append(starCenters[i]); } } return starCentersInSubFrame; } bool FITSData::checkForWCS() { #ifndef KSTARS_LITE #ifdef HAVE_WCSLIB int status = 0; char * header; int nkeyrec, nreject; // Free wcs before re-use if (m_wcs != nullptr) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; } if (fits_hdr2str(fptr, 1, nullptr, 0, &header, &nkeyrec, &status)) { char errmsg[512]; fits_get_errstatus(status, errmsg); lastError = errmsg; return false; } if ((status = wcspih(header, nkeyrec, WCSHDR_all, -3, &nreject, &m_nwcs, &m_wcs)) != 0) { free(header); wcsvfree(&m_nwcs, &m_wcs); lastError = QString("wcspih ERROR %1: %2.").arg(status).arg(wcshdr_errmsg[status]); return false; } free(header); if (m_wcs == nullptr) { lastError = i18n("No world coordinate systems found."); return false; } // FIXME: Call above goes through EVEN if no WCS is present, so we're adding this to return for now. if (m_wcs->crpix[0] == 0) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; lastError = i18n("No world coordinate systems found."); return false; } if ((status = wcsset(m_wcs)) != 0) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; lastError = QString("wcsset error %1: %2.").arg(status).arg(wcs_errmsg[status]); return false; } HasWCS = true; #endif #endif return HasWCS; } bool FITSData::loadWCS() { #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) if (WCSLoaded) { qWarning() << "WCS data already loaded"; return true; } if (m_wcs != nullptr) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; } qCDebug(KSTARS_FITS) << "Started WCS Data Processing..."; int status = 0; char * header; int nkeyrec, nreject, nwcs, stat[2]; double imgcrd[2], phi = 0, pixcrd[2], theta = 0, world[2]; int w = width(); int h = height(); if (fits_hdr2str(fptr, 1, nullptr, 0, &header, &nkeyrec, &status)) { char errmsg[512]; fits_get_errstatus(status, errmsg); lastError = errmsg; return false; } if ((status = wcspih(header, nkeyrec, WCSHDR_all, -3, &nreject, &nwcs, &m_wcs)) != 0) { free(header); wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; lastError = QString("wcspih ERROR %1: %2.").arg(status).arg(wcshdr_errmsg[status]); return false; } free(header); if (m_wcs == nullptr) { lastError = i18n("No world coordinate systems found."); return false; } // FIXME: Call above goes through EVEN if no WCS is present, so we're adding this to return for now. if (m_wcs->crpix[0] == 0) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; lastError = i18n("No world coordinate systems found."); return false; } if ((status = wcsset(m_wcs)) != 0) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; lastError = QString("wcsset error %1: %2.").arg(status).arg(wcs_errmsg[status]); return false; } delete[] wcs_coord; wcs_coord = new wcs_point[w * h]; if (wcs_coord == nullptr) { wcsvfree(&m_nwcs, &m_wcs); m_wcs = nullptr; lastError = "Not enough memory for WCS data!"; return false; } wcs_point * p = wcs_coord; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { pixcrd[0] = j; pixcrd[1] = i; if ((status = wcsp2s(m_wcs, 1, 2, &pixcrd[0], &imgcrd[0], &phi, &theta, &world[0], &stat[0])) != 0) { lastError = QString("wcsp2s error %1: %2.").arg(status).arg(wcs_errmsg[status]); } else { p->ra = world[0]; p->dec = world[1]; p++; } } } findObjectsInImage(&world[0], phi, theta, &imgcrd[0], &pixcrd[0], &stat[0]); WCSLoaded = true; HasWCS = true; qCDebug(KSTARS_FITS) << "Finished WCS Data processing..."; return true; #else return false; #endif } bool FITSData::wcsToPixel(SkyPoint &wcsCoord, QPointF &wcsPixelPoint, QPointF &wcsImagePoint) { #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) int status = 0; int stat[2]; double imgcrd[2], worldcrd[2], pixcrd[2], phi[2], theta[2]; if (m_wcs == nullptr) { lastError = i18n("No world coordinate systems found."); return false; } worldcrd[0] = wcsCoord.ra0().Degrees(); worldcrd[1] = wcsCoord.dec0().Degrees(); if ((status = wcss2p(m_wcs, 1, 2, &worldcrd[0], &phi[0], &theta[0], &imgcrd[0], &pixcrd[0], &stat[0])) != 0) { lastError = QString("wcss2p error %1: %2.").arg(status).arg(wcs_errmsg[status]); return false; } wcsImagePoint.setX(imgcrd[0]); wcsImagePoint.setY(imgcrd[1]); wcsPixelPoint.setX(pixcrd[0]); wcsPixelPoint.setY(pixcrd[1]); return true; #else Q_UNUSED(wcsCoord); Q_UNUSED(wcsPixelPoint); Q_UNUSED(wcsImagePoint); return false; #endif } bool FITSData::pixelToWCS(const QPointF &wcsPixelPoint, SkyPoint &wcsCoord) { #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) int status = 0; int stat[2]; double imgcrd[2], phi, pixcrd[2], theta, world[2]; if (m_wcs == nullptr) { lastError = i18n("No world coordinate systems found."); return false; } pixcrd[0] = wcsPixelPoint.x(); pixcrd[1] = wcsPixelPoint.y(); if ((status = wcsp2s(m_wcs, 1, 2, &pixcrd[0], &imgcrd[0], &phi, &theta, &world[0], &stat[0])) != 0) { lastError = QString("wcsp2s error %1: %2.").arg(status).arg(wcs_errmsg[status]); return false; } else { wcsCoord.setRA0(world[0] / 15.0); wcsCoord.setDec0(world[1]); } return true; #else Q_UNUSED(wcsPixelPoint); Q_UNUSED(wcsCoord); return false; #endif } #if !defined(KSTARS_LITE) && defined(HAVE_WCSLIB) void FITSData::findObjectsInImage(double world[], double phi, double theta, double imgcrd[], double pixcrd[], int stat[]) { int w = width(); int h = height(); int status = 0; char date[64]; KSNumbers * num = nullptr; if (fits_read_keyword(fptr, "DATE-OBS", date, nullptr, &status) == 0) { QString tsString(date); tsString = tsString.remove('\'').trimmed(); // Add Zulu time to indicate UTC tsString += "Z"; QDateTime ts = QDateTime::fromString(tsString, Qt::ISODate); if (ts.isValid()) num = new KSNumbers(KStarsDateTime(ts).djd()); } if (num == nullptr) num = new KSNumbers(KStarsData::Instance()->ut().djd()); //Set to current time if the above does not work. SkyMapComposite * map = KStarsData::Instance()->skyComposite(); wcs_point * wcs_coord = getWCSCoord(); if (wcs_coord != nullptr) { int size = w * h; objList.clear(); SkyPoint p1; p1.setRA0(dms(wcs_coord[0].ra)); p1.setDec0(dms(wcs_coord[0].dec)); p1.updateCoordsNow(num); SkyPoint p2; p2.setRA0(dms(wcs_coord[size - 1].ra)); p2.setDec0(dms(wcs_coord[size - 1].dec)); p2.updateCoordsNow(num); QList list = map->findObjectsInArea(p1, p2); foreach (SkyObject * object, list) { int type = object->type(); if (object->name() == "star" || type == SkyObject::PLANET || type == SkyObject::ASTEROID || type == SkyObject::COMET || type == SkyObject::SUPERNOVA || type == SkyObject::MOON || type == SkyObject::SATELLITE) { //DO NOT DISPLAY, at least for now, because these things move and change. } int x = -100; int y = -100; world[0] = object->ra0().Degrees(); world[1] = object->dec0().Degrees(); if ((status = wcss2p(m_wcs, 1, 2, &world[0], &phi, &theta, &imgcrd[0], &pixcrd[0], &stat[0])) != 0) { fprintf(stderr, "wcsp2s ERROR %d: %s.\n", status, wcs_errmsg[status]); } else { x = pixcrd[0]; //The X and Y are set to the found position if it does work. y = pixcrd[1]; } if (x > 0 && y > 0 && x < w && y < h) objList.append(new FITSSkyObject(object, x, y)); } } delete (num); } #endif QList FITSData::getSkyObjects() { return objList; } -FITSSkyObject::FITSSkyObject(SkyObject * object, int xPos, int yPos) : QObject() -{ - skyObjectStored = object; - xLoc = xPos; - yLoc = yPos; -} - -SkyObject * FITSSkyObject::skyObject() -{ - return skyObjectStored; -} - -int FITSSkyObject::x() -{ - return xLoc; -} - -int FITSSkyObject::y() -{ - return yLoc; -} - -void FITSSkyObject::setX(int xPos) -{ - xLoc = xPos; -} - -void FITSSkyObject::setY(int yPos) -{ - yLoc = yPos; -} - int FITSData::getFlipVCounter() const { return flipVCounter; } void FITSData::setFlipVCounter(int value) { flipVCounter = value; } int FITSData::getFlipHCounter() const { return flipHCounter; } void FITSData::setFlipHCounter(int value) { flipHCounter = value; } int FITSData::getRotCounter() const { return rotCounter; } void FITSData::setRotCounter(int value) { rotCounter = value; } /* Rotate an image by 90, 180, or 270 degrees, with an optional * reflection across the vertical or horizontal axis. * verbose generates extra info on stdout. * return nullptr if successful or rotated image. */ template bool FITSData::rotFITS(int rotate, int mirror) { int ny, nx; int x1, y1, x2, y2; uint8_t * rotimage = nullptr; int offset = 0; if (rotate == 1) rotate = 90; else if (rotate == 2) rotate = 180; else if (rotate == 3) rotate = 270; else if (rotate < 0) rotate = rotate + 360; nx = stats.width; ny = stats.height; int BBP = stats.bytesPerPixel; /* Allocate buffer for rotated image */ rotimage = new uint8_t[stats.samples_per_channel * m_Channels * BBP]; if (rotimage == nullptr) { qWarning() << "Unable to allocate memory for rotated image buffer!"; return false; } auto * rotBuffer = reinterpret_cast(rotimage); auto * buffer = reinterpret_cast(m_ImageBuffer); /* Mirror image without rotation */ if (rotate < 45 && rotate > -45) { if (mirror == 1) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (x1 = 0; x1 < nx; x1++) { x2 = nx - x1 - 1; for (y1 = 0; y1 < ny; y1++) rotBuffer[(y1 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } else if (mirror == 2) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { y2 = ny - y1 - 1; for (x1 = 0; x1 < nx; x1++) rotBuffer[(y2 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } else { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { for (x1 = 0; x1 < nx; x1++) rotBuffer[(y1 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } /* Rotate by 90 degrees */ else if (rotate >= 45 && rotate < 135) { if (mirror == 1) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { x2 = ny - y1 - 1; for (x1 = 0; x1 < nx; x1++) { y2 = nx - x1 - 1; rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } else if (mirror == 2) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { for (x1 = 0; x1 < nx; x1++) rotBuffer[(x1 * ny) + y1 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } else { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { x2 = ny - y1 - 1; for (x1 = 0; x1 < nx; x1++) { y2 = x1; rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } stats.width = ny; stats.height = nx; } /* Rotate by 180 degrees */ else if (rotate >= 135 && rotate < 225) { if (mirror == 1) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { y2 = ny - y1 - 1; for (x1 = 0; x1 < nx; x1++) rotBuffer[(y2 * nx) + x1 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } else if (mirror == 2) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (x1 = 0; x1 < nx; x1++) { x2 = nx - x1 - 1; for (y1 = 0; y1 < ny; y1++) rotBuffer[(y1 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } else { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { y2 = ny - y1 - 1; for (x1 = 0; x1 < nx; x1++) { x2 = nx - x1 - 1; rotBuffer[(y2 * nx) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } } /* Rotate by 270 degrees */ else if (rotate >= 225 && rotate < 315) { if (mirror == 1) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { for (x1 = 0; x1 < nx; x1++) rotBuffer[(x1 * ny) + y1 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } else if (mirror == 2) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { x2 = ny - y1 - 1; for (x1 = 0; x1 < nx; x1++) { y2 = nx - x1 - 1; rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } else { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { x2 = y1; for (x1 = 0; x1 < nx; x1++) { y2 = nx - x1 - 1; rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } stats.width = ny; stats.height = nx; } /* If rotating by more than 315 degrees, assume top-bottom reflection */ else if (rotate >= 315 && mirror) { for (int i = 0; i < m_Channels; i++) { offset = stats.samples_per_channel * i; for (y1 = 0; y1 < ny; y1++) { for (x1 = 0; x1 < nx; x1++) { x2 = y1; y2 = x1; rotBuffer[(y2 * ny) + x2 + offset] = buffer[(y1 * nx) + x1 + offset]; } } } } delete[] m_ImageBuffer; m_ImageBuffer = rotimage; return true; } void FITSData::rotWCSFITS(int angle, int mirror) { int status = 0; char comment[100]; double ctemp1, ctemp2, ctemp3, ctemp4, naxis1, naxis2; int WCS_DECIMALS = 6; naxis1 = stats.width; naxis2 = stats.height; if (fits_read_key_dbl(fptr, "CD1_1", &ctemp1, comment, &status)) { // No WCS keywords return; } /* Reset CROTAn and CD matrix if axes have been exchanged */ if (angle == 90) { if (!fits_read_key_dbl(fptr, "CROTA1", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CROTA1", ctemp1 + 90.0, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CROTA2", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CROTA2", ctemp1 + 90.0, WCS_DECIMALS, comment, &status); } status = 0; /* Negate rotation angle if mirrored */ if (mirror != 0) { if (!fits_read_key_dbl(fptr, "CROTA1", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CROTA1", -ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CROTA2", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CROTA2", -ctemp1, WCS_DECIMALS, comment, &status); status = 0; if (!fits_read_key_dbl(fptr, "LTM1_1", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "LTM1_1", -ctemp1, WCS_DECIMALS, comment, &status); status = 0; if (!fits_read_key_dbl(fptr, "CD1_1", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CD1_1", -ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CD1_2", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CD1_2", -ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CD2_1", &ctemp1, comment, &status)) fits_update_key_dbl(fptr, "CD2_1", -ctemp1, WCS_DECIMALS, comment, &status); } status = 0; /* Unbin CRPIX and CD matrix */ if (!fits_read_key_dbl(fptr, "LTM1_1", &ctemp1, comment, &status)) { if (ctemp1 != 1.0) { if (!fits_read_key_dbl(fptr, "LTM2_2", &ctemp2, comment, &status)) if (ctemp1 == ctemp2) { double ltv1 = 0.0; double ltv2 = 0.0; status = 0; if (!fits_read_key_dbl(fptr, "LTV1", <v1, comment, &status)) fits_delete_key(fptr, "LTV1", &status); if (!fits_read_key_dbl(fptr, "LTV2", <v2, comment, &status)) fits_delete_key(fptr, "LTV2", &status); status = 0; if (!fits_read_key_dbl(fptr, "CRPIX1", &ctemp3, comment, &status)) fits_update_key_dbl(fptr, "CRPIX1", (ctemp3 - ltv1) / ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CRPIX2", &ctemp3, comment, &status)) fits_update_key_dbl(fptr, "CRPIX2", (ctemp3 - ltv2) / ctemp1, WCS_DECIMALS, comment, &status); status = 0; if (!fits_read_key_dbl(fptr, "CD1_1", &ctemp3, comment, &status)) fits_update_key_dbl(fptr, "CD1_1", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CD1_2", &ctemp3, comment, &status)) fits_update_key_dbl(fptr, "CD1_2", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CD2_1", &ctemp3, comment, &status)) fits_update_key_dbl(fptr, "CD2_1", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); if (!fits_read_key_dbl(fptr, "CD2_2", &ctemp3, comment, &status)) fits_update_key_dbl(fptr, "CD2_2", ctemp3 / ctemp1, WCS_DECIMALS, comment, &status); status = 0; fits_delete_key(fptr, "LTM1_1", &status); fits_delete_key(fptr, "LTM1_2", &status); } } } status = 0; /* Reset CRPIXn */ if (!fits_read_key_dbl(fptr, "CRPIX1", &ctemp1, comment, &status) && !fits_read_key_dbl(fptr, "CRPIX2", &ctemp2, comment, &status)) { if (mirror != 0) { if (angle == 0) fits_update_key_dbl(fptr, "CRPIX1", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); else if (angle == 90) { fits_update_key_dbl(fptr, "CRPIX1", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CRPIX2", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); } else if (angle == 180) { fits_update_key_dbl(fptr, "CRPIX1", ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CRPIX2", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); } else if (angle == 270) { fits_update_key_dbl(fptr, "CRPIX1", ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CRPIX2", ctemp1, WCS_DECIMALS, comment, &status); } } else { if (angle == 90) { fits_update_key_dbl(fptr, "CRPIX1", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CRPIX2", ctemp1, WCS_DECIMALS, comment, &status); } else if (angle == 180) { fits_update_key_dbl(fptr, "CRPIX1", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CRPIX2", naxis2 - ctemp2, WCS_DECIMALS, comment, &status); } else if (angle == 270) { fits_update_key_dbl(fptr, "CRPIX1", ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CRPIX2", naxis1 - ctemp1, WCS_DECIMALS, comment, &status); } } } status = 0; /* Reset CDELTn (degrees per pixel) */ if (!fits_read_key_dbl(fptr, "CDELT1", &ctemp1, comment, &status) && !fits_read_key_dbl(fptr, "CDELT2", &ctemp2, comment, &status)) { if (mirror != 0) { if (angle == 0) fits_update_key_dbl(fptr, "CDELT1", -ctemp1, WCS_DECIMALS, comment, &status); else if (angle == 90) { fits_update_key_dbl(fptr, "CDELT1", -ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CDELT2", -ctemp1, WCS_DECIMALS, comment, &status); } else if (angle == 180) { fits_update_key_dbl(fptr, "CDELT1", ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CDELT2", -ctemp2, WCS_DECIMALS, comment, &status); } else if (angle == 270) { fits_update_key_dbl(fptr, "CDELT1", ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CDELT2", ctemp1, WCS_DECIMALS, comment, &status); } } else { if (angle == 90) { fits_update_key_dbl(fptr, "CDELT1", -ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CDELT2", ctemp1, WCS_DECIMALS, comment, &status); } else if (angle == 180) { fits_update_key_dbl(fptr, "CDELT1", -ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CDELT2", -ctemp2, WCS_DECIMALS, comment, &status); } else if (angle == 270) { fits_update_key_dbl(fptr, "CDELT1", ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CDELT2", -ctemp1, WCS_DECIMALS, comment, &status); } } } /* Reset CD matrix, if present */ ctemp1 = 0.0; ctemp2 = 0.0; ctemp3 = 0.0; ctemp4 = 0.0; status = 0; if (!fits_read_key_dbl(fptr, "CD1_1", &ctemp1, comment, &status)) { fits_read_key_dbl(fptr, "CD1_2", &ctemp2, comment, &status); fits_read_key_dbl(fptr, "CD2_1", &ctemp3, comment, &status); fits_read_key_dbl(fptr, "CD2_2", &ctemp4, comment, &status); status = 0; if (mirror != 0) { if (angle == 0) { fits_update_key_dbl(fptr, "CD1_2", -ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", -ctemp3, WCS_DECIMALS, comment, &status); } else if (angle == 90) { fits_update_key_dbl(fptr, "CD1_1", -ctemp4, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD1_2", -ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", -ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_2", -ctemp1, WCS_DECIMALS, comment, &status); } else if (angle == 180) { fits_update_key_dbl(fptr, "CD1_1", ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD1_2", ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", -ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_2", -ctemp4, WCS_DECIMALS, comment, &status); } else if (angle == 270) { fits_update_key_dbl(fptr, "CD1_1", ctemp4, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD1_2", ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_2", ctemp1, WCS_DECIMALS, comment, &status); } } else { if (angle == 90) { fits_update_key_dbl(fptr, "CD1_1", -ctemp4, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD1_2", -ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_2", ctemp1, WCS_DECIMALS, comment, &status); } else if (angle == 180) { fits_update_key_dbl(fptr, "CD1_1", -ctemp1, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD1_2", -ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", -ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_2", -ctemp4, WCS_DECIMALS, comment, &status); } else if (angle == 270) { fits_update_key_dbl(fptr, "CD1_1", ctemp4, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD1_2", ctemp3, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_1", -ctemp2, WCS_DECIMALS, comment, &status); fits_update_key_dbl(fptr, "CD2_2", -ctemp1, WCS_DECIMALS, comment, &status); } } } /* Delete any polynomial solution */ /* (These could maybe be switched, but I don't want to work them out yet */ status = 0; if (!fits_read_key_dbl(fptr, "CO1_1", &ctemp1, comment, &status)) { int i; char keyword[16]; for (i = 1; i < 13; i++) { sprintf(keyword, "CO1_%d", i); fits_delete_key(fptr, keyword, &status); } for (i = 1; i < 13; i++) { sprintf(keyword, "CO2_%d", i); fits_delete_key(fptr, keyword, &status); } } } uint8_t * FITSData::getWritableImageBuffer() { return m_ImageBuffer; } uint8_t const * FITSData::getImageBuffer() const { return m_ImageBuffer; } void FITSData::setImageBuffer(uint8_t * buffer) { delete[] m_ImageBuffer; m_ImageBuffer = buffer; } bool FITSData::checkDebayer() { int status = 0; char bayerPattern[64]; // Let's search for BAYERPAT keyword, if it's not found we return as there is no bayer pattern in this image if (fits_read_keyword(fptr, "BAYERPAT", bayerPattern, nullptr, &status)) return false; if (stats.bitpix != 16 && stats.bitpix != 8) { KSNotification::error(i18n("Only 8 and 16 bits bayered images supported."), i18n("Debayer error")); return false; } QString pattern(bayerPattern); pattern = pattern.remove('\'').trimmed(); if (pattern == "RGGB") debayerParams.filter = DC1394_COLOR_FILTER_RGGB; else if (pattern == "GBRG") debayerParams.filter = DC1394_COLOR_FILTER_GBRG; else if (pattern == "GRBG") debayerParams.filter = DC1394_COLOR_FILTER_GRBG; else if (pattern == "BGGR") debayerParams.filter = DC1394_COLOR_FILTER_BGGR; // We return unless we find a valid pattern else { KSNotification::error(i18n("Unsupported bayer pattern %1.", pattern), i18n("Debayer error")); return false; } fits_read_key(fptr, TINT, "XBAYROFF", &debayerParams.offsetX, nullptr, &status); fits_read_key(fptr, TINT, "YBAYROFF", &debayerParams.offsetY, nullptr, &status); if (debayerParams.offsetX == 1) { // This may leave odd values in the 0th column if the color filter is not there // in the sensor, but otherwise should process the offset correctly. // Only offsets of 0 or 1 are implemented in debayer_8bit() and debayer_16bit(). switch (debayerParams.filter) { case DC1394_COLOR_FILTER_RGGB: debayerParams.filter = DC1394_COLOR_FILTER_GRBG; break; case DC1394_COLOR_FILTER_GBRG: debayerParams.filter = DC1394_COLOR_FILTER_BGGR; break; case DC1394_COLOR_FILTER_GRBG: debayerParams.filter = DC1394_COLOR_FILTER_RGGB; break; case DC1394_COLOR_FILTER_BGGR: debayerParams.filter = DC1394_COLOR_FILTER_GBRG; break; } debayerParams.offsetX = 0; } if (debayerParams.offsetX != 0 || debayerParams.offsetY > 1 || debayerParams.offsetY < 0) { KSNotification::error(i18n("Unsupported bayer offsets %1 %2.", debayerParams.offsetX, debayerParams.offsetY), i18n("Debayer error")); return false; } HasDebayer = true; return true; } void FITSData::getBayerParams(BayerParams * param) { param->method = debayerParams.method; param->filter = debayerParams.filter; param->offsetX = debayerParams.offsetX; param->offsetY = debayerParams.offsetY; } void FITSData::setBayerParams(BayerParams * param) { debayerParams.method = param->method; debayerParams.filter = param->filter; debayerParams.offsetX = param->offsetX; debayerParams.offsetY = param->offsetY; } bool FITSData::debayer() { // if (m_ImageBuffer == nullptr) // { // int anynull = 0, status = 0; // //m_BayerBuffer = m_ImageBuffer; // if (fits_read_img(fptr, m_DataType, 1, stats.samples_per_channel, nullptr, m_ImageBuffer, &anynull, &status)) // { // char errmsg[512]; // fits_get_errstatus(status, errmsg); // KSNotification::error(i18n("Error reading image: %1", QString(errmsg)), i18n("Debayer error")); // return false; // } // } switch (m_DataType) { case TBYTE: return debayer_8bit(); case TUSHORT: return debayer_16bit(); default: return false; } } bool FITSData::debayer_8bit() { dc1394error_t error_code; uint32_t rgb_size = stats.samples_per_channel * 3 * stats.bytesPerPixel; auto * destinationBuffer = new uint8_t[rgb_size]; auto * bayer_source_buffer = reinterpret_cast(m_ImageBuffer); auto * bayer_destination_buffer = reinterpret_cast(destinationBuffer); if (bayer_destination_buffer == nullptr) { KSNotification::error(i18n("Unable to allocate memory for temporary bayer buffer."), i18n("Debayer error")); return false; } int ds1394_height = stats.height; auto dc1394_source = bayer_source_buffer; if (debayerParams.offsetY == 1) { dc1394_source += stats.width; ds1394_height--; } // offsetX == 1 is handled in checkDebayer() and should be 0 here. error_code = dc1394_bayer_decoding_8bit(dc1394_source, bayer_destination_buffer, stats.width, ds1394_height, debayerParams.filter, debayerParams.method); if (error_code != DC1394_SUCCESS) { KSNotification::error(i18n("Debayer failed (%1)", error_code), i18n("Debayer error")); m_Channels = 1; delete[] destinationBuffer; return false; } if (m_ImageBufferSize != rgb_size) { delete[] m_ImageBuffer; m_ImageBuffer = new uint8_t[rgb_size]; if (m_ImageBuffer == nullptr) { delete[] destinationBuffer; KSNotification::error(i18n("Unable to allocate memory for temporary bayer buffer."), i18n("Debayer error")); return false; } m_ImageBufferSize = rgb_size; } auto bayered_buffer = reinterpret_cast(m_ImageBuffer); // Data in R1G1B1, we need to copy them into 3 layers for FITS uint8_t * rBuff = bayered_buffer; uint8_t * gBuff = bayered_buffer + (stats.width * stats.height); uint8_t * bBuff = bayered_buffer + (stats.width * stats.height * 2); int imax = stats.samples_per_channel * 3 - 3; for (int i = 0; i <= imax; i += 3) { *rBuff++ = bayer_destination_buffer[i]; *gBuff++ = bayer_destination_buffer[i + 1]; *bBuff++ = bayer_destination_buffer[i + 2]; } m_Channels = (m_Mode == FITS_NORMAL) ? 3 : 1; delete[] destinationBuffer; return true; } bool FITSData::debayer_16bit() { dc1394error_t error_code; uint32_t rgb_size = stats.samples_per_channel * 3 * stats.bytesPerPixel; auto * destinationBuffer = new uint8_t[rgb_size]; auto * bayer_source_buffer = reinterpret_cast(m_ImageBuffer); auto * bayer_destination_buffer = reinterpret_cast(destinationBuffer); if (bayer_destination_buffer == nullptr) { KSNotification::error(i18n("Unable to allocate memory for temporary bayer buffer."), i18n("Debayer error")); return false; } int ds1394_height = stats.height; auto dc1394_source = bayer_source_buffer; if (debayerParams.offsetY == 1) { dc1394_source += stats.width; ds1394_height--; } // offsetX == 1 is handled in checkDebayer() and should be 0 here. error_code = dc1394_bayer_decoding_16bit(dc1394_source, bayer_destination_buffer, stats.width, ds1394_height, debayerParams.filter, debayerParams.method, 16); if (error_code != DC1394_SUCCESS) { KSNotification::error(i18n("Debayer failed (%1)", error_code), i18n("Debayer error")); m_Channels = 1; delete[] destinationBuffer; return false; } if (m_ImageBufferSize != rgb_size) { delete[] m_ImageBuffer; m_ImageBuffer = new uint8_t[rgb_size]; if (m_ImageBuffer == nullptr) { delete[] destinationBuffer; KSNotification::error(i18n("Unable to allocate memory for temporary bayer buffer."), i18n("Debayer error")); return false; } m_ImageBufferSize = rgb_size; } auto bayered_buffer = reinterpret_cast(m_ImageBuffer); // Data in R1G1B1, we need to copy them into 3 layers for FITS uint16_t * rBuff = bayered_buffer; uint16_t * gBuff = bayered_buffer + (stats.width * stats.height); uint16_t * bBuff = bayered_buffer + (stats.width * stats.height * 2); int imax = stats.samples_per_channel * 3 - 3; for (int i = 0; i <= imax; i += 3) { *rBuff++ = bayer_destination_buffer[i]; *gBuff++ = bayer_destination_buffer[i + 1]; *bBuff++ = bayer_destination_buffer[i + 2]; } m_Channels = (m_Mode == FITS_NORMAL) ? 3 : 1; delete[] destinationBuffer; return true; } double FITSData::getADU() const { double adu = 0; for (int i = 0; i < m_Channels; i++) adu += stats.mean[i]; return (adu / static_cast(m_Channels)); } QString FITSData::getLastError() const { return lastError; } bool FITSData::getAutoRemoveTemporaryFITS() const { return autoRemoveTemporaryFITS; } void FITSData::setAutoRemoveTemporaryFITS(bool value) { autoRemoveTemporaryFITS = value; } template void FITSData::convertToQImage(double dataMin, double dataMax, double scale, double zero, QImage &image) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" auto * buffer = (T *)getImageBuffer(); #pragma GCC diagnostic pop const T limit = std::numeric_limits::max(); T bMin = dataMin < 0 ? 0 : dataMin; T bMax = dataMax > limit ? limit : dataMax; uint16_t w = width(); uint16_t h = height(); uint32_t size = w * h; double val; if (channels() == 1) { /* Fill in pixel values using indexed map, linear scale */ for (int j = 0; j < h; j++) { unsigned char * scanLine = image.scanLine(j); for (int i = 0; i < w; i++) { val = qBound(bMin, buffer[j * w + i], bMax); val = val * scale + zero; scanLine[i] = qBound(0, (unsigned char)val, 255); } } } else { double rval = 0, gval = 0, bval = 0; QRgb value; /* Fill in pixel values using indexed map, linear scale */ for (int j = 0; j < h; j++) { auto * scanLine = reinterpret_cast((image.scanLine(j))); for (int i = 0; i < w; i++) { rval = qBound(bMin, buffer[j * w + i], bMax); gval = qBound(bMin, buffer[j * w + i + size], bMax); bval = qBound(bMin, buffer[j * w + i + size * 2], bMax); value = qRgb(rval * scale + zero, gval * scale + zero, bval * scale + zero); scanLine[i] = value; } } } } QImage FITSData::FITSToImage(const QString &filename) { QImage fitsImage; double min, max; FITSData data; QFuture future = data.loadFITS(filename); // Wait synchronously future.waitForFinished(); if (future.result() == false) return fitsImage; data.getMinMax(&min, &max); if (min == max) { fitsImage.fill(Qt::white); return fitsImage; } if (data.channels() == 1) { fitsImage = QImage(data.width(), data.height(), QImage::Format_Indexed8); fitsImage.setColorCount(256); for (int i = 0; i < 256; i++) fitsImage.setColor(i, qRgb(i, i, i)); } else { fitsImage = QImage(data.width(), data.height(), QImage::Format_RGB32); } double dataMin = data.stats.mean[0] - data.stats.stddev[0]; double dataMax = data.stats.mean[0] + data.stats.stddev[0] * 3; double bscale = 255. / (dataMax - dataMin); double bzero = (-dataMin) * (255. / (dataMax - dataMin)); // Long way to do this since we do not want to use templated functions here switch (data.property("dataType").toInt()) { case TBYTE: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TSHORT: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TUSHORT: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TLONG: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TULONG: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TFLOAT: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TLONGLONG: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; case TDOUBLE: data.convertToQImage(dataMin, dataMax, bscale, bzero, fitsImage); break; default: break; } return fitsImage; } bool FITSData::ImageToFITS(const QString &filename, const QString &format, QString &output) { if (QImageReader::supportedImageFormats().contains(format.toLatin1()) == false) { qCCritical(KSTARS_FITS) << "Failed to convert" << filename << "to FITS since format" << format << "is not supported in Qt"; return false; } QImage input; if (input.load(filename, format.toLatin1()) == false) { qCCritical(KSTARS_FITS) << "Failed to open image" << filename; return false; } output = QString(KSPaths::writableLocation(QStandardPaths::TempLocation) + QFileInfo(filename).fileName() + ".fits"); //This section sets up the FITS File fitsfile *fptr = nullptr; int status = 0; long fpixel = 1, naxis = input.allGray() ? 2 : 3, nelements, exposure; long naxes[3] = { input.width(), input.height(), naxis == 3 ? 3 : 1 }; char error_status[512] = {0}; if (fits_create_file(&fptr, QString('!' + output).toLatin1().data(), &status)) { fits_get_errstatus(status, error_status); qCCritical(KSTARS_FITS) << "Failed to create FITS file. Error:" << error_status; return false; } if (fits_create_img(fptr, BYTE_IMG, naxis, naxes, &status)) { qCWarning(KSTARS_FITS) << "fits_create_img failed:" << error_status; status = 0; fits_flush_file(fptr, &status); fits_close_file(fptr, &status); return false; } exposure = 1; fits_update_key(fptr, TLONG, "EXPOSURE", &exposure, "Total Exposure Time", &status); // Gray image if (naxis == 2) { nelements = naxes[0] * naxes[1]; if (fits_write_img(fptr, TBYTE, fpixel, nelements, input.bits(), &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_FITS) << "fits_write_img GRAY failed:" << error_status; status = 0; fits_flush_file(fptr, &status); fits_close_file(fptr, &status); return false; } } // RGB image, we have to convert from ARGB format to R G B for each plane else { nelements = naxes[0] * naxes[1] * 3; uint8_t *srcBuffer = input.bits(); // ARGB uint32_t srcBytes = naxes[0] * naxes[1] * 4 - 4; uint8_t *rgbBuffer = new uint8_t[nelements]; if (rgbBuffer == nullptr) { qCWarning(KSTARS_FITS) << "Not enough memory for RGB buffer"; fits_flush_file(fptr, &status); fits_close_file(fptr, &status); return false; } uint8_t *subR = rgbBuffer; uint8_t *subG = rgbBuffer + naxes[0] * naxes[1]; uint8_t *subB = rgbBuffer + naxes[0] * naxes[1] * 2; for (uint32_t i = 0; i < srcBytes; i += 4) { *subB++ = srcBuffer[i]; *subG++ = srcBuffer[i + 1]; *subR++ = srcBuffer[i + 2]; } if (fits_write_img(fptr, TBYTE, fpixel, nelements, rgbBuffer, &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_FITS) << "fits_write_img RGB failed:" << error_status; status = 0; fits_flush_file(fptr, &status); fits_close_file(fptr, &status); delete [] rgbBuffer; return false; } delete [] rgbBuffer; } if (fits_flush_file(fptr, &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_FITS) << "fits_flush_file failed:" << error_status; status = 0; fits_close_file(fptr, &status); return false; } if (fits_close_file(fptr, &status)) { fits_get_errstatus(status, error_status); qCWarning(KSTARS_FITS) << "fits_close_file failed:" << error_status; return false; } return true; } #if 0 bool FITSData::injectWCS(const QString &newWCSFile, double orientation, double ra, double dec, double pixscale) { int status = 0, exttype = 0; long nelements; fitsfile * new_fptr; char errMsg[512]; qCInfo(KSTARS_FITS) << "Creating new WCS file:" << newWCSFile << "with parameters Orientation:" << orientation << "RA:" << ra << "DE:" << dec << "Pixel Scale:" << pixscale; nelements = stats.samples_per_channel * m_Channels; /* Create a new File, overwriting existing*/ if (fits_create_file(&new_fptr, QString('!' + newWCSFile).toLatin1(), &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } if (fits_movabs_hdu(fptr, 1, &exttype, &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } if (fits_copy_file(fptr, new_fptr, 1, 1, 1, &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } /* close current file */ if (fits_close_file(fptr, &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } status = 0; if (m_isTemporary && autoRemoveTemporaryFITS) { QFile::remove(m_Filename); m_isTemporary = false; qCDebug(KSTARS_FITS) << "Removing FITS File: " << m_Filename; } m_Filename = newWCSFile; m_isTemporary = true; fptr = new_fptr; if (fits_movabs_hdu(fptr, 1, &exttype, &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } /* Write Data */ if (fits_write_img(fptr, m_DataType, 1, nelements, m_ImageBuffer, &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } /* Write keywords */ // Minimum if (fits_update_key(fptr, TDOUBLE, "DATAMIN", &(stats.min), "Minimum value", &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } // Maximum if (fits_update_key(fptr, TDOUBLE, "DATAMAX", &(stats.max), "Maximum value", &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } // NAXIS1 if (fits_update_key(fptr, TUSHORT, "NAXIS1", &(stats.width), "length of data axis 1", &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } // NAXIS2 if (fits_update_key(fptr, TUSHORT, "NAXIS2", &(stats.height), "length of data axis 2", &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } fits_update_key(fptr, TDOUBLE, "OBJCTRA", &ra, "Object RA", &status); fits_update_key(fptr, TDOUBLE, "OBJCTDEC", &dec, "Object DEC", &status); int epoch = 2000; fits_update_key(fptr, TINT, "EQUINOX", &epoch, "Equinox", &status); fits_update_key(fptr, TDOUBLE, "CRVAL1", &ra, "CRVAL1", &status); fits_update_key(fptr, TDOUBLE, "CRVAL2", &dec, "CRVAL1", &status); char radecsys[8] = "FK5"; char ctype1[16] = "RA---TAN"; char ctype2[16] = "DEC--TAN"; fits_update_key(fptr, TSTRING, "RADECSYS", radecsys, "RADECSYS", &status); fits_update_key(fptr, TSTRING, "CTYPE1", ctype1, "CTYPE1", &status); fits_update_key(fptr, TSTRING, "CTYPE2", ctype2, "CTYPE2", &status); double crpix1 = width() / 2.0; double crpix2 = height() / 2.0; fits_update_key(fptr, TDOUBLE, "CRPIX1", &crpix1, "CRPIX1", &status); fits_update_key(fptr, TDOUBLE, "CRPIX2", &crpix2, "CRPIX2", &status); // Arcsecs per Pixel double secpix1 = pixscale; double secpix2 = pixscale; fits_update_key(fptr, TDOUBLE, "SECPIX1", &secpix1, "SECPIX1", &status); fits_update_key(fptr, TDOUBLE, "SECPIX2", &secpix2, "SECPIX2", &status); double degpix1 = secpix1 / 3600.0; double degpix2 = secpix2 / 3600.0; fits_update_key(fptr, TDOUBLE, "CDELT1", °pix1, "CDELT1", &status); fits_update_key(fptr, TDOUBLE, "CDELT2", °pix2, "CDELT2", &status); // Rotation is CW, we need to convert it to CCW per CROTA1 definition double rotation = 360 - orientation; if (rotation > 360) rotation -= 360; fits_update_key(fptr, TDOUBLE, "CROTA1", &rotation, "CROTA1", &status); fits_update_key(fptr, TDOUBLE, "CROTA2", &rotation, "CROTA2", &status); // ISO Date if (fits_write_date(fptr, &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } QString history = QString("Modified by KStars on %1").arg(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss")); // History if (fits_write_history(fptr, history.toLatin1(), &status)) { fits_get_errstatus(status, errMsg); lastError = QString(errMsg); fits_report_error(stderr, status); return false; } fits_flush_file(fptr, &status); WCSLoaded = false; qCDebug(KSTARS_FITS) << "Finished creating WCS file: " << newWCSFile; return true; } #endif bool FITSData::injectWCS(double orientation, double ra, double dec, double pixscale) { int status = 0; fits_update_key(fptr, TDOUBLE, "OBJCTRA", &ra, "Object RA", &status); fits_update_key(fptr, TDOUBLE, "OBJCTDEC", &dec, "Object DEC", &status); int epoch = 2000; fits_update_key(fptr, TINT, "EQUINOX", &epoch, "Equinox", &status); fits_update_key(fptr, TDOUBLE, "CRVAL1", &ra, "CRVAL1", &status); fits_update_key(fptr, TDOUBLE, "CRVAL2", &dec, "CRVAL1", &status); char radecsys[8] = "FK5"; char ctype1[16] = "RA---TAN"; char ctype2[16] = "DEC--TAN"; fits_update_key(fptr, TSTRING, "RADECSYS", radecsys, "RADECSYS", &status); fits_update_key(fptr, TSTRING, "CTYPE1", ctype1, "CTYPE1", &status); fits_update_key(fptr, TSTRING, "CTYPE2", ctype2, "CTYPE2", &status); double crpix1 = width() / 2.0; double crpix2 = height() / 2.0; fits_update_key(fptr, TDOUBLE, "CRPIX1", &crpix1, "CRPIX1", &status); fits_update_key(fptr, TDOUBLE, "CRPIX2", &crpix2, "CRPIX2", &status); // Arcsecs per Pixel double secpix1 = pixscale; double secpix2 = pixscale; fits_update_key(fptr, TDOUBLE, "SECPIX1", &secpix1, "SECPIX1", &status); fits_update_key(fptr, TDOUBLE, "SECPIX2", &secpix2, "SECPIX2", &status); double degpix1 = secpix1 / 3600.0; double degpix2 = secpix2 / 3600.0; fits_update_key(fptr, TDOUBLE, "CDELT1", °pix1, "CDELT1", &status); fits_update_key(fptr, TDOUBLE, "CDELT2", °pix2, "CDELT2", &status); // Rotation is CW, we need to convert it to CCW per CROTA1 definition double rotation = 360 - orientation; if (rotation > 360) rotation -= 360; fits_update_key(fptr, TDOUBLE, "CROTA1", &rotation, "CROTA1", &status); fits_update_key(fptr, TDOUBLE, "CROTA2", &rotation, "CROTA2", &status); WCSLoaded = false; qCDebug(KSTARS_FITS) << "Finished update WCS info."; return true; } bool FITSData::contains(const QPointF &point) const { return (point.x() >= 0 && point.y() >= 0 && point.x() <= stats.width && point.y() <= stats.height); } void FITSData::saveStatistics(Statistic &other) { other = stats; } void FITSData::restoreStatistics(Statistic &other) { stats = other; } diff --git a/kstars/fitsviewer/fitsdata.h b/kstars/fitsviewer/fitsdata.h index 99684a4a0..b0e679a2a 100644 --- a/kstars/fitsviewer/fitsdata.h +++ b/kstars/fitsviewer/fitsdata.h @@ -1,534 +1,518 @@ /*************************************************************************** fitsimage.cpp - FITS Image ------------------- begin : Tue Feb 24 2004 copyright : (C) 2004 by Jasem Mutlaq email : mutlaqja@ikarustech.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* * See http://members.aol.com/pkirchg for more details. * ***************************************************************************/ #pragma once #include "config-kstars.h" #include "bayer.h" #include "fitscommon.h" #include "fitsstardetector.h" #ifdef WIN32 // This header must be included before fitsio.h to avoid compiler errors with Visual Studio #include #endif #include #include #include #include #include #ifndef KSTARS_LITE #include #ifdef HAVE_WCSLIB #include #endif #endif +#include "fitsskyobject.h" + class QProgressDialog; -class SkyObject; class SkyPoint; class FITSHistogram; typedef struct { float ra; float dec; } wcs_point; class Edge; -class FITSSkyObject : public QObject -{ - Q_OBJECT - public: - explicit FITSSkyObject(SkyObject *object, int xPos, int yPos); - SkyObject *skyObject(); - int x(); - int y(); - void setX(int xPos); - void setY(int yPos); - - private: - SkyObject *skyObjectStored; - int xLoc; - int yLoc; -}; - class FITSData : public QObject { Q_OBJECT // Name of FITS file Q_PROPERTY(QString filename READ filename) // Size of file in bytes Q_PROPERTY(qint64 size READ size) // Width in pixels Q_PROPERTY(quint16 width READ width) // Height in pixels Q_PROPERTY(quint16 height READ height) // FITS MODE --> Normal, Focus, Guide..etc Q_PROPERTY(FITSMode mode MEMBER m_Mode) // 1 channel (grayscale) or 3 channels (RGB) Q_PROPERTY(quint8 channels READ channels) // Data type (BYTE, SHORT, INT..etc) Q_PROPERTY(quint32 dataType MEMBER m_DataType) // Bits per pixel Q_PROPERTY(quint8 bpp READ bpp WRITE setBPP) // Does FITS have WSC header? Q_PROPERTY(bool hasWCS READ hasWCS) // Does FITS have bayer data? Q_PROPERTY(bool hasDebyaer READ hasDebayer) public: explicit FITSData(FITSMode fitsMode = FITS_NORMAL); explicit FITSData(const FITSData *other); ~FITSData(); /** Structure to hold FITS Header records */ typedef struct { QString key; /** FITS Header Key */ QVariant value; /** FITS Header Value */ QString comment; /** FITS Header Comment, if any */ } Record; /// Stats struct to hold statisical data about the FITS data typedef struct { double min[3] = {0}, max[3] = {0}; double mean[3] = {0}; double stddev[3] = {0}; double median[3] = {0}; double SNR { 0 }; int bitpix { 8 }; int bytesPerPixel { 1 }; int ndim { 2 }; int64_t size { 0 }; uint32_t samples_per_channel { 0 }; uint16_t width { 0 }; uint16_t height { 0 }; } Statistic; /** * @brief loadFITS Loading FITS file asynchronously. * @param inFilename Path to FITS file (or compressed fits.gz) * @param silent If set, error messages are ignored. If set to false, the error message will get displayed in a popup. * @return A QFuture that can be watched until the async operation is complete. */ QFuture loadFITS(const QString &inFilename, bool silent = true); /** * @brief loadFITSFromMemory Loading FITS from memory buffer. * @param inFilename Potential future path to FITS file (or compressed fits.gz), stored in a fitsdata class variable * @param fits_buffer The memory buffer containing the fits data. * @param fits_buffer_size The size in bytes of the buffer. * @param silent If set, error messages are ignored. If set to false, the error message will get displayed in a popup. * @return bool indicating success or failure. */ bool loadFITSFromMemory(const QString &inFilename, void *fits_buffer, size_t fits_buffer_size, bool silent); /* Save FITS */ int saveFITS(const QString &newFilename); /* Rescale image lineary from image_buffer, fit to window if desired */ int rescale(FITSZoom type); /* Calculate stats */ void calculateStats(bool refresh = false); /* Check if a particular point exists within the image */ bool contains(const QPointF &point) const; // Access functions void clearImageBuffers(); void setImageBuffer(uint8_t *buffer); uint8_t const *getImageBuffer() const; uint8_t *getWritableImageBuffer(); // Statistics void saveStatistics(Statistic &other); void restoreStatistics(Statistic &other); Statistic const &getStatistics() const { return stats; }; uint16_t width() const { return stats.width; } uint16_t height() const { return stats.height; } int64_t size() const { return stats.size; } int channels() const { return m_Channels; } double getMin(uint8_t channel = 0) const { return stats.min[channel]; } double getMax(uint8_t channel = 0) const { return stats.max[channel]; } void setMinMax(double newMin, double newMax, uint8_t channel = 0); void getMinMax(double *min, double *max, uint8_t channel = 0) const { *min = stats.min[channel]; *max = stats.max[channel]; } void setStdDev(double value, uint8_t channel = 0) { stats.stddev[channel] = value; } double getStdDev(uint8_t channel = 0) const { return stats.stddev[channel]; } void setMean(double value, uint8_t channel = 0) { stats.mean[channel] = value; } double getMean(uint8_t channel = 0) const { return stats.mean[channel]; } void setMedian(double val, uint8_t channel = 0) { stats.median[channel] = val; } double getMedian(uint8_t channel = 0) const { return stats.median[channel]; } int getBytesPerPixel() const { return stats.bytesPerPixel; } void setSNR(double val) { stats.SNR = val; } double getSNR() const { return stats.SNR; } void setBPP(uint8_t value) { stats.bitpix = value; } uint32_t bpp() const { return stats.bitpix; } double getADU() const; // FITS Record bool getRecordValue(const QString &key, QVariant &value) const; const QList &getRecords() const { return records; } // Star Detection - Native KStars implementation void setStarAlgorithm(StarAlgorithm algorithm) { starAlgorithm = algorithm; } int getDetectedStars() const { return starCenters.count(); } bool areStarsSearched() const { return starsSearched; } void appendStar(Edge *newCenter) { starCenters.append(newCenter); } QList getStarCenters() const { return starCenters; } QList getStarCentersInSubFrame(QRect subFrame) const; int findStars(StarAlgorithm algorithm = ALGORITHM_CENTROID, const QRect &trackingBox = QRect()); // Use SEP (Sextractor Library) to find stars template void getFloatBuffer(float *buffer, int x, int y, int w, int h) const; int findSEPStars(QList&, const QRect &boundary = QRect()) const; // Apply ring filter to searched stars int filterStars(const float innerRadius, const float outerRadius); // Half Flux Radius Edge *getMaxHFRStar() const { return maxHFRStar; } double getHFR(HFRType type = HFR_AVERAGE); double getHFR(int x, int y); // WCS // Check if image has valid WCS header information and set HasWCS accordingly. Call in loadFITS() bool checkForWCS(); // Does image have valid WCS? bool hasWCS() { return HasWCS; } // Load WCS data bool loadWCS(); // Is WCS Image loaded? bool isWCSLoaded() { return WCSLoaded; } wcs_point *getWCSCoord() { return wcs_coord; } /** * @brief wcsToPixel Given J2000 (RA0,DE0) coordinates. Find in the image the corresponding pixel coordinates. * @param wcsCoord Coordinates of target * @param wcsPixelPoint Return XY FITS coordinates * @param wcsImagePoint Return XY Image coordinates * @return True if conversion is successful, false otherwise. */ bool wcsToPixel(SkyPoint &wcsCoord, QPointF &wcsPixelPoint, QPointF &wcsImagePoint); /** * @brief pixelToWCS Convert Pixel coordinates to J2000 world coordinates * @param wcsPixelPoint Pixel coordinates in XY Image space. * @param wcsCoord Store back WCS world coordinate in wcsCoord * @return True if successful, false otherwise. */ bool pixelToWCS(const QPointF &wcsPixelPoint, SkyPoint &wcsCoord); /** * @brief injectWCS Add WCS keywords to file * @param orientation Solver orientation, degrees E of N. * @param ra J2000 Right Ascension * @param dec J2000 Declination * @param pixscale Pixel scale in arcsecs per pixel * @return True if file is successfully updated with WCS info. */ bool injectWCS(double orientation, double ra, double dec, double pixscale); // Debayer bool hasDebayer() { return HasDebayer; } bool debayer(); bool debayer_8bit(); bool debayer_16bit(); void getBayerParams(BayerParams *param); void setBayerParams(BayerParams *param); // Histogram #ifndef KSTARS_LITE void setHistogram(FITSHistogram *inHistogram) { histogram = inHistogram; } #endif // Filter void applyFilter(FITSScale type, uint8_t *image = nullptr, QVector *targetMin = nullptr, QVector *targetMax = nullptr); // Rotation counter. We keep count to rotate WCS keywords on save int getRotCounter() const; void setRotCounter(int value); // Filename const QString &filename() const { return m_Filename; } const QString &compressedFilename() const { return m_compressedFilename; } bool isTempFile() const { return m_isTemporary; } bool isCompressed() const { return m_isCompressed; } // Horizontal flip counter. We keep count to rotate WCS keywords on save int getFlipHCounter() const; void setFlipHCounter(int value); // Horizontal flip counter. We keep count to rotate WCS keywords on save int getFlipVCounter() const; void setFlipVCounter(int value); #ifndef KSTARS_LITE #ifdef HAVE_WCSLIB void findObjectsInImage(double world[], double phi, double theta, double imgcrd[], double pixcrd[], int stat[]); #endif #endif QList getSkyObjects(); QList objList; //Does this need to be public?? // Create autostretch image from FITS File static QImage FITSToImage(const QString &filename); /** * @brief ImageToFITS Convert an image file with supported format to a FITS file. * @param filename full path to filename without extension * @param format file extension. Supported formats are whatever supported by Qt (e.g. PNG, JPG,..etc) * @param output Output temporary file path. The created file is generated by the function and store in output. * @return True if conversion is successful, false otherwise. */ static bool ImageToFITS(const QString &filename, const QString &format, QString &output); bool getAutoRemoveTemporaryFITS() const; void setAutoRemoveTemporaryFITS(bool value); QString getLastError() const; signals: void converted(QImage); private: void loadCommon(const QString &inFilename); bool privateLoad(void *fits_buffer, size_t fits_buffer_size, bool silent); void rotWCSFITS(int angle, int mirror); int calculateMinMax(bool refresh = false); bool checkDebayer(); void readWCSKeys(); // FITS Record bool parseHeader(); //int getFITSRecord(QString &recordList, int &nkeys); // Templated functions template bool debayer(); template bool rotFITS(int rotate, int mirror); // Apply Filter template void applyFilter(FITSScale type, uint8_t *targetImage, QVector * min = nullptr, QVector * max = nullptr); template void calculateMinMax(); template QPair getParitionMinMax(uint32_t start, uint32_t stride); /* Calculate running average & standard deviation using Welford’s method for computing variance */ template void runningAverageStdDev(); template QPair getSquaredSumAndMean(uint32_t start, uint32_t stride); template void convertToQImage(double dataMin, double dataMax, double scale, double zero, QImage &image); #ifndef KSTARS_LITE FITSHistogram *histogram { nullptr }; // Pointer to the FITS data histogram #endif /// Pointer to CFITSIO FITS file struct fitsfile *fptr { nullptr }; /// FITS image data type (TBYTE, TUSHORT, TINT, TFLOAT, TLONG, TDOUBLE) uint32_t m_DataType { 0 }; /// Number of channels uint8_t m_Channels { 1 }; /// Generic data image buffer uint8_t *m_ImageBuffer { nullptr }; /// Above buffer size in bytes uint32_t m_ImageBufferSize { 0 }; /// Is this a temporary file or one loaded from disk? bool m_isTemporary { false }; /// is this file compress (.fits.fz)? bool m_isCompressed { false }; /// Did we search for stars yet? bool starsSearched { false }; ///Star Selection Algorithm StarAlgorithm starAlgorithm { ALGORITHM_GRADIENT }; /// Do we have WCS keywords in this FITS data? bool HasWCS { false }; /// Is the image debayarable? bool HasDebayer { false }; /// Is WCS data loaded? bool WCSLoaded { false }; /// Do we need to mark stars for the user? bool markStars { false }; /// Our very own file name QString m_Filename, m_compressedFilename; /// FITS Mode (Normal, WCS, Guide, Focus..etc) FITSMode m_Mode; /// How many times the image was rotated? Useful for WCS keywords rotation on save. int rotCounter { 0 }; /// How many times the image was flipped horizontally? int flipHCounter { 0 }; /// How many times the image was flipped vertically? int flipVCounter { 0 }; /// Pointer to WCS coordinate data, if any. wcs_point *wcs_coord { nullptr }; /// WCS Struct struct wcsprm *m_wcs { nullptr }; int m_nwcs = 0; /// All the stars we detected, if any. QList starCenters; QList localStarCenters; /// The biggest fattest star in the image. Edge *maxHFRStar { nullptr }; //uint8_t *m_BayerBuffer { nullptr }; /// Bayer parameters BayerParams debayerParams; Statistic stats; // A list of header records QList records; /// Remove temporary files after closing bool autoRemoveTemporaryFITS { true }; QString lastError; static const QString m_TemporaryPath; }; diff --git a/kstars/fitsviewer/fitsskyobject.cpp b/kstars/fitsviewer/fitsskyobject.cpp new file mode 100644 index 000000000..2d42e2dc7 --- /dev/null +++ b/kstars/fitsviewer/fitsskyobject.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + fitsskyobject.cpp - FITS Image + ------------------- + begin : Tue Apr 07 2020 + copyright : (C) 2004 by Jasem Mutlaq, (C) 2020 by Eric Dejouhanet + email : mutlaqja@ikarustech.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* + * See http://members.aol.com/pkirchg for more details. * + ***************************************************************************/ + +#include "fitsskyobject.h" + +FITSSkyObject::FITSSkyObject(SkyObject /*const*/ * object, int xPos, int yPos) : QObject() +{ + skyObjectStored = object; + xLoc = xPos; + yLoc = yPos; +} + +SkyObject /*const*/ * FITSSkyObject::skyObject() +{ + return skyObjectStored; +} + +int FITSSkyObject::x() const +{ + return xLoc; +} + +int FITSSkyObject::y() const +{ + return yLoc; +} + +void FITSSkyObject::setX(int xPos) +{ + xLoc = xPos; +} + +void FITSSkyObject::setY(int yPos) +{ + yLoc = yPos; +} diff --git a/kstars/fitsviewer/fitsskyobject.h b/kstars/fitsviewer/fitsskyobject.h new file mode 100644 index 000000000..be161dc5a --- /dev/null +++ b/kstars/fitsviewer/fitsskyobject.h @@ -0,0 +1,63 @@ +/*************************************************************************** + fitsskyobject.cpp - FITS Image + ------------------- + begin : Tue Apr 07 2020 + copyright : (C) 2004 by Jasem Mutlaq, (C) 2020 by Eric Dejouhanet + email : mutlaqja@ikarustech.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* + * See http://members.aol.com/pkirchg for more details. * + ***************************************************************************/ + +#ifndef FITSSKYOBJECT_H +#define FITSSKYOBJECT_H + +#include + +class SkyObject; + +class FITSSkyObject : public QObject +{ + Q_OBJECT + +public: + /** @brief Locate a SkyObject at a pixel position. + * @param object is the SkyObject to locate in the frame. + * @param xPos and yPos are the pixel position of the SkyObject in the frame. + */ + explicit FITSSkyObject(SkyObject /*const*/ *object, int xPos, int yPos); + +public: + /** @brief Getting the SkyObject this instance locates. + */ + SkyObject /*const*/ *skyObject(); + +public: + /** @brief Getting the pixel position of the SkyObject this instance locates. */ + /** @{ */ + int x() const; + int y() const; + /** @} */ + +public: + /** @brief Setting the pixel position of the SkyObject this instance locates. */ + /** @{ */ + void setX(int xPos); + void setY(int yPos); + /** @} */ + +protected: + SkyObject /*const*/ *skyObjectStored { nullptr }; + int xLoc { 0 }; + int yLoc { 0 }; +}; + +#endif // FITSSKYOBJECT_H diff --git a/kstars/fitsviewer/fitsthresholddetector.cpp b/kstars/fitsviewer/fitsthresholddetector.cpp index 69999972f..105592bba 100644 --- a/kstars/fitsviewer/fitsthresholddetector.cpp +++ b/kstars/fitsviewer/fitsthresholddetector.cpp @@ -1,227 +1,222 @@ /*************************************************************************** fitsthresholddetector.cpp - FITS Image ------------------- begin : Sat March 28 2020 copyright : (C) 2004 by Jasem Mutlaq, (C) 2020 by Eric Dejouhanet email : eric.dejouhanet@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* * See http://members.aol.com/pkirchg for more details. * ***************************************************************************/ #include #include "fits_debug.h" #include "fitsthresholddetector.h" FITSStarDetector& FITSThresholdDetector::configure(const QString &setting, const QVariant &value) { - bool ok = false; - - if (!setting.compare("threshold", Qt::CaseInsensitive)) - { - double const _focusThreshold = value.toDouble(&ok); - if (ok) - focusThreshold = _focusThreshold; - } + if (!setting.compare("THRESHOLD_PERCENTAGE", Qt::CaseInsensitive)) + if (value.canConvert ()) + THRESHOLD_PERCENTAGE = value.value (); return *this; } int FITSThresholdDetector::findSources(QList &starCenters, QRect const &boundary) { switch (parent()->property("dataType").toInt()) { case TBYTE: return findOneStar(starCenters, boundary); case TSHORT: return findOneStar(starCenters, boundary); case TUSHORT: return findOneStar(starCenters, boundary); case TLONG: return findOneStar(starCenters, boundary); case TULONG: return findOneStar(starCenters, boundary); case TFLOAT: return findOneStar(starCenters, boundary); case TLONGLONG: return findOneStar(starCenters, boundary); case TDOUBLE: return findOneStar(starCenters, boundary); default: break; } return 0; } template int FITSThresholdDetector::findOneStar(QList &starCenters, const QRect &boundary) const { FITSData const * const image_data = reinterpret_cast(parent()); if (image_data == nullptr) return 0; FITSData::Statistic const &stats = image_data->getStatistics(); if (boundary.isEmpty()) return -1; int subX = boundary.x(); int subY = boundary.y(); int subW = subX + boundary.width(); int subH = subY + boundary.height(); float massX = 0, massY = 0, totalMass = 0; auto * buffer = reinterpret_cast(image_data->getImageBuffer()); // TODO replace magic number with something more useful to understand - double threshold = stats.mean[0] * focusThreshold / 100.0; + double threshold = stats.mean[0] * THRESHOLD_PERCENTAGE / 100.0; for (int y = subY; y < subH; y++) { for (int x = subX; x < subW; x++) { T pixel = buffer[x + y * stats.width]; if (pixel > threshold) { totalMass += pixel; massX += x * pixel; massY += y * pixel; } } } qCDebug(KSTARS_FITS) << "FITS: Weighted Center is X: " << massX / totalMass << " Y: " << massY / totalMass; auto * center = new Edge; center->width = -1; center->x = massX / totalMass + 0.5; center->y = massY / totalMass + 0.5; center->HFR = 1; // Maximum Radius int maxR = qMin(subW - 1, subH - 1) / 2; // Critical threshold double critical_threshold = threshold * 0.7; double running_threshold = threshold; while (running_threshold >= critical_threshold) { for (int r = maxR; r > 1; r--) { int pass = 0; for (float theta = 0; theta < 2 * M_PI; theta += (2 * M_PI) / 10.0) { int testX = center->x + std::cos(theta) * r; int testY = center->y + std::sin(theta) * r; // if out of bound, break; if (testX < subX || testX > subW || testY < subY || testY > subH) break; if (buffer[testX + testY * stats.width] > running_threshold) pass++; } //qDebug() << "Testing for radius " << r << " passes # " << pass << " @ threshold " << running_threshold; //if (pass >= 6) if (pass >= 5) { center->width = r * 2; break; } } if (center->width > 0) break; // Increase threshold fuzziness by 10% running_threshold -= running_threshold * 0.1; } // If no stars were detected if (center->width == -1) { delete center; return 0; } // 30% fuzzy //center->width += center->width*0.3 * (running_threshold / threshold); double FSum = 0, HF = 0, TF = 0, min = stats.min[0]; const double resolution = 1.0 / 20.0; int cen_y = qRound(center->y); double rightEdge = center->x + center->width / 2.0; double leftEdge = center->x - center->width / 2.0; QVector subPixels; subPixels.reserve(center->width / resolution); for (double x = leftEdge; x <= rightEdge; x += resolution) { //subPixels[x] = resolution * (image_buffer[static_cast(floor(x)) + cen_y * stats.width] - min); double slice = resolution * (buffer[static_cast(floor(x)) + cen_y * stats.width] - min); FSum += slice; subPixels.append(slice); } // Half flux HF = FSum / 2.0; //double subPixelCenter = center->x - fmod(center->x,resolution); int subPixelCenter = (center->width / resolution) / 2; // Start from center TF = subPixels[subPixelCenter]; double lastTF = TF; // Integrate flux along radius axis until we reach half flux //for (double k=resolution; k < (center->width/(2*resolution)); k += resolution) for (int k = 1; k < subPixelCenter; k++) { TF += subPixels[subPixelCenter + k]; TF += subPixels[subPixelCenter - k]; if (TF >= HF) { // We have two ways to calculate HFR. The first is the correct method but it can get quite variable within 10% due to random fluctuations of the measured star. // The second method is not truly HFR but is much more resistant to noise. // #1 Approximate HFR, accurate and reliable but quite variable to small changes in star flux center->HFR = (k - 1 + ((HF - lastTF) / (TF - lastTF)) * 2) * resolution; // #2 Not exactly HFR, but much more stable //center->HFR = (k*resolution) * (HF/TF); break; } lastTF = TF; } starCenters.append(center); return 1; } diff --git a/kstars/fitsviewer/fitsthresholddetector.h b/kstars/fitsviewer/fitsthresholddetector.h index b86a73b86..f32fae4e4 100644 --- a/kstars/fitsviewer/fitsthresholddetector.h +++ b/kstars/fitsviewer/fitsthresholddetector.h @@ -1,62 +1,59 @@ /*************************************************************************** fitsthresholddetector.h - FITS Image ------------------- begin : Sat March 28 2020 copyright : (C) 2004 by Jasem Mutlaq, (C) 2020 by Eric Dejouhanet email : eric.dejouhanet@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * Some code fragments were adapted from Peter Kirchgessner's FITS plugin* * See http://members.aol.com/pkirchg for more details. * ***************************************************************************/ #ifndef FITSTHRESHOLDDETECTOR_H #define FITSTHRESHOLDDETECTOR_H #include "fitsstardetector.h" class FITSThresholdDetector: public FITSStarDetector { Q_OBJECT public: explicit FITSThresholdDetector(FITSData *parent): FITSStarDetector(parent) {}; public: /** @brief Find sources in the parent FITS data file. * @see FITSStarDetector::findSources(). */ int findSources(QList &starCenters, QRect const &boundary = QRect()) override; /** @brief Configure the detection method. * @see FITSStarDetector::configure(). * @note Parameter "threshold" defaults to THRESHOLD_PERCENTAGE of the mean pixel value of the frame. * @todo Provide parameters for detection configuration. */ FITSStarDetector & configure(const QString &setting, const QVariant &value) override; public: - /** @group Detection internals + /** @group Detection parameters. * @{ */ - static constexpr int THRESHOLD_PERCENTAGE { 120 }; + int THRESHOLD_PERCENTAGE { 120 }; /** @} */ protected: /** @internal Find sources in the parent FITS data file, dependent of the pixel depth. * @see FITSGradientDetector::findSources. */ template int findOneStar(QList &starCenters, const QRect &boundary) const; - -protected: - double focusThreshold { THRESHOLD_PERCENTAGE }; }; #endif // FITSTHRESHOLDDETECTOR_H diff --git a/kstars/fitsviewer/starprofileviewer.cpp b/kstars/fitsviewer/starprofileviewer.cpp index 6c3d633b4..f140c707b 100644 --- a/kstars/fitsviewer/starprofileviewer.cpp +++ b/kstars/fitsviewer/starprofileviewer.cpp @@ -1,1014 +1,1013 @@ /* StarProfileViewer Copyright (C) 2017 Robert Lancaster Based on the QT Surface Example https://doc.qt.io/qt-5.9/qtdatavisualization-surface-example.html and the QT Bars Example https://doc-snapshots.qt.io/qt5-5.9/qtdatavisualization-bars-example.html This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "starprofileviewer.h" #include using namespace QtDataVisualization; StarProfileViewer::StarProfileViewer(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif m_graph = new Q3DBars(); m_pixelValueAxis = m_graph->valueAxis(); m_xPixelAxis = m_graph->columnAxis(); m_yPixelAxis = m_graph->rowAxis(); m_pixelValueAxis->setTitle(i18n("Pixel Values")); m_pixelValueAxis->setLabelAutoRotation(30.0f); m_pixelValueAxis->setTitleVisible(true); m_xPixelAxis->setTitle(i18n("Horizontal")); m_xPixelAxis->setLabelAutoRotation(30.0f); m_xPixelAxis->setTitleVisible(true); m_yPixelAxis->setTitle(i18n("Vertical")); m_yPixelAxis->setLabelAutoRotation(30.0f); m_yPixelAxis->setTitleVisible(true); m_3DPixelSeries = new QBar3DSeries; m_3DPixelSeries->setMesh(QAbstract3DSeries::MeshBevelBar); m_graph->addSeries(m_3DPixelSeries); m_graph->activeTheme()->setLabelBackgroundEnabled(false); QWidget *container = QWidget::createWindowContainer(m_graph); if (!m_graph->hasContext()) { QMessageBox msgBox; msgBox.setText(i18n("Couldn't initialize the OpenGL context.")); msgBox.exec(); return; } QSize screenSize = m_graph->screen()->size(); container->setMinimumSize(QSize(300, 500)); container->setMaximumSize(screenSize); container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); container->setFocusPolicy(Qt::StrongFocus); this->setWindowTitle(i18n("View Star Profile")); QVBoxLayout *mainLayout = new QVBoxLayout(this); QHBoxLayout *topLayout = new QHBoxLayout(); QHBoxLayout *controlsLayout = new QHBoxLayout(); QWidget* rightWidget = new QWidget(); rightWidget->setVisible(false); QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget); QGridLayout *sliderLayout = new QGridLayout(); topLayout->addWidget(container, 1); topLayout->addWidget(rightWidget); mainLayout->addLayout(topLayout); mainLayout->addLayout(controlsLayout); controlsLayout->setAlignment(Qt::AlignLeft); maxValue=new QLabel(this); maxValue->setToolTip(i18n("Maximum Value on the graph")); cutoffValue=new QLabel(this); cutoffValue->setToolTip(i18n("Cuttoff Maximum for eliminating hot pixels and bright stars.")); QCheckBox *toggleEnableCutoff= new QCheckBox(this); toggleEnableCutoff->setToolTip(i18n("Enable or Disable the Max Value Cutoff")); toggleEnableCutoff->setText(i18n("Toggle Cutoff")); toggleEnableCutoff->setChecked(false); blackPointSlider=new QSlider( Qt::Vertical, this); blackPointSlider->setToolTip(i18n("Sets the Minimum Value on the graph")); sliderLayout->addWidget(blackPointSlider,0,0); sliderLayout->addWidget(new QLabel(i18n("Min")),1,0); whitePointSlider=new QSlider( Qt::Vertical, this); whitePointSlider->setToolTip(i18n("Sets the Maximum Value on the graph")); sliderLayout->addWidget(whitePointSlider,0,1); sliderLayout->addWidget(new QLabel(i18n("Max")),1,1); cutoffSlider=new QSlider( Qt::Vertical, this); cutoffSlider->setToolTip(i18n("Sets the Cuttoff Maximum for eliminating hot pixels and bright stars.")); sliderLayout->addWidget(cutoffSlider,0,2); sliderLayout->addWidget(new QLabel(i18n("Cut")),1,2); cutoffSlider->setEnabled(false); minValue = new QLabel(this); minValue->setToolTip(i18n("Minimum Value on the graph")); autoScale = new QCheckBox(this); autoScale->setText(i18n("AutoScale")); autoScale->setToolTip(i18n("Automatically scales the sliders for the subFrame.\nUncheck to leave them unchanged when you pan around.")); autoScale->setChecked(true); showScaling = new QPushButton(this); showScaling->setIcon(QIcon::fromTheme("transform-move-vertical")); showScaling->setCheckable(true); showScaling->setMaximumSize(22, 22); showScaling->setAttribute(Qt::WA_LayoutUsesWidgetRect); showScaling->setToolTip(i18n("Hides and shows the scaling side panel")); showScaling->setChecked(false); rightLayout->addWidget(toggleEnableCutoff); rightLayout->addWidget(cutoffValue); rightLayout->addWidget(maxValue); rightLayout->addLayout(sliderLayout); rightLayout->addWidget(minValue); rightLayout->addWidget(autoScale); selectionType = new QComboBox(this); selectionType->setToolTip(i18n("Changes the type of selection")); selectionType->addItem(i18n("Item")); selectionType->addItem(i18n("Horizontal")); selectionType->addItem(i18n("Vertical")); selectionType->setCurrentIndex(0); sliceB = new QPushButton(this); sliceB->setIcon(QIcon::fromTheme("view-object-histogram-linear")); sliceB->setCheckable(true); sliceB->setMaximumSize(22, 22); sliceB->setAttribute(Qt::WA_LayoutUsesWidgetRect); sliceB->setToolTip(i18n("Toggles the slice view when horizontal or vertical items are selected")); sliceB->setCheckable(true); sliceB->setChecked(false); sliceB->setEnabled(false); sliceB->setDefault(false); showCoordinates = new QPushButton(this); showCoordinates->setIcon(QIcon::fromTheme("coordinate")); showCoordinates->setCheckable(true); showCoordinates->setMaximumSize(22, 22); showCoordinates->setAttribute(Qt::WA_LayoutUsesWidgetRect); showCoordinates->setToolTip(i18n("Shows the x, y coordinates of star centers in the frame")); showCoordinates->setChecked(false); HFRReport = new QPushButton(this); HFRReport->setToolTip(i18n("Shows the HFR of stars in the frame")); HFRReport->setIcon(QIcon::fromTheme("tool-measure")); HFRReport->setCheckable(true); HFRReport->setMaximumSize(22, 22); HFRReport->setAttribute(Qt::WA_LayoutUsesWidgetRect); HFRReport->setChecked(true); reportBox = new QLabel(this); showPeakValues = new QPushButton(this); showPeakValues->setIcon(QIcon::fromTheme("kruler-east")); showPeakValues->setCheckable(true); showPeakValues->setMaximumSize(22, 22); showPeakValues->setAttribute(Qt::WA_LayoutUsesWidgetRect); showPeakValues->setToolTip(i18n("Shows the peak values of star centers in the frame")); showPeakValues->setChecked(true); sampleSize = new QComboBox(this); sampleSize->setToolTip(i18n("Changes the sample size shown in the graph")); sampleSize->addItem(QString::number(16)); sampleSize->addItem(QString::number(32)); sampleSize->addItem(QString::number(64)); sampleSize->addItem(QString::number(128)); sampleSize->addItem(QString::number(256)); sampleSize->addItem(QString::number(512)); sampleSize->setCurrentIndex(3); sampleSize->setVisible(false); zoomView = new QComboBox(this); zoomView->setToolTip(i18n("Zooms the view to preset locations.")); zoomView->addItem(i18n("ZoomTo:")); zoomView->addItem(i18n("Front")); zoomView->addItem(i18n("Front High")); zoomView->addItem(i18n("Overhead")); zoomView->addItem(i18n("Iso. L")); zoomView->addItem(i18n("Iso. R")); zoomView->addItem(i18n("Selected")); zoomView->setCurrentIndex(0); QPushButton *selectorsVisible = new QPushButton(this); selectorsVisible->setIcon(QIcon::fromTheme("adjustlevels")); selectorsVisible->setCheckable(true); selectorsVisible->setMaximumSize(22, 22); selectorsVisible->setAttribute(Qt::WA_LayoutUsesWidgetRect); selectorsVisible->setToolTip(i18n("Hides and shows the Vertical and Horizontal Selection Sliders")); selectorsVisible->setChecked(false); controlsLayout->addWidget(sampleSize); controlsLayout->addWidget(selectionType); controlsLayout->addWidget(selectorsVisible); controlsLayout->addWidget(sliceB); controlsLayout->addWidget(showScaling); //bottomLayout->addWidget(barSpacing); controlsLayout->addWidget(zoomView); //bottomLayout->addWidget(color); controlsLayout->addWidget(showCoordinates); controlsLayout->addWidget(HFRReport); controlsLayout->addWidget(showPeakValues); controlsLayout->addWidget(reportBox); QWidget *bottomSliderWidget= new QWidget(this); QGridLayout *bottomSliders = new QGridLayout(bottomSliderWidget); bottomSliderWidget->setLayout(bottomSliders); mainLayout->addWidget(bottomSliderWidget); bottomSliderWidget->setVisible(false); verticalSelector = new QSlider(Qt::Horizontal, this); verticalSelector->setToolTip(i18n("Selects the Vertical Value")); horizontalSelector = new QSlider(Qt::Horizontal, this); horizontalSelector->setToolTip(i18n("Selects the Horizontal Value")); bottomSliders->addWidget(new QLabel(i18n("Vertical: ")), 0, 0); bottomSliders->addWidget(verticalSelector, 0, 1); bottomSliders->addWidget(new QLabel(i18n("Horizontal: ")), 1, 0); bottomSliders->addWidget(horizontalSelector, 1, 1); QWidget *bottomControlsWidget= new QWidget(this); QHBoxLayout *bottomControlLayout = new QHBoxLayout(bottomControlsWidget); mainLayout->addWidget(bottomControlsWidget);\ bottomControlsWidget->setVisible(false); exploreMode = new QPushButton(this); exploreMode->setIcon(QIcon::fromTheme("visibility")); exploreMode->setCheckable(true); exploreMode->setMaximumSize(22, 22); exploreMode->setAttribute(Qt::WA_LayoutUsesWidgetRect); exploreMode->setToolTip(i18n("Zooms automatically as the sliders change")); exploreMode->setChecked(true); QDial *barSpacing=new QDial(this); barSpacing->setMinimum(0); barSpacing->setMaximum(100); barSpacing->setValue(50); barSpacing->setMaximumSize(32, 32); barSpacing->setWrapping(false); m_graph->setBarSpacing(QSizeF(0.5,0.5)); QComboBox *color = new QComboBox(this); color->setToolTip(i18n("Changes the color scheme")); QLinearGradient grGtoR(50, 1, 0, 0); grGtoR.setColorAt(1.0, Qt::darkGreen); grGtoR.setColorAt(0.5, Qt::yellow); grGtoR.setColorAt(0.2, Qt::red); grGtoR.setColorAt(0.0, Qt::darkRed); QPixmap pm(50, 10); QPainter pmp(&pm); pmp.setPen(Qt::NoPen); pmp.setBrush(QBrush(grGtoR)); pmp.drawRect(0, 0, 50, 10); color->addItem(""); color->setItemIcon(0,QIcon(pm)); QLinearGradient grBtoY(50, 1, 0, 0); grBtoY.setColorAt(1.0, Qt::black); grBtoY.setColorAt(0.67, Qt::blue); grBtoY.setColorAt(0.33, Qt::red); grBtoY.setColorAt(0.0, Qt::yellow); pmp.setBrush(QBrush(grBtoY)); pmp.drawRect(0, 0, 50, 10); color->addItem(""); color->setItemIcon(1,QIcon(pm)); color->setIconSize(QSize(50, 10)); color->setCurrentIndex(0); color->setMaximumWidth(80); pixelReport = new QLabel("", bottomControlsWidget); bottomControlLayout->addWidget(exploreMode); bottomControlLayout->addWidget(barSpacing); bottomControlLayout->addWidget(color); bottomControlLayout->addWidget(pixelReport); QObject::connect(selectionType, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSelectionType(int))); QObject::connect(zoomView, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomViewTo(int))); QObject::connect(sliceB, &QPushButton::pressed, this, &StarProfileViewer::toggleSlice); QObject::connect(showCoordinates, &QCheckBox::toggled, this, &StarProfileViewer::updateHFRandPeakSelection); QObject::connect(HFRReport, &QCheckBox::toggled, this, &StarProfileViewer::updateHFRandPeakSelection); QObject::connect(showPeakValues, &QCheckBox::toggled, this, &StarProfileViewer::updateHFRandPeakSelection); QObject::connect(blackPointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(whitePointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(cutoffSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateDisplayData); QObject::connect(autoScale, &QCheckBox::toggled, this, &StarProfileViewer::updateScale); QObject::connect(showScaling, &QCheckBox::toggled, rightWidget, &QWidget::setVisible); QObject::connect(sampleSize, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateSampleSize(QString))); QObject::connect(color, SIGNAL(currentIndexChanged(int)), this, SLOT(updateColor(int))); QObject::connect(verticalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::connect(horizontalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::connect(selectorsVisible, &QCheckBox::toggled, bottomSliderWidget, &QWidget::setVisible); QObject::connect(selectorsVisible, &QCheckBox::toggled, bottomControlsWidget, &QWidget::setVisible); QObject::connect(toggleEnableCutoff, &QCheckBox::toggled, this, &StarProfileViewer::toggleCutoffEnabled); QObject::connect(m_3DPixelSeries, &QBar3DSeries::selectedBarChanged, this, &StarProfileViewer::updateSelectorBars); QObject::connect(barSpacing, &QSlider::valueChanged, this, &StarProfileViewer::updateBarSpacing); m_graph->activeTheme()->setType(Q3DTheme::Theme(3)); //Stone Moss setGreenToRedGradient(); m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box #ifdef Q_OS_OSX QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); #endif show(); } StarProfileViewer::~StarProfileViewer() { delete m_graph; } void StarProfileViewer::loadData(FITSData * data, QRect sub, QList centers) { if(data) { imageData = data; subFrame=sub; starCenters=centers; switch (data->property("dataType").toInt()) { case TBYTE: loadDataPrivate(); break; case TSHORT: loadDataPrivate(); break; case TUSHORT: loadDataPrivate(); break; case TLONG: loadDataPrivate(); break; case TULONG: loadDataPrivate(); break; case TFLOAT: loadDataPrivate(); break; case TLONGLONG: loadDataPrivate(); break; case TDOUBLE: loadDataPrivate(); break; } updateScale(); // Add data to the data proxy (the data proxy assumes ownership of it) // We will retain a copy of the data set so that we can update the display updateDisplayData(); updateHFRandPeakSelection(); horizontalSelector->setRange(0, subFrame.width()-1); verticalSelector->setRange(0, subFrame.width()-1); //Width and height are the same } } template void StarProfileViewer::loadDataPrivate() { // Create data arrays dataSet = new QBarDataArray; QBarDataRow *dataRow; dataSet->reserve(subFrame.height()); QStringList rowLabels; QStringList columnLabels; - auto *buffer = reinterpret_cast(imageData->getImageBuffer()); + auto *buffer = reinterpret_cast(imageData->getImageBuffer()); int width = imageData->width(); for (int j = subFrame.y(); j < subFrame.y() + subFrame.height(); j++) { if( j % 10 == 0 ) rowLabels << QString::number(j); else rowLabels << ""; dataRow = new QBarDataRow(subFrame.width()); int x = 0; for (int i = subFrame.x(); i < subFrame.x() + subFrame.width(); i++) { if( i % 10 == 0 ) columnLabels << QString::number(i); else columnLabels << ""; if( i > 0 && i < imageData->width() && j > 0 && j < imageData->height()) (*dataRow)[x].setValue(*(buffer + i + j * width)); x++; } dataSet->insert(0, dataRow); //Note the row axis is displayed in the opposite direction of the y axis in the image. } std::reverse(rowLabels.begin(), rowLabels.end()); m_3DPixelSeries->dataProxy()->setRowLabels(rowLabels); m_3DPixelSeries->dataProxy()->setColumnLabels(columnLabels); } void StarProfileViewer::toggleCutoffEnabled(bool enable) { cutoffSlider->setEnabled(enable); cutOffEnabled = enable; updateDisplayData(); } void StarProfileViewer::updateScale() { //We need to disconnect these so that changing their ranges doesn't affect things QObject::disconnect(blackPointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::disconnect(whitePointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::disconnect(cutoffSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateDisplayData); float subFrameMin, subFrameMax; double dataMin, dataMax; float min, max; getSubFrameMinMax(&subFrameMin, &subFrameMax, &dataMin, &dataMax); int sliderDataMin = convertToSliderValue(dataMin) - 1; //Expands the slider range a little beyond the max and min values int sliderDataMax = convertToSliderValue(dataMax) + 1; if(autoScale->isChecked()) { min = subFrameMin; max = subFrameMax; int sliderMin = convertToSliderValue(min) - 1; //Expands the slider range a little beyond the max and min values int sliderMax = convertToSliderValue(max) + 1; blackPointSlider->setRange(sliderMin, sliderMax); blackPointSlider->setTickInterval((sliderMax - sliderMin) / 100); whitePointSlider->setRange(sliderMin, sliderMax); whitePointSlider->setTickInterval((sliderMax - sliderMin) / 100); cutoffSlider->setRange(sliderMin, sliderDataMax); cutoffSlider->setTickInterval((sliderDataMax - sliderMin) / 100); blackPointSlider->setValue(sliderMin); whitePointSlider->setValue(sliderMax); cutoffSlider->setValue(sliderDataMax); } else { min = convertFromSliderValue(blackPointSlider->value()); max = convertFromSliderValue(whitePointSlider->value()); blackPointSlider->setRange(sliderDataMin, sliderDataMax); blackPointSlider->setTickInterval((sliderDataMax - sliderDataMin) / 100); whitePointSlider->setRange(sliderDataMin, sliderDataMax); whitePointSlider->setTickInterval((sliderDataMax - sliderDataMin) / 100); cutoffSlider->setRange(sliderDataMin, sliderDataMax); cutoffSlider->setTickInterval((sliderDataMax - sliderDataMin) / 100); } m_pixelValueAxis->setRange(min, max); if(cutOffEnabled) cutoffValue->setText(i18n("Cut: %1", roundf(convertFromSliderValue(cutoffSlider->value()) * 100) / 100)); else cutoffValue->setText("Cut Disabled"); if(max < 10 ) { m_pixelValueAxis->setLabelFormat(QString(QStringLiteral("%.3f "))); m_3DPixelSeries->setItemLabelFormat(QString(QStringLiteral("%.3f "))); maxValue->setText(i18n("Max: %1", roundf(max * 100) / 100)); minValue->setText(i18n("Min: %1", roundf(min * 100) / 100)); } else { m_pixelValueAxis->setLabelFormat(QString(QStringLiteral("%.0f "))); m_3DPixelSeries->setItemLabelFormat(QString(QStringLiteral("%.0f "))); maxValue->setText(i18n("Max: %1", max)); minValue->setText(i18n("Min: %1", min)); } QObject::connect(blackPointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(whitePointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(cutoffSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateDisplayData); } void StarProfileViewer::updateBarSpacing(int value) { float spacing = (float)value/100.0; m_graph->setBarSpacing(QSizeF(spacing, spacing)); } void StarProfileViewer::zoomViewTo(int where) { if(where > 6) //One of the star centers { int star = where - 7; int x = starCenters[star]->x - subFrame.x(); int y = subFrame.height() - (starCenters[star]->y - subFrame.y()); m_graph->primarySeries()->setSelectedBar(QPoint( y , x )); //Note row, column y, x where = 6; //This is so it will zoom to the target. } switch (where) { case 0: //Zoom To break; case 1: //Front m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 2: //Front High m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 3: //Overhead m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetDirectlyAbove); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 4: //Isometric L m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeftHigh); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 5: //Isometric R m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricRightHigh); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 6: //Selected Item { QPoint selectedBar = m_graph->selectedSeries() ? m_graph->selectedSeries()->selectedBar() : QBar3DSeries::invalidSelectionPosition(); if (selectedBar != QBar3DSeries::invalidSelectionPosition()) { QVector3D target; float xMin = m_graph->columnAxis()->min(); float xRange = m_graph->columnAxis()->max() - xMin; float zMin = m_graph->rowAxis()->min(); float zRange = m_graph->rowAxis()->max() - zMin; target.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f); target.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f); qreal endAngleX = qAtan(qreal(target.z() / target.x())) / M_PI * -180.0 + 90.0; if (target.x() > 0.0f) endAngleX -= 180.0f; float barValue = m_graph->selectedSeries()->dataProxy()->itemAt(selectedBar.x(), selectedBar.y())->value(); float endAngleY = 60.0f; float zoom = 150 * 1/qSqrt(barValue / convertFromSliderValue(whitePointSlider->value())); m_graph->scene()->activeCamera()->setCameraPosition(endAngleX, endAngleY, zoom); m_graph->scene()->activeCamera()->setTarget(target); } zoomView->setCurrentIndex(0); break; } default: zoomView->setCurrentIndex(0); break; } } void StarProfileViewer::changeSelectionType(int type) { switch (type) { case 0: m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem); m_graph->scene()->setSlicingActive(false); sliceB->setEnabled(false); break; case 1: m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow); sliceB->setEnabled(true); break; case 2: m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn); sliceB->setEnabled(true); break; default: break; } } void StarProfileViewer::changeSelection() { int x = horizontalSelector->value(); int y = verticalSelector->value(); m_graph->primarySeries()->setSelectedBar(QPoint( y , x )); //Note row, column y, x if(exploreMode->isChecked()) zoomViewTo(6); //Zoom to SelectedItem updatePixelReport(); } void StarProfileViewer::updatePixelReport() { int x = horizontalSelector->value(); int y = verticalSelector->value(); //They need to be shifted to the location of the subframe x += subFrame.x(); y = (subFrame.height() - 1 - y) + subFrame.y(); //Note: Y is in reverse order on the graph. float barValue = getImageDataValue(x, y); pixelReport->setText(i18n("Selected Pixel: (%1, %2): %3", x + 1, y + 1, roundf(barValue * 100) / 100)); //Have to add 1 because humans start counting at 1 } void StarProfileViewer::updateSelectorBars(QPoint position) { //Note that we need to disconnect and then reconnect to avoid triggering changeSelection QObject::disconnect(verticalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::disconnect(horizontalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); //Note row, column y, x verticalSelector->setValue(position.x()); horizontalSelector->setValue(position.y()); updatePixelReport(); QObject::connect(verticalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::connect(horizontalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); } void StarProfileViewer::updateSampleSize(const QString &text) { emit sampleSizeUpdated(text.toInt()); } void StarProfileViewer::enableTrackingBox(bool enable) { sampleSize->setVisible(enable); } void StarProfileViewer::updateDisplayData() { if(cutOffEnabled) cutoffValue->setText(i18n("Cut: %1", roundf(convertFromSliderValue(cutoffSlider->value()) * 100) / 100)); else cutoffValue->setText(i18n("Cut Disabled")); if(dataSet != nullptr) { QBarDataArray *displayDataSet = new QBarDataArray; displayDataSet->reserve(dataSet->size()); for (int row = 0; row < dataSet->size(); row++) { QBarDataRow *dataRow = dataSet->at(row); QBarDataRow *newDataRow; newDataRow = new QBarDataRow(dataRow->size()); for (int column = 0; column < dataRow->size(); column++) { if(cutOffEnabled && dataRow->value(column).value() > convertFromSliderValue(cutoffSlider->value())) (*newDataRow)[column].setValue(0.0f); else (*newDataRow)[column].setValue(dataRow->value(column).value()); } displayDataSet->append(newDataRow); } m_3DPixelSeries->dataProxy()->resetArray(displayDataSet); //, m_3DPixelSeries->dataProxy()->rowLabels(), m_3DPixelSeries->dataProxy()->columnLabels() } } void StarProfileViewer::getSubFrameMinMax(float *subFrameMin, float *subFrameMax, double *dataMin, double *dataMax) { imageData->getMinMax(dataMin,dataMax); //Backwards so that we can find the min and max in subFrame *subFrameMin = *dataMax; *subFrameMax = *dataMin; switch (imageData->property("dataType").toInt()) { case TBYTE: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TSHORT: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TUSHORT: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TLONG: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TULONG: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TFLOAT: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TLONGLONG: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TDOUBLE: getSubFrameMinMax(subFrameMin, subFrameMax); break; } } template void StarProfileViewer::getSubFrameMinMax(float *subFrameMin, float *subFrameMax) { - T *buffer = reinterpret_cast(imageData->getImageBuffer()); + auto *buffer = reinterpret_cast(imageData->getImageBuffer()); T min = std::numeric_limits::max(); T max = std::numeric_limits::min(); int width = imageData->width(); for (int y = subFrame.y(); y < subFrame.y() + subFrame.height(); y++) { for (int x = subFrame.x(); x < subFrame.x() + subFrame.width(); x++) { if( x > 0 && x < imageData->width() && y > 0 && y < imageData->height()) { min = qMin(min, *(buffer + x + y * width)); max = qMax(max, *(buffer + x + y * width)); } } } *subFrameMin = min; *subFrameMax = max; } template float StarProfileViewer::getImageDataValue(int x, int y) { if(!imageData) return 0; - uint8_t *image_buffer = imageData->getImageBuffer(); - T *buffer = reinterpret_cast(image_buffer); + auto *buffer = reinterpret_cast(imageData->getImageBuffer()); return (float) buffer[y * imageData->width() + x]; } float StarProfileViewer::getImageDataValue(int x, int y) { switch (imageData->property("dataType").toInt()) { case TBYTE: return getImageDataValue(x, y); break; case TSHORT: return getImageDataValue(x, y); break; case TUSHORT: return getImageDataValue(x, y); break; case TLONG: return getImageDataValue(x, y); break; case TULONG: return getImageDataValue(x, y); break; case TFLOAT: return getImageDataValue(x, y); break; case TLONGLONG: return getImageDataValue(x, y); break; case TDOUBLE: return getImageDataValue(x, y); break; default: return 0; break; } } void StarProfileViewer::toggleSlice() { if(m_graph->selectionMode() == QAbstract3DGraph::SelectionItemAndRow || m_graph->selectionMode() == QAbstract3DGraph::SelectionItemAndColumn) { if(m_graph->scene()->isSlicingActive()) { m_graph->scene()->setSlicingActive(false); } else { QPoint selectedBar = m_graph->selectedSeries() ? m_graph->selectedSeries()->selectedBar() : QBar3DSeries::invalidSelectionPosition(); if (selectedBar != QBar3DSeries::invalidSelectionPosition()) m_graph->scene()->setSlicingActive(true); } } } void StarProfileViewer::updateVerticalAxis() { float blackPoint = convertFromSliderValue(blackPointSlider->value()); float whitePoint = convertFromSliderValue(whitePointSlider->value()); m_pixelValueAxis->setRange(blackPoint, whitePoint); maxValue->setText(i18n("Max: %1", roundf(whitePoint * 100) / 100)); minValue->setText(i18n("Min: %1", roundf(blackPoint * 100) / 100)); } void StarProfileViewer::updateHFRandPeakSelection() { m_graph->removeCustomItems(); reportBox->setText(""); QString reportString = ""; //Removes all the stars from the combo box. while(zoomView->count() > 7) zoomView->removeItem(7); for (int i = 0; i < starCenters.count(); i++) { int x = starCenters[i]->x; int row = x - subFrame.x(); int y = starCenters[i]->y; int col = subFrame.height() - (y - subFrame.y()); if(subFrame.contains(x,y)){ double newHFR = imageData->getHFR(x,y); int value = getImageDataValue(x, y); QCustom3DLabel *label = new QCustom3DLabel(); label->setFacingCamera(true); QString labelString = i18n("Star %1: ", i + 1); if(showCoordinates->isChecked()) { labelString = labelString + i18n("(%1, %2) ", x + 1, y + 1); } if(HFRReport->isChecked()) { labelString = labelString + i18n("HFR: %1 ", roundf(newHFR * 100) / 100); } if(showPeakValues->isChecked()) { labelString = labelString + i18n("Peak: %1", value); } if(showCoordinates->isChecked() || HFRReport->isChecked() || showPeakValues->isChecked()) { if (!reportString.isEmpty()) reportString += '\n'; reportString += labelString; label->setText(labelString); label->setPosition(QVector3D(row, value, col)); label->setScaling(QVector3D(1.0f, 1.0f, 1.0f)); m_graph->addCustomItem(label); } //Adds this star to the combo box. zoomView->addItem(i18n("Star %1", i + 1)); } } if (!reportString.isEmpty()) { reportBox->setText(reportString); } } void StarProfileViewer::updateColor(int selection) { switch (selection) { case 0: setGreenToRedGradient(); break; case 1: setBlackToYellowGradient(); break; default: break; } } void StarProfileViewer::setBlackToYellowGradient() { QLinearGradient gr; gr.setColorAt(0.0, Qt::black); gr.setColorAt(0.33, Qt::blue); gr.setColorAt(0.67, Qt::red); gr.setColorAt(1.0, Qt::yellow); QLinearGradient highGr; highGr.setColorAt(0.0, Qt::yellow); highGr.setColorAt(1.0, Qt::yellow); QLinearGradient sinHighGr; sinHighGr.setColorAt(0.0, Qt::red); sinHighGr.setColorAt(1.0, Qt::red); m_3DPixelSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); m_3DPixelSeries->setBaseGradient(gr); m_3DPixelSeries->setSingleHighlightGradient(sinHighGr); m_3DPixelSeries->setMultiHighlightGradient(highGr); } void StarProfileViewer::setGreenToRedGradient() { QLinearGradient gr; gr.setColorAt(0.0, Qt::darkGreen); gr.setColorAt(0.5, Qt::yellow); gr.setColorAt(0.8, Qt::red); gr.setColorAt(1.0, Qt::darkRed); QLinearGradient highGr; highGr.setColorAt(0.0, Qt::black); highGr.setColorAt(1.0, Qt::black); QLinearGradient sinHighGr; sinHighGr.setColorAt(0.0, Qt::red); sinHighGr.setColorAt(1.0, Qt::red); m_3DPixelSeries->setBaseGradient(gr); m_3DPixelSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); m_3DPixelSeries->setSingleHighlightGradient(sinHighGr); m_3DPixelSeries->setMultiHighlightGradient(highGr); } //Multiplying by 1000000 will take care of preserving decimals in an int slider //The sqrt function makes the slider non-linear, emphasising the lower values //Note that it is actually multiplying the number on the slider by 1000 or so since it is square rooted. int StarProfileViewer::convertToSliderValue(float value) { return (int) qSqrt((value * 1000000.0)); } float StarProfileViewer::convertFromSliderValue(int value) { return qPow((float)value,2) / 1000000.0; }